From 429935bdc7f8d29f627595639390348ec958754b Mon Sep 17 00:00:00 2001 From: zgtfx <2844350049@qq.com> Date: Sun, 21 Apr 2024 12:05:11 +0800 Subject: [PATCH 1/2] 1 --- README.md | 74 +- doc/~$环境使用手册.docx | Bin 0 -> 162 bytes doc/若依环境使用手册.docx | Bin 426497 -> 428430 bytes pom.xml | 78 +- ruoyi-admin/pom.xml | 46 +- .../main/java/com/ruoyi/RuoYiApplication.java | 2 +- .../controller/common/CaptchaController.java | 94 + .../controller/common/CommonController.java | 9 +- .../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 | 128 +- .../controller/monitor/DruidController.java | 26 - .../controller/monitor/ServerController.java | 20 +- .../monitor/SysLogininforController.java | 58 +- .../monitor/SysOperlogController.java | 53 +- .../monitor/SysUserOnlineController.java | 93 +- .../system/SysCaptchaController.java | 92 - .../system/SysConfigController.java | 116 +- .../controller/system/SysDeptController.java | 157 +- .../system/SysDictDataController.java | 114 +- .../system/SysDictTypeController.java | 147 +- .../controller/system/SysIndexController.java | 177 +- .../controller/system/SysLoginController.java | 120 +- .../controller/system/SysMenuController.java | 203 +- .../system/SysNoticeController.java | 91 +- .../controller/system/SysPostController.java | 117 +- .../system/SysProfileController.java | 194 +- .../system/SysRegisterController.java | 20 +- .../controller/system/SysRoleController.java | 312 +- .../controller/system/SysUserController.java | 333 +- .../web/controller/tool/BuildController.java | 26 - .../controller/tool/SwaggerController.java | 24 - .../ruoyi/web/core/config/SwaggerConfig.java | 68 +- .../META-INF/spring-devtools.properties | 1 + .../src/main/resources/application-druid.yml | 2 +- .../src/main/resources/application.yml | 111 +- .../main/resources/ehcache/ehcache-shiro.xml | 91 - .../{static => }/i18n/messages.properties | 75 +- .../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 2670 -> 0 bytes .../ajax/libs/bootstrap-fileinput/loading.gif | Bin 847 -> 0 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 | 674 - .../editable/bootstrap-editable.min.js | 7 - .../editable/bootstrap-table-editable.js | 189 - .../extensions/editable/clear.png | Bin 244 -> 0 bytes .../extensions/editable/loading.gif | Bin 1849 -> 0 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 | 1068 -- .../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 3785 -> 0 bytes .../static/ajax/libs/iCheck/green.png | Bin 20818 -> 0 bytes .../static/ajax/libs/iCheck/green@2x.png | Bin 7708 -> 0 bytes .../static/ajax/libs/iCheck/icheck.min.js | 11 - .../ajax/libs/jasny/jasny-bootstrap.css | 620 - .../static/ajax/libs/jasny/jasny-bootstrap.js | 324 - .../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 601 -> 0 bytes .../3.5/css/default/img/diy/1_open.png | Bin 580 -> 0 bytes .../3.5/css/default/img/diy/2.png | Bin 570 -> 0 bytes .../3.5/css/default/img/diy/3.png | Bin 762 -> 0 bytes .../3.5/css/default/img/diy/4.png | Bin 399 -> 0 bytes .../3.5/css/default/img/diy/5.png | Bin 710 -> 0 bytes .../3.5/css/default/img/diy/6.png | Bin 432 -> 0 bytes .../3.5/css/default/img/diy/7.png | Bin 534 -> 0 bytes .../3.5/css/default/img/diy/8.png | Bin 529 -> 0 bytes .../3.5/css/default/img/diy/9.png | Bin 467 -> 0 bytes .../3.5/css/default/img/line_conn.gif | Bin 45 -> 0 bytes .../3.5/css/default/img/loading.gif | Bin 381 -> 0 bytes .../3.5/css/default/img/zTreeStandard.gif | Bin 5564 -> 0 bytes .../3.5/css/default/img/zTreeStandard.png | Bin 11173 -> 0 bytes .../3.5/css/default/zTreeStyle.css | 102 - .../3.5/css/metro/img/line_conn.gif | Bin 45 -> 0 bytes .../3.5/css/metro/img/line_conn.png | Bin 933 -> 0 bytes .../3.5/css/metro/img/loading.gif | Bin 381 -> 0 bytes .../jquery-ztree/3.5/css/metro/img/metro.gif | Bin 3981 -> 0 bytes .../jquery-ztree/3.5/css/metro/img/metro.png | Bin 7273 -> 0 bytes .../jquery-ztree/3.5/css/metro/zTreeStyle.css | 107 - .../3.5/css/simple/img/left_menu.gif | Bin 216 -> 0 bytes .../3.5/css/simple/img/left_menu.png | Bin 421 -> 0 bytes .../3.5/css/simple/img/line_conn.gif | Bin 45 -> 0 bytes .../3.5/css/simple/img/loading.gif | Bin 381 -> 0 bytes .../3.5/css/simple/img/zTreeStandard.gif | Bin 5564 -> 0 bytes .../3.5/css/simple/img/zTreeStandard.png | Bin 11173 -> 0 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/css/layer.css | 286 - .../static/ajax/libs/layer/layer.min.js | 2 - .../ajax/libs/layer/theme/moon/default.png | Bin 7563 -> 0 bytes .../ajax/libs/layer/theme/moon/style.css | 169 - .../ajax/libs/layui/css/modules/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 12072 -> 0 bytes .../ajax/libs/summernote/font/summernote.ttf | Bin 11896 -> 0 bytes .../ajax/libs/summernote/font/summernote.woff | Bin 7428 -> 0 bytes .../libs/summernote/font/summernote.woff2 | Bin 6156 -> 0 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 | 189 - .../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 | 175 - .../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 16958 -> 0 bytes .../src/main/resources/static/file/rml.txt | 1 - .../resources/static/fonts/FontAwesome.otf | Bin 134808 -> 0 bytes .../static/fonts/Simple-Line-Icons.woff2 | Bin 30064 -> 0 bytes .../static/fonts/fontawesome-webfont.eot | Bin 165742 -> 0 bytes .../static/fonts/fontawesome-webfont.svg | 2671 ---- .../static/fonts/fontawesome-webfont.ttf | Bin 165548 -> 0 bytes .../static/fonts/fontawesome-webfont.woff | Bin 98024 -> 0 bytes .../static/fonts/fontawesome-webfont.woff2 | Bin 77160 -> 0 bytes .../fonts/glyphicons-halflings-regular.eot | Bin 20127 -> 0 bytes .../fonts/glyphicons-halflings-regular.svg | 288 - .../fonts/glyphicons-halflings-regular.ttf | Bin 45404 -> 0 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 23424 -> 0 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 18028 -> 0 bytes .../main/resources/static/fonts/iconfont.eot | Bin 54172 -> 0 bytes .../main/resources/static/fonts/iconfont.svg | 405 - .../main/resources/static/fonts/iconfont.ttf | Bin 53996 -> 0 bytes .../main/resources/static/fonts/iconfont.woff | Bin 34624 -> 0 bytes .../main/resources/static/fonts/zenicon.woff | Bin 84992 -> 0 bytes .../src/main/resources/static/img/blue.png | Bin 4088 -> 0 bytes .../resources/static/img/loading-upload.gif | Bin 1688 -> 0 bytes .../src/main/resources/static/img/loading.gif | Bin 2538 -> 0 bytes .../src/main/resources/static/img/locked.png | Bin 1132 -> 0 bytes .../resources/static/img/login-background.jpg | Bin 142718 -> 0 bytes .../main/resources/static/img/progress.png | Bin 1269 -> 0 bytes .../src/main/resources/static/img/qr_code.png | Bin 8602 -> 0 bytes .../src/main/resources/static/img/user.png | Bin 1106 -> 0 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 5645 -> 0 bytes .../main/resources/static/ruoyi/css/ry-ui.css | 1218 -- .../src/main/resources/static/ruoyi/index.js | 666 - .../main/resources/static/ruoyi/js/common.js | 586 - .../main/resources/static/ruoyi/js/ry-ui.js | 1792 --- .../src/main/resources/static/ruoyi/login.js | 98 - .../main/resources/static/ruoyi/register.js | 86 - .../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 | 447 - .../src/main/resources/templates/index.html | 381 - .../src/main/resources/templates/lock.html | 208 - .../src/main/resources/templates/login.html | 82 - .../src/main/resources/templates/main.html | 1712 --- .../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 | 188 - .../templates/monitor/server/server.html | 258 - .../main/resources/templates/register.html | 81 - .../src/main/resources/templates/skin.html | 165 - .../templates/system/config/add.html | 79 - .../templates/system/config/config.html | 148 - .../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 | 152 - .../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 | 196 - .../resources/templates/system/menu/edit.html | 224 - .../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 | 121 - .../templates/system/notice/view.html | 27 - .../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 | 190 - .../templates/system/role/selectUser.html | 113 - .../resources/templates/system/user/add.html | 259 - .../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 | 299 - .../system/user/profile/resetPwd.html | 105 - .../templates/system/user/resetPwd.html | 51 - .../resources/templates/system/user/user.html | 296 - .../resources/templates/system/user/view.html | 161 - .../resources/templates/tool/build/build.html | 168 - ruoyi-common/pom.xml | 57 +- .../ruoyi/common/annotation/DataScope.java | 2 +- .../com/ruoyi/common/annotation/Excels.java | 4 +- .../java/com/ruoyi/common/annotation/Log.java | 1 + .../ruoyi/common/annotation/RateLimiter.java | 40 + .../ruoyi/common/annotation/RepeatSubmit.java | 6 +- .../com/ruoyi/common/config/RuoYiConfig.java | 44 +- .../serializer/SensitiveJsonSerializer.java | 15 +- .../ruoyi/common/constant/CacheConstants.java | 44 + .../com/ruoyi/common/constant/Constants.java | 73 +- .../ruoyi/common/constant/GenConstants.java | 11 +- .../com/ruoyi/common/constant/HttpStatus.java | 94 + .../common/constant/PermissionConstants.java | 27 - .../ruoyi/common/constant/ShiroConstants.java | 79 - .../ruoyi/common/constant/UserConstants.java | 42 +- .../core/controller/BaseController.java | 121 +- .../ruoyi/common/core/domain/AjaxResult.java | 87 +- .../ruoyi/common/core/domain/CxSelect.java | 69 - .../java/com/ruoyi/common/core/domain/R.java | 5 +- .../ruoyi/common/core/domain/TreeEntity.java | 18 +- .../ruoyi/common/core/domain/TreeSelect.java | 77 + .../com/ruoyi/common/core/domain/Ztree.java | 104 - .../common/core/domain/entity/SysDept.java | 18 +- .../core/domain/entity/SysDictData.java | 6 +- .../core/domain/entity/SysDictType.java | 6 +- .../common/core/domain/entity/SysMenu.java | 105 +- .../common/core/domain/entity/SysRole.java | 72 +- .../common/core/domain/entity/SysUser.java | 125 +- .../common/core/domain/model/LoginBody.java | 69 + .../common/core/domain/model/LoginUser.java | 266 + .../core/domain/model/RegisterBody.java | 11 + .../ruoyi/common/core/page/PageDomain.java | 14 +- .../ruoyi/common/core/page/TableDataInfo.java | 2 +- .../ruoyi/common/core/redis/RedisCache.java | 268 + .../ruoyi/common/enums/BusinessStatus.java | 1 + .../com/ruoyi/common/enums/BusinessType.java | 2 +- .../com/ruoyi/common/enums/HttpMethod.java | 36 + .../com/ruoyi/common/enums/LimitType.java | 20 + .../com/ruoyi/common/enums/OnlineStatus.java | 24 - .../common/exception/ServiceException.java | 28 +- .../file/InvalidExtensionException.java | 2 +- .../user/CaptchaExpireException.java | 16 + .../exception/user/RoleBlockedException.java | 16 - .../exception/user/UserBlockedException.java | 16 - .../exception/user/UserDeleteException.java | 16 - .../UserPasswordRetryLimitCountException.java | 16 - ...UserPasswordRetryLimitExceedException.java | 4 +- .../filter/PropertyPreExcludeFilter.java | 24 + .../ruoyi/common/filter/RepeatableFilter.java | 52 + .../filter/RepeatedlyRequestWrapper.java | 76 + .../common/{xss => filter}/XssFilter.java | 147 +- .../filter/XssHttpServletRequestWrapper.java | 111 + .../main/java/com/ruoyi/common/json/JSON.java | 187 - .../com/ruoyi/common/json/JSONObject.java | 749 -- .../com/ruoyi/common/utils/CacheUtils.java | 197 - .../com/ruoyi/common/utils/CookieUtils.java | 138 - .../com/ruoyi/common/utils/DictUtils.java | 62 +- .../java/com/ruoyi/common/utils/LogUtils.java | 117 - .../com/ruoyi/common/utils/MapDataUtil.java | 54 - .../com/ruoyi/common/utils/SecurityUtils.java | 178 + .../com/ruoyi/common/utils/ServletUtils.java | 66 +- .../com/ruoyi/common/utils/ShiroUtils.java | 86 - .../com/ruoyi/common/utils/StringUtils.java | 42 +- .../java/com/ruoyi/common/utils/Threads.java | 2 +- .../common/utils/file/FileUploadUtils.java | 10 +- .../ruoyi/common/utils/file/FileUtils.java | 6 +- .../common/utils/file/MimeTypeUtils.java | 2 +- .../ruoyi/common/utils/html/HTMLFilter.java | 4 +- .../ruoyi/common/utils/http/HttpHelper.java | 55 + .../ruoyi/common/utils/http/HttpUtils.java | 2 +- .../common/utils/{ => ip}/AddressUtils.java | 110 +- .../ruoyi/common/utils/{ => ip}/IpUtils.java | 750 +- .../common/utils/security/CipherUtils.java | 36 - .../utils/security/PermissionUtils.java | 118 - .../com/ruoyi/common/utils/sign/Base64.java | 291 + .../utils/{security => sign}/Md5Utils.java | 134 +- .../common/utils/spring/SpringUtils.java | 7 +- .../com/ruoyi/common/utils/uuid/UUID.java | 10 +- .../xss/XssHttpServletRequestWrapper.java | 39 - ruoyi-framework/pom.xml | 22 +- .../framework/aspectj/DataScopeAspect.java | 16 +- .../framework/aspectj/DataSourceAspect.java | 2 +- .../ruoyi/framework/aspectj/LogAspect.java | 56 +- .../framework/aspectj/PermissionsAspect.java | 30 - .../framework/aspectj/RateLimiterAspect.java | 89 + .../framework/config/ApplicationConfig.java | 12 +- .../ruoyi/framework/config/DruidConfig.java | 4 +- .../config/FastJson2JsonRedisSerializer.java | 52 + .../ruoyi/framework/config/FilterConfig.java | 18 +- .../ruoyi/framework/config/I18nConfig.java | 2 +- .../framework/config/KaptchaTextCreator.java | 20 +- .../ruoyi/framework/config/RedisConfig.java | 69 + .../framework/config/ResourcesConfig.java | 53 +- .../framework/config/SecurityConfig.java | 148 + .../ruoyi/framework}/config/ServerConfig.java | 65 +- .../ruoyi/framework/config/ShiroConfig.java | 423 - .../framework/config}/ThreadPoolConfig.java | 126 +- .../properties/PermitAllUrlProperties.java | 101 +- .../datasource/DynamicDataSource.java | 1 - .../DynamicDataSourceContextHolder.java | 90 +- .../interceptor/RepeatSubmitInterceptor.java | 11 +- .../impl/SameUrlDataInterceptor.java | 53 +- .../ruoyi/framework/manager/AsyncManager.java | 2 +- .../framework/manager/ShutdownManager.java | 50 +- .../manager/factory/AsyncFactory.java | 124 +- .../context/AuthenticationContextHolder.java | 28 + .../context/PermissionContextHolder.java | 2 +- .../filter/JwtAuthenticationTokenFilter.java | 44 + .../handle/AuthenticationEntryPointImpl.java | 34 + .../handle/LogoutSuccessHandlerImpl.java | 53 + .../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 | 3 +- .../web/exception/GlobalExceptionHandler.java | 107 +- .../framework/web/service/CacheService.java | 83 - .../framework/web/service/ConfigService.java | 28 - .../framework/web/service/DictService.java | 46 - .../web/service/PermissionService.java | 299 +- .../web/service/SysLoginService.java | 181 + .../web/service/SysPasswordService.java | 86 + .../web/service/SysPermissionService.java | 83 + .../web/service/SysRegisterService.java | 115 + .../framework/web/service/TokenService.java | 231 + .../web/service/UserDetailsServiceImpl.java | 66 + ruoyi-generator/pom.xml | 2 +- .../generator/controller/GenController.java | 196 +- .../com/ruoyi/generator/domain/GenTable.java | 13 + .../generator/domain/GenTableColumn.java | 6 +- .../mapper/GenTableColumnMapper.java | 4 +- .../generator/mapper/GenTableMapper.java | 4 +- .../service/GenTableColumnServiceImpl.java | 68 + .../{impl => }/GenTableServiceImpl.java | 53 +- .../service/IGenTableColumnService.java | 4 +- .../generator/service/IGenTableService.java | 7 +- .../impl/GenTableColumnServiceImpl.java | 69 - .../com/ruoyi/generator/util/GenUtils.java | 11 +- .../ruoyi/generator/util/VelocityUtils.java | 146 +- .../src/main/resources/generator.yml | 1 - .../mapper/generator/GenTableColumnMapper.xml | 46 +- .../mapper/generator/GenTableMapper.xml | 40 +- .../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 | 163 +- .../src/main/resources/vm/java/mapper.java.vm | 4 +- .../main/resources/vm/java/service.java.vm | 14 +- .../resources/vm/java/serviceImpl.java.vm | 50 +- .../src/main/resources/vm/js/api.js.vm | 44 + .../src/main/resources/vm/sql/sql.vm | 24 +- .../main/resources/vm/vue/index-tree.vue.vm | 505 + .../src/main/resources/vm/vue/index.vue.vm | 602 + .../resources/vm/vue/v3/index-tree.vue.vm | 474 + .../src/main/resources/vm/vue/v3/index.vue.vm | 590 + .../src/main/resources/vm/xml/mapper.xml.vm | 14 +- ruoyi-quartz/pom.xml | 2 +- .../ruoyi/quartz/config/ScheduleConfig.java | 2 +- .../quartz/controller/SysJobController.java | 180 +- .../controller/SysJobLogController.java | 95 +- .../java/com/ruoyi/quartz/domain/SysJob.java | 12 +- .../com/ruoyi/quartz/domain/SysJobLog.java | 16 +- .../ruoyi/quartz/mapper/SysJobLogMapper.java | 6 +- .../com/ruoyi/quartz/mapper/SysJobMapper.java | 2 +- .../quartz/service/ISysJobLogService.java | 6 +- .../ruoyi/quartz/service/ISysJobService.java | 6 +- .../service/impl/SysJobLogServiceImpl.java | 7 +- .../service/impl/SysJobServiceImpl.java | 18 +- .../ruoyi/quartz/util/AbstractQuartzJob.java | 4 +- .../java/com/ruoyi/quartz/util/CronUtils.java | 31 - .../com/ruoyi/quartz/util/ScheduleUtils.java | 2 +- .../mapper/quartz/SysJobLogMapper.xml | 2 +- .../resources/templates/monitor/job/add.html | 100 - .../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 | 2 +- .../com/ruoyi/system/domain/SysCache.java | 81 + .../com/ruoyi/system/domain/SysConfig.java | 5 +- .../ruoyi/system/domain/SysLogininfor.java | 31 +- .../com/ruoyi/system/domain/SysOperLog.java | 27 +- .../java/com/ruoyi/system/domain/SysPost.java | 16 +- .../ruoyi/system/domain/SysUserOnline.java | 104 +- .../com/ruoyi/system/domain/vo/MetaVo.java | 106 + .../com/ruoyi/system/domain/vo/RouterVo.java | 148 + .../ruoyi/system/mapper/SysConfigMapper.java | 10 +- .../ruoyi/system/mapper/SysDeptMapper.java | 111 +- .../system/mapper/SysDictDataMapper.java | 6 +- .../system/mapper/SysDictTypeMapper.java | 6 +- .../system/mapper/SysLogininforMapper.java | 4 +- .../ruoyi/system/mapper/SysMenuMapper.java | 127 +- .../ruoyi/system/mapper/SysNoticeMapper.java | 16 +- .../ruoyi/system/mapper/SysOperLogMapper.java | 10 +- .../ruoyi/system/mapper/SysPostMapper.java | 38 +- .../ruoyi/system/mapper/SysRoleMapper.java | 59 +- .../system/mapper/SysRoleMenuMapper.java | 20 +- .../ruoyi/system/mapper/SysUserMapper.java | 77 +- .../system/mapper/SysUserOnlineMapper.java | 52 - .../system/mapper/SysUserPostMapper.java | 4 +- .../system/mapper/SysUserRoleMapper.java | 8 - .../system/service/ISysConfigService.java | 13 +- .../ruoyi/system/service/ISysDeptService.java | 105 +- .../system/service/ISysDictDataService.java | 6 +- .../system/service/ISysDictTypeService.java | 15 +- .../system/service/ISysLogininforService.java | 4 +- .../ruoyi/system/service/ISysMenuService.java | 101 +- .../system/service/ISysNoticeService.java | 12 +- .../system/service/ISysOperLogService.java | 4 +- .../ruoyi/system/service/ISysPostService.java | 82 +- .../ruoyi/system/service/ISysRoleService.java | 107 +- .../system/service/ISysUserOnlineService.java | 63 +- .../ruoyi/system/service/ISysUserService.java | 182 +- .../service/impl/SysConfigServiceImpl.java | 63 +- .../service/impl/SysDeptServiceImpl.java | 296 +- .../service/impl/SysDictDataServiceImpl.java | 8 +- .../service/impl/SysDictTypeServiceImpl.java | 43 +- .../impl/SysLogininforServiceImpl.java | 7 +- .../service/impl/SysMenuServiceImpl.java | 359 +- .../service/impl/SysNoticeServiceImpl.java | 20 +- .../service/impl/SysOperLogServiceImpl.java | 9 +- .../service/impl/SysPostServiceImpl.java | 149 +- .../service/impl/SysRoleServiceImpl.java | 295 +- .../impl/SysUserOnlineServiceImpl.java | 132 +- .../service/impl/SysUserServiceImpl.java | 476 +- .../mapper/system/SysConfigMapper.xml | 18 +- .../resources/mapper/system/SysDeptMapper.xml | 67 +- .../mapper/system/SysDictDataMapper.xml | 7 +- .../mapper/system/SysDictTypeMapper.xml | 4 +- .../mapper/system/SysLogininforMapper.xml | 15 +- .../resources/mapper/system/SysMenuMapper.xml | 193 +- .../mapper/system/SysNoticeMapper.xml | 6 +- .../mapper/system/SysOperLogMapper.xml | 3 +- .../resources/mapper/system/SysPostMapper.xml | 50 +- .../resources/mapper/system/SysRoleMapper.xml | 152 +- .../mapper/system/SysRoleMenuMapper.xml | 8 +- .../resources/mapper/system/SysUserMapper.xml | 284 +- .../mapper/system/SysUserOnlineMapper.xml | 57 - .../mapper/system/SysUserRoleMapper.xml | 8 +- ruoyi-ui/.editorconfig | 22 + ruoyi-ui/.env.development | 11 + ruoyi-ui/.env.production | 8 + ruoyi-ui/.env.staging | 10 + ruoyi-ui/.eslintignore | 10 + ruoyi-ui/.eslintrc.js | 199 + ruoyi-ui/README.md | 30 + ruoyi-ui/babel.config.js | 13 + ruoyi-ui/bin/build.bat | 12 + ruoyi-ui/bin/package.bat | 12 + ruoyi-ui/bin/run-web.bat | 12 + ruoyi-ui/build/index.js | 35 + ruoyi-ui/package.json | 90 + ruoyi-ui/public/favicon.ico | Bin 0 -> 5663 bytes .../static => ruoyi-ui/public}/html/ie.html | 90 +- ruoyi-ui/public/index.html | 208 + ruoyi-ui/public/robots.txt | 2 + ruoyi-ui/src/App.vue | 28 + ruoyi-ui/src/api/login.js | 60 + ruoyi-ui/src/api/menu.js | 9 + ruoyi-ui/src/api/monitor/cache.js | 57 + ruoyi-ui/src/api/monitor/job.js | 71 + ruoyi-ui/src/api/monitor/jobLog.js | 26 + ruoyi-ui/src/api/monitor/logininfor.js | 34 + ruoyi-ui/src/api/monitor/online.js | 18 + ruoyi-ui/src/api/monitor/operlog.js | 26 + ruoyi-ui/src/api/monitor/server.js | 9 + ruoyi-ui/src/api/system/config.js | 60 + ruoyi-ui/src/api/system/dept.js | 52 + ruoyi-ui/src/api/system/dict/data.js | 52 + ruoyi-ui/src/api/system/dict/type.js | 60 + ruoyi-ui/src/api/system/menu.js | 60 + ruoyi-ui/src/api/system/notice.js | 44 + ruoyi-ui/src/api/system/post.js | 44 + ruoyi-ui/src/api/system/role.js | 119 + ruoyi-ui/src/api/system/user.js | 135 + ruoyi-ui/src/api/tool/gen.js | 85 + ruoyi-ui/src/assets/401_images/401.gif | Bin 0 -> 164227 bytes ruoyi-ui/src/assets/404_images/404.png | Bin 0 -> 98071 bytes ruoyi-ui/src/assets/404_images/404_cloud.png | Bin 0 -> 4766 bytes ruoyi-ui/src/assets/icons/index.js | 9 + ruoyi-ui/src/assets/icons/svg/404.svg | 1 + ruoyi-ui/src/assets/icons/svg/bug.svg | 1 + ruoyi-ui/src/assets/icons/svg/build.svg | 1 + ruoyi-ui/src/assets/icons/svg/button.svg | 1 + ruoyi-ui/src/assets/icons/svg/cascader.svg | 1 + ruoyi-ui/src/assets/icons/svg/chart.svg | 1 + ruoyi-ui/src/assets/icons/svg/checkbox.svg | 1 + ruoyi-ui/src/assets/icons/svg/clipboard.svg | 1 + ruoyi-ui/src/assets/icons/svg/code.svg | 1 + ruoyi-ui/src/assets/icons/svg/color.svg | 1 + ruoyi-ui/src/assets/icons/svg/component.svg | 1 + ruoyi-ui/src/assets/icons/svg/dashboard.svg | 1 + ruoyi-ui/src/assets/icons/svg/date-range.svg | 1 + ruoyi-ui/src/assets/icons/svg/date.svg | 1 + ruoyi-ui/src/assets/icons/svg/dict.svg | 1 + .../src/assets/icons/svg/documentation.svg | 1 + ruoyi-ui/src/assets/icons/svg/download.svg | 1 + ruoyi-ui/src/assets/icons/svg/drag.svg | 1 + ruoyi-ui/src/assets/icons/svg/druid.svg | 1 + ruoyi-ui/src/assets/icons/svg/edit.svg | 1 + ruoyi-ui/src/assets/icons/svg/education.svg | 1 + ruoyi-ui/src/assets/icons/svg/email.svg | 1 + ruoyi-ui/src/assets/icons/svg/example.svg | 1 + ruoyi-ui/src/assets/icons/svg/excel.svg | 1 + .../src/assets/icons/svg/exit-fullscreen.svg | 1 + ruoyi-ui/src/assets/icons/svg/eye-open.svg | 1 + ruoyi-ui/src/assets/icons/svg/eye.svg | 1 + ruoyi-ui/src/assets/icons/svg/form.svg | 1 + ruoyi-ui/src/assets/icons/svg/fullscreen.svg | 1 + ruoyi-ui/src/assets/icons/svg/github.svg | 1 + ruoyi-ui/src/assets/icons/svg/guide.svg | 1 + ruoyi-ui/src/assets/icons/svg/icon.svg | 1 + ruoyi-ui/src/assets/icons/svg/input.svg | 1 + .../src/assets/icons/svg/international.svg | 1 + ruoyi-ui/src/assets/icons/svg/job.svg | 1 + ruoyi-ui/src/assets/icons/svg/language.svg | 1 + ruoyi-ui/src/assets/icons/svg/link.svg | 1 + ruoyi-ui/src/assets/icons/svg/list.svg | 1 + ruoyi-ui/src/assets/icons/svg/lock.svg | 1 + ruoyi-ui/src/assets/icons/svg/log.svg | 1 + ruoyi-ui/src/assets/icons/svg/logininfor.svg | 1 + ruoyi-ui/src/assets/icons/svg/message.svg | 1 + ruoyi-ui/src/assets/icons/svg/money.svg | 1 + ruoyi-ui/src/assets/icons/svg/monitor.svg | 2 + ruoyi-ui/src/assets/icons/svg/nested.svg | 1 + ruoyi-ui/src/assets/icons/svg/number.svg | 1 + ruoyi-ui/src/assets/icons/svg/online.svg | 1 + ruoyi-ui/src/assets/icons/svg/password.svg | 1 + ruoyi-ui/src/assets/icons/svg/pdf.svg | 1 + ruoyi-ui/src/assets/icons/svg/people.svg | 1 + ruoyi-ui/src/assets/icons/svg/peoples.svg | 1 + ruoyi-ui/src/assets/icons/svg/phone.svg | 1 + ruoyi-ui/src/assets/icons/svg/post.svg | 1 + ruoyi-ui/src/assets/icons/svg/qq.svg | 1 + ruoyi-ui/src/assets/icons/svg/question.svg | 1 + ruoyi-ui/src/assets/icons/svg/radio.svg | 1 + ruoyi-ui/src/assets/icons/svg/rate.svg | 1 + ruoyi-ui/src/assets/icons/svg/redis-list.svg | 2 + ruoyi-ui/src/assets/icons/svg/redis.svg | 1 + ruoyi-ui/src/assets/icons/svg/row.svg | 1 + ruoyi-ui/src/assets/icons/svg/search.svg | 1 + ruoyi-ui/src/assets/icons/svg/select.svg | 1 + ruoyi-ui/src/assets/icons/svg/server.svg | 1 + ruoyi-ui/src/assets/icons/svg/shopping.svg | 1 + ruoyi-ui/src/assets/icons/svg/size.svg | 1 + ruoyi-ui/src/assets/icons/svg/skill.svg | 1 + ruoyi-ui/src/assets/icons/svg/slider.svg | 1 + ruoyi-ui/src/assets/icons/svg/star.svg | 1 + ruoyi-ui/src/assets/icons/svg/swagger.svg | 1 + ruoyi-ui/src/assets/icons/svg/switch.svg | 1 + ruoyi-ui/src/assets/icons/svg/system.svg | 2 + ruoyi-ui/src/assets/icons/svg/tab.svg | 1 + ruoyi-ui/src/assets/icons/svg/table.svg | 1 + ruoyi-ui/src/assets/icons/svg/textarea.svg | 1 + ruoyi-ui/src/assets/icons/svg/theme.svg | 1 + ruoyi-ui/src/assets/icons/svg/time-range.svg | 1 + ruoyi-ui/src/assets/icons/svg/time.svg | 1 + ruoyi-ui/src/assets/icons/svg/tool.svg | 1 + ruoyi-ui/src/assets/icons/svg/tree-table.svg | 1 + ruoyi-ui/src/assets/icons/svg/tree.svg | 1 + ruoyi-ui/src/assets/icons/svg/upload.svg | 1 + ruoyi-ui/src/assets/icons/svg/user.svg | 1 + ruoyi-ui/src/assets/icons/svg/validCode.svg | 1 + ruoyi-ui/src/assets/icons/svg/wechat.svg | 1 + ruoyi-ui/src/assets/icons/svg/zip.svg | 1 + ruoyi-ui/src/assets/icons/svgo.yml | 22 + ruoyi-ui/src/assets/images/dark.svg | 39 + ruoyi-ui/src/assets/images/light.svg | 39 + .../src/assets/images/login-background.jpg | Bin 0 -> 521275 bytes .../src/assets/images}/pay.png | Bin .../src/assets/images}/profile.jpg | Bin ruoyi-ui/src/assets/logo/logo.png | Bin 0 -> 5663 bytes ruoyi-ui/src/assets/styles/btn.scss | 99 + ruoyi-ui/src/assets/styles/element-ui.scss | 92 + .../src/assets/styles/element-variables.scss | 31 + ruoyi-ui/src/assets/styles/index.scss | 182 + ruoyi-ui/src/assets/styles/mixin.scss | 66 + ruoyi-ui/src/assets/styles/ruoyi.scss | 291 + ruoyi-ui/src/assets/styles/sidebar.scss | 227 + ruoyi-ui/src/assets/styles/transition.scss | 49 + ruoyi-ui/src/assets/styles/variables.scss | 54 + ruoyi-ui/src/components/Breadcrumb/index.vue | 74 + ruoyi-ui/src/components/Crontab/day.vue | 161 + ruoyi-ui/src/components/Crontab/hour.vue | 114 + ruoyi-ui/src/components/Crontab/index.vue | 430 + ruoyi-ui/src/components/Crontab/min.vue | 116 + ruoyi-ui/src/components/Crontab/month.vue | 114 + ruoyi-ui/src/components/Crontab/result.vue | 559 + ruoyi-ui/src/components/Crontab/second.vue | 117 + ruoyi-ui/src/components/Crontab/week.vue | 202 + ruoyi-ui/src/components/Crontab/year.vue | 131 + ruoyi-ui/src/components/DictData/index.js | 49 + ruoyi-ui/src/components/DictTag/index.vue | 89 + ruoyi-ui/src/components/Editor/index.vue | 274 + ruoyi-ui/src/components/FileUpload/index.vue | 216 + ruoyi-ui/src/components/Hamburger/index.vue | 44 + .../src/components/HeaderSearch/index.vue | 198 + ruoyi-ui/src/components/IconSelect/index.vue | 104 + .../src/components/IconSelect/requireIcons.js | 11 + .../src/components/ImagePreview/index.vue | 90 + ruoyi-ui/src/components/ImageUpload/index.vue | 226 + ruoyi-ui/src/components/Pagination/index.vue | 114 + ruoyi-ui/src/components/PanThumb/index.vue | 142 + ruoyi-ui/src/components/ParentView/index.vue | 3 + ruoyi-ui/src/components/RightPanel/index.vue | 106 + .../src/components/RightToolbar/index.vue | 129 + ruoyi-ui/src/components/RuoYi/Doc/index.vue | 21 + ruoyi-ui/src/components/RuoYi/Git/index.vue | 21 + ruoyi-ui/src/components/Screenfull/index.vue | 57 + ruoyi-ui/src/components/SizeSelect/index.vue | 56 + ruoyi-ui/src/components/SvgIcon/index.vue | 61 + ruoyi-ui/src/components/ThemePicker/index.vue | 173 + ruoyi-ui/src/components/TopNav/index.vue | 195 + ruoyi-ui/src/components/iFrame/index.vue | 36 + ruoyi-ui/src/directive/dialog/drag.js | 64 + ruoyi-ui/src/directive/dialog/dragHeight.js | 34 + ruoyi-ui/src/directive/dialog/dragWidth.js | 30 + ruoyi-ui/src/directive/index.js | 23 + ruoyi-ui/src/directive/module/clipboard.js | 54 + ruoyi-ui/src/directive/permission/hasPermi.js | 28 + ruoyi-ui/src/directive/permission/hasRole.js | 28 + ruoyi-ui/src/layout/components/AppMain.vue | 75 + .../layout/components/IframeToggle/index.vue | 33 + .../src/layout/components/InnerLink/index.vue | 47 + ruoyi-ui/src/layout/components/Navbar.vue | 200 + .../src/layout/components/Settings/index.vue | 260 + .../layout/components/Sidebar/FixiOSBug.js | 25 + .../src/layout/components/Sidebar/Item.vue | 33 + .../src/layout/components/Sidebar/Link.vue | 43 + .../src/layout/components/Sidebar/Logo.vue | 93 + .../layout/components/Sidebar/SidebarItem.vue | 100 + .../src/layout/components/Sidebar/index.vue | 57 + .../layout/components/TagsView/ScrollPane.vue | 94 + .../src/layout/components/TagsView/index.vue | 332 + ruoyi-ui/src/layout/components/index.js | 5 + ruoyi-ui/src/layout/index.vue | 111 + ruoyi-ui/src/layout/mixin/ResizeHandler.js | 45 + ruoyi-ui/src/main.js | 86 + ruoyi-ui/src/permission.js | 58 + ruoyi-ui/src/plugins/auth.js | 60 + ruoyi-ui/src/plugins/cache.js | 77 + ruoyi-ui/src/plugins/download.js | 79 + ruoyi-ui/src/plugins/index.js | 20 + ruoyi-ui/src/plugins/modal.js | 83 + ruoyi-ui/src/plugins/tab.js | 71 + ruoyi-ui/src/router/index.js | 183 + ruoyi-ui/src/settings.js | 44 + ruoyi-ui/src/store/getters.js | 19 + ruoyi-ui/src/store/index.js | 25 + ruoyi-ui/src/store/modules/app.js | 66 + ruoyi-ui/src/store/modules/dict.js | 50 + ruoyi-ui/src/store/modules/permission.js | 137 + ruoyi-ui/src/store/modules/settings.js | 42 + ruoyi-ui/src/store/modules/tagsView.js | 228 + ruoyi-ui/src/store/modules/user.js | 101 + ruoyi-ui/src/utils/auth.js | 15 + ruoyi-ui/src/utils/dict/Dict.js | 82 + ruoyi-ui/src/utils/dict/DictConverter.js | 17 + ruoyi-ui/src/utils/dict/DictData.js | 13 + ruoyi-ui/src/utils/dict/DictMeta.js | 38 + ruoyi-ui/src/utils/dict/DictOptions.js | 51 + ruoyi-ui/src/utils/dict/index.js | 33 + ruoyi-ui/src/utils/errorCode.js | 6 + ruoyi-ui/src/utils/generator/config.js | 438 + ruoyi-ui/src/utils/generator/css.js | 18 + .../src/utils/generator/drawingDefault.js | 29 + ruoyi-ui/src/utils/generator/html.js | 359 + ruoyi-ui/src/utils/generator/icon.json | 1 + ruoyi-ui/src/utils/generator/js.js | 235 + ruoyi-ui/src/utils/generator/render.js | 126 + ruoyi-ui/src/utils/index.js | 390 + ruoyi-ui/src/utils/jsencrypt.js | 30 + ruoyi-ui/src/utils/permission.js | 47 + ruoyi-ui/src/utils/request.js | 152 + ruoyi-ui/src/utils/ruoyi.js | 233 + ruoyi-ui/src/utils/scroll-to.js | 58 + ruoyi-ui/src/utils/validate.js | 80 + ruoyi-ui/src/views/dashboard/BarChart.vue | 102 + ruoyi-ui/src/views/dashboard/LineChart.vue | 135 + ruoyi-ui/src/views/dashboard/PanelGroup.vue | 181 + ruoyi-ui/src/views/dashboard/PieChart.vue | 79 + ruoyi-ui/src/views/dashboard/RaddarChart.vue | 116 + ruoyi-ui/src/views/dashboard/mixins/resize.js | 56 + ruoyi-ui/src/views/error/401.vue | 88 + ruoyi-ui/src/views/error/404.vue | 233 + ruoyi-ui/src/views/index.vue | 1067 ++ ruoyi-ui/src/views/index_v1.vue | 98 + ruoyi-ui/src/views/login.vue | 219 + ruoyi-ui/src/views/monitor/cache/index.vue | 148 + ruoyi-ui/src/views/monitor/cache/list.vue | 241 + ruoyi-ui/src/views/monitor/druid/index.vue | 15 + ruoyi-ui/src/views/monitor/job/index.vue | 513 + ruoyi-ui/src/views/monitor/job/log.vue | 295 + .../src/views/monitor/logininfor/index.vue | 246 + ruoyi-ui/src/views/monitor/online/index.vue | 122 + ruoyi-ui/src/views/monitor/operlog/index.vue | 323 + ruoyi-ui/src/views/monitor/server/index.vue | 207 + ruoyi-ui/src/views/redirect.vue | 12 + ruoyi-ui/src/views/register.vue | 210 + ruoyi-ui/src/views/system/config/index.vue | 343 + ruoyi-ui/src/views/system/dept/index.vue | 340 + ruoyi-ui/src/views/system/dict/data.vue | 402 + ruoyi-ui/src/views/system/dict/index.vue | 347 + ruoyi-ui/src/views/system/menu/index.vue | 452 + ruoyi-ui/src/views/system/notice/index.vue | 312 + ruoyi-ui/src/views/system/post/index.vue | 309 + ruoyi-ui/src/views/system/role/authUser.vue | 199 + ruoyi-ui/src/views/system/role/index.vue | 605 + ruoyi-ui/src/views/system/role/selectUser.vue | 138 + ruoyi-ui/src/views/system/user/authRole.vue | 117 + ruoyi-ui/src/views/system/user/index.vue | 676 + .../src/views/system/user/profile/index.vue | 91 + .../views/system/user/profile/resetPwd.vue | 69 + .../views/system/user/profile/userAvatar.vue | 184 + .../views/system/user/profile/userInfo.vue | 88 + .../src/views/tool/build/CodeTypeDialog.vue | 106 + .../src/views/tool/build/DraggableItem.vue | 100 + ruoyi-ui/src/views/tool/build/IconsDialog.vue | 123 + ruoyi-ui/src/views/tool/build/RightPanel.vue | 946 ++ .../src/views/tool/build/TreeNodeDialog.vue | 149 + ruoyi-ui/src/views/tool/build/index.vue | 768 ++ ruoyi-ui/src/views/tool/gen/basicInfoForm.vue | 60 + ruoyi-ui/src/views/tool/gen/createTable.vue | 45 + ruoyi-ui/src/views/tool/gen/editTable.vue | 234 + ruoyi-ui/src/views/tool/gen/genInfoForm.vue | 312 + ruoyi-ui/src/views/tool/gen/importTable.vue | 120 + ruoyi-ui/src/views/tool/gen/index.vue | 354 + ruoyi-ui/src/views/tool/swagger/index.vue | 15 + ruoyi-ui/vue.config.js | 130 + sql/ruoyi.html | 2890 ----- sql/ruoyi.pdm | 4851 -------- sql/{ry_20240112.sql => ry_20231130.sql} | 319 +- 924 files changed, 37241 insertions(+), 128223 deletions(-) create mode 100644 doc/~$环境使用手册.docx create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoDialogController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoFormController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoIconController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoOperateController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoReportController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoTableController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/CustomerModel.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/GoodsModel.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/UserOperateModel.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/DruidController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysCaptchaController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BuildController.java delete mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java create mode 100644 ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties delete mode 100644 ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml rename ruoyi-admin/src/main/resources/{static => }/i18n/messages.properties (96%) delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/auto-refresh/bootstrap-table-auto-refresh.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/custom-view/bootstrap-table-custom-view.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/clear.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/loading.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/bootstrap-table-export.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/tableExport.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/mobile/bootstrap-table-mobile.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/print/bootstrap-table-print.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-columns/bootstrap-table-reorder-columns.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-columns/jquery.dragtable.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-rows/bootstrap-table-reorder-rows.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-rows/jquery.tablednd.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/resizable/bootstrap-table-resizable.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/resizable/jquery.resizableColumns.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/curvedLines.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.pie.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.resize.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.spline.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.symbol.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.tooltip.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/fullscreen/jquery.fullscreen.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/highlight/default.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/highlight/highlight.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/custom.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/green-login.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/green.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/green@2x.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/icheck.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-layout/jquery.layout-latest.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-layout/jquery.layout-latest.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/1_close.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/1_open.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/2.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/3.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/4.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/5.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/6.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/7.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/8.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/diy/9.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/line_conn.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/loading.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/zTreeStandard.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/img/zTreeStandard.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/zTreeStyle.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/line_conn.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/line_conn.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/loading.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/metro.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/img/metro.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/zTreeStyle.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/left_menu.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/left_menu.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/line_conn.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/loading.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/zTreeStandard.gif delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/img/zTreeStandard.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/zTreeStyle.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.core-3.5.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.excheck-3.5.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.exedit-3.5.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.exhide-3.5.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/log v3.x.txt delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jsonview/jquery.jsonview.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/jsonview/jquery.jsonview.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/css/layer.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/layer.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/moon/default.png delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/moon/style.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/css/modules/laydate.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/layui.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/layui/modules/laydate.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/report/echarts/echarts-all.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/report/peity/jquery.peity.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/report/sparkline/jquery.sparkline.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2-bootstrap.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/smartwizard/jquery.smartWizard.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/smartwizard/smart_wizard_all.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/suggest/bootstrap-suggest.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/suggest/bootstrap-suggest.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.eot delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.ttf delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.woff delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/font/summernote.woff2 delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote-zh-CN.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.css delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/typeahead/bootstrap-typeahead.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/typeahead/bootstrap-typeahead.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/additional-methods.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/jquery.validate.extend.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/jquery.validate.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/validate/messages_zh.js delete mode 100644 ruoyi-admin/src/main/resources/static/css/animate.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/bootstrap.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/font-awesome.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/jquery.contextMenu.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/login.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/login.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/skins.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/style.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/style.min.css delete mode 100644 ruoyi-admin/src/main/resources/static/css/zen-checkbox.css delete mode 100644 ruoyi-admin/src/main/resources/static/favicon.ico delete mode 100644 ruoyi-admin/src/main/resources/static/file/rml.txt delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/FontAwesome.otf delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/Simple-Line-Icons.woff2 delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.eot delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.svg delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.ttf delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.woff delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/fontawesome-webfont.woff2 delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.eot delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.svg delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.woff delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/glyphicons-halflings-regular.woff2 delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/iconfont.eot delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/iconfont.svg delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/iconfont.ttf delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/iconfont.woff delete mode 100644 ruoyi-admin/src/main/resources/static/fonts/zenicon.woff delete mode 100644 ruoyi-admin/src/main/resources/static/img/blue.png delete mode 100644 ruoyi-admin/src/main/resources/static/img/loading-upload.gif delete mode 100644 ruoyi-admin/src/main/resources/static/img/loading.gif delete mode 100644 ruoyi-admin/src/main/resources/static/img/locked.png delete mode 100644 ruoyi-admin/src/main/resources/static/img/login-background.jpg delete mode 100644 ruoyi-admin/src/main/resources/static/img/progress.png delete mode 100644 ruoyi-admin/src/main/resources/static/img/qr_code.png delete mode 100644 ruoyi-admin/src/main/resources/static/img/user.png delete mode 100644 ruoyi-admin/src/main/resources/static/js/bootstrap.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/cron.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/jquery-ui-1.10.4.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.contextMenu.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.i18n.properties.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/jquery.tmpl.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/plugins/metisMenu/jquery.metisMenu.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/plugins/slimscroll/jquery.slimscroll.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/resize-tabs.js delete mode 100644 ruoyi-admin/src/main/resources/static/js/three.min.js delete mode 100644 ruoyi-admin/src/main/resources/static/ruoyi.png delete mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/css/ry-ui.css delete mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/index.js delete mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/js/common.js delete mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js delete mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/login.js delete mode 100644 ruoyi-admin/src/main/resources/static/ruoyi/register.js delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/autocomplete.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/basic.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/button.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/cards.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/cxselect.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/datetime.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/duallistbox.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/grid.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/invoice.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/jasny.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/labels_tips.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/localrefresh.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/progress_bars.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/select.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/sortable.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/summernote.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/tabs_panels.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/timeline.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/upload.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/validate.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/form/wizard.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/icon/fontawesome.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/icon/glyphicons.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/dialog.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/form.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/layer.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/check.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/frame1.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/frame2.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/parent.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/modal/table/radio.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/detail.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/other.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/operate/table.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/echarts.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/metrics.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/peity.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/report/sparkline.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/asynTree.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/button.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/child.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/curd.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/customView.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/data.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/detail.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/dynamicColumns.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/editable.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/event.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/export.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/exportSelected.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/fixedColumns.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/footer.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/groupHeader.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/headerStyle.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/image.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/multi.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/other.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/pageGo.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/params.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/print.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/refresh.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/remember.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/reorderColumns.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/reorderRows.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/resizable.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/search.html delete mode 100644 ruoyi-admin/src/main/resources/templates/demo/table/subdata.html delete mode 100644 ruoyi-admin/src/main/resources/templates/error/404.html delete mode 100644 ruoyi-admin/src/main/resources/templates/error/500.html delete mode 100644 ruoyi-admin/src/main/resources/templates/error/service.html delete mode 100644 ruoyi-admin/src/main/resources/templates/error/unauth.html delete mode 100644 ruoyi-admin/src/main/resources/templates/include.html delete mode 100644 ruoyi-admin/src/main/resources/templates/index-topnav.html delete mode 100644 ruoyi-admin/src/main/resources/templates/index.html delete mode 100644 ruoyi-admin/src/main/resources/templates/lock.html delete mode 100644 ruoyi-admin/src/main/resources/templates/login.html delete mode 100644 ruoyi-admin/src/main/resources/templates/main.html delete mode 100644 ruoyi-admin/src/main/resources/templates/main_v1.html delete mode 100644 ruoyi-admin/src/main/resources/templates/monitor/cache/cache.html delete mode 100644 ruoyi-admin/src/main/resources/templates/monitor/logininfor/logininfor.html delete mode 100644 ruoyi-admin/src/main/resources/templates/monitor/online/online.html delete mode 100644 ruoyi-admin/src/main/resources/templates/monitor/operlog/detail.html delete mode 100644 ruoyi-admin/src/main/resources/templates/monitor/operlog/operlog.html delete mode 100644 ruoyi-admin/src/main/resources/templates/monitor/server/server.html delete mode 100644 ruoyi-admin/src/main/resources/templates/register.html delete mode 100644 ruoyi-admin/src/main/resources/templates/skin.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/config/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/config/config.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/config/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/dept.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dept/tree.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/data/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/data/data.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/data/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/tree.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/dict/type/type.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/icon.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/menu.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/menu/tree.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/notice/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/notice/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/notice/notice.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/notice/view.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/post/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/post/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/post/post.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/role/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/role/authUser.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/role/dataScope.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/role/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/role/role.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/role/selectUser.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/add.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/authRole.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/deptTree.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/edit.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/profile/avatar.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/profile/profile.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/profile/resetPwd.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/resetPwd.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/user.html delete mode 100644 ruoyi-admin/src/main/resources/templates/system/user/view.html delete mode 100644 ruoyi-admin/src/main/resources/templates/tool/build/build.html create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/PermissionConstants.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java delete 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/TreeSelect.java delete 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/model/LoginBody.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/enums/OnlineStatus.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/RoleBlockedException.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserBlockedException.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserDeleteException.java delete 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/filter/PropertyPreExcludeFilter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java rename ruoyi-common/src/main/java/com/ruoyi/common/{xss => filter}/XssFilter.java (91%) create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/json/JSON.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/json/JSONObject.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/CookieUtils.java delete 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/SecurityUtils.java delete 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/http/HttpHelper.java rename ruoyi-common/src/main/java/com/ruoyi/common/utils/{ => ip}/AddressUtils.java (83%) rename ruoyi-common/src/main/java/com/ruoyi/common/utils/{ => ip}/IpUtils.java (96%) delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java delete 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/sign/Base64.java rename ruoyi-common/src/main/java/com/ruoyi/common/utils/{security => sign}/Md5Utils.java (93%) delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/PermissionsAspect.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java rename {ruoyi-common/src/main/java/com/ruoyi/common => ruoyi-framework/src/main/java/com/ruoyi/framework}/config/ServerConfig.java (92%) delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java rename {ruoyi-common/src/main/java/com/ruoyi/common/config/thread => ruoyi-framework/src/main/java/com/ruoyi/framework/config}/ThreadPoolConfig.java (95%) rename {ruoyi-common/src/main/java/com/ruoyi/common/config => ruoyi-framework/src/main/java/com/ruoyi/framework}/datasource/DynamicDataSourceContextHolder.java (92%) create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java rename {ruoyi-common/src/main/java/com/ruoyi/common/core => ruoyi-framework/src/main/java/com/ruoyi/framework/security}/context/PermissionContextHolder.java (94%) create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysRegisterService.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/util/AuthorizationUtils.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/CustomShiroFilterFactoryBean.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/captcha/CaptchaValidateFilter.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/kickout/KickoutSessionFilter.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ConfigService.java delete 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/SysLoginService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java create mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java rename ruoyi-generator/src/main/java/com/ruoyi/generator/service/{impl => }/GenTableServiceImpl.java (92%) delete mode 100644 ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableColumnServiceImpl.java delete mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/createTable.html delete mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/edit.html delete mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/gen.html delete mode 100644 ruoyi-generator/src/main/resources/templates/tool/gen/importTable.html delete mode 100644 ruoyi-generator/src/main/resources/vm/html/add.html.vm delete mode 100644 ruoyi-generator/src/main/resources/vm/html/edit.html.vm delete mode 100644 ruoyi-generator/src/main/resources/vm/html/list-tree.html.vm delete mode 100644 ruoyi-generator/src/main/resources/vm/html/list.html.vm delete mode 100644 ruoyi-generator/src/main/resources/vm/html/tree.html.vm create mode 100644 ruoyi-generator/src/main/resources/vm/js/api.js.vm create mode 100644 ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm create mode 100644 ruoyi-generator/src/main/resources/vm/vue/index.vue.vm create mode 100644 ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm create mode 100644 ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm delete mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/add.html delete mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/cron.html delete mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html delete mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/edit.html delete mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/job.html delete mode 100644 ruoyi-quartz/src/main/resources/templates/monitor/job/jobLog.html create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java delete mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java delete mode 100644 ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml create mode 100644 ruoyi-ui/.editorconfig create mode 100644 ruoyi-ui/.env.development create mode 100644 ruoyi-ui/.env.production create mode 100644 ruoyi-ui/.env.staging create mode 100644 ruoyi-ui/.eslintignore create mode 100644 ruoyi-ui/.eslintrc.js create mode 100644 ruoyi-ui/README.md create mode 100644 ruoyi-ui/babel.config.js create mode 100644 ruoyi-ui/bin/build.bat create mode 100644 ruoyi-ui/bin/package.bat create mode 100644 ruoyi-ui/bin/run-web.bat create mode 100644 ruoyi-ui/build/index.js create mode 100644 ruoyi-ui/package.json create mode 100644 ruoyi-ui/public/favicon.ico rename {ruoyi-admin/src/main/resources/static => ruoyi-ui/public}/html/ie.html (99%) create mode 100644 ruoyi-ui/public/index.html create mode 100644 ruoyi-ui/public/robots.txt create mode 100644 ruoyi-ui/src/App.vue create mode 100644 ruoyi-ui/src/api/login.js create mode 100644 ruoyi-ui/src/api/menu.js create mode 100644 ruoyi-ui/src/api/monitor/cache.js create mode 100644 ruoyi-ui/src/api/monitor/job.js create mode 100644 ruoyi-ui/src/api/monitor/jobLog.js create mode 100644 ruoyi-ui/src/api/monitor/logininfor.js create mode 100644 ruoyi-ui/src/api/monitor/online.js create mode 100644 ruoyi-ui/src/api/monitor/operlog.js create mode 100644 ruoyi-ui/src/api/monitor/server.js create mode 100644 ruoyi-ui/src/api/system/config.js create mode 100644 ruoyi-ui/src/api/system/dept.js create mode 100644 ruoyi-ui/src/api/system/dict/data.js create mode 100644 ruoyi-ui/src/api/system/dict/type.js create mode 100644 ruoyi-ui/src/api/system/menu.js create mode 100644 ruoyi-ui/src/api/system/notice.js create mode 100644 ruoyi-ui/src/api/system/post.js create mode 100644 ruoyi-ui/src/api/system/role.js create mode 100644 ruoyi-ui/src/api/system/user.js create mode 100644 ruoyi-ui/src/api/tool/gen.js create mode 100644 ruoyi-ui/src/assets/401_images/401.gif create mode 100644 ruoyi-ui/src/assets/404_images/404.png create mode 100644 ruoyi-ui/src/assets/404_images/404_cloud.png create mode 100644 ruoyi-ui/src/assets/icons/index.js create mode 100644 ruoyi-ui/src/assets/icons/svg/404.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/bug.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/build.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/button.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/cascader.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/chart.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/checkbox.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/clipboard.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/code.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/color.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/component.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/dashboard.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/date-range.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/date.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/dict.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/documentation.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/download.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/drag.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/druid.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/edit.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/education.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/email.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/example.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/excel.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/eye-open.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/eye.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/form.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/fullscreen.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/github.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/guide.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/icon.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/input.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/international.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/job.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/language.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/link.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/list.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/lock.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/log.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/logininfor.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/message.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/money.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/monitor.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/nested.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/number.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/online.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/password.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/pdf.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/people.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/peoples.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/phone.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/post.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/qq.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/question.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/radio.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/rate.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/redis-list.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/redis.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/row.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/search.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/select.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/server.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/shopping.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/size.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/skill.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/slider.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/star.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/swagger.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/switch.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/system.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/tab.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/table.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/textarea.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/theme.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/time-range.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/time.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/tool.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/tree-table.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/tree.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/upload.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/user.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/validCode.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/wechat.svg create mode 100644 ruoyi-ui/src/assets/icons/svg/zip.svg create mode 100644 ruoyi-ui/src/assets/icons/svgo.yml create mode 100644 ruoyi-ui/src/assets/images/dark.svg create mode 100644 ruoyi-ui/src/assets/images/light.svg create mode 100644 ruoyi-ui/src/assets/images/login-background.jpg rename {ruoyi-admin/src/main/resources/static/img => ruoyi-ui/src/assets/images}/pay.png (100%) rename {ruoyi-admin/src/main/resources/static/img => ruoyi-ui/src/assets/images}/profile.jpg (100%) create mode 100644 ruoyi-ui/src/assets/logo/logo.png create mode 100644 ruoyi-ui/src/assets/styles/btn.scss create mode 100644 ruoyi-ui/src/assets/styles/element-ui.scss create mode 100644 ruoyi-ui/src/assets/styles/element-variables.scss create mode 100644 ruoyi-ui/src/assets/styles/index.scss create mode 100644 ruoyi-ui/src/assets/styles/mixin.scss create mode 100644 ruoyi-ui/src/assets/styles/ruoyi.scss create mode 100644 ruoyi-ui/src/assets/styles/sidebar.scss create mode 100644 ruoyi-ui/src/assets/styles/transition.scss create mode 100644 ruoyi-ui/src/assets/styles/variables.scss create mode 100644 ruoyi-ui/src/components/Breadcrumb/index.vue create mode 100644 ruoyi-ui/src/components/Crontab/day.vue create mode 100644 ruoyi-ui/src/components/Crontab/hour.vue create mode 100644 ruoyi-ui/src/components/Crontab/index.vue create mode 100644 ruoyi-ui/src/components/Crontab/min.vue create mode 100644 ruoyi-ui/src/components/Crontab/month.vue create mode 100644 ruoyi-ui/src/components/Crontab/result.vue create mode 100644 ruoyi-ui/src/components/Crontab/second.vue create mode 100644 ruoyi-ui/src/components/Crontab/week.vue create mode 100644 ruoyi-ui/src/components/Crontab/year.vue create mode 100644 ruoyi-ui/src/components/DictData/index.js create mode 100644 ruoyi-ui/src/components/DictTag/index.vue create mode 100644 ruoyi-ui/src/components/Editor/index.vue create mode 100644 ruoyi-ui/src/components/FileUpload/index.vue create mode 100644 ruoyi-ui/src/components/Hamburger/index.vue create mode 100644 ruoyi-ui/src/components/HeaderSearch/index.vue create mode 100644 ruoyi-ui/src/components/IconSelect/index.vue create mode 100644 ruoyi-ui/src/components/IconSelect/requireIcons.js create mode 100644 ruoyi-ui/src/components/ImagePreview/index.vue create mode 100644 ruoyi-ui/src/components/ImageUpload/index.vue create mode 100644 ruoyi-ui/src/components/Pagination/index.vue create mode 100644 ruoyi-ui/src/components/PanThumb/index.vue create mode 100644 ruoyi-ui/src/components/ParentView/index.vue create mode 100644 ruoyi-ui/src/components/RightPanel/index.vue create mode 100644 ruoyi-ui/src/components/RightToolbar/index.vue create mode 100644 ruoyi-ui/src/components/RuoYi/Doc/index.vue create mode 100644 ruoyi-ui/src/components/RuoYi/Git/index.vue create mode 100644 ruoyi-ui/src/components/Screenfull/index.vue create mode 100644 ruoyi-ui/src/components/SizeSelect/index.vue create mode 100644 ruoyi-ui/src/components/SvgIcon/index.vue create mode 100644 ruoyi-ui/src/components/ThemePicker/index.vue create mode 100644 ruoyi-ui/src/components/TopNav/index.vue create mode 100644 ruoyi-ui/src/components/iFrame/index.vue create mode 100644 ruoyi-ui/src/directive/dialog/drag.js create mode 100644 ruoyi-ui/src/directive/dialog/dragHeight.js create mode 100644 ruoyi-ui/src/directive/dialog/dragWidth.js create mode 100644 ruoyi-ui/src/directive/index.js create mode 100644 ruoyi-ui/src/directive/module/clipboard.js create mode 100644 ruoyi-ui/src/directive/permission/hasPermi.js create mode 100644 ruoyi-ui/src/directive/permission/hasRole.js create mode 100644 ruoyi-ui/src/layout/components/AppMain.vue create mode 100644 ruoyi-ui/src/layout/components/IframeToggle/index.vue create mode 100644 ruoyi-ui/src/layout/components/InnerLink/index.vue create mode 100644 ruoyi-ui/src/layout/components/Navbar.vue create mode 100644 ruoyi-ui/src/layout/components/Settings/index.vue create mode 100644 ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js create mode 100644 ruoyi-ui/src/layout/components/Sidebar/Item.vue create mode 100644 ruoyi-ui/src/layout/components/Sidebar/Link.vue create mode 100644 ruoyi-ui/src/layout/components/Sidebar/Logo.vue create mode 100644 ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue create mode 100644 ruoyi-ui/src/layout/components/Sidebar/index.vue create mode 100644 ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue create mode 100644 ruoyi-ui/src/layout/components/TagsView/index.vue create mode 100644 ruoyi-ui/src/layout/components/index.js create mode 100644 ruoyi-ui/src/layout/index.vue create mode 100644 ruoyi-ui/src/layout/mixin/ResizeHandler.js create mode 100644 ruoyi-ui/src/main.js create mode 100644 ruoyi-ui/src/permission.js create mode 100644 ruoyi-ui/src/plugins/auth.js create mode 100644 ruoyi-ui/src/plugins/cache.js create mode 100644 ruoyi-ui/src/plugins/download.js create mode 100644 ruoyi-ui/src/plugins/index.js create mode 100644 ruoyi-ui/src/plugins/modal.js create mode 100644 ruoyi-ui/src/plugins/tab.js create mode 100644 ruoyi-ui/src/router/index.js create mode 100644 ruoyi-ui/src/settings.js create mode 100644 ruoyi-ui/src/store/getters.js create mode 100644 ruoyi-ui/src/store/index.js create mode 100644 ruoyi-ui/src/store/modules/app.js create mode 100644 ruoyi-ui/src/store/modules/dict.js create mode 100644 ruoyi-ui/src/store/modules/permission.js create mode 100644 ruoyi-ui/src/store/modules/settings.js create mode 100644 ruoyi-ui/src/store/modules/tagsView.js create mode 100644 ruoyi-ui/src/store/modules/user.js create mode 100644 ruoyi-ui/src/utils/auth.js create mode 100644 ruoyi-ui/src/utils/dict/Dict.js create mode 100644 ruoyi-ui/src/utils/dict/DictConverter.js create mode 100644 ruoyi-ui/src/utils/dict/DictData.js create mode 100644 ruoyi-ui/src/utils/dict/DictMeta.js create mode 100644 ruoyi-ui/src/utils/dict/DictOptions.js create mode 100644 ruoyi-ui/src/utils/dict/index.js create mode 100644 ruoyi-ui/src/utils/errorCode.js create mode 100644 ruoyi-ui/src/utils/generator/config.js create mode 100644 ruoyi-ui/src/utils/generator/css.js create mode 100644 ruoyi-ui/src/utils/generator/drawingDefault.js create mode 100644 ruoyi-ui/src/utils/generator/html.js create mode 100644 ruoyi-ui/src/utils/generator/icon.json create mode 100644 ruoyi-ui/src/utils/generator/js.js create mode 100644 ruoyi-ui/src/utils/generator/render.js create mode 100644 ruoyi-ui/src/utils/index.js create mode 100644 ruoyi-ui/src/utils/jsencrypt.js create mode 100644 ruoyi-ui/src/utils/permission.js create mode 100644 ruoyi-ui/src/utils/request.js create mode 100644 ruoyi-ui/src/utils/ruoyi.js create mode 100644 ruoyi-ui/src/utils/scroll-to.js create mode 100644 ruoyi-ui/src/utils/validate.js create mode 100644 ruoyi-ui/src/views/dashboard/BarChart.vue create mode 100644 ruoyi-ui/src/views/dashboard/LineChart.vue create mode 100644 ruoyi-ui/src/views/dashboard/PanelGroup.vue create mode 100644 ruoyi-ui/src/views/dashboard/PieChart.vue create mode 100644 ruoyi-ui/src/views/dashboard/RaddarChart.vue create mode 100644 ruoyi-ui/src/views/dashboard/mixins/resize.js create mode 100644 ruoyi-ui/src/views/error/401.vue create mode 100644 ruoyi-ui/src/views/error/404.vue create mode 100644 ruoyi-ui/src/views/index.vue create mode 100644 ruoyi-ui/src/views/index_v1.vue create mode 100644 ruoyi-ui/src/views/login.vue create mode 100644 ruoyi-ui/src/views/monitor/cache/index.vue create mode 100644 ruoyi-ui/src/views/monitor/cache/list.vue create mode 100644 ruoyi-ui/src/views/monitor/druid/index.vue create mode 100644 ruoyi-ui/src/views/monitor/job/index.vue create mode 100644 ruoyi-ui/src/views/monitor/job/log.vue create mode 100644 ruoyi-ui/src/views/monitor/logininfor/index.vue create mode 100644 ruoyi-ui/src/views/monitor/online/index.vue create mode 100644 ruoyi-ui/src/views/monitor/operlog/index.vue create mode 100644 ruoyi-ui/src/views/monitor/server/index.vue create mode 100644 ruoyi-ui/src/views/redirect.vue create mode 100644 ruoyi-ui/src/views/register.vue create mode 100644 ruoyi-ui/src/views/system/config/index.vue create mode 100644 ruoyi-ui/src/views/system/dept/index.vue create mode 100644 ruoyi-ui/src/views/system/dict/data.vue create mode 100644 ruoyi-ui/src/views/system/dict/index.vue create mode 100644 ruoyi-ui/src/views/system/menu/index.vue create mode 100644 ruoyi-ui/src/views/system/notice/index.vue create mode 100644 ruoyi-ui/src/views/system/post/index.vue create mode 100644 ruoyi-ui/src/views/system/role/authUser.vue create mode 100644 ruoyi-ui/src/views/system/role/index.vue create mode 100644 ruoyi-ui/src/views/system/role/selectUser.vue create mode 100644 ruoyi-ui/src/views/system/user/authRole.vue create mode 100644 ruoyi-ui/src/views/system/user/index.vue create mode 100644 ruoyi-ui/src/views/system/user/profile/index.vue create mode 100644 ruoyi-ui/src/views/system/user/profile/resetPwd.vue create mode 100644 ruoyi-ui/src/views/system/user/profile/userAvatar.vue create mode 100644 ruoyi-ui/src/views/system/user/profile/userInfo.vue create mode 100644 ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue create mode 100644 ruoyi-ui/src/views/tool/build/DraggableItem.vue create mode 100644 ruoyi-ui/src/views/tool/build/IconsDialog.vue create mode 100644 ruoyi-ui/src/views/tool/build/RightPanel.vue create mode 100644 ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue create mode 100644 ruoyi-ui/src/views/tool/build/index.vue create mode 100644 ruoyi-ui/src/views/tool/gen/basicInfoForm.vue create mode 100644 ruoyi-ui/src/views/tool/gen/createTable.vue create mode 100644 ruoyi-ui/src/views/tool/gen/editTable.vue create mode 100644 ruoyi-ui/src/views/tool/gen/genInfoForm.vue create mode 100644 ruoyi-ui/src/views/tool/gen/importTable.vue create mode 100644 ruoyi-ui/src/views/tool/gen/index.vue create mode 100644 ruoyi-ui/src/views/tool/swagger/index.vue create mode 100644 ruoyi-ui/vue.config.js delete mode 100644 sql/ruoyi.html delete mode 100644 sql/ruoyi.pdm rename sql/{ry_20240112.sql => ry_20231130.sql} (62%) diff --git a/README.md b/README.md index 09a4f7492..f39163083 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,26 @@

- logo + logo

-

RuoYi v4.7.8

-

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

+

RuoYi v3.8.7

+

基于SpringBoot+Vue前后端分离的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 框架。 +* 前端采用Vue、Element UI。 +* 后端采用Spring Boot、Spring Security、Redis & Jwt。 +* 权限认证使用Jwt,支持多终端认证系统。 +* 支持加载动态权限菜单,多方式轻松权限控制。 +* 高效率开发,使用代码生成器可以一键生成前后端代码。 +* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。 +* 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast),Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。 +* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) * 阿里云折扣场:[点我进入](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)   @@ -39,7 +41,7 @@ 13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 14. 系统接口:根据业务代码自动生成相关的api接口文档。 15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 -16. 缓存监控:对系统的缓存查询,删除、清空等操作。 +16. 缓存监控:对系统的缓存信息查询,命令统计等。 17. 在线构建器:拖动表单元素生成相应的HTML代码。 18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 @@ -48,55 +50,47 @@ - admin/admin123 - 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 -演示地址:http://ruoyi.vip +演示地址:http://vue.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) [![加入QQ群](https://img.shields.io/badge/174942938-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=lqMHu_5Fskm7H2S1vNAQTtzAUokVydwc&authKey=ptw0Fpch5pbNocML3CIJKKqZBaq2DI7cusKuzIgfMNiY3t9Pvd9hP%2BA8WYx3yaY1&noverify=0&group_code=174942938) \ No newline at end of file +QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) 点击按钮入群。 \ No newline at end of file diff --git a/doc/~$环境使用手册.docx b/doc/~$环境使用手册.docx new file mode 100644 index 0000000000000000000000000000000000000000..0a832e2c2593db75d3108280f05d350124f0e464 GIT binary patch literal 162 pcmd-`&PdIRFG;IlAO&zRBr{|%q%!02sIq%n}Dn-rTE7ywx82Co1B literal 0 HcmV?d00001 diff --git a/doc/若依环境使用手册.docx b/doc/若依环境使用手册.docx index fa5b62d7dcc9b9a31cc58db611ecac7b00cc385a..9e4daef4d9be2e445419109a02eaf321cd4d537e 100644 GIT binary patch delta 19487 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 QH25I~5Cg<9Bn1lm$ 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 y5&>%RrZ4JXvt - - 4.0.0 - + + 4.0.0 + com.ruoyi ruoyi - 4.7.8 + 3.8.7 ruoyi http://www.ruoyi.vip 若依管理系统 - 4.7.8 + 3.8.7 UTF-8 UTF-8 1.8 3.1.1 - 1.13.0 5.3.33 - 2.1.0 1.2.20 1.21 - 2.3.3 3.0.0 + 2.3.3 1.4.7 - 1.2.83 + 2.0.43 6.5.0 2.13.0 4.1.2 2.3 + 0.9.1 @@ -60,41 +60,6 @@ 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} - @@ -153,11 +118,25 @@ - com.alibaba - fastjson + com.alibaba.fastjson2 + fastjson2 ${fastjson.version} + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + pro.fessional + kaptcha + ${kaptcha.version} + + com.ruoyi @@ -206,11 +185,6 @@ pom - - - - - diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 2e28443e9..b97ef8863 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 4.7.8 + 3.8.7 4.0.0 jar @@ -17,12 +17,6 @@ - - - org.springframework.boot - spring-boot-starter-thymeleaf - - org.springframework.boot @@ -43,7 +37,7 @@ 1.6.2 - + mysql mysql-connector-java @@ -89,44 +83,12 @@ org.apache.maven.plugins maven-war-plugin - 3.0.0 + 3.1.0 false ${project.artifactId} - - + ${project.artifactId} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java index a0e726bd4..e3c56ee54 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -27,4 +27,4 @@ public class RuoYiApplication " | | \\ / \\ / \n" + " ''-' `'-' `-..-' "); } -} \ No newline at end of file +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java new file mode 100644 index 000000000..d2d6e8c47 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -0,0 +1,94 @@ +package com.ruoyi.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import com.google.code.kaptcha.Producer; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.sign.Base64; +import com.ruoyi.common.utils.uuid.IdUtils; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@RestController +public class CaptchaController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = RuoYiConfig.getCaptchaType(); + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} 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 index c2db93ac9..d51d61d00 100644 --- 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 @@ -8,26 +8,25 @@ 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.bind.annotation.RestController; 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; +import com.ruoyi.framework.config.ServerConfig; /** * 通用请求处理 * * @author ruoyi */ -@Controller +@RestController @RequestMapping("/common") public class CommonController { @@ -74,7 +73,6 @@ public class CommonController * 通用上传请求(单个) */ @PostMapping("/upload") - @ResponseBody public AjaxResult uploadFile(MultipartFile file) throws Exception { try @@ -101,7 +99,6 @@ public class CommonController * 通用上传请求(多个) */ @PostMapping("/uploads") - @ResponseBody public AjaxResult uploadFiles(List files) throws Exception { try 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 deleted file mode 100644 index 6b95a7420..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoDialogController.java +++ /dev/null @@ -1,98 +0,0 @@ -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 deleted file mode 100644 index 0f72816fc..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoFormController.java +++ /dev/null @@ -1,399 +0,0 @@ -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 deleted file mode 100644 index 490c3e061..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoIconController.java +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index 95412d84f..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoOperateController.java +++ /dev/null @@ -1,326 +0,0 @@ -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 deleted file mode 100644 index 318e8f08b..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoReportController.java +++ /dev/null @@ -1,53 +0,0 @@ -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 deleted file mode 100644 index ed1ab0308..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoTableController.java +++ /dev/null @@ -1,846 +0,0 @@ -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 deleted file mode 100644 index d1aebf2ba..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/CustomerModel.java +++ /dev/null @@ -1,116 +0,0 @@ -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 deleted file mode 100644 index 897a010e6..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/GoodsModel.java +++ /dev/null @@ -1,99 +0,0 @@ -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 deleted file mode 100644 index 3324cc7ca..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/UserOperateModel.java +++ /dev/null @@ -1,149 +0,0 @@ -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 index 995ce8af2..41029fd91 100644 --- 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 @@ -1,90 +1,120 @@ package com.ruoyi.web.controller.monitor; -import org.apache.shiro.authz.annotation.RequiresPermissions; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import com.ruoyi.common.core.controller.BaseController; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.framework.web.service.CacheService; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysCache; /** * 缓存监控 * * @author ruoyi */ -@Controller +@RestController @RequestMapping("/monitor/cache") -public class CacheController extends BaseController +public class CacheController { - private String prefix = "monitor/cache"; - @Autowired - private CacheService cacheService; + private RedisTemplate redisTemplate; - @RequiresPermissions("monitor:cache:view") + private final static List caches = new ArrayList(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") @GetMapping() - public String cache(ModelMap mmap) + public AjaxResult getInfo() throws Exception { - mmap.put("cacheNames", cacheService.getCacheNames()); - return prefix + "/cache"; + Properties info = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info()); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) connection -> connection.dbSize()); + + Map result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); } - @RequiresPermissions("monitor:cache:view") - @PostMapping("/getNames") - public String getCacheNames(String fragment, ModelMap mmap) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public AjaxResult cache() { - mmap.put("cacheNames", cacheService.getCacheNames()); - return prefix + "/cache::" + fragment; + return AjaxResult.success(caches); } - @RequiresPermissions("monitor:cache:view") - @PostMapping("/getKeys") - public String getCacheKeys(String fragment, String cacheName, ModelMap mmap) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) { - mmap.put("cacheName", cacheName); - mmap.put("cacheKeys", cacheService.getCacheKeys(cacheName)); - return prefix + "/cache::" + fragment; + Set cacheKeys = redisTemplate.keys(cacheName + "*"); + return AjaxResult.success(cacheKeys); } - @RequiresPermissions("monitor:cache:view") - @PostMapping("/getValue") - public String getCacheValue(String fragment, String cacheName, String cacheKey, ModelMap mmap) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) { - mmap.put("cacheName", cacheName); - mmap.put("cacheKey", cacheKey); - mmap.put("cacheValue", cacheService.getCacheValue(cacheName, cacheKey)); - return prefix + "/cache::" + fragment; + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue); + return AjaxResult.success(sysCache); } - @RequiresPermissions("monitor:cache:view") - @PostMapping("/clearCacheName") - @ResponseBody - public AjaxResult clearCacheName(String cacheName, ModelMap mmap) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) { - cacheService.clearCacheName(cacheName); + Collection cacheKeys = redisTemplate.keys(cacheName + "*"); + redisTemplate.delete(cacheKeys); return AjaxResult.success(); } - @RequiresPermissions("monitor:cache:view") - @PostMapping("/clearCacheKey") - @ResponseBody - public AjaxResult clearCacheKey(String cacheName, String cacheKey, ModelMap mmap) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) { - cacheService.clearCacheKey(cacheName, cacheKey); + redisTemplate.delete(cacheKey); return AjaxResult.success(); } - @RequiresPermissions("monitor:cache:view") - @GetMapping("/clearAll") - @ResponseBody - public AjaxResult clearAll(ModelMap mmap) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() { - cacheService.clearAll(); + Collection cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); 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 deleted file mode 100644 index 98e4242f5..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/DruidController.java +++ /dev/null @@ -1,26 +0,0 @@ -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 index 764197d39..082027beb 100644 --- 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 @@ -1,11 +1,10 @@ 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.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import com.ruoyi.common.core.controller.BaseController; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.framework.web.domain.Server; /** @@ -13,19 +12,16 @@ import com.ruoyi.framework.web.domain.Server; * * @author ruoyi */ -@Controller +@RestController @RequestMapping("/monitor/server") -public class ServerController extends BaseController +public class ServerController { - private String prefix = "monitor/server"; - - @RequiresPermissions("monitor:server:view") + @PreAuthorize("@ss.hasPermi('monitor:server:list')") @GetMapping() - public String server(ModelMap mmap) throws Exception + public AjaxResult getInfo() throws Exception { Server server = new Server(); server.copyTo(); - mmap.put("server", server); - return prefix + "/server"; + return AjaxResult.success(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 index c18c860dd..f9f262e00 100644 --- 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 @@ -1,20 +1,22 @@ 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 javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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.framework.web.service.SysPasswordService; import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.service.ISysLogininforService; @@ -23,28 +25,18 @@ import com.ruoyi.system.service.ISysLogininforService; * * @author ruoyi */ -@Controller +@RestController @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 + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") public TableDataInfo list(SysLogininfor logininfor) { startPage(); @@ -53,42 +45,38 @@ public class SysLogininforController extends BaseController } @Log(title = "登录日志", businessType = BusinessType.EXPORT) - @RequiresPermissions("monitor:logininfor:export") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysLogininfor logininfor) + public void export(HttpServletResponse response, SysLogininfor logininfor) { List list = logininforService.selectLogininforList(logininfor); ExcelUtil util = new ExcelUtil(SysLogininfor.class); - return util.exportExcel(list, "登录日志"); + util.exportExcel(response, list, "登录日志"); } - @RequiresPermissions("monitor:logininfor:remove") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") @Log(title = "登录日志", businessType = BusinessType.DELETE) - @PostMapping("/remove") - @ResponseBody - public AjaxResult remove(String ids) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) { - return toAjax(logininforService.deleteLogininforByIds(ids)); + return toAjax(logininforService.deleteLogininforByIds(infoIds)); } - - @RequiresPermissions("monitor:logininfor:remove") + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") @Log(title = "登录日志", businessType = BusinessType.CLEAN) - @PostMapping("/clean") - @ResponseBody + @DeleteMapping("/clean") public AjaxResult clean() { logininforService.cleanLogininfor(); return success(); } - @RequiresPermissions("monitor:logininfor:unlock") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") @Log(title = "账户解锁", businessType = BusinessType.OTHER) - @PostMapping("/unlock") - @ResponseBody - public AjaxResult unlock(String loginName) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) { - passwordService.clearLoginRecordCache(loginName); + passwordService.clearLoginRecordCache(userName); 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 index 2375fe6fc..5378424a8 100644 --- 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 @@ -1,15 +1,15 @@ package com.ruoyi.web.controller.monitor; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +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.security.access.prepost.PreAuthorize; +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.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -24,25 +24,15 @@ import com.ruoyi.system.service.ISysOperLogService; * * @author ruoyi */ -@Controller +@RestController @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 + @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") + @GetMapping("/list") public TableDataInfo list(SysOperLog operLog) { startPage(); @@ -51,37 +41,26 @@ public class SysOperlogController extends BaseController } @Log(title = "操作日志", businessType = BusinessType.EXPORT) - @RequiresPermissions("monitor:operlog:export") + @PreAuthorize("@ss.hasPermi('monitor:operlog:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysOperLog operLog) + public void export(HttpServletResponse response, SysOperLog operLog) { List list = operLogService.selectOperLogList(operLog); ExcelUtil util = new ExcelUtil(SysOperLog.class); - return util.exportExcel(list, "操作日志"); + util.exportExcel(response, list, "操作日志"); } @Log(title = "操作日志", businessType = BusinessType.DELETE) - @RequiresPermissions("monitor:operlog:remove") - @PostMapping("/remove") - @ResponseBody - public AjaxResult remove(String ids) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable Long[] operIds) { - return toAjax(operLogService.deleteOperLogByIds(ids)); + return toAjax(operLogService.deleteOperLogByIds(operIds)); } - @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 + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/clean") public AjaxResult clean() { operLogService.cleanOperLog(); 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 index 3ae9e81ea..7c343e210 100644 --- 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 @@ -1,24 +1,25 @@ package com.ruoyi.web.controller.monitor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; 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.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.page.TableDataInfo; -import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.core.redis.RedisCache; 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.common.utils.StringUtils; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.service.ISysUserOnlineService; @@ -27,62 +28,56 @@ import com.ruoyi.system.service.ISysUserOnlineService; * * @author ruoyi */ -@Controller +@RestController @RequestMapping("/monitor/online") public class SysUserOnlineController extends BaseController { - private String prefix = "monitor/online"; - @Autowired private ISysUserOnlineService userOnlineService; @Autowired - private OnlineSessionDAO onlineSessionDAO; + private RedisCache redisCache; - @RequiresPermissions("monitor:online:view") - @GetMapping() - public String online() + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) { - 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)) + Collection keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List userOnlineList = new ArrayList(); + for (String key : keys) { - SysUserOnline online = userOnlineService.selectOnlineById(sessionId); - if (online == null) + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) { - return error("用户已下线"); + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); } - OnlineSession onlineSession = (OnlineSession) onlineSessionDAO.readSession(online.getSessionId()); - if (onlineSession == null) + else if (StringUtils.isNotEmpty(ipaddr)) { - return error("用户已下线"); + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); } - if (sessionId.equals(ShiroUtils.getSessionId())) + else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) { - return error("当前登录用户无法强退"); + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } + else + { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); } - onlineSessionDAO.delete(onlineSession); - online.setStatus(OnlineStatus.off_line); - userOnlineService.saveOnline(online); - userOnlineService.removeUserCache(online.getLoginName(), sessionId); } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) + { + redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); 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 deleted file mode 100644 index 230cc3b29..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysCaptchaController.java +++ /dev/null @@ -1,92 +0,0 @@ -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 index 9c0bdbe22..95bcaeceb 100644 --- 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 @@ -1,16 +1,18 @@ package com.ruoyi.web.controller.system; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -25,28 +27,18 @@ import com.ruoyi.system.service.ISysConfigService; * * @author ruoyi */ -@Controller +@RestController @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 + @PreAuthorize("@ss.hasPermi('system:config:list')") + @GetMapping("/list") public TableDataInfo list(SysConfig config) { startPage(); @@ -55,103 +47,87 @@ public class SysConfigController extends BaseController } @Log(title = "参数管理", businessType = BusinessType.EXPORT) - @RequiresPermissions("system:config:export") + @PreAuthorize("@ss.hasPermi('system:config:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysConfig config) + public void export(HttpServletResponse response, SysConfig config) { List list = configService.selectConfigList(config); ExcelUtil util = new ExcelUtil(SysConfig.class); - return util.exportExcel(list, "参数数据"); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return success(configService.selectConfigByKey(configKey)); } /** * 新增参数配置 */ - @GetMapping("/add") - public String add() - { - return prefix + "/add"; - } - - /** - * 新增保存参数配置 - */ - @RequiresPermissions("system:config:add") + @PreAuthorize("@ss.hasPermi('system:config:add')") @Log(title = "参数管理", businessType = BusinessType.INSERT) - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysConfig config) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) { if (!configService.checkConfigKeyUnique(config)) { return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); } - config.setCreateBy(getLoginName()); + config.setCreateBy(getUsername()); 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") + @PreAuthorize("@ss.hasPermi('system:config:edit')") @Log(title = "参数管理", businessType = BusinessType.UPDATE) - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysConfig config) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) { if (!configService.checkConfigKeyUnique(config)) { return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); } - config.setUpdateBy(getLoginName()); + config.setUpdateBy(getUsername()); return toAjax(configService.updateConfig(config)); } /** * 删除参数配置 */ - @RequiresPermissions("system:config:remove") + @PreAuthorize("@ss.hasPermi('system:config:remove')") @Log(title = "参数管理", businessType = BusinessType.DELETE) - @PostMapping("/remove") - @ResponseBody - public AjaxResult remove(String ids) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) { - configService.deleteConfigByIds(ids); + configService.deleteConfigByIds(configIds); return success(); } /** * 刷新参数缓存 */ - @RequiresPermissions("system:config:remove") + @PreAuthorize("@ss.hasPermi('system:config:remove')") @Log(title = "参数管理", businessType = BusinessType.CLEAN) - @GetMapping("/refreshCache") - @ResponseBody + @DeleteMapping("/refreshCache") 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 index fad2736e8..4d70b655d 100644 --- 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 @@ -1,21 +1,22 @@ package com.ruoyi.web.controller.system; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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; @@ -26,87 +27,70 @@ import com.ruoyi.system.service.ISysDeptService; * * @author ruoyi */ -@Controller +@RestController @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() + /** + * 获取部门列表 + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) { - return prefix + "/dept"; + List depts = deptService.selectDeptList(dept); + return success(depts); } - @RequiresPermissions("system:dept:list") - @PostMapping("/list") - @ResponseBody - public List list(SysDept dept) + /** + * 查询部门列表(排除节点) + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) { - List deptList = deptService.selectDeptList(dept); - return deptList; + List depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return success(deptService.selectDeptById(deptId)); } /** * 新增部门 */ - @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"; - } - - /** - * 新增保存部门 - */ + @PreAuthorize("@ss.hasPermi('system:dept:add')") @Log(title = "部门管理", businessType = BusinessType.INSERT) - @RequiresPermissions("system:dept:add") - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysDept dept) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) { if (!deptService.checkDeptNameUnique(dept)) { return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); } - dept.setCreateBy(getLoginName()); + dept.setCreateBy(getUsername()); 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"; - } - - /** - * 修改保存部门 - */ + @PreAuthorize("@ss.hasPermi('system:dept:edit')") @Log(title = "部门管理", businessType = BusinessType.UPDATE) - @RequiresPermissions("system:dept:edit") - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysDept dept) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDept dept) { Long deptId = dept.getDeptId(); deptService.checkDeptDataScope(deptId); @@ -120,68 +104,29 @@ public class SysDeptController extends BaseController } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) { - return AjaxResult.error("该部门包含未停用的子部门!"); + return error("该部门包含未停用的子部门!"); } - dept.setUpdateBy(getLoginName()); + dept.setUpdateBy(getUsername()); return toAjax(deptService.updateDept(dept)); } /** - * 删除 + * 删除部门 */ + @PreAuthorize("@ss.hasPermi('system:dept:remove')") @Log(title = "部门管理", businessType = BusinessType.DELETE) - @RequiresPermissions("system:dept:remove") - @GetMapping("/remove/{deptId}") - @ResponseBody - public AjaxResult remove(@PathVariable("deptId") Long deptId) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable Long deptId) { - if (deptService.selectDeptCount(deptId) > 0) + if (deptService.hasChildByDeptId(deptId)) { - return AjaxResult.warn("存在下级部门,不允许删除"); + return warn("存在下级部门,不允许删除"); } if (deptService.checkDeptExistUser(deptId)) { - return AjaxResult.warn("部门存在用户,不允许删除"); + return 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 index d7fe5ef74..1914b998c 100644 --- 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 @@ -1,49 +1,47 @@ package com.ruoyi.web.controller.system; +import java.util.ArrayList; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.service.ISysDictDataService; +import com.ruoyi.system.service.ISysDictTypeService; /** * 数据字典信息 * * @author ruoyi */ -@Controller +@RestController @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"; - } + @Autowired + private ISysDictTypeService dictTypeService; - @PostMapping("/list") - @RequiresPermissions("system:dict:list") - @ResponseBody + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") public TableDataInfo list(SysDictData dictData) { startPage(); @@ -52,70 +50,72 @@ public class SysDictDataController extends BaseController } @Log(title = "字典数据", businessType = BusinessType.EXPORT) - @RequiresPermissions("system:dict:export") + @PreAuthorize("@ss.hasPermi('system:dict:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysDictData dictData) + public void export(HttpServletResponse response, SysDictData dictData) { List list = dictDataService.selectDictDataList(dictData); ExcelUtil util = new ExcelUtil(SysDictData.class); - return util.exportExcel(list, "字典数据"); + util.exportExcel(response, list, "字典数据"); + } + + /** + * 查询字典数据详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable Long dictCode) + { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable String dictType) + { + List data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) + { + data = new ArrayList(); + } + return success(data); } /** * 新增字典类型 */ - @GetMapping("/add/{dictType}") - public String add(@PathVariable("dictType") String dictType, ModelMap mmap) - { - mmap.put("dictType", dictType); - return prefix + "/add"; - } - - /** - * 新增保存字典类型 - */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") @Log(title = "字典数据", businessType = BusinessType.INSERT) - @RequiresPermissions("system:dict:add") - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysDictData dict) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) { - dict.setCreateBy(getLoginName()); + dict.setCreateBy(getUsername()); 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"; - } - /** * 修改保存字典类型 */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") @Log(title = "字典数据", businessType = BusinessType.UPDATE) - @RequiresPermissions("system:dict:edit") - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysDictData dict) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) { - dict.setUpdateBy(getLoginName()); + dict.setUpdateBy(getUsername()); return toAjax(dictDataService.updateDictData(dict)); } - @Log(title = "字典数据", businessType = BusinessType.DELETE) - @RequiresPermissions("system:dict:remove") - @PostMapping("/remove") - @ResponseBody - public AjaxResult remove(String ids) + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable Long[] dictCodes) { - dictDataService.deleteDictDataByIds(ids); + dictDataService.deleteDictDataByIds(dictCodes); 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 index 136b00f37..980884246 100644 --- 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 @@ -1,20 +1,21 @@ package com.ruoyi.web.controller.system; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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; @@ -26,25 +27,15 @@ import com.ruoyi.system.service.ISysDictTypeService; * * @author ruoyi */ -@Controller -@RequestMapping("/system/dict") +@RestController +@RequestMapping("/system/dict/type") 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 + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") public TableDataInfo list(SysDictType dictType) { startPage(); @@ -53,88 +44,75 @@ public class SysDictTypeController extends BaseController } @Log(title = "字典类型", businessType = BusinessType.EXPORT) - @RequiresPermissions("system:dict:export") + @PreAuthorize("@ss.hasPermi('system:dict:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysDictType dictType) + public void export(HttpServletResponse response, SysDictType dictType) { - List list = dictTypeService.selectDictTypeList(dictType); ExcelUtil util = new ExcelUtil(SysDictType.class); - return util.exportExcel(list, "字典类型"); + util.exportExcel(response, list, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return success(dictTypeService.selectDictTypeById(dictId)); } /** * 新增字典类型 */ - @GetMapping("/add") - public String add() - { - return prefix + "/add"; - } - - /** - * 新增保存字典类型 - */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") @Log(title = "字典类型", businessType = BusinessType.INSERT) - @RequiresPermissions("system:dict:add") - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysDictType dict) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); } - dict.setCreateBy(getLoginName()); + dict.setCreateBy(getUsername()); 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"; - } - - /** - * 修改保存字典类型 - */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") @Log(title = "字典类型", businessType = BusinessType.UPDATE) - @RequiresPermissions("system:dict:edit") - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysDictType dict) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); } - dict.setUpdateBy(getLoginName()); + dict.setUpdateBy(getUsername()); return toAjax(dictTypeService.updateDictType(dict)); } + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") @Log(title = "字典类型", businessType = BusinessType.DELETE) - @RequiresPermissions("system:dict:remove") - @PostMapping("/remove") - @ResponseBody - public AjaxResult remove(String ids) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable Long[] dictIds) { - dictTypeService.deleteDictTypeByIds(ids); + dictTypeService.deleteDictTypeByIds(dictIds); return success(); } /** * 刷新字典缓存 */ - @RequiresPermissions("system:dict:remove") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") @Log(title = "字典类型", businessType = BusinessType.CLEAN) - @GetMapping("/refreshCache") - @ResponseBody + @DeleteMapping("/refreshCache") public AjaxResult refreshCache() { dictTypeService.resetDictCache(); @@ -142,47 +120,12 @@ public class SysDictTypeController extends BaseController } /** - * 查询字典详细 + * 获取字典选择框列表 */ - @RequiresPermissions("system:dict:list") - @GetMapping("/detail/{dictId}") - public String detail(@PathVariable("dictId") Long dictId, ModelMap mmap) + @GetMapping("/optionselect") + public AjaxResult optionselect() { - 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; + List dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); } } 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 index 10da47fb4..009219e02 100644 --- 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 @@ -1,178 +1,29 @@ 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 org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; 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 +@RestController +public class SysIndexController { + /** 系统基础配置 */ @Autowired - private ISysMenuService menuService; + private RuoYiConfig ruoyiConfig; - @Autowired - private ISysConfigService configService; - - @Autowired - private SysPasswordService passwordService; - - // 系统首页 - @GetMapping("/index") - public String index(ModelMap mmap) + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() { - // 取身份信息 - 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; + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); } } 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 index 55aecae62..d959a17e0 100644 --- 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 @@ -1,82 +1,86 @@ 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 java.util.List; +import java.util.Set; 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 org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.constant.Constants; 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; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.system.service.ISysMenuService; /** * 登录验证 * * @author ruoyi */ -@Controller -public class SysLoginController extends BaseController +@RestController +public class SysLoginController { - /** - * 是否开启记住我功能 - */ - @Value("${shiro.rememberMe.enabled: false}") - private boolean rememberMe; + @Autowired + private SysLoginService loginService; @Autowired - private ConfigService configService; + private ISysMenuService menuService; - @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"; - } + @Autowired + private SysPermissionService permissionService; + /** + * 登录方法 + * + * @param loginBody 登录信息 + * @return 结果 + */ @PostMapping("/login") - @ResponseBody - public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe) + public AjaxResult login(@RequestBody LoginBody loginBody) { - 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); - } + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, token); + return ajax; } - @GetMapping("/unauth") - public String unauth() + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("getInfo") + public AjaxResult getInfo() { - return "error/unauth"; + SysUser user = SecurityUtils.getLoginUser().getUser(); + // 角色集合 + Set roles = permissionService.getRolePermission(user); + // 权限集合 + Set permissions = permissionService.getMenuPermission(user); + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("getRouters") + public AjaxResult getRouters() + { + Long userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); } } 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 index 30ab7955f..eaaebd3eb 100644 --- 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 @@ -1,25 +1,24 @@ 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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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.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.common.utils.StringUtils; import com.ruoyi.system.service.ISysMenuService; /** @@ -27,171 +26,117 @@ import com.ruoyi.system.service.ISysMenuService; * * @author ruoyi */ -@Controller +@RestController @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() + /** + * 获取菜单列表 + */ + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu 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; + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menus); } /** - * 删除菜单 + * 根据菜单编号获取详细信息 */ - @Log(title = "菜单管理", businessType = BusinessType.DELETE) - @RequiresPermissions("system:menu:remove") - @GetMapping("/remove/{menuId}") - @ResponseBody - public AjaxResult remove(@PathVariable("menuId") Long menuId) + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable 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)); + return success(menuService.selectMenuById(menuId)); } /** - * 新增 + * 获取菜单下拉树列表 */ - @GetMapping("/add/{parentId}") - public String add(@PathVariable("parentId") Long parentId, ModelMap mmap) + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) { - 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"; + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menuService.buildMenuTreeSelect(menus)); } /** - * 新增保存菜单 + * 加载对应角色菜单列表树 */ + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) + { + List menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + @PreAuthorize("@ss.hasPermi('system:menu:add')") @Log(title = "菜单管理", businessType = BusinessType.INSERT) - @RequiresPermissions("system:menu:add") - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysMenu menu) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) { if (!menuService.checkMenuNameUnique(menu)) { return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); } - menu.setCreateBy(getLoginName()); - AuthorizationUtils.clearAllCachedAuthorizationInfo(); + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); 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"; - } - - /** - * 修改保存菜单 - */ + @PreAuthorize("@ss.hasPermi('system:menu:edit')") @Log(title = "菜单管理", businessType = BusinessType.UPDATE) - @RequiresPermissions("system:menu:edit") - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysMenu menu) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) { if (!menuService.checkMenuNameUnique(menu)) { return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); } - menu.setUpdateBy(getLoginName()); - AuthorizationUtils.clearAllCachedAuthorizationInfo(); + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); return toAjax(menuService.updateMenu(menu)); } /** - * 选择菜单图标 + * 删除菜单 */ - @GetMapping("/icon") - public String icon() + @PreAuthorize("@ss.hasPermi('system:menu:remove')") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) { - 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"; + if (menuService.hasChildByMenuId(menuId)) + { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return warn("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); } } \ 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 index c83af2824..36c62b991 100644 --- 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 @@ -1,16 +1,17 @@ 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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -24,28 +25,18 @@ import com.ruoyi.system.service.ISysNoticeService; * * @author ruoyi */ -@Controller +@RestController @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 + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/list") public TableDataInfo list(SysNotice notice) { startPage(); @@ -54,71 +45,47 @@ public class SysNoticeController extends BaseController } /** - * 新增公告 + * 根据通知公告编号获取详细信息 */ - @GetMapping("/add") - public String add() + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) { - return prefix + "/add"; + return success(noticeService.selectNoticeById(noticeId)); } /** - * 新增保存公告 + * 新增通知公告 */ - @RequiresPermissions("system:notice:add") + @PreAuthorize("@ss.hasPermi('system:notice:add')") @Log(title = "通知公告", businessType = BusinessType.INSERT) - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysNotice notice) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) { - notice.setCreateBy(getLoginName()); + notice.setCreateBy(getUsername()); 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") + @PreAuthorize("@ss.hasPermi('system:notice:edit')") @Log(title = "通知公告", businessType = BusinessType.UPDATE) - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysNotice notice) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) { - notice.setUpdateBy(getLoginName()); + notice.setUpdateBy(getUsername()); return toAjax(noticeService.updateNotice(notice)); } /** - * 查询公告详细 + * 删除通知公告 */ - @RequiresPermissions("system:notice:list") - @GetMapping("/view/{noticeId}") - public String view(@PathVariable("noticeId") Long noticeId, ModelMap mmap) - { - mmap.put("notice", noticeService.selectNoticeById(noticeId)); - return prefix + "/view"; - } - - /** - * 删除公告 - */ - @RequiresPermissions("system:notice:remove") + @PreAuthorize("@ss.hasPermi('system:notice:remove')") @Log(title = "通知公告", businessType = BusinessType.DELETE) - @PostMapping("/remove") - @ResponseBody - public AjaxResult remove(String ids) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable Long[] noticeIds) { - return toAjax(noticeService.deleteNoticeByIds(ids)); + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); } } 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 index 22aea2e0f..a5b96c876 100644 --- 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 @@ -1,16 +1,18 @@ package com.ruoyi.web.controller.system; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -25,76 +27,52 @@ import com.ruoyi.system.service.ISysPostService; * * @author ruoyi */ -@Controller +@RestController @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 + /** + * 获取岗位列表 + */ + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") public TableDataInfo list(SysPost post) { startPage(); List list = postService.selectPostList(post); return getDataTable(list); } - + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) - @RequiresPermissions("system:post:export") + @PreAuthorize("@ss.hasPermi('system:post:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysPost post) + public void export(HttpServletResponse response, SysPost post) { List list = postService.selectPostList(post); ExcelUtil util = new ExcelUtil(SysPost.class); - return util.exportExcel(list, "岗位数据"); + util.exportExcel(response, list, "岗位数据"); } - @RequiresPermissions("system:post:remove") - @Log(title = "岗位管理", businessType = BusinessType.DELETE) - @PostMapping("/remove") - @ResponseBody - public AjaxResult remove(String ids) + /** + * 根据岗位编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) { - try - { - return toAjax(postService.deletePostByIds(ids)); - } - catch (Exception e) - { - return error(e.getMessage()); - } + return success(postService.selectPostById(postId)); } /** * 新增岗位 */ - @GetMapping("/add") - public String add() - { - return prefix + "/add"; - } - - /** - * 新增保存岗位 - */ - @RequiresPermissions("system:post:add") + @PreAuthorize("@ss.hasPermi('system:post:add')") @Log(title = "岗位管理", businessType = BusinessType.INSERT) - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysPost post) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) { if (!postService.checkPostNameUnique(post)) { @@ -104,29 +82,17 @@ public class SysPostController extends BaseController { return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); } - post.setCreateBy(getLoginName()); + post.setCreateBy(getUsername()); 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") + @PreAuthorize("@ss.hasPermi('system:post:edit')") @Log(title = "岗位管理", businessType = BusinessType.UPDATE) - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysPost post) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) { if (!postService.checkPostNameUnique(post)) { @@ -136,27 +102,28 @@ public class SysPostController extends BaseController { return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); } - post.setUpdateBy(getLoginName()); + post.setUpdateBy(getUsername()); return toAjax(postService.updatePost(post)); } /** - * 校验岗位名称 + * 删除岗位 */ - @PostMapping("/checkPostNameUnique") - @ResponseBody - public boolean checkPostNameUnique(SysPost post) + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable Long[] postIds) { - return postService.checkPostNameUnique(post); + return toAjax(postService.deletePostByIds(postIds)); } /** - * 校验岗位编码 + * 获取岗位选择框列表 */ - @PostMapping("/checkPostCodeUnique") - @ResponseBody - public boolean checkPostCodeUnique(SysPost post) + @GetMapping("/optionselect") + public AjaxResult optionselect() { - return postService.checkPostCodeUnique(post); + List posts = postService.selectPostAll(); + return success(posts); } } 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 index e82c1da4a..cd8abd6f5 100644 --- 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 @@ -1,28 +1,26 @@ 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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; 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.bind.annotation.RestController; 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.core.domain.model.LoginUser; import com.ruoyi.common.enums.BusinessType; -import com.ruoyi.common.utils.DateUtils; -import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.SecurityUtils; 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.framework.web.service.TokenService; import com.ruoyi.system.service.ISysUserService; /** @@ -30,152 +28,110 @@ import com.ruoyi.system.service.ISysUserService; * * @author ruoyi */ -@Controller +@RestController @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; + private TokenService tokenService; /** * 个人信息 */ - @GetMapping() - public String profile(ModelMap mmap) + @GetMapping + public AjaxResult profile() { - SysUser user = getSysUser(); - mmap.put("user", user); - mmap.put("roleGroup", userService.selectUserRoleGroup(user.getUserId())); - mmap.put("postGroup", userService.selectUserPostGroup(user.getUserId())); - return prefix + "/profile"; + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + return ajax; } - @GetMapping("/checkPassword") - @ResponseBody - public boolean checkPassword(String password) + /** + * 修改用户 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) { - SysUser user = getSysUser(); - return passwordService.matches(user, password); + LoginUser loginUser = getLoginUser(); + SysUser currentUser = loginUser.getUser(); + currentUser.setNickName(user.getNickName()); + currentUser.setEmail(user.getEmail()); + currentUser.setPhonenumber(user.getPhonenumber()); + currentUser.setSex(user.getSex()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) + { + return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) + { + return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在"); + } + if (userService.updateUserProfile(currentUser) > 0) + { + // 更新缓存用户信息 + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); } - @GetMapping("/resetPwd") - public String resetPwd(ModelMap mmap) + /** + * 重置密码 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(String oldPassword, String newPassword) { - 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)) + LoginUser loginUser = getLoginUser(); + String userName = loginUser.getUsername(); + String password = loginUser.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) { return error("修改密码失败,旧密码错误"); } - if (passwordService.matches(user, newPassword)) + if (SecurityUtils.matchesPassword(newPassword, password)) { return error("新密码不能与旧密码相同"); } - user.setSalt(ShiroUtils.randomSalt()); - user.setPassword(passwordService.encryptPassword(user.getLoginName(), newPassword, user.getSalt())); - user.setPwdUpdateDate(DateUtils.getNowDate()); - if (userService.resetUserPwd(user) > 0) + newPassword = SecurityUtils.encryptPassword(newPassword); + if (userService.resetUserPwd(userName, newPassword) > 0) { - setSysUser(userService.selectUserById(user.getUserId())); + // 更新缓存用户密码 + loginUser.getUser().setPassword(newPassword); + tokenService.setLoginUser(loginUser); return success(); } return error("修改密码异常,请联系管理员"); } /** - * 修改用户 + * 头像上传 */ - @GetMapping("/edit") - public String edit(ModelMap mmap) + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception { - 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)) + if (!file.isEmpty()) { - 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()) + LoginUser loginUser = getLoginUser(); + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); + if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) { - 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(); - } + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", avatar); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(avatar); + tokenService.setLoginUser(loginUser); + return ajax; } - return error(); - } - catch (Exception e) - { - log.error("修改头像失败!", e); - return error(e.getMessage()); } + return error("上传图片异常,请联系管理员"); } } 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 index 9bcd8b9e4..f1552e033 100644 --- 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 @@ -1,15 +1,14 @@ 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 org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; 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.core.domain.model.RegisterBody; import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.framework.shiro.service.SysRegisterService; +import com.ruoyi.framework.web.service.SysRegisterService; import com.ruoyi.system.service.ISysConfigService; /** @@ -17,7 +16,7 @@ import com.ruoyi.system.service.ISysConfigService; * * @author ruoyi */ -@Controller +@RestController public class SysRegisterController extends BaseController { @Autowired @@ -26,15 +25,8 @@ public class SysRegisterController extends BaseController @Autowired private ISysConfigService configService; - @GetMapping("/register") - public String register() - { - return "register"; - } - @PostMapping("/register") - @ResponseBody - public AjaxResult ajaxRegister(SysUser user) + public AjaxResult register(@RequestBody RegisterBody user) { if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) { 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 index 2cdf08055..ebe7460e1 100644 --- 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 @@ -1,26 +1,31 @@ package com.ruoyi.web.controller.system; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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.domain.model.LoginUser; 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.framework.shiro.util.AuthorizationUtils; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysRoleService; @@ -31,31 +36,27 @@ import com.ruoyi.system.service.ISysUserService; * * @author ruoyi */ -@Controller +@RestController @RequestMapping("/system/role") public class SysRoleController extends BaseController { - private String prefix = "system/role"; - @Autowired private ISysRoleService roleService; + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + @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 + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") public TableDataInfo list(SysRole role) { startPage(); @@ -64,33 +65,33 @@ public class SysRoleController extends BaseController } @Log(title = "角色管理", businessType = BusinessType.EXPORT) - @RequiresPermissions("system:role:export") + @PreAuthorize("@ss.hasPermi('system:role:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysRole role) + public void export(HttpServletResponse response, SysRole role) { List list = roleService.selectRoleList(role); ExcelUtil util = new ExcelUtil(SysRole.class); - return util.exportExcel(list, "角色数据"); + util.exportExcel(response, list, "角色数据"); + } + + /** + * 根据角色编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable Long roleId) + { + roleService.checkRoleDataScope(roleId); + return success(roleService.selectRoleById(roleId)); } /** * 新增角色 */ - @GetMapping("/add") - public String add() - { - return prefix + "/add"; - } - - /** - * 新增保存角色 - */ - @RequiresPermissions("system:role:add") + @PreAuthorize("@ss.hasPermi('system:role:add')") @Log(title = "角色管理", businessType = BusinessType.INSERT) - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysRole role) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysRole role) { if (!roleService.checkRoleNameUnique(role)) { @@ -100,32 +101,18 @@ public class SysRoleController extends BaseController { return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); } - role.setCreateBy(getLoginName()); - AuthorizationUtils.clearAllCachedAuthorizationInfo(); + role.setCreateBy(getUsername()); 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") + @PreAuthorize("@ss.hasPermi('system:role:edit')") @Log(title = "角色管理", businessType = BusinessType.UPDATE) - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysRole role) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRole role) { roleService.checkRoleAllowed(role); roleService.checkRoleDataScope(role.getRoleId()); @@ -137,110 +124,76 @@ public class SysRoleController extends BaseController { 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) + role.setUpdateBy(getUsername()); + + if (roleService.updateRole(role) > 0) { - setSysUser(userService.selectUserById(getUserId())); + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) + { + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + tokenService.setLoginUser(loginUser); + } 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)); + return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); } /** - * 校验角色名称 - */ - @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"; - } - - /** - * 角色状态修改 + * 修改保存数据权限 */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") @Log(title = "角色管理", businessType = BusinessType.UPDATE) - @RequiresPermissions("system:role:edit") - @PostMapping("/changeStatus") - @ResponseBody - public AjaxResult changeStatus(SysRole role) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) { roleService.checkRoleAllowed(role); roleService.checkRoleDataScope(role.getRoleId()); - return toAjax(roleService.changeStatus(role)); + return toAjax(roleService.authDataScope(role)); } /** - * 分配用户 + * 状态修改 */ - @RequiresPermissions("system:role:edit") - @GetMapping("/authUser/{roleId}") - public String authUser(@PathVariable("roleId") Long roleId, ModelMap mmap) + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) { - mmap.put("role", roleService.selectRoleById(roleId)); - return prefix + "/authUser"; + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + role.setUpdateBy(getUsername()); + return toAjax(roleService.updateRoleStatus(role)); + } + + /** + * 删除角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:remove')") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public AjaxResult remove(@PathVariable Long[] roleIds) + { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + return success(roleService.selectRoleAll()); } /** * 查询已分配用户角色列表 */ - @RequiresPermissions("system:role:list") - @PostMapping("/authUser/allocatedList") - @ResponseBody + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") public TableDataInfo allocatedList(SysUser user) { startPage(); @@ -248,46 +201,11 @@ public class SysRoleController extends BaseController 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 + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") public TableDataInfo unallocatedList(SysUser user) { startPage(); @@ -295,28 +213,50 @@ public class SysRoleController extends BaseController return getDataTable(list); } + /** + * 取消授权用户 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) + { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) + { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + /** * 批量选择用户授权 */ - @RequiresPermissions("system:role:edit") + @PreAuthorize("@ss.hasPermi('system:role:edit')") @Log(title = "角色管理", businessType = BusinessType.GRANT) - @PostMapping("/authUser/selectAll") - @ResponseBody - public AjaxResult selectAuthUserAll(Long roleId, String userIds) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) { roleService.checkRoleDataScope(roleId); return toAjax(roleService.insertAuthUsers(roleId, userIds)); } /** - * 加载角色部门(数据权限)列表树 + * 获取对应角色部门树列表 */ - @RequiresPermissions("system:role:edit") - @GetMapping("/deptTreeData") - @ResponseBody - public List deptTreeData(SysRole role) + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) { - List ztrees = deptService.roleDeptTreeData(role); - return ztrees; + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; } -} \ 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 index b1f6dd585..dc29d4982 100644 --- 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 @@ -2,34 +2,31 @@ package com.ruoyi.web.controller.system; import java.util.List; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; 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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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.DateUtils; -import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.SecurityUtils; 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; @@ -40,37 +37,27 @@ import com.ruoyi.system.service.ISysUserService; * * @author ruoyi */ -@Controller +@RestController @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 + /** + * 获取用户列表 + */ + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") public TableDataInfo list(SysUser user) { startPage(); @@ -79,270 +66,186 @@ public class SysUserController extends BaseController } @Log(title = "用户管理", businessType = BusinessType.EXPORT) - @RequiresPermissions("system:user:export") + @PreAuthorize("@ss.hasPermi('system:user:export')") @PostMapping("/export") - @ResponseBody - public AjaxResult export(SysUser user) + public void export(HttpServletResponse response, SysUser user) { List list = userService.selectUserList(user); ExcelUtil util = new ExcelUtil(SysUser.class); - return util.exportExcel(list, "用户数据"); + util.exportExcel(response, list, "用户数据"); } @Log(title = "用户管理", businessType = BusinessType.IMPORT) - @RequiresPermissions("system:user:import") + @PreAuthorize("@ss.hasPermi('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); + String operName = getUsername(); + String message = userService.importUser(userList, updateSupport, operName); + return success(message); } - @RequiresPermissions("system:user:view") - @GetMapping("/importTemplate") - @ResponseBody - public AjaxResult importTemplate() + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { ExcelUtil util = new ExcelUtil(SysUser.class); - return util.importTemplateExcel("用户数据"); + util.importTemplateExcel(response, "用户数据"); + } + + /** + * 根据用户编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping(value = { "/", "/{userId}" }) + public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) + { + userService.checkUserDataScope(userId); + AjaxResult ajax = AjaxResult.success(); + List roles = roleService.selectRoleAll(); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + ajax.put("posts", postService.selectPostAll()); + if (StringUtils.isNotNull(userId)) + { + SysUser sysUser = userService.selectUserById(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); + } + return ajax; } /** * 新增用户 */ - @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") + @PreAuthorize("@ss.hasPermi('system:user:add')") @Log(title = "用户管理", businessType = BusinessType.INSERT) - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(@Validated SysUser user) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysUser user) { - if (!userService.checkLoginNameUnique(user)) + if (!userService.checkUserNameUnique(user)) { - return error("新增用户'" + user.getLoginName() + "'失败,登录账号已存在"); + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { - return error("新增用户'" + user.getLoginName() + "'失败,手机号码已存在"); + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { - return error("新增用户'" + user.getLoginName() + "'失败,邮箱账号已存在"); + return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } - user.setSalt(ShiroUtils.randomSalt()); - user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt())); - user.setPwdUpdateDate(DateUtils.getNowDate()); - user.setCreateBy(getLoginName()); + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); 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:list") - @GetMapping("/view/{userId}") - public String view(@PathVariable("userId") Long userId, ModelMap mmap) - { - userService.checkUserDataScope(userId); - mmap.put("user", userService.selectUserById(userId)); - mmap.put("roleGroup", userService.selectUserRoleGroup(userId)); - mmap.put("postGroup", userService.selectUserPostGroup(userId)); - return prefix + "/view"; - } - - /** - * 修改保存用户 - */ - @RequiresPermissions("system:user:edit") + @PreAuthorize("@ss.hasPermi('system:user:edit')") @Log(title = "用户管理", businessType = BusinessType.UPDATE) - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(@Validated SysUser user) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) { userService.checkUserAllowed(user); userService.checkUserDataScope(user.getUserId()); - if (!userService.checkLoginNameUnique(user)) + if (!userService.checkUserNameUnique(user)) { - return error("修改用户'" + user.getLoginName() + "'失败,登录账号已存在"); + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { - return error("修改用户'" + user.getLoginName() + "'失败,手机号码已存在"); + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { - return error("修改用户'" + user.getLoginName() + "'失败,邮箱账号已存在"); + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } - user.setUpdateBy(getLoginName()); - AuthorizationUtils.clearAllCachedAuthorizationInfo(); + user.setUpdateBy(getUsername()); return toAjax(userService.updateUser(user)); } - @RequiresPermissions("system:user:resetPwd") - @GetMapping("/resetPwd/{userId}") - public String resetPwd(@PathVariable("userId") Long userId, ModelMap mmap) + /** + * 删除用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:remove')") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable Long[] userIds) { - 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 (ArrayUtils.contains(userIds, getUserId())) { - if (ShiroUtils.getUserId().longValue() == user.getUserId().longValue()) - { - setSysUser(userService.selectUserById(user.getUserId())); - } - return success(); + return error("当前用户不能删除"); } - return error(); + return toAjax(userService.deleteUserByIds(userIds)); } /** - * 进入授权角色页 + * 重置密码 */ - @GetMapping("/authRole/{userId}") - public String authRole(@PathVariable("userId") Long userId, ModelMap mmap) + @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) + { + AjaxResult ajax = AjaxResult.success(); 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"; + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; } /** * 用户授权角色 */ - @RequiresPermissions("system:user:edit") + @PreAuthorize("@ss.hasPermi('system:user:edit')") @Log(title = "用户管理", businessType = BusinessType.GRANT) - @PostMapping("/authRole/insertAuthRole") - @ResponseBody + @PutMapping("/authRole") 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) + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) { - return userService.checkLoginNameUnique(user); + return success(deptService.selectDeptTreeList(dept)); } - - /** - * 校验手机号码 - */ - @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 deleted file mode 100644 index 53ce0f1ba..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BuildController.java +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 079d7d2a7..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java +++ /dev/null @@ -1,24 +0,0 @@ -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/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java index 0cfbfbadb..0b9424507 100644 --- 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 @@ -1,16 +1,25 @@ package com.ruoyi.web.core.config; +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; 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 io.swagger.models.auth.In; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; /** @@ -21,10 +30,18 @@ import springfox.documentation.spring.web.plugins.Docket; @Configuration public class SwaggerConfig { + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + /** 是否开启swagger */ @Value("${swagger.enabled}") private boolean enabled; - + + /** 设置请求的统一前缀 */ + @Value("${swagger.pathMapping}") + private String pathMapping; + /** * 创建API */ @@ -41,10 +58,51 @@ public class SwaggerConfig // 扫描所有有注解的api,用这种方式更灵活 .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 扫描指定包中的swagger注解 - //.apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) + // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) // 扫描所有 .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) - .build(); + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List securitySchemes() + { + List apiKeyList = new ArrayList(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List securityContexts() + { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List defaultAuth() + { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; } /** @@ -59,9 +117,9 @@ public class SwaggerConfig // 描述 .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") // 作者信息 - .contact(new Contact(RuoYiConfig.getName(), null, null)) + .contact(new Contact(ruoyiConfig.getName(), null, null)) // 版本 - .version("版本号:" + RuoYiConfig.getVersion()) + .version("版本号:" + ruoyiConfig.getVersion()) .build(); } } diff --git a/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 000000000..37e7b5806 --- /dev/null +++ b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson2.*.jar \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index a69d8feb2..bcfad3eae 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -6,7 +6,7 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: password # 从库数据源 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 0dc885f68..6db16fbf8 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -3,20 +3,20 @@ ruoyi: # 名称 name: RuoYi # 版本 - version: 4.7.8 + version: 3.8.7 # 版权年份 copyrightYear: 2024 - # 实例演示开关 - demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: D:/ruoyi/uploadPath # 获取ip地址开关 addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math # 开发环境配置 server: - # 服务器的HTTP端口,默认为80 - port: 80 + # 服务器的HTTP端口,默认为8080 + port: 8080 servlet: # 应用的访问路径 context-path: / @@ -30,7 +30,7 @@ server: max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 - + # 日志配置 logging: level: @@ -40,24 +40,17 @@ logging: # 用户配置 user: password: - # 密码错误{maxRetryCount}次锁定10分钟 + # 密码最大错误次数 maxRetryCount: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 # 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 + basename: i18n/messages profiles: active: druid # 文件上传 @@ -72,8 +65,39 @@ spring: restart: # 热部署开关 enabled: true + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms -# MyBatis +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 + +# MyBatis配置 mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain @@ -88,55 +112,18 @@ pagehelper: 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 +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /dev-api # 防止XSS攻击 xss: # 过滤开关 enabled: true # 排除链接(多个用逗号分隔) - excludes: /system/notice/* + excludes: /system/notice # 匹配链接 urlPatterns: /system/*,/monitor/*,/tool/* - -# Swagger配置 -swagger: - # 是否开启swagger - enabled: true diff --git a/ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml b/ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml deleted file mode 100644 index 8ffd5ed04..000000000 --- a/ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties similarity index 96% rename from ruoyi-admin/src/main/resources/static/i18n/messages.properties rename to ruoyi-admin/src/main/resources/i18n/messages.properties index 02cb2fda6..93de0055b 100644 --- a/ruoyi-admin/src/main/resources/static/i18n/messages.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages.properties @@ -1,37 +1,38 @@ -#错误消息 -not.null=* 必须填写 -user.jcaptcha.error=验证码错误 -user.not.exists=用户不存在/密码错误 -user.password.not.match=用户不存在/密码错误 -user.password.retry.limit.count=密码输入错误{0}次 -user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟 -user.password.delete=对不起,您的账号已被删除 -user.blocked=用户已封禁,请联系管理员 -role.blocked=角色已封禁,请联系管理员 -login.blocked=很遗憾,访问IP已被列入系统黑名单 -user.logout.success=退出成功 - -length.not.valid=长度必须在{min}到{max}个字符之间 - -user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 -user.password.not.valid=* 5-50个字符 - -user.email.not.valid=邮箱格式错误 -user.mobile.phone.number.not.valid=手机号格式错误 -user.login.success=登录成功 -user.register.success=注册成功 -user.notfound=请重新登录 -user.forcelogout=管理员强制退出,请重新登录 -user.unknown.error=未知错误,请重新登录 - -##文件上传消息 -upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! -upload.filename.exceed.length=上传的文件名最长{0}个字符 - -##权限 -no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] -no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] -no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] -no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] -no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] -no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] 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 deleted file mode 100644 index 4f7dd9273..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js +++ /dev/null @@ -1,617 +0,0 @@ -/*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
deleted file mode 100644
index 8a9d70893..000000000
--- a/ruoyi-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js
+++ /dev/null
@@ -1,620 +0,0 @@
-/*!
- * 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 deleted file mode 100644 index b6c0062b0..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css +++ /dev/null @@ -1,688 +0,0 @@ -/*! - * 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 deleted file mode 100644 index 75466c06f..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js +++ /dev/null @@ -1,6681 +0,0 @@ -/*! - * 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 deleted file mode 100644 index 33a833047..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * 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 deleted file mode 100644 index d6e0ccfe3..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * 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 deleted file mode 100644 index 44e3b7a0f702aa1d301468b1d6c1d74d45dfdfa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 deleted file mode 100644 index 4becc8e60..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js +++ /dev/null @@ -1,3247 +0,0 @@ -/*! - * 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 deleted file mode 100644 index f2a6c0c87..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * 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 deleted file mode 100644 index 46cf10e9d..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * 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 deleted file mode 100644 index 7e150cf59..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @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 deleted file mode 100644 index f7e0b862a..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @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 deleted file mode 100644 index 56ec11b3e..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/auto-refresh/bootstrap-table-auto-refresh.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @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 deleted file mode 100644 index e35b89f93..000000000 --- a/ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @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 deleted file mode 100644 index 2988a6578..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/basic.html +++ /dev/null @@ -1,593 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    基本表单 简单登录表单示例
    -
    - - - - - - - - - - -
    -
    -
    -
    -
    -

    登录

    -

    欢迎登录本站(⊙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 deleted file mode 100644 index 70e8b0808..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/button.html +++ /dev/null @@ -1,620 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    按钮颜色
    -
    - - - - - - - - - - -
    -
    -
    -

    - 可使用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 deleted file mode 100644 index 35a7f68d4..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/cards.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - 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 deleted file mode 100644 index 450fcec85..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/cxselect.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    多级联动下拉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 deleted file mode 100644 index 591ba101b..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/datetime.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    日期选择器 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 deleted file mode 100644 index ef80a3dec..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/duallistbox.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    双重列表框 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 deleted file mode 100644 index 402805d32..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/grid.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    栅格设置
    -
    - - - - - - - - - - -
    -
    -
    - -

    通过下表可以详细查看 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 deleted file mode 100644 index d321ff2d8..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/invoice.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - -
    - -
    -
    -
    -
    -
    -
    - 北京百度在线网络技术有限公司
    - 北京市海淀区上地十街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 deleted file mode 100644 index 5cdf730b1..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/jasny.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    文件上传控件 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 deleted file mode 100644 index 599897de5..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/labels_tips.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    徽章 (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 deleted file mode 100644 index 530092744..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/localrefresh.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -

    任务列表

    -

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

    - -
    - - - - -
    -
    - -
    -
    -
    -
    -
    -
    - - - - 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 deleted file mode 100644 index 032c58303..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/progress_bars.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    进度条 (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 deleted file mode 100644 index 2abac45cd..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/select.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - -
    -
    -
    -
    -
    -
    -
    下拉框 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 deleted file mode 100644 index 99ede8935..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/sortable.html +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -

    任务列表

    -

    在列表之间拖动任务面板

    - -
    - - - - -
    - -
      -
    • - 加强过程管理,及时统计教育经费使用情况,做到底码清楚, -
      - 标签 - 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 deleted file mode 100644 index a14b04319..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/summernote.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    Summernote 富文本编辑器
    -
    -
    -
    -

    若依后台管理系统

    -

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

    -

    - 当前版本:v4.7.8 -

    -

    - 免费开源 -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    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.8 -

    -

    - 开源免费 -

    -
    - -
    -
    -
    -
    -
    - - - - - 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 deleted file mode 100644 index 3cc586677..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/tabs_panels.html +++ /dev/null @@ -1,353 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    基本面板 这是一个自定义面板
    -
    - - - - - - - - - - -
    -
    -
    -

    - 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 deleted file mode 100644 index 132977e58..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/timeline.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - 时间轴 - - - - - - -
    -
    -
    - 打开/关闭颜色/背景或方向版本: - 轻型版本 - 黑色版本 -
    -
    -
    -
    - - -
    -

    会议

    -

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

    - 更多信息 - - 今天
    - 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 deleted file mode 100644 index 0a7b9d959..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/upload.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    文件上传控件 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 deleted file mode 100644 index 8fb68d729..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/validate.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    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 deleted file mode 100644 index 696870d2d..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/form/wizard.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - - -
    -
    -
    -
    -
    -
    - 表单向导 - 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 deleted file mode 100644 index b16b56cbb..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/icon/fontawesome.html +++ /dev/null @@ -1,1054 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    所有图标 所有图标集合 - 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 deleted file mode 100644 index fdeae0ba3..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/icon/glyphicons.html +++ /dev/null @@ -1,1364 +0,0 @@ - - - - - - -
    -
    -
    -
    -

    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 deleted file mode 100644 index 6cbad431f..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/dialog.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    模态窗口
    - -
    -
    -

    创建自定义的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 deleted file mode 100644 index f9c749d65..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/form.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/demo/modal/layer.html b/ruoyi-admin/src/main/resources/templates/demo/modal/layer.html deleted file mode 100644 index 1dc924fa6..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/layer.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    信息框
    -
    -
    -

    通过调用$.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 deleted file mode 100644 index f573615d0..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/table.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    弹层框
    -
    -
    -

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

    - - -
    -
    -
    -
    -
    -
    -
    弹层框
    -
    -
    -

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

    - - - -

    -
    -
    -
    -
    -
    -
    -
    弹层框
    -
    -
    -

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

    - -

    -
    -
    -
    -
    -
    - - - - 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 deleted file mode 100644 index 61599fe7c..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/table/check.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index af5fe62dd..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame1.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index b4940fefb..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/table/frame2.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - - \ 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 deleted file mode 100644 index c1fa36b99..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/table/parent.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 832c79de2..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/modal/table/radio.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index e1cf566ec..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/operate/add.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/detail.html b/ruoyi-admin/src/main/resources/templates/demo/operate/detail.html deleted file mode 100644 index ca302663d..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/operate/detail.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/edit.html b/ruoyi-admin/src/main/resources/templates/demo/operate/edit.html deleted file mode 100644 index 2e6adc073..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/operate/edit.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/demo/operate/other.html b/ruoyi-admin/src/main/resources/templates/demo/operate/other.html deleted file mode 100644 index 19da6b355..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/operate/other.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -   -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -   -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 04dcb7d47..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/operate/table.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - -
    - - - - - \ 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 deleted file mode 100644 index 799800bde..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/report/echarts.html +++ /dev/null @@ -1,1264 +0,0 @@ - - - - - - -
    -
    -

    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 deleted file mode 100644 index 6493eed7a..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/report/metrics.html +++ /dev/null @@ -1,478 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    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 deleted file mode 100644 index 4ebbc17c8..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/report/peity.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - -
    - -
    -
    -
    -

    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 deleted file mode 100644 index 401900b18..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/report/sparkline.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - -
    - -
    -
    -
    -

    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 deleted file mode 100644 index 90e71f8e6..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/asynTree.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index bfc23af5a..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/button.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 44448bac5..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/child.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - -
    -
    -
    - -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index ee7617729..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/curd.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - -
    - - - \ 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 deleted file mode 100644 index 70111d85b..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/customView.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - -
    -
    -
    - -
    -
    -
    -
    - - - -
    - - - - \ 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 deleted file mode 100644 index 50a9d0c46..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/data.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index d0031db62..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/detail.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 392b95248..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/dynamicColumns.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 要增加的列: - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 7a25782dc..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/editable.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - \ 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 deleted file mode 100644 index bafc455b0..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/event.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - -
    -
    -
    -

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

    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 65cb3bf2c..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/export.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 657bb8357..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/exportSelected.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    - 勾选数据导出指定列,否则为全部 - - 导出 - -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index c93622a7b..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/fixedColumns.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - -
    - - - - \ 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 deleted file mode 100644 index 3aaed27e0..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/footer.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index a152dfb4f..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/groupHeader.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 0af7a0249..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/headerStyle.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 9a34e86bb..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/image.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 949077714..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/multi.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index b0a6e9cce..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/other.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - -
    - - - \ 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 deleted file mode 100644 index 7f3d59701..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/pageGo.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 30b12572e..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/params.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - -
    -
    -
    -

    通过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 deleted file mode 100644 index 8695376ad..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/print.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - -
    -
    -
    - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 44fbb8878..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/refresh.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - -
    -
    -
    - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 1fff9a3f2..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/remember.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - -
    - -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index 8e15f2e53..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/reorderColumns.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - -
    - -
    -
    -

    按住表格列拖拽

    -
    -
    -
    -
    -
    - - - - - \ 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 deleted file mode 100644 index ded9ecec9..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/reorderRows.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - -
    - -
    -
    -

    按住表格行拖拽

    -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 680cfdd8e..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/resizable.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - - - \ 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 deleted file mode 100644 index 553cf71de..000000000 --- a/ruoyi-admin/src/main/resources/templates/demo/table/search.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - -
    -
    -
    -

    普通条件查询

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

    时间条件查询

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

    多级联动下拉查询

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

    下拉多选条件查询

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

    复杂条件查询

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

    客户信息

    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -

    商品数据

    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - -
    -
    -   - -
    -
    - - - - - - - - - \ 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 deleted file mode 100644 index cbf1968f9..000000000 --- a/ruoyi-admin/src/main/resources/templates/error/404.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - 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 deleted file mode 100644 index fbcefd0d0..000000000 --- a/ruoyi-admin/src/main/resources/templates/error/500.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - 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 deleted file mode 100644 index b64341d3f..000000000 --- a/ruoyi-admin/src/main/resources/templates/error/service.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - 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 deleted file mode 100644 index 64c8de8fe..000000000 --- a/ruoyi-admin/src/main/resources/templates/error/unauth.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - RuoYi - 403 - - - - - -
    -

    403

    -

    您没有访问权限!

    - -
    - 对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面 - 返回主页 -
    -
    - - - diff --git a/ruoyi-admin/src/main/resources/templates/include.html b/ruoyi-admin/src/main/resources/templates/include.html deleted file mode 100644 index 7a600944c..000000000 --- a/ruoyi-admin/src/main/resources/templates/include.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - -
    - -
    -
    - -
    - - -
    - - -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - - -
    - - -
    - - -
    - - -
    - - -
    - - -
    - -
    -
    - - -
    - - -
    - - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    diff --git a/ruoyi-admin/src/main/resources/templates/index-topnav.html b/ruoyi-admin/src/main/resources/templates/index-topnav.html deleted file mode 100644 index 2d99720de..000000000 --- a/ruoyi-admin/src/main/resources/templates/index-topnav.html +++ /dev/null @@ -1,447 +0,0 @@ - - - - - - - 若依系统首页 - - - - - - - - - - - - - -
    - - - - - - -
    - -
    - - - - 刷新 -
    - - - -
    - -
    - - -
    - -
    - - - - - - - - - - - - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/index.html b/ruoyi-admin/src/main/resources/templates/index.html deleted file mode 100644 index 22377fd7b..000000000 --- a/ruoyi-admin/src/main/resources/templates/index.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - - - - 若依系统首页 - - - - - - - - - - - - -
    - - - - - - -
    - -
    - - - - 刷新 -
    - - - -
    - -
    - - -
    - -
    - - - - - - - - - - - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/lock.html b/ruoyi-admin/src/main/resources/templates/lock.html deleted file mode 100644 index 3700ec452..000000000 --- a/ruoyi-admin/src/main/resources/templates/lock.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - 锁定屏幕 - - - - - - -
    -
    -
    [[ ${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 deleted file mode 100644 index 6614e55a4..000000000 --- a/ruoyi-admin/src/main/resources/templates/login.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - 登录若依系统 - - - - - - - - - - - - - - - -
    -
    -
    - -
    -
    -
    -

    登录:

    -

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

    - - -
    -
    - -
    -
    - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - - - - - - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/main.html b/ruoyi-admin/src/main/resources/templates/main.html deleted file mode 100644 index 4f26eb75a..000000000 --- a/ruoyi-admin/src/main/resources/templates/main.html +++ /dev/null @@ -1,1712 +0,0 @@ - - - - - - - 若依介绍 - - - - - - - -
    -
    -
    - 领取阿里云通用云产品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 174942938 -

    -

    微信:/ *若依 -

    -

    支付宝:/ *若依 -

    -
    -
    -
    -
    -
    -
    -
    更新日志
    -
    -
    -
    -
    -
    -
    -
    - v4.7.82023.11.23 -
    -
    -
    -
    -
      -
    1. 用户列表新增抽屉效果详细信息
    2. -
    3. 操作日志列表新增IP地址查询
    4. -
    5. 定时任务新增页去除状态选项
    6. -
    7. 系统管理角色列表显示数据权限
    8. -
    9. 通用排序属性orderBy参数限制长度
    10. -
    11. 新增isScrollToTop页签切换滚动到顶部
    12. -
    13. Excel自定义数据处理器增加单元格/工作簿对象
    14. -
    15. 新增表格参数(数据值为空时显示的内容undefinedText)
    16. -
    17. 升级oshi到最新版本6.4.7
    18. -
    19. 升级shiro到最新版本1.13.0
    20. -
    21. 升级druid到最新版本1.2.20
    22. -
    23. 升级pagehelper到最新版1.4.7
    24. -
    25. 升级spring-boot到最新版本2.5.15
    26. -
    27. 升级jquery到最新版v3.7.1
    28. -
    29. 升级layer到最新版本v3.7.0
    30. -
    31. 升级layui到最新版本v2.8.18
    32. -
    33. 升级x-editable到最新版本1.5.3
    34. -
    35. 修复自定义字典样式不生效的问题
    36. -
    37. 修复弹窗按钮启用禁用方法无效问题
    38. -
    39. 修复横向菜单关闭最后一个页签状态问题
    40. -
    41. 修复Excel导入数据临时文件无法删除问题
    42. -
    43. 修复表格行内编辑启用翻页记住选择无效问题
    44. -
    45. 修复Excels导入时无法获取到dictType字典值问题
    46. -
    47. 优化重置密码鼠标按下显示密码
    48. -
    49. 优化参数键值文本框改为文本域
    50. -
    51. 优化表格重置默认返回到第一页
    52. -
    53. 优化菜单管理类型为按钮状态可选
    54. -
    55. 优化数字金额大写转换精度丢失问题
    56. -
    57. 优化树表查询无数据时清除分页信息
    58. -
    59. 优化通用detail详细信息弹窗不显示按钮
    60. -
    61. 其他细节优化
    62. -
    -
    -
    -
    -
    -
    -
    - v4.7.72023.04.14 -
    -
    -
    -
    -
      -
    1. 操作日志新增消耗时间属性
    2. -
    3. 日志管理使用索引提升查询性能
    4. -
    5. 日志注解支持排除指定的请求参数
    6. -
    7. 新增监控页面图标显示
    8. -
    9. 新增支持登录IP黑名单限制
    10. -
    11. 更新fontawesome图标示例
    12. -
    13. 屏蔽定时任务bean违规的字符
    14. -
    15. 支持自定义隐藏属性列过滤子对象
    16. -
    17. 连接池Druid支持新的配置connectTimeout和socketTimeout
    18. -
    19. 升级jquery到最新版v3.6.3
    20. -
    21. 升级layui到最新版本2.7.6
    22. -
    23. 升级jasny-bootstrap到最新版4.0.0
    24. -
    25. 升级oshi到最新版本6.4.1
    26. -
    27. 升级druid到最新版本1.2.16
    28. -
    29. 修复异步表格树子项排序问题
    30. -
    31. 修复冻结列不支持IE浏览器的问题
    32. -
    33. 修复主子表使用suggest插件无法新增问题
    34. -
    35. 修复菜单栏快速点击导致展开折叠样式问题
    36. -
    37. 修复用户多角色数据权限可能出现权限抬升的情况
    38. -
    39. 修复异步加载表格树重置列表父节点展开异常问题
    40. -
    41. 修复页签属性refresh为undefined时页面被刷新问题
    42. -
    43. 移除apache/commons-fileupload依赖
    44. -
    45. 优化前端属性提醒说明
    46. -
    47. 优化用户导入更新时需获取用户编号问题
    48. -
    49. 优化主子表根据序号删除方法加入表格ID参数
    50. -
    51. 优化导出Excel时设置dictType属性重复查缓存问题
    52. -
    53. 优化在线用户服务缓存改为从Bean容器获取不使用自动装配
    54. -
    55. 优化表格示例行拖拽后列表底部总记录条数变成了undefined问题
    56. -
    57. 其他细节优化
    58. -
    -
    -
    -
    -
    -
    -
    - 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 deleted file mode 100644 index 4df4d1aab..000000000 --- a/ruoyi-admin/src/main/resources/templates/main_v1.html +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - 统计 - - - - - - - - -
    - -
    -
    -
    -
    - -
    收入
    -
    -
    -

    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 deleted file mode 100644 index 38df336ac..000000000 --- a/ruoyi-admin/src/main/resources/templates/monitor/cache/cache.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - -
    - -
    -
    -
    -
    -
    -
    缓存列表
    -
    - -
    -
    -
    - - - - - - - - - - - - - - - -
    缓存名称操作
    [[${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 deleted file mode 100644 index 7b2df062f..000000000 --- a/ruoyi-admin/src/main/resources/templates/monitor/logininfor/logininfor.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - - \ 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 deleted file mode 100644 index 88e5a543f..000000000 --- a/ruoyi-admin/src/main/resources/templates/monitor/online/online.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - - \ 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 deleted file mode 100644 index 3ef3dd4d0..000000000 --- a/ruoyi-admin/src/main/resources/templates/monitor/operlog/detail.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - - - - \ 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 deleted file mode 100644 index 1022216af..000000000 --- a/ruoyi-admin/src/main/resources/templates/monitor/operlog/operlog.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - - - \ 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 deleted file mode 100644 index 18c3315b1..000000000 --- a/ruoyi-admin/src/main/resources/templates/monitor/server/server.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
    -
    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 deleted file mode 100644 index dacc5209c..000000000 --- a/ruoyi-admin/src/main/resources/templates/register.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - 注册若依系统 - - - - - - - - - - - - - - -
    -
    -
    - -
    -
    -
    -

    注册:

    -

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

    - - - -
    -
    - -
    -
    - - - -
    -
    -
    - - 使用条款 -
    - -
    -
    -
    - -
    - - - - - - - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/skin.html b/ruoyi-admin/src/main/resources/templates/skin.html deleted file mode 100644 index c25f24e42..000000000 --- a/ruoyi-admin/src/main/resources/templates/skin.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - 主题选择 - - - - - - - - - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/config/add.html b/ruoyi-admin/src/main/resources/templates/system/config/add.html deleted file mode 100644 index fd718e6fe..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/config/add.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/config/config.html b/ruoyi-admin/src/main/resources/templates/system/config/config.html deleted file mode 100644 index 4f54c2229..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/config/config.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 参数名称: -
    • -
    • - 参数键名: -
    • -
    • - 系统内置: -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index bff71221b..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/config/edit.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/add.html b/ruoyi-admin/src/main/resources/templates/system/dept/add.html deleted file mode 100644 index bc3739ee8..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dept/add.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - -
    -
    - -
    - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/dept.html b/ruoyi-admin/src/main/resources/templates/system/dept/dept.html deleted file mode 100644 index 6663d1848..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dept/dept.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 部门名称: -
    • -
    • - 部门状态: -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 46bfcb867..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dept/edit.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - -
    -
    - - -
    - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/dept/tree.html b/ruoyi-admin/src/main/resources/templates/system/dept/tree.html deleted file mode 100644 index a681811a7..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dept/tree.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - -
    - - -
    - -
    - 展开 / - 折叠 -
    -
    -
    - - - - - 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 deleted file mode 100644 index 068ddb260..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dict/data/add.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 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 deleted file mode 100644 index 917f51531..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dict/data/data.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 字典名称: -
    • -
    • - 字典标签: -
    • -
    • - 数据状态: -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - - \ 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 deleted file mode 100644 index a1a55531f..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dict/data/edit.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 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 deleted file mode 100644 index fe9fd8071..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dict/type/add.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 数据存储中的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 deleted file mode 100644 index 82c33808c..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dict/type/edit.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - - 数据存储中的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 deleted file mode 100644 index aae7d18e4..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dict/type/tree.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - -
    - - -
    - -
    -
    - - - - - 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 deleted file mode 100644 index cfd18cb6a..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/dict/type/type.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 字典名称: -
    • -
    • - 字典类型: -
    • -
    • - 字典状态: -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index b9a6cb1d1..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/menu/add.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - -
    -
    - -
    - -
    -
    - - -
    -
    -
    -
    - -
    - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 控制器中定义的权限标识,如:@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 deleted file mode 100644 index d20194cea..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/menu/edit.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - -
    -
    - - -
    - -
    -
    - - -
    -
    -
    -
    - -
    - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 控制器中定义的权限标识,如:@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 deleted file mode 100644 index 61cb177ba..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/menu/icon.html +++ /dev/null @@ -1,928 +0,0 @@ - - - - - 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 deleted file mode 100644 index 45baba914..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/menu/menu.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    - - - - - \ 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 deleted file mode 100644 index c578b1c72..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/menu/tree.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - -
    - - -
    - -
    - 展开 / - 折叠 -
    -
    -
    - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/add.html b/ruoyi-admin/src/main/resources/templates/system/notice/add.html deleted file mode 100644 index 6a1a94b4d..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/notice/add.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/edit.html b/ruoyi-admin/src/main/resources/templates/system/notice/edit.html deleted file mode 100644 index 311d54bd6..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/notice/edit.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/notice.html b/ruoyi-admin/src/main/resources/templates/system/notice/notice.html deleted file mode 100644 index 4ad5c8950..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/notice/notice.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 公告标题: -
    • -
    • - 操作人员: -
    • -
    • - 公告类型: -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/notice/view.html b/ruoyi-admin/src/main/resources/templates/system/notice/view.html deleted file mode 100644 index 38791c64a..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/notice/view.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -

    [[${notice.noticeTitle}]]

    -
    - 发送时间:[[${#dates.format(notice.createTime, 'yyyy-MM-dd HH:mm:ss')}]] - 发件人: [[${notice.createBy}]] -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index ac2d554f8..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/post/add.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/post/edit.html b/ruoyi-admin/src/main/resources/templates/system/post/edit.html deleted file mode 100644 index d3e84be28..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/post/edit.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/post/post.html b/ruoyi-admin/src/main/resources/templates/system/post/post.html deleted file mode 100644 index 52700770f..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/post/post.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 岗位编码: -
    • -
    • - 岗位名称: -
    • -
    • - 岗位状态: -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 216c13c4e..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/role/add.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 控制器中定义的权限字符,如:@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 deleted file mode 100644 index a3d087be6..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/role/authUser.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -
    -
    -
    -
    - -
    - -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 2f3b0f751..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/role/dataScope.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 特殊情况下,设置为“自定数据权限” -
    -
    -
    - -
    - - - -
    -
    -
    -
    -
    - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/role/edit.html b/ruoyi-admin/src/main/resources/templates/system/role/edit.html deleted file mode 100644 index d85f8ebbb..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/role/edit.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - - 控制器中定义的权限字符,如:@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 deleted file mode 100644 index f464ea576..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/role/role.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 角色名称: -
    • -
    • - 权限字符: -
    • -
    • - 角色状态: -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 018d8b81f..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/role/selectUser.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 017497763..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/add.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - -
    -
    - -

    基本信息

    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -

    其他信息

    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -   - -
    -
    - - - - - \ 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 deleted file mode 100644 index feaee5094..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/authRole.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - -
    -
    - -

    基本信息

    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - -

    分配角色

    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -   - -
    -
    - - - - \ 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 deleted file mode 100644 index 1286ba3a2..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/deptTree.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - -
    - - -
    - -
    - 展开 / - 折叠 -
    -
    -
    - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/user/edit.html b/ruoyi-admin/src/main/resources/templates/system/user/edit.html deleted file mode 100644 index 74cc8d4f0..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/edit.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - -
    -
    - - -

    基本信息

    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -

    其他信息

    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    -   - -
    -
    - - - - - \ 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 deleted file mode 100644 index f64b96f28..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/profile/avatar.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - - - -
    -
    -
    - -
    -
    - - - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - - 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 deleted file mode 100644 index 4cb88b1ee..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/profile/profile.html +++ /dev/null @@ -1,299 +0,0 @@ - - - - - - - - - -
    -
    -
    -
    -
    -
    个人资料
    -
    -
    -
    - -

    修改头像

    -
    -
      -
    • - 登录名称: -

      [[${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 deleted file mode 100644 index dfb4ee2a8..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/profile/resetPwd.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - - - - 密码只能为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 deleted file mode 100644 index fb57bd88b..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/resetPwd.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - -
    -
    - -
    - -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    - - - - - diff --git a/ruoyi-admin/src/main/resources/templates/system/user/user.html b/ruoyi-admin/src/main/resources/templates/system/user/user.html deleted file mode 100644 index a241941be..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/user.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - - - -
    -
    -
    -
    - 组织机构 -
    -
    - - - - -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - -
    -
      -
    • - 登录名称: -
    • -
    • - 手机号码: -
    • -
    • - 用户状态: -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/user/view.html b/ruoyi-admin/src/main/resources/templates/system/user/view.html deleted file mode 100644 index d3d17716f..000000000 --- a/ruoyi-admin/src/main/resources/templates/system/user/view.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - -
    -
    -

    基本信息

    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    -

    [[${#strings.defaultString(postGroup, '无岗位')}]]

    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    -

    [[${#strings.defaultString(roleGroup, '无角色')}]]

    -
    -
    -
    -
    -

    其他信息

    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    -

    -
    -
    -
    -
    -
    -
    - - - \ 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 deleted file mode 100644 index b60c9eb36..000000000 --- a/ruoyi-admin/src/main/resources/templates/tool/build/build.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - -
    -
    -
    -
    -
    -
    元素
    -
    - - - - - - - - - - -
    -
    -
    -
    - 拖拽左侧的表单元素到右侧区域,即可生成相应的HTML代码,表单代码,轻松搞定! -
    -
    -
    - -
    - -
    -
    - -
    - -
    - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - - -
    -

    这里是纯文字信息

    -
    -
    -
    - - -
    - - -
    -
    -
    - - -
    - - - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    拖拽左侧表单元素到此区域
    -
    - 请选择显示的列数: - -
    -
    - -
    -
    -
    -
    - - -
    - -
    -
    -
    -
    -
    - - - - - - - diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 8806c451e..38326a1fa 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 4.7.8 + 3.8.7 4.0.0 @@ -29,16 +29,10 @@ spring-web - + - org.apache.shiro - shiro-core - - - - - org.apache.shiro - shiro-ehcache + org.springframework.boot + spring-boot-starter-security @@ -58,17 +52,24 @@ org.apache.commons commons-lang3 - + com.fasterxml.jackson.core jackson-databind + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.5.2 + - com.alibaba - fastjson + com.alibaba.fastjson2 + fastjson2 @@ -89,6 +90,36 @@ snakeyaml + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + eu.bitwalker + UserAgentUtils + + javax.servlet 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 index d3115328e..cebe592c6 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java @@ -27,7 +27,7 @@ public @interface DataScope public String userAlias() default ""; /** - * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@RequiresPermissions获取,多个权限用逗号分隔开来 + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 */ public String permission() default ""; } 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 index 8c6870c20..1f1cc81bf 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java @@ -14,5 +14,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface Excels { - Excel[] value(); -} \ No newline at end of file + public Excel[] value(); +} 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 index 18fd2313d..5ae846a1a 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java @@ -12,6 +12,7 @@ import com.ruoyi.common.enums.OperatorType; * 自定义操作日志记录注解 * * @author ruoyi + * */ @Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java new file mode 100644 index 000000000..0f024c7d4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +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.constant.CacheConstants; +import com.ruoyi.common.enums.LimitType; + +/** + * 限流注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.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 index 3e06e9584..b76974819 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java @@ -2,6 +2,7 @@ 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; @@ -12,6 +13,7 @@ import java.lang.annotation.Target; * @author ruoyi * */ +@Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @@ -25,5 +27,5 @@ public @interface RepeatSubmit /** * 提示消息 */ - public String message() default "不允许重复提交,请稍后再试"; -} \ No newline at end of file + public String message() default "不允许重复提交,请稍候再试"; +} 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 index cee48bcac..eac3da152 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java @@ -4,7 +4,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** - * 全局配置类 + * 读取项目相关配置 * * @author ruoyi */ @@ -13,16 +13,13 @@ import org.springframework.stereotype.Component; public class RuoYiConfig { /** 项目名称 */ - private static String name; + private String name; /** 版本 */ - private static String version; + private String version; /** 版权年份 */ - private static String copyrightYear; - - /** 实例演示开关 */ - private static boolean demoEnabled; + private String copyrightYear; /** 上传路径 */ private static String profile; @@ -30,44 +27,37 @@ public class RuoYiConfig /** 获取地址开关 */ private static boolean addressEnabled; - public static String getName() + /** 验证码类型 */ + private static String captchaType; + + public String getName() { return name; } public void setName(String name) { - RuoYiConfig.name = name; + this.name = name; } - public static String getVersion() + public String getVersion() { return version; } public void setVersion(String version) { - RuoYiConfig.version = version; + this.version = version; } - public static String getCopyrightYear() + public 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; + this.copyrightYear = copyrightYear; } public static String getProfile() @@ -90,6 +80,14 @@ public class RuoYiConfig RuoYiConfig.addressEnabled = addressEnabled; } + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + RuoYiConfig.captchaType = captchaType; + } + /** * 获取导入上传路径 */ diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java index 02241c5a2..e819a1d7b 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java @@ -9,9 +9,9 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.ruoyi.common.annotation.Sensitive; -import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.enums.DesensitizedType; -import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.SecurityUtils; /** * 数据脱敏序列化过滤 @@ -53,12 +53,15 @@ public class SensitiveJsonSerializer extends JsonSerializer implements C */ private boolean desensitization() { - SysUser securityUser = ShiroUtils.getSysUser(); - if (securityUser == null) + try + { + LoginUser securityUser = SecurityUtils.getLoginUser(); + // 管理员不脱敏 + return !securityUser.getUser().isAdmin(); + } + catch (Exception e) { return true; } - // 管理员不脱敏 - return !securityUser.isAdmin(); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java new file mode 100644 index 000000000..0080343a9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -0,0 +1,44 @@ +package com.ruoyi.common.constant; + +/** + * 缓存的key 常量 + * + * @author ruoyi + */ +public class CacheConstants +{ + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; +} 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 index 9da01ab4f..a94c3fa35 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -1,6 +1,7 @@ package com.ruoyi.common.constant; import java.util.Locale; +import io.jsonwebtoken.Claims; /** * 通用常量信息 @@ -24,6 +25,11 @@ public class Constants */ public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; + /** + * www主域 + */ + public static final String WWW = "www."; + /** * http请求 */ @@ -65,29 +71,69 @@ public class Constants public static final String LOGIN_FAIL = "Error"; /** - * 系统用户授权缓存 + * 所有权限标识 */ - public static final String SYS_AUTH_CACHE = "sys-authCache"; + public static final String ALL_PERMISSION = "*:*:*"; /** - * 参数管理 cache name + * 管理员角色权限标识 */ - public static final String SYS_CONFIG_CACHE = "sys-config"; + public static final String SUPER_ADMIN = "admin"; /** - * 参数管理 cache key + * 角色权限分隔符 */ - public static final String SYS_CONFIG_KEY = "sys_config:"; + public static final String ROLE_DELIMETER = ","; /** - * 字典管理 cache name + * 权限标识分隔符 */ - public static final String SYS_DICT_CACHE = "sys-dict"; + public static final String PERMISSION_DELIMETER = ","; /** - * 字典管理 cache key + * 验证码有效期(分钟) */ - public static final String SYS_DICT_KEY = "sys_dict:"; + public static final Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ + public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; /** * 资源映射路径 前缀 @@ -109,6 +155,11 @@ public class Constants */ public static final String LOOKUP_LDAPS = "ldaps:"; + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" }; + /** * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) */ @@ -119,4 +170,4 @@ public class Constants */ 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", "com.ruoyi.generator" }; -} \ 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 index d5fa65e14..e7382b3f1 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java @@ -59,7 +59,7 @@ public class GenConstants 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[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" }; /** 文本框 */ public static final String HTML_INPUT = "input"; @@ -79,11 +79,14 @@ public class GenConstants /** 日期控件 */ public static final String HTML_DATETIME = "datetime"; - /** 上传控件 */ - public static final String HTML_UPLOAD = "upload"; + /** 图片上传控件 */ + public static final String HTML_IMAGE_UPLOAD = "imageUpload"; + + /** 文件上传控件 */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; /** 富文本控件 */ - public static final String HTML_SUMMERNOTE = "summernote"; + public static final String HTML_EDITOR = "editor"; /** 字符串类型 */ public static final String TYPE_STRING = "String"; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java new file mode 100644 index 000000000..a983c77ec --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java @@ -0,0 +1,94 @@ +package com.ruoyi.common.constant; + +/** + * 返回状态码 + * + * @author ruoyi + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} 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 deleted file mode 100644 index 7caf18542..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/PermissionConstants.java +++ /dev/null @@ -1,27 +0,0 @@ -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/ShiroConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java deleted file mode 100644 index 239d36fc7..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java +++ /dev/null @@ -1,79 +0,0 @@ -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 LOGIN_RECORD_CACHE = "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 index 90779ca2e..b09b6f3d5 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java @@ -35,8 +35,32 @@ public class UserConstants /** 是否为系统默认(是) */ public static final String YES = "Y"; + + /** 是否菜单外链(是) */ + public static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; - /** 是否唯一的返回标识 */ + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验是否唯一的返回标识 */ public final static boolean UNIQUE = true; public final static boolean NOT_UNIQUE = false; @@ -51,20 +75,4 @@ public class UserConstants */ 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/controller/BaseController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java index c2985040a..c131bc478 100644 --- 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 @@ -3,25 +3,21 @@ 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.constant.HttpStatus; 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.domain.model.LoginUser; 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.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.sql.SqlUtil; @@ -80,30 +76,6 @@ public class BaseController PageUtils.clearPage(); } - /** - * 获取request - */ - public HttpServletRequest getRequest() - { - return ServletUtils.getRequest(); - } - - /** - * 获取response - */ - public HttpServletResponse getResponse() - { - return ServletUtils.getResponse(); - } - - /** - * 获取session - */ - public HttpSession getSession() - { - return getRequest().getSession(); - } - /** * 响应请求分页数据 */ @@ -111,34 +83,13 @@ public class BaseController protected TableDataInfo getDataTable(List list) { TableDataInfo rspData = new TableDataInfo(); - rspData.setCode(0); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); 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(); - } - /** * 返回成功 */ @@ -162,13 +113,13 @@ public class BaseController { return AjaxResult.success(message); } - + /** - * 返回成功数据 + * 返回成功消息 */ - public static AjaxResult success(Object data) + public AjaxResult success(Object data) { - return AjaxResult.success("操作成功", data); + return AjaxResult.success(data); } /** @@ -180,11 +131,33 @@ public class BaseController } /** - * 返回错误码消息 + * 返回警告消息 */ - public AjaxResult error(Type type, String message) + public AjaxResult warn(String message) { - return new AjaxResult(type, message); + return AjaxResult.warn(message); + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); } /** @@ -198,17 +171,9 @@ public class BaseController /** * 获取用户缓存信息 */ - public SysUser getSysUser() + public LoginUser getLoginUser() { - return ShiroUtils.getSysUser(); - } - - /** - * 设置用户缓存信息 - */ - public void setSysUser(SysUser user) - { - ShiroUtils.setSysUser(user); + return SecurityUtils.getLoginUser(); } /** @@ -216,14 +181,22 @@ public class BaseController */ public Long getUserId() { - return getSysUser().getUserId(); + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() + { + return getLoginUser().getDeptId(); } /** * 获取登录用户名 */ - public String getLoginName() + public String getUsername() { - return getSysUser().getLoginName(); + return getLoginUser().getUsername(); } } 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 index 1f59cbcb9..42dcbcded 100644 --- 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 @@ -2,11 +2,12 @@ package com.ruoyi.common.core.domain; import java.util.HashMap; import java.util.Objects; +import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.utils.StringUtils; /** * 操作消息提醒 - * + * * @author ruoyi */ public class AjaxResult extends HashMap @@ -22,30 +23,6 @@ public class AjaxResult extends HashMap /** 数据对象 */ 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 对象,使其表示一个空消息。 */ @@ -55,26 +32,26 @@ public class AjaxResult extends HashMap /** * 初始化一个新创建的 AjaxResult 对象 - * - * @param type 状态类型 + * + * @param code 状态码 * @param msg 返回内容 */ - public AjaxResult(Type type, String msg) + public AjaxResult(int code, String msg) { - super.put(CODE_TAG, type.value); + super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } /** * 初始化一个新创建的 AjaxResult 对象 - * - * @param type 状态类型 + * + * @param code 状态码 * @param msg 返回内容 * @param data 数据对象 */ - public AjaxResult(Type type, String msg, Object data) + public AjaxResult(int code, String msg, Object data) { - super.put(CODE_TAG, type.value); + super.put(CODE_TAG, code); super.put(MSG_TAG, msg); if (StringUtils.isNotNull(data)) { @@ -84,7 +61,7 @@ public class AjaxResult extends HashMap /** * 返回成功消息 - * + * * @return 成功消息 */ public static AjaxResult success() @@ -94,7 +71,7 @@ public class AjaxResult extends HashMap /** * 返回成功数据 - * + * * @return 成功消息 */ public static AjaxResult success(Object data) @@ -104,7 +81,7 @@ public class AjaxResult extends HashMap /** * 返回成功消息 - * + * * @param msg 返回内容 * @return 成功消息 */ @@ -115,14 +92,14 @@ public class AjaxResult extends HashMap /** * 返回成功消息 - * + * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { - return new AjaxResult(Type.SUCCESS, msg, data); + return new AjaxResult(HttpStatus.SUCCESS, msg, data); } /** @@ -145,13 +122,13 @@ public class AjaxResult extends HashMap */ public static AjaxResult warn(String msg, Object data) { - return new AjaxResult(Type.WARN, msg, data); + return new AjaxResult(HttpStatus.WARN, msg, data); } /** * 返回错误消息 - * - * @return + * + * @return 错误消息 */ public static AjaxResult error() { @@ -160,9 +137,9 @@ public class AjaxResult extends HashMap /** * 返回错误消息 - * + * * @param msg 返回内容 - * @return 警告消息 + * @return 错误消息 */ public static AjaxResult error(String msg) { @@ -171,14 +148,26 @@ public class AjaxResult extends HashMap /** * 返回错误消息 - * + * * @param msg 返回内容 * @param data 数据对象 - * @return 警告消息 + * @return 错误消息 */ public static AjaxResult error(String msg, Object data) { - return new AjaxResult(Type.ERROR, msg, data); + return new AjaxResult(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); } /** @@ -188,7 +177,7 @@ public class AjaxResult extends HashMap */ public boolean isSuccess() { - return Objects.equals(Type.SUCCESS.value, this.get(CODE_TAG)); + return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG)); } /** @@ -198,7 +187,7 @@ public class AjaxResult extends HashMap */ public boolean isWarn() { - return Objects.equals(Type.WARN.value, this.get(CODE_TAG)); + return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG)); } /** @@ -208,7 +197,7 @@ public class AjaxResult extends HashMap */ public boolean isError() { - return Objects.equals(Type.ERROR.value, this.get(CODE_TAG)); + return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG)); } /** 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 deleted file mode 100644 index 28c123520..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/CxSelect.java +++ /dev/null @@ -1,69 +0,0 @@ -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 index 835f68b86..e0626d0ce 100644 --- 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 @@ -1,6 +1,7 @@ package com.ruoyi.common.core.domain; import java.io.Serializable; +import com.ruoyi.common.constant.HttpStatus; /** * 响应信息主体 @@ -12,10 +13,10 @@ public class R implements Serializable private static final long serialVersionUID = 1L; /** 成功 */ - public static final int SUCCESS = 0; + public static final int SUCCESS = HttpStatus.SUCCESS; /** 失败 */ - public static final int FAIL = 500; + public static final int FAIL = HttpStatus.ERROR; private int code; 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 index 7a0efe62a..171f04c9b 100644 --- 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 @@ -1,5 +1,8 @@ package com.ruoyi.common.core.domain; +import java.util.ArrayList; +import java.util.List; + /** * Tree基类 * @@ -21,6 +24,9 @@ public class TreeEntity extends BaseEntity /** 祖级列表 */ private String ancestors; + /** 子部门 */ + private List children = new ArrayList<>(); + public String getParentName() { return parentName; @@ -60,4 +66,14 @@ public class TreeEntity extends BaseEntity { this.ancestors = ancestors; } -} \ No newline at end of file + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java new file mode 100644 index 000000000..bd835db99 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java @@ -0,0 +1,77 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysMenu; + +/** + * Treeselect树结构实体类 + * + * @author ruoyi + */ +public class TreeSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点名称 */ + private String label; + + /** 子节点 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + public TreeSelect() + { + + } + + public TreeSelect(SysDept dept) + { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysMenu menu) + { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} 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 deleted file mode 100644 index e7fe780e4..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Ztree.java +++ /dev/null @@ -1,104 +0,0 @@ -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 index a3a25d09d..693461b9f 100644 --- 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 @@ -1,12 +1,13 @@ package com.ruoyi.common.core.domain.entity; +import java.util.ArrayList; +import java.util.List; 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; /** @@ -50,9 +51,9 @@ public class SysDept extends BaseEntity /** 父部门名称 */ private String parentName; - - /** 排除编号 */ - private Long excludeId; + + /** 子部门 */ + private List children = new ArrayList(); public Long getDeptId() { @@ -170,15 +171,14 @@ public class SysDept extends BaseEntity this.parentName = parentName; } - @JsonIgnore - public Long getExcludeId() + public List getChildren() { - return excludeId; + return children; } - public void setExcludeId(Long excludeId) + public void setChildren(List children) { - this.excludeId = excludeId; + this.children = children; } @Override 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 index 404c14bb1..e79eaa20c 100644 --- 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 @@ -1,6 +1,7 @@ package com.ruoyi.common.core.domain.entity; -import javax.validation.constraints.*; +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.annotation.Excel; @@ -38,7 +39,6 @@ public class SysDictData extends BaseEntity private String dictType; /** 样式属性(其他样式扩展) */ - @Excel(name = "字典样式") private String cssClass; /** 表格字典样式 */ @@ -153,7 +153,7 @@ public class SysDictData extends BaseEntity { this.status = status; } - + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) 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 index 1ce61a824..0befbf434 100644 --- 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 @@ -1,6 +1,8 @@ package com.ruoyi.common.core.domain.entity; -import javax.validation.constraints.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; @@ -76,7 +78,7 @@ public class SysDictType extends BaseEntity { this.status = status; } - + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) 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 index da1869a5c..9f3a6f659 100644 --- 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 @@ -1,8 +1,10 @@ package com.ruoyi.common.core.domain.entity; -import java.util.List; import java.util.ArrayList; -import javax.validation.constraints.*; +import java.util.List; +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.ruoyi.common.core.domain.BaseEntity; @@ -29,22 +31,31 @@ public class SysMenu extends BaseEntity private Long parentId; /** 显示顺序 */ - private String orderNum; + private Integer orderNum; - /** 菜单URL */ - private String url; + /** 路由地址 */ + private String path; - /** 打开方式(menuItem页签 menuBlank新窗口) */ - private String target; + /** 组件路径 */ + private String component; + + /** 路由参数 */ + private String query; + + /** 是否为外链(0是 1否) */ + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + private String isCache; /** 类型(M目录 C菜单 F按钮) */ private String menuType; - /** 菜单状态(0显示 1隐藏) */ + /** 显示状态(0显示 1隐藏) */ private String visible; - - /** 是否刷新(0刷新 1不刷新) */ - private String isRefresh; + + /** 菜单状态(0正常 1停用) */ + private String status; /** 权限字符串 */ private String perms; @@ -97,36 +108,67 @@ public class SysMenu extends BaseEntity this.parentId = parentId; } - @NotBlank(message = "显示顺序不能为空") - public String getOrderNum() + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() { return orderNum; } - public void setOrderNum(String orderNum) + public void setOrderNum(Integer orderNum) { this.orderNum = orderNum; } - @Size(min = 0, max = 200, message = "请求地址不能超过200个字符") - public String getUrl() + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() { - return url; + return path; } - public void setUrl(String url) + public void setPath(String path) { - this.url = url; + this.path = path; } - public String getTarget() + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() { - return target; + return component; } - public void setTarget(String target) + public void setComponent(String component) { - this.target = target; + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public String getIsFrame() + { + return isFrame; + } + + public void setIsFrame(String isFrame) + { + this.isFrame = isFrame; + } + + public String getIsCache() + { + return isCache; + } + + public void setIsCache(String isCache) + { + this.isCache = isCache; } @NotBlank(message = "菜单类型不能为空") @@ -150,14 +192,14 @@ public class SysMenu extends BaseEntity this.visible = visible; } - public String getIsRefresh() + public String getStatus() { - return isRefresh; + return status; } - public void setIsRefresh(String isRefresh) + public void setStatus(String status) { - this.isRefresh = isRefresh; + this.status = status; } @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") @@ -190,7 +232,7 @@ public class SysMenu extends BaseEntity { this.children = children; } - + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) @@ -198,10 +240,13 @@ public class SysMenu extends BaseEntity .append("menuName", getMenuName()) .append("parentId", getParentId()) .append("orderNum", getOrderNum()) - .append("url", getUrl()) - .append("target", getTarget()) + .append("path", getPath()) + .append("component", getComponent()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) .append("menuType", getMenuType()) .append("visible", getVisible()) + .append("status ", getStatus()) .append("perms", getPerms()) .append("icon", getIcon()) .append("createBy", getCreateBy()) 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 index 03aa3b0ac..2396f8022 100644 --- 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 @@ -1,7 +1,9 @@ package com.ruoyi.common.core.domain.entity; import java.util.Set; -import javax.validation.constraints.*; +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.ruoyi.common.annotation.Excel; @@ -30,13 +32,19 @@ public class SysRole extends BaseEntity private String roleKey; /** 角色排序 */ - @Excel(name = "角色排序", cellType = ColumnType.NUMERIC) - private String roleSort; + @Excel(name = "角色排序") + private Integer roleSort; /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") private String dataScope; + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + private boolean deptCheckStrictly; + /** 角色状态(0正常 1停用) */ @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") private String status; @@ -86,16 +94,6 @@ public class SysRole extends BaseEntity 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() @@ -120,22 +118,57 @@ public class SysRole extends BaseEntity this.roleKey = roleKey; } - @NotBlank(message = "显示顺序不能为空") - public String getRoleSort() + @NotNull(message = "显示顺序不能为空") + public Integer getRoleSort() { return roleSort; } - public void setRoleSort(String roleSort) + public void setRoleSort(Integer roleSort) { this.roleSort = roleSort; } + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + public boolean isMenuCheckStrictly() + { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(boolean menuCheckStrictly) + { + this.menuCheckStrictly = menuCheckStrictly; + } + + public boolean isDeptCheckStrictly() + { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(boolean deptCheckStrictly) + { + this.deptCheckStrictly = deptCheckStrictly; + } + public String getStatus() { return status; } + public void setStatus(String status) + { + this.status = status; + } + public String getDelFlag() { return delFlag; @@ -146,11 +179,6 @@ public class SysRole extends BaseEntity this.delFlag = delFlag; } - public void setStatus(String status) - { - this.status = status; - } - public boolean isFlag() { return flag; @@ -199,6 +227,8 @@ public class SysRole extends BaseEntity .append("roleKey", getRoleKey()) .append("roleSort", getRoleSort()) .append("dataScope", getDataScope()) + .append("menuCheckStrictly", isMenuCheckStrictly()) + .append("deptCheckStrictly", isDeptCheckStrictly()) .append("status", getStatus()) .append("delFlag", getDelFlag()) .append("createBy", getCreateBy()) 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 index 15c1dead2..4e04642d0 100644 --- 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 @@ -5,7 +5,6 @@ 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; @@ -30,22 +29,13 @@ public class SysUser extends BaseEntity @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 nickName; /** 用户邮箱 */ @Excel(name = "用户邮箱") @@ -65,9 +55,6 @@ public class SysUser extends BaseEntity /** 密码 */ private String password; - /** 盐加密 */ - private String salt; - /** 帐号状态(0正常 1停用) */ @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") private String status; @@ -83,9 +70,6 @@ public class SysUser extends BaseEntity @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), @@ -93,6 +77,7 @@ public class SysUser extends BaseEntity }) private SysDept dept; + /** 角色对象 */ private List roles; /** 角色组 */ @@ -101,6 +86,9 @@ public class SysUser extends BaseEntity /** 岗位组 */ private Long[] postIds; + /** 角色ID */ + private Long roleId; + public SysUser() { @@ -141,41 +129,21 @@ public class SysUser extends BaseEntity 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 getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + @Xss(message = "用户账号不能包含脚本字符") + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") public String getUserName() { return userName; @@ -186,16 +154,6 @@ public class SysUser extends BaseEntity 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() @@ -239,7 +197,6 @@ public class SysUser extends BaseEntity this.avatar = avatar; } - @JsonIgnore public String getPassword() { return password; @@ -250,17 +207,6 @@ public class SysUser extends BaseEntity this.password = password; } - @JsonIgnore - public String getSalt() - { - return salt; - } - - public void setSalt(String salt) - { - this.salt = salt; - } - public String getStatus() { return status; @@ -301,22 +247,8 @@ public class SysUser extends BaseEntity 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; } @@ -355,20 +287,28 @@ public class SysUser extends BaseEntity this.postIds = postIds; } + 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("deptId", getDeptId()) - .append("loginName", getLoginName()) .append("userName", getUserName()) - .append("userType", getUserType()) + .append("nickName", getNickName()) .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()) @@ -379,7 +319,6 @@ public class SysUser extends BaseEntity .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/domain/model/LoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java new file mode 100644 index 000000000..b5bc8c8e2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java @@ -0,0 +1,69 @@ +package com.ruoyi.common.core.domain.model; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +public class LoginBody +{ + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid; + + 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 getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getUuid() + { + return uuid; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java new file mode 100644 index 000000000..670e6b353 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java @@ -0,0 +1,266 @@ +package com.ruoyi.common.core.domain.model; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.ruoyi.common.core.domain.entity.SysUser; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.Collection; +import java.util.Set; + +/** + * 登录用户身份权限 + * + * @author ruoyi + */ +public class LoginUser implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + 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 Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection getAuthorities() + { + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java new file mode 100644 index 000000000..868a1fc52 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java @@ -0,0 +1,11 @@ +package com.ruoyi.common.core.domain.model; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class RegisterBody extends LoginBody +{ + +} 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 index 975c465b3..844168c6a 100644 --- 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 @@ -70,7 +70,19 @@ public class PageDomain public void setIsAsc(String isAsc) { - this.isAsc = isAsc; + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } } public Boolean getReasonable() 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 index 29f60e7fc..a3487b8ed 100644 --- 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 @@ -82,4 +82,4 @@ public class TableDataInfo implements Serializable { this.msg = msg; } -} \ No newline at end of file +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java new file mode 100644 index 000000000..44e80d83a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java @@ -0,0 +1,268 @@ +package com.ruoyi.common.core.redis; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisCache +{ + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) + { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) + { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) + { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) + { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) + { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) + { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) + { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) + { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) + { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) + { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) + { + return redisTemplate.keys(pattern); + } +} 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 index 43612e5d7..78188fc16 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java @@ -4,6 +4,7 @@ package com.ruoyi.common.enums; * 操作状态 * * @author ruoyi + * */ public enum BusinessStatus { 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 index d22bccbd3..2bbeaf7d3 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java @@ -53,7 +53,7 @@ public enum BusinessType GENCODE, /** - * 清空 + * 清空数据 */ CLEAN, } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java new file mode 100644 index 000000000..be6f73929 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java @@ -0,0 +1,36 @@ +package com.ruoyi.common.enums; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.lang.Nullable; + +/** + * 请求方式 + * + * @author ruoyi + */ +public enum HttpMethod +{ + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + private static final Map mappings = new HashMap<>(16); + + static + { + for (HttpMethod httpMethod : values()) + { + mappings.put(httpMethod.name(), httpMethod); + } + } + + @Nullable + public static HttpMethod resolve(@Nullable String method) + { + return (method != null ? mappings.get(method) : null); + } + + public boolean matches(String method) + { + return (this == resolve(method)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java new file mode 100644 index 000000000..c609fd8ac --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} 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 deleted file mode 100644 index b0bdd1003..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/OnlineStatus.java +++ /dev/null @@ -1,24 +0,0 @@ -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/exception/ServiceException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java index e04facca1..a9247b570 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java @@ -9,6 +9,11 @@ public final class ServiceException extends RuntimeException { private static final long serialVersionUID = 1L; + /** + * 错误码 + */ + private Integer code; + /** * 错误提示 */ @@ -33,26 +38,37 @@ public final class ServiceException extends RuntimeException this.message = message; } + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + public String getDetailMessage() { return detailMessage; } - public ServiceException setDetailMessage(String detailMessage) - { - this.detailMessage = detailMessage; - return this; - } - @Override public String getMessage() { return message; } + public Integer getCode() + { + return code; + } + public ServiceException setMessage(String message) { this.message = message; return this; } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } } \ No newline at end of file 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 index f24d744ac..f3e9c3314 100644 --- 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 @@ -67,7 +67,7 @@ public class InvalidExtensionException extends FileUploadException super(allowedExtension, extension, filename); } } - + public static class InvalidVideoExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java new file mode 100644 index 000000000..85f94861b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 验证码失效异常类 + * + * @author ruoyi + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", 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 deleted file mode 100644 index e11624426..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/RoleBlockedException.java +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 8feb8493e..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserBlockedException.java +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index cf0724228..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserDeleteException.java +++ /dev/null @@ -1,16 +0,0 @@ -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/UserPasswordRetryLimitCountException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java deleted file mode 100644 index 4222135f5..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java +++ /dev/null @@ -1,16 +0,0 @@ -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 index b48c40657..0de8d2408 100644 --- 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 @@ -9,8 +9,8 @@ public class UserPasswordRetryLimitExceedException extends UserException { private static final long serialVersionUID = 1L; - public UserPasswordRetryLimitExceedException(int retryLimitCount) + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) { - super("user.password.retry.limit.exceed", new Object[] { retryLimitCount }); + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java new file mode 100644 index 000000000..e1e431b19 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.filter; + +import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; + +/** + * 排除JSON敏感属性 + * + * @author ruoyi + */ +public class PropertyPreExcludeFilter extends SimplePropertyPreFilter +{ + public PropertyPreExcludeFilter() + { + } + + public PropertyPreExcludeFilter addExcludes(String... filters) + { + for (int i = 0; i < filters.length; i++) + { + this.getExcludes().add(filters[i]); + } + return this; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java new file mode 100644 index 000000000..a1bcfe2a4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +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 org.springframework.http.MediaType; +import com.ruoyi.common.utils.StringUtils; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) + { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) + { + chain.doFilter(request, response); + } + else + { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 000000000..407d1ba42 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import com.ruoyi.common.utils.http.HttpHelper; +import com.ruoyi.common.constant.Constants; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + @Override + public int read() throws IOException + { + return bais.read(); + } + + @Override + public int available() throws IOException + { + return body.length; + } + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + }; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java similarity index 91% rename from ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java rename to ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java index 30fd69ce8..9052f0da3 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java @@ -1,74 +1,75 @@ -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() - { - - } +package com.ruoyi.common.filter; + +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; +import com.ruoyi.common.enums.HttpMethod; + +/** + * 防止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 || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) + { + 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/filter/XssHttpServletRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 000000000..05149f018 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.ruoyi.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.ruoyi.common.utils.StringUtils; +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[] escapesValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapesValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapesValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes("utf-8"); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public int available() throws IOException + { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file 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 deleted file mode 100644 index 119147b6a..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/json/JSON.java +++ /dev/null @@ -1,187 +0,0 @@ -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 deleted file mode 100644 index 24ef6ccb7..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/json/JSONObject.java +++ /dev/null @@ -1,749 +0,0 @@ -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/CacheUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java deleted file mode 100644 index ae27682de..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java +++ /dev/null @@ -1,197 +0,0 @@ -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 deleted file mode 100644 index 13c1d5ed0..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/CookieUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -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/DictUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java index 5d5dedf1e..cc5eab2c5 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java @@ -1,16 +1,18 @@ package com.ruoyi.common.utils; +import java.util.Collection; import java.util.List; -import org.springframework.stereotype.Component; -import com.ruoyi.common.constant.Constants; +import com.alibaba.fastjson2.JSONArray; +import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.spring.SpringUtils; /** * 字典工具类 * * @author ruoyi */ -@Component public class DictUtils { /** @@ -26,7 +28,7 @@ public class DictUtils */ public static void setDictCache(String key, List dictDatas) { - CacheUtils.put(getCacheName(), getCacheKey(key), dictDatas); + SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas); } /** @@ -37,10 +39,10 @@ public class DictUtils */ public static List getDictCache(String key) { - Object cacheObj = CacheUtils.get(getCacheName(), getCacheKey(key)); - if (StringUtils.isNotNull(cacheObj)) + JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(arrayCache)) { - return StringUtils.cast(cacheObj); + return arrayCache.toList(SysDictData.class); } return null; } @@ -82,27 +84,30 @@ public class DictUtils StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); - if (StringUtils.containsAny(dictValue, separator) && StringUtils.isNotEmpty(datas)) + if (StringUtils.isNotNull(datas)) { - for (SysDictData dict : datas) + if (StringUtils.containsAny(separator, dictValue)) { - for (String value : dictValue.split(separator)) + for (SysDictData dict : datas) { - if (value.equals(dict.getDictValue())) + for (String value : dictValue.split(separator)) { - propertyString.append(dict.getDictLabel()).append(separator); - break; + if (value.equals(dict.getDictValue())) + { + propertyString.append(dict.getDictLabel()).append(separator); + break; + } } } } - } - else - { - for (SysDictData dict : datas) + else { - if (dictValue.equals(dict.getDictValue())) + for (SysDictData dict : datas) { - return dict.getDictLabel(); + if (dictValue.equals(dict.getDictValue())) + { + return dict.getDictLabel(); + } } } } @@ -122,7 +127,7 @@ public class DictUtils StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); - if (StringUtils.containsAny(dictLabel, separator) && StringUtils.isNotEmpty(datas)) + if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) { for (SysDictData dict : datas) { @@ -156,7 +161,7 @@ public class DictUtils */ public static void removeDictCache(String key) { - CacheUtils.remove(getCacheName(), getCacheKey(key)); + SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key)); } /** @@ -164,17 +169,8 @@ public class DictUtils */ public static void clearDictCache() { - CacheUtils.removeAll(getCacheName()); - } - - /** - * 获取cache name - * - * @return 缓存名 - */ - public static String getCacheName() - { - return Constants.SYS_DICT_CACHE; + Collection keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisCache.class).deleteObject(keys); } /** @@ -185,6 +181,6 @@ public class DictUtils */ public static String getCacheKey(String configKey) { - return Constants.SYS_DICT_KEY + configKey; + return CacheConstants.SYS_DICT_KEY + configKey; } } 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 index d5a357c53..e415c9c38 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java @@ -1,14 +1,5 @@ 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; - /** * 处理并记录日志文件 * @@ -16,93 +7,6 @@ import com.ruoyi.common.json.JSON; */ 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) @@ -111,25 +15,4 @@ public class LogUtils } 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 deleted file mode 100644 index 327e54fd1..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MapDataUtil.java +++ /dev/null @@ -1,54 +0,0 @@ -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/SecurityUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java new file mode 100644 index 000000000..0d3ac5f59 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java @@ -0,0 +1,178 @@ +package com.ruoyi.common.utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.util.PatternMatchUtils; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; + +/** + * 安全服务工具类 + * + * @author ruoyi + */ +public class SecurityUtils +{ + + /** + * 用户ID + **/ + public static Long getUserId() + { + try + { + return getLoginUser().getUserId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() + { + try + { + return getLoginUser().getDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getUsername(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() + { + try + { + return (LoginUser) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(String permission) + { + return hasPermi(getLoginUser().getPermissions(), permission); + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(Collection authorities, String permission) + { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 验证用户是否拥有某个角色 + * + * @param role 角色标识 + * @return 用户是否具备某角色 + */ + public static boolean hasRole(String role) + { + List roleList = getLoginUser().getUser().getRoles(); + Collection roles = roleList.stream().map(SysRole::getRoleKey).collect(Collectors.toSet()); + return hasRole(roles, role); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * @return 用户是否具备某角色权限 + */ + public static boolean hasRole(Collection roles, String role) + { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role)); + } + +} 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 index 6214f5643..292b8dacc 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java @@ -4,6 +4,10 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -20,11 +24,6 @@ import com.ruoyi.common.core.text.Convert; */ public class ServletUtils { - /** - * 定义移动端请求的所有可能类型 - */ - private final static String[] agent = { "Android", "iPhone", "iPod", "iPad", "Windows Phone", "MQQBrowser" }; - /** * 获取String参数 */ @@ -73,6 +72,34 @@ public class ServletUtils return Convert.toBool(getRequest().getParameter(name), defaultValue); } + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) + { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) + { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) + { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); + } + return params; + } + /** * 获取request */ @@ -108,12 +135,12 @@ public class ServletUtils * * @param response 渲染对象 * @param string 待渲染的字符串 - * @return null */ - public static String renderString(HttpServletResponse response, String string) + public static void renderString(HttpServletResponse response, String string) { try { + response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(string); @@ -122,7 +149,6 @@ public class ServletUtils { e.printStackTrace(); } - return null; } /** @@ -154,30 +180,6 @@ public class ServletUtils 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; - } - /** * 内容编码 * 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 deleted file mode 100644 index 1c7ab4ddb..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ShiroUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -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 index b623672a7..fc6c6b5e0 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -286,6 +286,30 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils return str.substring(start, end); } + /** + * 判断是否为空,并且不是空白字符 + * + * @param str 要判断的value + * @return 结果 + */ + public static boolean hasText(String str) + { + return (str != null && !str.isEmpty() && containsText(str)); + } + + private static boolean containsText(CharSequence str) + { + int strLen = str.length(); + for (int i = 0; i < strLen; i++) + { + if (!Character.isWhitespace(str.charAt(i))) + { + return true; + } + } + return false; + } + /** * 格式化文本, {} 表示占位符
    * 此方法只是简单将占位符 {} 按照顺序替换为参数
    @@ -490,22 +514,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils 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 * @@ -673,4 +681,4 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils } return sb.toString(); } -} +} \ No newline at end of file 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 index 193419506..71fe6d52e 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java @@ -36,7 +36,7 @@ public class Threads * 停止线程池 * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. - * 如果仍人超時,則強制退出. + * 如果仍然超時,則強制退出. * 另对在shutdown时线程本身被调用中断做了处理. */ public static void shutdownAndAwaitTermination(ExecutorService pool) 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 index 51fa59c84..4652a2985 100644 --- 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 @@ -17,7 +17,7 @@ import com.ruoyi.common.utils.uuid.Seq; /** * 文件上传工具类 - * + * * @author ruoyi */ public class FileUploadUtils @@ -102,8 +102,8 @@ public class FileUploadUtils throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException { - int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); - if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); } @@ -216,7 +216,7 @@ public class FileUploadUtils /** * 获取文件名的后缀 - * + * * @param file 表单文件 * @return 后缀名 */ @@ -229,4 +229,4 @@ public class FileUploadUtils } 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 index 7d5aa6f89..5c96f7cba 100644 --- 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 @@ -11,13 +11,13 @@ 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; +import org.apache.commons.io.FilenameUtils; /** * 文件处理工具类 @@ -196,7 +196,6 @@ public class FileUtils * * @param response 响应对象 * @param realFileName 真实文件名 - * @return */ public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException { @@ -210,7 +209,9 @@ public class FileUtils .append("utf-8''") .append(percentEncodedFileName); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); } /** @@ -288,4 +289,3 @@ public class FileUtils return baseName; } } - 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 index 371e82323..f968f1a12 100644 --- 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 @@ -16,7 +16,7 @@ public class MimeTypeUtils 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" }; 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 index cd8cd4ff8..ebff3fd63 100644 --- 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 @@ -332,7 +332,7 @@ public final class HTMLFilter final String name = m.group(1).toLowerCase(); if (allowed(name)) { - if (false == inArray(name, vSelfClosingTags)) + if (!inArray(name, vSelfClosingTags)) { if (vTagCounts.containsKey(name)) { @@ -387,7 +387,7 @@ public final class HTMLFilter { paramValue = processParamProtocol(paramValue); } - params.append(' ').append(paramName).append("=\"").append(paramValue).append("\""); + params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\""); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java new file mode 100644 index 000000000..589d12310 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.ruoyi.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author ruoyi + */ +public class HttpHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) + { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) + { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + } + catch (IOException e) + { + LOGGER.warn("getBodyString出现问题!"); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} 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 index 46f56ab02..d3b61cad5 100644 --- 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 @@ -216,7 +216,7 @@ public class HttpUtils String ret = ""; while ((ret = br.readLine()) != null) { - if (ret != null && !ret.trim().equals("")) + if (ret != null && !"".equals(ret.trim())) { result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java similarity index 83% rename from ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java rename to ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java index a259975ba..edfe419a0 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java @@ -1,54 +1,56 @@ -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; - } -} +package com.ruoyi.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; +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 = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } + catch (Exception e) + { + log.error("获取地理位置异常 {}", ip); + } + } + return UNKNOWN; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java similarity index 96% rename from ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java rename to ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java index 1a3f210cd..8e89e30d0 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java @@ -1,370 +1,382 @@ -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; - } +package com.ruoyi.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import javax.servlet.http.HttpServletRequest; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 获取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 + * + * @return IP地址 + */ + public static String getIpAddr() + { + return getIpAddr(ServletUtils.getRequest()); + } + + /** + * 获取客户端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/security/CipherUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java deleted file mode 100644 index 34c1a79de..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -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/PermissionUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/PermissionUtils.java deleted file mode 100644 index 3b6f098ca..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/PermissionUtils.java +++ /dev/null @@ -1,118 +0,0 @@ -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/sign/Base64.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java new file mode 100644 index 000000000..ca1cd9248 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.ruoyi.common.utils.sign; + +/** + * Base64工具类 + * + * @author ruoyi + */ +public final class Base64 +{ + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } +} 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/sign/Md5Utils.java similarity index 93% rename from ruoyi-common/src/main/java/com/ruoyi/common/utils/security/Md5Utils.java rename to ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java index 502eb0fd9..c1c58dbc0 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/security/Md5Utils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java @@ -1,67 +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; - } - } -} +package com.ruoyi.common.utils.sign; + +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/spring/SpringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java index c5699ad49..f290ec376 100644 --- 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 @@ -16,7 +16,7 @@ import com.ruoyi.common.utils.StringUtils; * @author ruoyi */ @Component -public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; @@ -24,13 +24,13 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC private static ApplicationContext applicationContext; @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } @@ -155,5 +155,4 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC { return applicationContext.getEnvironment().getRequiredProperty(key); } - } 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 index e3b032f4e..97497f285 100644 --- 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 @@ -66,7 +66,7 @@ public final class UUID implements java.io.Serializable, Comparable } /** - * 获取类型 4(伪随机生成的)UUID 的静态工厂。 + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 * * @return 随机生成的 {@code UUID} */ @@ -343,25 +343,25 @@ public final class UUID implements java.io.Serializable, Comparable final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); // time_low builder.append(digits(mostSigBits >> 32, 8)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } // time_mid builder.append(digits(mostSigBits >> 16, 4)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } // time_high_and_version builder.append(digits(mostSigBits, 4)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } // variant_and_sequence builder.append(digits(leastSigBits >> 48, 4)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } 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 deleted file mode 100644 index 516db0a90..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java +++ /dev/null @@ -1,39 +0,0 @@ -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-framework/pom.xml b/ruoyi-framework/pom.xml index 002b5a9ee..a81c38cda 100644 --- a/ruoyi-framework/pom.xml +++ b/ruoyi-framework/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 4.7.8 + 3.8.7 4.0.0 @@ -18,7 +18,7 @@ - + org.springframework.boot spring-boot-starter-web @@ -47,24 +47,6 @@ - - - org.apache.shiro - shiro-spring - - - - - com.github.theborakompanioni - thymeleaf-extras-shiro - - - - - eu.bitwalker - UserAgentUtils - - com.github.oshi 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 index 85ba2f215..35a6b50d0 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java @@ -7,17 +7,18 @@ 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.domain.model.LoginUser; import com.ruoyi.common.core.text.Convert; -import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.security.context.PermissionContextHolder; /** * 数据过滤处理 - * + * * @author ruoyi */ @Aspect @@ -64,11 +65,12 @@ public class DataScopeAspect protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) { // 获取当前的用户 - SysUser currentUser = ShiroUtils.getSysUser(); - if (currentUser != null) + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) { + SysUser currentUser = loginUser.getUser(); // 如果是超级管理员,则不过滤数据 - if (!currentUser.isAdmin()) + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) { String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), @@ -79,7 +81,7 @@ public class DataScopeAspect /** * 数据范围过滤 - * + * * @param joinPoint 切点 * @param user 用户 * @param deptAlias 部门别名 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 index f72b8051b..4648fcd3c 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java @@ -12,8 +12,8 @@ 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; +import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; /** * 多数据源处理 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 index d40b0f8ed..bd2205257 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -16,14 +16,17 @@ 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.alibaba.fastjson2.JSON; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.enums.BusinessStatus; +import com.ruoyi.common.enums.HttpMethod; +import com.ruoyi.common.filter.PropertyPreExcludeFilter; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.ServletUtils; -import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.system.domain.SysOperLog; @@ -82,20 +85,20 @@ public class LogAspect try { // 获取当前的用户 - SysUser currentUser = ShiroUtils.getSysUser(); + LoginUser loginUser = SecurityUtils.getLoginUser(); // *========数据库日志=========*// SysOperLog operLog = new SysOperLog(); operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); // 请求的地址 - String ip = ShiroUtils.getIp(); + String ip = IpUtils.getIpAddr(); operLog.setOperIp(ip); operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); - if (currentUser != null) + if (loginUser != null) { - operLog.setOperName(currentUser.getLoginName()); - if (StringUtils.isNotNull(currentUser.getDept()) - && StringUtils.isNotEmpty(currentUser.getDept().getDeptName())) + operLog.setOperName(loginUser.getUsername()); + SysUser currentUser = loginUser.getUser(); + if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept())) { operLog.setDeptName(currentUser.getDept().getDeptName()); } @@ -155,7 +158,7 @@ public class LogAspect // 是否需要保存response,参数和值 if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { - operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000)); + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); } } @@ -167,31 +170,20 @@ public class LogAspect */ private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception { - Map map = ServletUtils.getRequest().getParameterMap(); - if (StringUtils.isNotEmpty(map)) + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + if (StringUtils.isEmpty(paramsMap) + && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) { - String params = JSONObject.toJSONString(map, excludePropertyPreFilter(excludeParamNames)); + String params = argsArrayToString(joinPoint.getArgs(), 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)); - } + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); } } - /** - * 忽略敏感属性 - */ - public PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter(String[] excludeParamNames) - { - return new PropertyPreFilters().addFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); - } - /** * 参数拼装 */ @@ -206,7 +198,7 @@ public class LogAspect { try { - Object jsonObj = JSONObject.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); params += jsonObj.toString() + " "; } catch (Exception e) @@ -218,6 +210,14 @@ public class LogAspect return params.trim(); } + /** + * 忽略敏感属性 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) + { + return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + /** * 判断是否需要过滤的对象。 * 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 deleted file mode 100644 index 9d9831f82..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/PermissionsAspect.java +++ /dev/null @@ -1,30 +0,0 @@ -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/aspectj/RateLimiterAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 000000000..b720bc182 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,89 @@ +package com.ruoyi.framework.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import com.ruoyi.common.annotation.RateLimiter; +import com.ruoyi.common.enums.LimitType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; + +/** + * 限流处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr()).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} 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 index b6b0e88c6..1d4dc1f72 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java @@ -1,6 +1,9 @@ package com.ruoyi.framework.config; +import java.util.TimeZone; import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @@ -16,5 +19,12 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy; @MapperScan("com.ruoyi.**.mapper") public class ApplicationConfig { - + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } } 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 index b4c356bb0..f671b3a30 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java @@ -58,7 +58,7 @@ public class DruidConfig setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); return new DynamicDataSource(masterDataSource, targetDataSources); } - + /** * 设置数据源 * @@ -99,7 +99,6 @@ public class DruidConfig public void init(javax.servlet.FilterConfig filterConfig) throws ServletException { } - @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException @@ -114,7 +113,6 @@ public class DruidConfig text = text.replaceAll("powered.*?shrek.wang", ""); response.getWriter().write(text); } - @Override public void destroy() { diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 000000000..4adbb7fd9 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,52 @@ +package com.ruoyi.framework.config; + +import java.nio.charset.Charset; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import com.ruoyi.common.constant.Constants; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} 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 index b7d10b8f9..610807a86 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -8,8 +8,9 @@ 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.filter.RepeatableFilter; +import com.ruoyi.common.filter.XssFilter; import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.xss.XssFilter; /** * Filter配置 @@ -17,7 +18,6 @@ import com.ruoyi.common.xss.XssFilter; * @author ruoyi */ @Configuration -@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public class FilterConfig { @Value("${xss.excludes}") @@ -28,6 +28,7 @@ public class FilterConfig @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); @@ -41,4 +42,17 @@ public class FilterConfig registration.setInitParameters(initParameters); return registration; } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + 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 index 1e977ba0e..7d0838b88 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java @@ -40,4 +40,4 @@ public class I18nConfig implements WebMvcConfigurer { 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 index bd0dabb01..1907319f0 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java @@ -1,12 +1,11 @@ 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 @@ -17,11 +16,11 @@ public class KaptchaTextCreator extends DefaultTextCreator public String getText() { Integer result = 0; - Random random = new SecureRandom(); + Random random = new Random(); int x = random.nextInt(10); int y = random.nextInt(10); StringBuilder suChinese = new StringBuilder(); - int randomoperands = (int) Math.round(Math.random() * 2); + int randomoperands = random.nextInt(3); if (randomoperands == 0) { result = x * y; @@ -31,7 +30,7 @@ public class KaptchaTextCreator extends DefaultTextCreator } else if (randomoperands == 1) { - if (!(x == 0) && y % x == 0) + if ((x != 0) && y % x == 0) { result = y / x; suChinese.append(CNUMBERS[y]); @@ -46,7 +45,7 @@ public class KaptchaTextCreator extends DefaultTextCreator suChinese.append(CNUMBERS[y]); } } - else if (randomoperands == 2) + else { if (x >= y) { @@ -63,14 +62,7 @@ public class KaptchaTextCreator extends DefaultTextCreator suChinese.append(CNUMBERS[x]); } } - else - { - result = x + y; - suChinese.append(CNUMBERS[x]); - suChinese.append("+"); - suChinese.append(CNUMBERS[y]); - } suChinese.append("=?@" + result); return suChinese.toString(); } -} +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java new file mode 100644 index 000000000..3f4f485f0 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java @@ -0,0 +1,69 @@ +package com.ruoyi.framework.config; + +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author ruoyi + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() + { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} 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 index 98c36c6a8..4e067a7b1 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java @@ -1,11 +1,15 @@ package com.ruoyi.framework.config; +import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; 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; @@ -19,32 +23,20 @@ import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; @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() + "/"); + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); /** swagger配置 */ - registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());; } /** @@ -55,4 +47,27 @@ public class ResourcesConfig implements WebMvcConfigurer { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() + { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } } \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java new file mode 100644 index 000000000..212585323 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -0,0 +1,148 @@ +package com.ruoyi.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; +import com.ruoyi.framework.config.properties.PermitAllUrlProperties; +import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; +import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; +import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author ruoyi + */ +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter +{ + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * 解决 无法直接注入 AuthenticationManager + * + * @return + * @throws Exception + */ + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception + { + return super.authenticationManagerBean(); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception + { + // 注解标记允许匿名访问的url + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); + permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); + + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 禁用HTTP响应标头 + .headers().cacheControl().disable().and() + // 认证失败处理类 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + // 基于token,所以不需要session + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + // 过滤请求 + .authorizeRequests() + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + .antMatchers("/login", "/register", "/captchaImage").permitAll() + // 静态资源,可匿名访问 + .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() + .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated() + .and() + .headers().frameOptions().disable(); + // 添加Logout filter + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + // 添加CORS filter + httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); + httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() + { + return new BCryptPasswordEncoder(); + } + + /** + * 身份认证接口 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception + { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java similarity index 92% rename from ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java rename to ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java index ddb873097..b5b7de316 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java @@ -1,33 +1,32 @@ -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(); - } -} +package com.ruoyi.framework.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-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java deleted file mode 100644 index cd3e41164..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java +++ /dev/null @@ -1,423 +0,0 @@ -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.Autowired; -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.config.properties.PermitAllUrlProperties; -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; - - @Autowired - private PermitAllUrlProperties permitAllUrl; - - /** - * 缓存管理器 使用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"); - // 匿名访问不鉴权注解列表 - permitAllUrl.getUrls().forEach(url -> filterChainDefinitionMap.put(url, "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-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java similarity index 95% rename from ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java rename to ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java index 8a4c7ce2a..7840141e5 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java @@ -1,63 +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); - } - }; - } -} +package com.ruoyi.framework.config; + +import com.ruoyi.common.utils.Threads; +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 java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置 + * + * @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-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java index 068310246..29118fa80 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java @@ -1,22 +1,21 @@ package com.ruoyi.framework.config.properties; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; -import org.springframework.aop.framework.Advised; +import java.util.Optional; +import java.util.regex.Pattern; +import org.apache.commons.lang3.RegExUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import com.ruoyi.common.annotation.Anonymous; /** @@ -27,81 +26,33 @@ import com.ruoyi.common.annotation.Anonymous; @Configuration public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware { - private List urls = new ArrayList<>(); + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); private ApplicationContext applicationContext; + private List urls = new ArrayList<>(); + + public String ASTERISK = "*"; + @Override - public void afterPropertiesSet() throws Exception + public void afterPropertiesSet() { - Map controllers = applicationContext.getBeansWithAnnotation(Controller.class); - for (Object bean : controllers.values()) - { - if (!(bean instanceof Advised)) - { - continue; - } - Class beanClass = ((Advised) bean).getTargetSource().getTarget().getClass(); - RequestMapping base = beanClass.getAnnotation(RequestMapping.class); - String[] baseUrl = {}; - if (Objects.nonNull(base)) - { - baseUrl = base.value(); - } - Method[] methods = beanClass.getDeclaredMethods(); - for (Method method : methods) - { - if (method.isAnnotationPresent(Anonymous.class) && method.isAnnotationPresent(RequestMapping.class)) - { - RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); - String[] uri = requestMapping.value(); - urls.addAll(rebuildUrl(baseUrl, uri)); - } - else if (method.isAnnotationPresent(Anonymous.class) && method.isAnnotationPresent(GetMapping.class)) - { - GetMapping requestMapping = method.getAnnotation(GetMapping.class); - String[] uri = requestMapping.value(); - urls.addAll(rebuildUrl(baseUrl, uri)); - } - else if (method.isAnnotationPresent(Anonymous.class) && method.isAnnotationPresent(PostMapping.class)) - { - PostMapping requestMapping = method.getAnnotation(PostMapping.class); - String[] uri = requestMapping.value(); - urls.addAll(rebuildUrl(baseUrl, uri)); - } - else if (method.isAnnotationPresent(Anonymous.class) && method.isAnnotationPresent(PutMapping.class)) - { - PutMapping requestMapping = method.getAnnotation(PutMapping.class); - String[] uri = requestMapping.value(); - urls.addAll(rebuildUrl(baseUrl, uri)); - } - else if (method.isAnnotationPresent(Anonymous.class) && method.isAnnotationPresent(DeleteMapping.class)) - { - DeleteMapping requestMapping = method.getAnnotation(DeleteMapping.class); - String[] uri = requestMapping.value(); - urls.addAll(rebuildUrl(baseUrl, uri)); - } - } + RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); - } - } + map.keySet().forEach(info -> { + HandlerMethod handlerMethod = map.get(info); - private List rebuildUrl(String[] bases, String[] uris) - { - List urls = new ArrayList<>(); - for (String base : bases) - { - for (String uri : uris) - { - urls.add(prefix(base) + prefix(uri)); - } - } - return urls; - } + // 获取方法上边的注解 替代path variable 为 * + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); - private String prefix(String seg) - { - return seg.startsWith("/") ? seg : "/" + seg; + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + }); } @Override 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 index a5f764072..014ae3804 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java @@ -3,7 +3,6 @@ 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; /** * 动态数据源 diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java similarity index 92% rename from ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java rename to ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java index 3b089f66a..9770af6dd 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java @@ -1,45 +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(); - } -} +package com.ruoyi.framework.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-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java index 6df2e5edd..c49eaf4c6 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java @@ -6,7 +6,7 @@ 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.alibaba.fastjson2.JSON; import com.ruoyi.common.annotation.RepeatSubmit; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.ServletUtils; @@ -32,7 +32,7 @@ public abstract class RepeatSubmitInterceptor implements HandlerInterceptor if (this.isRepeatSubmit(request, annotation)) { AjaxResult ajaxResult = AjaxResult.error(annotation.message()); - ServletUtils.renderString(response, JSON.marshal(ajaxResult)); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); return false; } } @@ -47,9 +47,10 @@ public abstract class RepeatSubmitInterceptor implements HandlerInterceptor /** * 验证是否重复提交由子类实现具体的防重复提交的规则 * - * @param request 请求对象 - * @param annotation 防复注解 + * @param request 请求信息 + * @param annotation 防重复注解参数 * @return 结果 + * @throws Exception */ - public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception; + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); } 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 index fc0e1e998..9dc9511b3 100644 --- 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 @@ -2,15 +2,22 @@ package com.ruoyi.framework.interceptor.impl; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; import com.ruoyi.common.annotation.RepeatSubmit; -import com.ruoyi.common.json.JSON; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.filter.RepeatedlyRequestWrapper; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.HttpHelper; import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; /** - * 判断请求url和数据是否和上一次相同, + * 判断请求url和数据是否和上一次相同, * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 * * @author ruoyi @@ -22,23 +29,43 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor public final String REPEAT_TIME = "repeatTime"; - public final String SESSION_REPEAT_KEY = "repeatData"; + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @Autowired + private RedisCache redisCache; @SuppressWarnings("unchecked") @Override - public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) { - // 本次参数及系统时间 - String nowParams = JSON.marshal(request.getParameterMap()); + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } Map nowDataMap = new HashMap(); nowDataMap.put(REPEAT_PARAMS, nowParams); nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); - // 请求地址(作为存放session的key值) + // 请求地址(作为存放cache的key值) String url = request.getRequestURI(); - HttpSession session = request.getSession(); - Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY); + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; + + Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); if (sessionObj != null) { Map sessionMap = (Map) sessionObj; @@ -51,9 +78,9 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor } } } - Map sessionMap = new HashMap(); - sessionMap.put(url, nowDataMap); - session.setAttribute(SESSION_REPEAT_KEY, sessionMap); + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); 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 index a30067dd9..6ed6d5b74 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java @@ -9,7 +9,7 @@ import com.ruoyi.common.utils.spring.SpringUtils; /** * 异步任务管理器 * - * @author liuhulu + * @author ruoyi */ public class AsyncManager { 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 index 78a4af372..e36ca3c58 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java @@ -1,55 +1,24 @@ 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 + * @author ruoyi */ @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); - } - } } /** @@ -67,21 +36,4 @@ public class ShutdownManager 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 index f125664c6..98517b781 100644 --- 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 @@ -4,58 +4,78 @@ 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.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; 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.ISysLogininforService; 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 - * + * @author ruoyi */ public class AsyncFactory { private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); /** - * 同步session到数据库 + * 记录登录信息 * - * @param session 在线用户会话 + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 * @return 任务task */ - public static TimerTask syncSessionToDb(final OnlineSession session) + 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 = IpUtils.getIpAddr(); 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); - + 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.setUserName(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(ISysLogininforService.class).insertLogininfor(logininfor); } }; } @@ -79,58 +99,4 @@ public class AsyncFactory } }; } - - /** - * 记录登录信息 - * - * @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/security/context/AuthenticationContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 000000000..6c776ce3f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,28 @@ +package com.ruoyi.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息 + * + * @author ruoyi + */ +public class AuthenticationContextHolder +{ + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static Authentication getContext() + { + return contextHolder.get(); + } + + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java similarity index 94% rename from ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java rename to ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java index f06b8fab2..5472f3d2b 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java @@ -1,4 +1,4 @@ -package com.ruoyi.common.core.context; +package com.ruoyi.framework.security.context; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 000000000..3eb24954e --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.ruoyi.framework.security.filter; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author ruoyi + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) + { + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + chain.doFilter(request, response); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 000000000..93b703259 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.ruoyi.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author ruoyi + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + int code = HttpStatus.UNAUTHORIZED; + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 000000000..2f89a91be --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,53 @@ +package com.ruoyi.framework.security.handle; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.TokenService; + +/** + * 自定义退出处理类 返回成功 + * + * @author ruoyi + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler +{ + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + * + * @return + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) + { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.delLoginUser(loginUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); + } + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success")))); + } +} 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 deleted file mode 100644 index 8cda35e2d..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java +++ /dev/null @@ -1,158 +0,0 @@ -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 deleted file mode 100644 index 8e9db1db5..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java +++ /dev/null @@ -1,185 +0,0 @@ -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"))); - 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 deleted file mode 100644 index 338e7293e..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java +++ /dev/null @@ -1,85 +0,0 @@ -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.LOGIN_RECORD_CACHE); - } - - 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 deleted file mode 100644 index b0b84498a..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysRegisterService.java +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100644 index 1fb9c7e46..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java +++ /dev/null @@ -1,62 +0,0 @@ -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 deleted file mode 100644 index 95d9b4c7e..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java +++ /dev/null @@ -1,165 +0,0 @@ -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 deleted file mode 100644 index bbcf1e342..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java +++ /dev/null @@ -1,117 +0,0 @@ -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 deleted file mode 100644 index f1e5a3ee7..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -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 deleted file mode 100644 index 3334ec792..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/util/AuthorizationUtils.java +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index 1d42bdf75..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/CustomShiroFilterFactoryBean.java +++ /dev/null @@ -1,85 +0,0 @@ -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 deleted file mode 100644 index f8861b649..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java +++ /dev/null @@ -1,90 +0,0 @@ -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 deleted file mode 100644 index 40939e91c..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/captcha/CaptchaValidateFilter.java +++ /dev/null @@ -1,79 +0,0 @@ -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 deleted file mode 100644 index d92db3401..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/kickout/KickoutSessionFilter.java +++ /dev/null @@ -1,176 +0,0 @@ -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 deleted file mode 100644 index bbbe6d66e..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java +++ /dev/null @@ -1,99 +0,0 @@ -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 deleted file mode 100644 index f8e51422c..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index 695824243..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java +++ /dev/null @@ -1,175 +0,0 @@ -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 deleted file mode 100644 index f5c9d1e6a..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java +++ /dev/null @@ -1,131 +0,0 @@ -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 index e7312f05b..30a1957eb 100644 --- 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 @@ -5,7 +5,7 @@ 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.common.utils.ip.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; @@ -28,7 +28,6 @@ import oshi.util.Util; */ public class Server { - private static final int OSHI_WAIT_SECOND = 1000; /** 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 index 10f30f456..a3ec18232 100644 --- 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 @@ -1,21 +1,21 @@ 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.security.access.AccessDeniedException; import org.springframework.validation.BindException; import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingPathVariableException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import org.springframework.web.servlet.ModelAndView; +import com.ruoyi.common.constant.HttpStatus; 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; +import com.ruoyi.common.utils.StringUtils; /** * 全局异常处理器 @@ -28,21 +28,14 @@ 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) + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException 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"); - } + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); } /** @@ -57,6 +50,39 @@ public class GlobalExceptionHandler return AjaxResult.error(e.getMessage()); } + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); + } + /** * 拦截未知的运行时异常 */ @@ -79,46 +105,6 @@ public class GlobalExceptionHandler 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(MissingPathVariableException.class) - public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) - { - String requestURI = request.getRequestURI(); - log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); - return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); - } - - /** - * 请求参数类型不匹配 - */ - @ExceptionHandler(MethodArgumentTypeMismatchException.class) - public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, - HttpServletRequest request) - { - String requestURI = request.getRequestURI(); - log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); - return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); - } - /** * 自定义验证异常 */ @@ -130,6 +116,17 @@ public class GlobalExceptionHandler return AjaxResult.error(message); } + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + /** * 演示模式异常 */ 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 deleted file mode 100644 index 33d17ac46..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100644 index 3b2d0566b..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ConfigService.java +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index ced0e787e..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DictService.java +++ /dev/null @@ -1,46 +0,0 @@ -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 index c806a364e..6892467db 100644 --- 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 @@ -1,169 +1,81 @@ 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 java.util.Set; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.security.context.PermissionContextHolder; /** - * RuoYi首创 js调用 thymeleaf 实现按钮权限可见性 + * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母 * * @author ruoyi */ -@Service("permission") +@Service("ss") 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) + public boolean 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_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_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 (StringUtils.isEmpty(permission)) { - if (delimeter == null || delimeter.length() == 0) - { - delimeter = PERMISSION_DELIMETER; - } + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permission); + return hasPermissions(loginUser.getPermissions(), permission); + } - for (String permission : permissions.split(delimeter)) + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) + { + return hasPermi(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) + { + if (StringUtils.isEmpty(permissions)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permissions); + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)) + { + if (permission != null && hasPermissions(authorities, permission)) { - if (permission != null && subject.isPermitted(permission.trim()) == true) - { - return true; - } + return true; } } - return false; } @@ -173,9 +85,26 @@ public class PermissionService * @param role 角色字符串 * @return 用户是否具备某角色 */ - public boolean isRole(String role) + public boolean hasRole(String role) { - return SecurityUtils.getSubject().hasRole(role); + if (StringUtils.isEmpty(role)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (SysRole sysRole : loginUser.getUser().getRoles()) + { + String roleKey = sysRole.getRoleKey(); + if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) + { + return true; + } + } + return false; } /** @@ -184,79 +113,47 @@ public class PermissionService * @param role 角色名称 * @return 用户是否不具备某角色 */ - public boolean isLacksRole(String role) + public boolean lacksRole(String role) { - return isRole(role) != true; + return hasRole(role) != true; } /** - * 验证用户是否具有以下任意一个角色。 + * 验证用户是否具有以下任意一个角色 * * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @return 用户是否具有以下任意一个角色 */ - public boolean isAnyRoles(String roles) + public boolean hasAnyRoles(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 (StringUtils.isEmpty(roles)) { - if (delimeter == null || delimeter.length() == 0) + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (String role : roles.split(Constants.ROLE_DELIMETER)) + { + if (hasRole(role)) { - delimeter = ROLE_DELIMETER; - } - - for (String role : roles.split(delimeter)) - { - if (subject.hasRole(role.trim()) == true) - { - return true; - } + return true; } } - return false; } /** - * 返回用户属性值 - * - * @param property 属性名称 - * @return 用户属性值 + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 */ - public Object getPrincipalProperty(String property) + private boolean hasPermissions(Set permissions, String permission) { - 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; + return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); } } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java new file mode 100644 index 000000000..67267b770 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -0,0 +1,181 @@ +package com.ruoyi.framework.web.service; + +import javax.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.exception.user.BlackListException; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +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.MessageUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.security.context.AuthenticationContextHolder; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 登录校验方法 + * + * @author ruoyi + */ +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + // 验证码校验 + validateCaptcha(username, code, uuid); + // 登录前置校验 + loginPreCheck(username, password); + // 用户验证 + Authentication authentication = null; + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + } + + /** + * 登录前置校验 + * @param username 用户名 + * @param password 用户密码 + */ + public void loginPreCheck(String username, String password) + { + // 用户名或密码为空 错误 + 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, IpUtils.getIpAddr())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr()); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java new file mode 100644 index 000000000..6728c7bb3 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java @@ -0,0 +1,86 @@ +package com.ruoyi.framework.web.service; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; +import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.security.context.AuthenticationContextHolder; + +/** + * 登录密码方法 + * + * @author ruoyi + */ +@Component +public class SysPasswordService +{ + @Autowired + private RedisCache redisCache; + + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + @Value(value = "${user.password.lockTime}") + private int lockTime; + + /** + * 登录账户密码错误次数缓存键名 + * + * @param username 用户名 + * @return 缓存键key + */ + private String getCacheKey(String username) + { + return CacheConstants.PWD_ERR_CNT_KEY + username; + } + + public void validate(SysUser user) + { + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); + + if (retryCount == null) + { + retryCount = 0; + } + + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + + if (!matches(user, password)) + { + retryCount = retryCount + 1; + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) + { + if (redisCache.hasKey(getCacheKey(loginName))) + { + redisCache.deleteObject(getCacheKey(loginName)); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java new file mode 100644 index 000000000..d1fb4ed85 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java @@ -0,0 +1,83 @@ +package com.ruoyi.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.service.ISysMenuService; +import com.ruoyi.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author ruoyi + */ +@Component +public class SysPermissionService +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set getRolePermission(SysUser user) + { + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + roles.add("admin"); + } + else + { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermission(SysUser user) + { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + perms.add("*:*:*"); + } + else + { + List roles = user.getRoles(); + if (!CollectionUtils.isEmpty(roles)) + { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) + { + Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + perms.addAll(rolePerms); + } + } + else + { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java new file mode 100644 index 000000000..f2afe31fe --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java @@ -0,0 +1,115 @@ +package com.ruoyi.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +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.ISysUserService; + +/** + * 注册校验方法 + * + * @author ruoyi + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private RedisCache redisCache; + + /** + * 注册 + */ + public String register(RegisterBody registerBody) + { + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + // 验证码开关 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (!userService.checkUserNameUnique(sysUser)) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java new file mode 100644 index 000000000..aa112dae1 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java @@ -0,0 +1,231 @@ +package com.ruoyi.framework.web.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.uuid.IdUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * token验证处理 + * + * @author ruoyi + */ +@Component +public class TokenService +{ + private static final Logger log = LoggerFactory.getLogger(TokenService.class); + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + // 令牌有效期(默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; + + @Autowired + private RedisCache redisCache; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) + { + try + { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + String userKey = getTokenKey(uuid); + LoginUser user = redisCache.getCacheObject(userKey); + return user; + } + catch (Exception e) + { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + return createToken(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) + { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = IpUtils.getIpAddr(); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) + { + String token = Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) + { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) + { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 获取请求token + * + * @param request + * @return token + */ + private String getToken(HttpServletRequest request) + { + String token = request.getHeader(header); + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) + { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + return token; + } + + private String getTokenKey(String uuid) + { + return CacheConstants.LOGIN_TOKEN_KEY + uuid; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 000000000..5dcdf90ed --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,66 @@ +package com.ruoyi.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author ruoyi + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService +{ + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysPermissionService permissionService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException + { + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) + { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException(MessageUtils.message("user.not.exists")); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException(MessageUtils.message("user.password.delete")); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException(MessageUtils.message("user.blocked")); + } + + passwordService.validate(user); + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) + { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/ruoyi-generator/pom.xml b/ruoyi-generator/pom.xml index 2bf2b6824..04b679eb0 100644 --- a/ruoyi-generator/pom.xml +++ b/ruoyi-generator/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 4.7.8 + 3.8.7 4.0.0 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 index 49b02bae1..b320853e4 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java @@ -2,35 +2,33 @@ package com.ruoyi.generator.controller; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; 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.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; 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.SecurityUtils; import com.ruoyi.common.utils.sql.SqlUtil; import com.ruoyi.generator.domain.GenTable; import com.ruoyi.generator.domain.GenTableColumn; @@ -42,31 +40,21 @@ import com.ruoyi.generator.service.IGenTableService; * * @author ruoyi */ -@Controller +@RestController @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 + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") public TableDataInfo genList(GenTable genTable) { startPage(); @@ -74,12 +62,28 @@ public class GenController extends BaseController return getDataTable(list); } + /** + * 修改代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable Long tableId) + { + GenTable table = genTableService.selectGenTableById(tableId); + List tables = genTableService.selectGenTableAll(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + Map map = new HashMap(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return success(map); + } + /** * 查询数据库列表 */ - @RequiresPermissions("tool:gen:list") - @PostMapping("/db/list") - @ResponseBody + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") public TableDataInfo dataList(GenTable genTable) { startPage(); @@ -90,112 +94,39 @@ public class GenController extends BaseController /** * 查询数据表字段列表 */ - @RequiresPermissions("tool:gen:list") - @PostMapping("/column/list") - @ResponseBody - public TableDataInfo columnList(GenTableColumn genTableColumn) + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(Long tableId) { TableDataInfo dataInfo = new TableDataInfo(); - List list = genTableColumnService.selectGenTableColumnListByTableId(genTableColumn); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); 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") + @PreAuthorize("@ss.hasPermi('tool:gen:import')") @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(); + genTableService.importGenTable(tableList, SecurityUtils.getUsername()); + return 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") + @PreAuthorize("@ss.hasRole('admin')") @Log(title = "创建表", businessType = BusinessType.OTHER) @PostMapping("/createTable") - @ResponseBody - public AjaxResult create(String sql) + public AjaxResult createTableSave(String sql) { try { @@ -215,7 +146,7 @@ public class GenController extends BaseController } } List tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); - String operName = Convert.toStr(PermissionUtils.getPrincipalProperty("loginName")); + String operName = SecurityUtils.getUsername(); genTableService.importGenTable(tableList, operName); return AjaxResult.success(); } @@ -226,22 +157,46 @@ public class GenController extends BaseController } } + /** + * 修改保存代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTable genTable) + { + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + return success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + /** * 预览代码 */ - @RequiresPermissions("tool:gen:preview") + @PreAuthorize("@ss.hasPermi('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); + return success(dataMap); } /** * 生成代码(下载方式) */ - @RequiresPermissions("tool:gen:code") + @PreAuthorize("@ss.hasPermi('tool:gen:code')") @Log(title = "代码生成", businessType = BusinessType.GENCODE) @GetMapping("/download/{tableName}") public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException @@ -253,36 +208,33 @@ public class GenController extends BaseController /** * 生成代码(自定义路径) */ - @RequiresPermissions("tool:gen:code") + @PreAuthorize("@ss.hasPermi('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(); + return success(); } /** * 同步数据库 */ - @RequiresPermissions("tool:gen:edit") + @PreAuthorize("@ss.hasPermi('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(); + return success(); } /** * 批量生成代码 */ - @RequiresPermissions("tool:gen:code") + @PreAuthorize("@ss.hasPermi('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); @@ -296,6 +248,8 @@ public class GenController extends BaseController private void genCode(HttpServletResponse response, byte[] data) throws IOException { response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\""); response.addHeader("Content-Length", "" + data.length); response.setContentType("application/octet-stream; charset=UTF-8"); 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 index 2abb94fa1..c3af38f09 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java @@ -41,6 +41,9 @@ public class GenTable extends BaseEntity /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ private String tplCategory; + /** 前端类型(element-ui模版 element-plus模版) */ + private String tplWebType; + /** 生成包路径 */ @NotBlank(message = "生成包路径不能为空") private String packageName; @@ -165,6 +168,16 @@ public class GenTable extends BaseEntity this.tplCategory = tplCategory; } + public String getTplWebType() + { + return tplWebType; + } + + public void setTplWebType(String tplWebType) + { + this.tplWebType = tplWebType; + } + public String getPackageName() { return packageName; 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 index 4b1268c75..9140d4dae 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java @@ -59,11 +59,11 @@ public class GenTableColumn extends BaseEntity /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ private String queryType; - /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、upload上传控件、summernote富文本控件) */ + /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */ private String htmlType; /** 字典类型 */ - private String dictType = ""; + private String dictType; /** 排序 */ private Integer sort; @@ -370,4 +370,4 @@ public class GenTableColumn extends BaseEntity 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 index a819c741b..0dc342895 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java @@ -21,10 +21,10 @@ public interface GenTableColumnMapper /** * 查询业务字段列表 * - * @param genTableColumn 业务字段信息 + * @param tableId 业务字段编号 * @return 业务字段集合 */ - public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn); + public List selectGenTableColumnListByTableId(Long tableId); /** * 新增业务字段 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 index 7265d423b..937656dc9 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java @@ -84,8 +84,8 @@ public interface GenTableMapper /** * 创建表 * - * @param sql + * @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/GenTableColumnServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 000000000..0679689d2 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.ruoyi.generator.service; + +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; + +/** + * 业务字段 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * + * @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)); + } +} 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/GenTableServiceImpl.java similarity index 92% rename from ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java rename to ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java index 1f25c534f..3fb6383e6 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java @@ -1,4 +1,4 @@ -package com.ruoyi.generator.service.impl; +package com.ruoyi.generator.service; import java.io.ByteArrayOutputStream; import java.io.File; @@ -21,19 +21,17 @@ 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.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.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; @@ -130,9 +128,9 @@ public class GenTableServiceImpl implements IGenTableService int row = genTableMapper.updateGenTable(genTable); if (row > 0) { - for (GenTableColumn genTableColumn : genTable.getColumns()) + for (GenTableColumn cenTableColumn : genTable.getColumns()) { - genTableColumnMapper.updateGenTableColumn(genTableColumn); + genTableColumnMapper.updateGenTableColumn(cenTableColumn); } } } @@ -140,15 +138,15 @@ public class GenTableServiceImpl implements IGenTableService /** * 删除业务对象 * - * @param ids 需要删除的数据ID + * @param tableIds 需要删除的数据ID * @return 结果 */ @Override @Transactional - public void deleteGenTableByIds(String ids) + public void deleteGenTableByIds(Long[] tableIds) { - genTableMapper.deleteGenTableByIds(Convert.toLongArray(ids)); - genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + genTableMapper.deleteGenTableByIds(tableIds); + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); } /** @@ -165,9 +163,8 @@ public class GenTableServiceImpl implements IGenTableService /** * 导入表结构 - * + * * @param tableList 导入表列表 - * @param operName 操作人员 */ @Override @Transactional @@ -219,7 +216,7 @@ public class GenTableServiceImpl implements IGenTableService VelocityContext context = VelocityUtils.prepareContext(table); // 获取模板列表 - List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); for (String template : templates) { // 渲染模板 @@ -267,10 +264,10 @@ public class GenTableServiceImpl implements IGenTableService VelocityContext context = VelocityUtils.prepareContext(table); // 获取模板列表 - List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); for (String template : templates) { - if (!StringUtils.contains(template, "sql.vm")) + if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) { // 渲染模板 StringWriter sw = new StringWriter(); @@ -380,7 +377,7 @@ public class GenTableServiceImpl implements IGenTableService VelocityContext context = VelocityUtils.prepareContext(table); // 获取模板列表 - List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); for (String template : templates) { // 渲染模板 @@ -414,7 +411,7 @@ public class GenTableServiceImpl implements IGenTableService if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) { String options = JSON.toJSONString(genTable.getParams()); - JSONObject paramsObj = JSONObject.parseObject(options); + JSONObject paramsObj = JSON.parseObject(options); if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) { throw new ServiceException("树编码字段不能为空"); @@ -427,16 +424,16 @@ public class GenTableServiceImpl implements IGenTableService { throw new ServiceException("树名称字段不能为空"); } - } - else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) - { - if (StringUtils.isEmpty(genTable.getSubTableName())) + else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) { - throw new ServiceException("关联子表的表名不能为空"); - } - else if (StringUtils.isEmpty(genTable.getSubTableFkName())) - { - throw new ServiceException("子表关联的外键名不能为空"); + if (StringUtils.isEmpty(genTable.getSubTableName())) + { + throw new ServiceException("关联子表的表名不能为空"); + } + else if (StringUtils.isEmpty(genTable.getSubTableFkName())) + { + throw new ServiceException("子表关联的外键名不能为空"); + } } } } @@ -498,7 +495,7 @@ public class GenTableServiceImpl implements IGenTableService */ public void setTableFromOptions(GenTable genTable) { - JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions()); + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); if (StringUtils.isNotNull(paramsObj)) { String treeCode = paramsObj.getString(GenConstants.TREE_CODE); 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 index 2ff5f5663..2130d0431 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java @@ -13,10 +13,10 @@ public interface IGenTableColumnService /** * 查询业务字段列表 * - * @param genTableColumn 业务字段信息 + * @param tableId 业务字段编号 * @return 业务字段集合 */ - public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn); + public List selectGenTableColumnListByTableId(Long tableId); /** * 新增业务字段 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 index 4af8fea26..695426e91 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java @@ -61,10 +61,10 @@ public interface IGenTableService /** * 删除业务信息 * - * @param ids 需要删除的数据ID + * @param tableIds 需要删除的表数据ID * @return 结果 */ - public void deleteGenTableByIds(String ids); + public void deleteGenTableByIds(Long[] tableIds); /** * 创建表 @@ -102,9 +102,10 @@ public interface IGenTableService * 生成代码(自定义路径) * * @param tableName 表名称 + * @return 数据 */ public void generatorCode(String tableName); - + /** * 同步数据库 * 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 deleted file mode 100644 index 45d015af2..000000000 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableColumnServiceImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -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/util/GenUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java index 727dd2259..e28c0bb0e 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java @@ -113,15 +113,20 @@ public class GenUtils { column.setHtmlType(GenConstants.HTML_SELECT); } - // 文件字段设置上传控件 + // 图片字段设置图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 else if (StringUtils.endsWithIgnoreCase(columnName, "file")) { - column.setHtmlType(GenConstants.HTML_UPLOAD); + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); } // 内容字段设置富文本控件 else if (StringUtils.endsWithIgnoreCase(columnName, "content")) { - column.setHtmlType(GenConstants.HTML_SUMMERNOTE); + column.setHtmlType(GenConstants.HTML_EDITOR); } } 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 index 2698e0b9f..1a146819c 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java @@ -3,15 +3,21 @@ package com.ruoyi.generator.util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.velocity.VelocityContext; -import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.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; +/** + * 模板处理工具类 + * + * @author ruoyi + */ public class VelocityUtils { /** 项目空间路径 */ @@ -20,15 +26,12 @@ public class VelocityUtils /** 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) @@ -46,6 +49,7 @@ public class VelocityUtils velocityContext.put("ClassName", genTable.getClassName()); velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); velocityContext.put("moduleName", genTable.getModuleName()); + velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName())); velocityContext.put("businessName", genTable.getBusinessName()); velocityContext.put("basePackage", getPackagePrefix(packageName)); velocityContext.put("packageName", packageName); @@ -56,6 +60,7 @@ public class VelocityUtils velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); velocityContext.put("columns", genTable.getColumns()); velocityContext.put("table", genTable); + velocityContext.put("dicts", getDicts(genTable)); setMenuVelocityContext(velocityContext, genTable); if (GenConstants.TPL_TREE.equals(tplCategory)) { @@ -71,7 +76,7 @@ public class VelocityUtils public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) { String options = genTable.getOptions(); - JSONObject paramsObj = JSONObject.parseObject(options); + JSONObject paramsObj = JSON.parseObject(options); String parentMenuId = getParentMenuId(paramsObj); context.put("parentMenuId", parentMenuId); } @@ -79,7 +84,7 @@ public class VelocityUtils public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) { String options = genTable.getOptions(); - JSONObject paramsObj = JSONObject.parseObject(options); + JSONObject paramsObj = JSON.parseObject(options); String treeCode = getTreecode(paramsObj); String treeParentCode = getTreeParentCode(paramsObj); String treeName = getTreeName(paramsObj); @@ -118,11 +123,17 @@ public class VelocityUtils /** * 获取模板信息 - * + * @param tplCategory 生成的模板 + * @param tplWebType 前端类型 * @return 模板列表 */ - public static List getTemplateList(String tplCategory) + public static List getTemplateList(String tplCategory, String tplWebType) { + String useWebType = "vm/vue"; + if ("element-plus".equals(tplWebType)) + { + useWebType = "vm/vue/v3"; + } List templates = new ArrayList(); templates.add("vm/java/domain.java.vm"); templates.add("vm/java/mapper.java.vm"); @@ -130,23 +141,21 @@ public class VelocityUtils templates.add("vm/java/serviceImpl.java.vm"); templates.add("vm/java/controller.java.vm"); templates.add("vm/xml/mapper.xml.vm"); + templates.add("vm/sql/sql.vm"); + templates.add("vm/js/api.js.vm"); if (GenConstants.TPL_CRUD.equals(tplCategory)) { - templates.add("vm/html/list.html.vm"); + templates.add(useWebType + "/index.vue.vm"); } else if (GenConstants.TPL_TREE.equals(tplCategory)) { - templates.add("vm/html/tree.html.vm"); - templates.add("vm/html/list-tree.html.vm"); + templates.add(useWebType + "/index-tree.vue.vm"); } else if (GenConstants.TPL_SUB.equals(tplCategory)) { - templates.add("vm/html/list.html.vm"); + templates.add(useWebType + "/index.vue.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; } @@ -168,7 +177,7 @@ public class VelocityUtils String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); String mybatisPath = MYBATIS_PATH + "/" + moduleName; - String htmlPath = TEMPLATES_PATH + "/" + moduleName + "/" + businessName; + String vuePath = "vue"; if (template.contains("domain.java.vm")) { @@ -198,51 +207,28 @@ public class VelocityUtils { 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"; } + else if (template.contains("api.js.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("index.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("index-tree.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } 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 包前缀名称 */ @@ -283,8 +269,46 @@ public class VelocityUtils } /** - * 获取权限前缀 + * 根据列类型获取字典组 * + * @param genTable 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTable genTable) + { + List columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List columns) + { + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) + { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 获取权限前缀 + * * @param moduleName 模块名称 * @param businessName 业务名称 * @return 返回权限前缀 @@ -296,7 +320,7 @@ public class VelocityUtils /** * 获取上级菜单ID字段 - * + * * @param paramsObj 生成其他选项 * @return 上级菜单ID字段 */ @@ -312,7 +336,7 @@ public class VelocityUtils /** * 获取树编码 - * + * * @param paramsObj 生成其他选项 * @return 树编码 */ @@ -327,7 +351,7 @@ public class VelocityUtils /** * 获取树父编码 - * + * * @param paramsObj 生成其他选项 * @return 树父编码 */ @@ -342,7 +366,7 @@ public class VelocityUtils /** * 获取树名称 - * + * * @param paramsObj 生成其他选项 * @return 树名称 */ @@ -357,14 +381,14 @@ public class VelocityUtils /** * 获取需要在哪一列上面显示展开按钮 - * + * * @param genTable 业务表对象 * @return 展开按钮列序号 */ public static int getExpandColumn(GenTable genTable) { String options = genTable.getOptions(); - JSONObject paramsObj = JSONObject.parseObject(options); + JSONObject paramsObj = JSON.parseObject(options); String treeName = paramsObj.getString(GenConstants.TREE_NAME); int num = 0; for (GenTableColumn column : genTable.getColumns()) diff --git a/ruoyi-generator/src/main/resources/generator.yml b/ruoyi-generator/src/main/resources/generator.yml index 29b3dd2b5..1aeec14ef 100644 --- a/ruoyi-generator/src/main/resources/generator.yml +++ b/ruoyi-generator/src/main/resources/generator.yml @@ -1,4 +1,3 @@ - # 代码生成 gen: # 作者 diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml index cab47441a..efd0e70db 100644 --- a/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml +++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -3,7 +3,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + @@ -28,23 +28,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + 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 - - where table_id = #{tableId} order by sort - + - + insert into gen_table_column ( table_id, @@ -88,37 +88,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 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}, + 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 + delete from gen_table_column where table_id in #{tableId} - delete from gen_table_column where column_id in + delete from gen_table_column where column_id in #{item.columnId} diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml index bc7cf5277..253212efb 100644 --- a/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml +++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -12,6 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -25,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + @@ -54,7 +55,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - 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 + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, 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 @@ -99,7 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - - - - - \ 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 deleted file mode 100644 index fee2b3604..000000000 --- a/ruoyi-generator/src/main/resources/templates/tool/gen/edit.html +++ /dev/null @@ -1,608 +0,0 @@ - - - - - - - - -

    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - diff --git a/ruoyi-generator/src/main/resources/templates/tool/gen/gen.html b/ruoyi-generator/src/main/resources/templates/tool/gen/gen.html deleted file mode 100644 index bf980d9a2..000000000 --- a/ruoyi-generator/src/main/resources/templates/tool/gen/gen.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 表名称: -
    • -
    • - 表描述: -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - - - \ 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 deleted file mode 100644 index b432702f1..000000000 --- a/ruoyi-generator/src/main/resources/templates/tool/gen/importTable.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index c64ccd9da..000000000 --- a/ruoyi-generator/src/main/resources/vm/html/add.html.vm +++ /dev/null @@ -1,379 +0,0 @@ - - - - -#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 deleted file mode 100644 index cddfe1d68..000000000 --- a/ruoyi-generator/src/main/resources/vm/html/edit.html.vm +++ /dev/null @@ -1,391 +0,0 @@ - - - - -#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 deleted file mode 100644 index dcb91e83c..000000000 --- a/ruoyi-generator/src/main/resources/vm/html/list-tree.html.vm +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -#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 deleted file mode 100644 index a7021a371..000000000 --- a/ruoyi-generator/src/main/resources/vm/html/list.html.vm +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -#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 deleted file mode 100644 index 128dad955..000000000 --- a/ruoyi-generator/src/main/resources/vm/html/tree.html.vm +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - -#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 index 8ce683e50..41c72ad71 100644 --- a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm @@ -1,27 +1,27 @@ package ${packageName}.controller; import java.util.List; -import org.apache.shiro.authz.annotation.RequiresPermissions; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; 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.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; 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 /** @@ -30,29 +30,19 @@ import com.ruoyi.common.core.domain.Ztree; * @author ${author} * @date ${datetime} */ -@Controller +@RestController @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 + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) public TableDataInfo list(${ClassName} ${className}) { startPage(); @@ -60,65 +50,43 @@ public class ${ClassName}Controller extends BaseController return getDataTable(list); } #elseif($table.tree) - /** - * 查询${functionName}树列表 - */ - @RequiresPermissions("${permissionPrefix}:list") - @PostMapping("/list") - @ResponseBody - public List<${ClassName}> list(${ClassName} ${className}) + public AjaxResult list(${ClassName} ${className}) { List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); - return list; + return success(list); } #end /** * 导出${functionName}列表 */ - @RequiresPermissions("${permissionPrefix}:export") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") @Log(title = "${functionName}", businessType = BusinessType.EXPORT) @PostMapping("/export") - @ResponseBody - public AjaxResult export(${ClassName} ${className}) + public void export(HttpServletResponse response, ${ClassName} ${className}) { List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); - return util.exportExcel(list, "${functionName}数据"); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } -#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") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") @Log(title = "${functionName}", businessType = BusinessType.INSERT) - @PostMapping("/add") - @ResponseBody - public AjaxResult addSave(${ClassName} ${className}) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) { return toAjax(${className}Service.insert${ClassName}(${className})); } @@ -126,77 +94,22 @@ public class ${ClassName}Controller extends BaseController /** * 修改${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") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") @Log(title = "${functionName}", businessType = BusinessType.UPDATE) - @PostMapping("/edit") - @ResponseBody - public AjaxResult editSave(${ClassName} ${className}) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) { return toAjax(${className}Service.update${ClassName}(${className})); } -#if($table.crud || $table.sub) /** * 删除${functionName} */ - @RequiresPermissions("${permissionPrefix}:remove") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") @Log(title = "${functionName}", businessType = BusinessType.DELETE) - @PostMapping( "/remove") - @ResponseBody - public AjaxResult remove(String ids) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) { - return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(ids)); + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); } -#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/mapper.java.vm b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm index f8e1bf3fa..6f0317905 100644 --- a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm @@ -60,7 +60,7 @@ public interface ${ClassName}Mapper * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ - public int delete${ClassName}By${pkColumn.capJavaField}s(String[] ${pkColumn.javaField}s); + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); #if($table.sub) /** @@ -69,7 +69,7 @@ public interface ${ClassName}Mapper * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ - public int delete${subClassName}By${subTableFkClassName}s(String[] ${pkColumn.javaField}s); + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); /** * 批量新增${subTable.functionName} diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm index c30ec41ca..250f68c8c 100644 --- a/ruoyi-generator/src/main/resources/vm/java/service.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm @@ -2,9 +2,6 @@ package ${packageName}.service; import java.util.List; import ${packageName}.domain.${ClassName}; -#if($table.tree) -import com.ruoyi.common.core.domain.Ztree; -#end /** * ${functionName}Service接口 @@ -52,7 +49,7 @@ public interface I${ClassName}Service * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 * @return 结果 */ - public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s); + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); /** * 删除${functionName}信息 @@ -61,13 +58,4 @@ public interface I${ClassName}Service * @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 index 53d12d7b6..116f6c544 100644 --- a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -1,10 +1,6 @@ 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; @@ -22,7 +18,6 @@ import ${packageName}.domain.${subClassName}; import ${packageName}.mapper.${ClassName}Mapper; import ${packageName}.domain.${ClassName}; import ${packageName}.service.I${ClassName}Service; -import com.ruoyi.common.core.text.Convert; /** * ${functionName}Service业务层处理 @@ -120,12 +115,12 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service @Transactional #end @Override - public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s) + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) { #if($table.sub) - ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(Convert.toStrArray(${pkColumn.javaField}s)); + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); #end - return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(Convert.toStrArray(${pkColumn.javaField}s)); + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); } /** @@ -145,45 +140,6 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service #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) /** diff --git a/ruoyi-generator/src/main/resources/vm/js/api.js.vm b/ruoyi-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 000000000..9295524a4 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/ruoyi-generator/src/main/resources/vm/sql/sql.vm b/ruoyi-generator/src/main/resources/vm/sql/sql.vm index 8124cd27a..b50ef2155 100644 --- a/ruoyi-generator/src/main/resources/vm/sql/sql.vm +++ b/ruoyi-generator/src/main/resources/vm/sql/sql.vm @@ -1,22 +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}菜单'); +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', '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, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', '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, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '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, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '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, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '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, ''); +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 000000000..4819c2a9f --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,505 @@ + + + diff --git a/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 000000000..6296014bc --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,602 @@ + + + diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 000000000..c54d62bf9 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 000000000..8b25665aa --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,590 @@ + + + diff --git a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm index 1366ade0c..5b704e737 100644 --- a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm +++ b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -7,9 +7,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #foreach ($column in $columns) -#end -#if($table.tree) - #end #if($table.sub) @@ -59,20 +56,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #end #end -#if($table.tree) - order by ${tree_parent_code} -#end -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 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 deleted file mode 100644 index e15ed941d..000000000 --- a/ruoyi-quartz/src/main/resources/templates/monitor/job/cron.html +++ /dev/null @@ -1,1172 +0,0 @@ - - - - - - Cron表达式在线生成 - - - - -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -

    表达式

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    分钟小时星期
    表达式字段
    Cron 表达式
    -
    - - - -
    -
    -
    -
    - - - - - - - - - diff --git a/ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html b/ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html deleted file mode 100644 index 7fd3f9413..000000000 --- a/ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    默认策略
    -
    立即执行
    -
    执行一次
    -
    放弃执行
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    - - \ 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 deleted file mode 100644 index 208cbf678..000000000 --- a/ruoyi-quartz/src/main/resources/templates/monitor/job/edit.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - -
    -
    - - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 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 deleted file mode 100644 index ca619254e..000000000 --- a/ruoyi-quartz/src/main/resources/templates/monitor/job/job.html +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 任务名称: -
    • -
    • - 任务分组: -
    • -
    • - 任务状态: -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - \ 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 deleted file mode 100644 index 7f953b2b6..000000000 --- a/ruoyi-quartz/src/main/resources/templates/monitor/job/jobLog.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - -
    -
    -
    -
    -
    -
      -
    • - 任务名称: -
    • -
    • - 任务分组: -
    • -
    • - 执行状态: -
    • -
    • - - - - - -
    • -
    • -  搜索 -  重置 -
    • -
    -
    -
    -
    - - - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index a583118a5..2ae39ab53 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 4.7.8 + 3.8.7 4.0.0 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java new file mode 100644 index 000000000..83f0703a0 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java @@ -0,0 +1,81 @@ +package com.ruoyi.system.domain; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 缓存信息 + * + * @author ruoyi + */ +public class SysCache +{ + /** 缓存名称 */ + private String cacheName = ""; + + /** 缓存键名 */ + private String cacheKey = ""; + + /** 缓存内容 */ + private String cacheValue = ""; + + /** 备注 */ + private String remark = ""; + + public SysCache() + { + + } + + public SysCache(String cacheName, String remark) + { + this.cacheName = cacheName; + this.remark = remark; + } + + public SysCache(String cacheName, String cacheKey, String cacheValue) + { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } + + public String getCacheName() + { + return cacheName; + } + + public void setCacheName(String cacheName) + { + this.cacheName = cacheName; + } + + public String getCacheKey() + { + return cacheKey; + } + + public void setCacheKey(String cacheKey) + { + this.cacheKey = cacheKey; + } + + public String getCacheValue() + { + return cacheValue; + } + + public void setCacheValue(String cacheValue) + { + this.cacheValue = cacheValue; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} 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 index da884de81..676a8916b 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java @@ -1,6 +1,7 @@ package com.ruoyi.system.domain; -import javax.validation.constraints.*; +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.annotation.Excel; @@ -91,7 +92,7 @@ public class SysConfig extends BaseEntity { this.configType = configType; } - + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) 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 index cf79d99d2..02a7fb5c0 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java @@ -1,8 +1,7 @@ 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.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; @@ -22,7 +21,7 @@ public class SysLogininfor extends BaseEntity /** 用户账号 */ @Excel(name = "用户账号") - private String loginName; + private String userName; /** 登录状态 0成功 1失败 */ @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败") @@ -49,6 +48,7 @@ public class SysLogininfor extends BaseEntity private String msg; /** 访问时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date loginTime; @@ -62,14 +62,14 @@ public class SysLogininfor extends BaseEntity this.infoId = infoId; } - public String getLoginName() + public String getUserName() { - return loginName; + return userName; } - public void setLoginName(String loginName) + public void setUserName(String userName) { - this.loginName = loginName; + this.userName = userName; } public String getStatus() @@ -141,19 +141,4 @@ public class SysLogininfor extends BaseEntity { 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/SysOperLog.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java index 7896152cf..7286573e6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java @@ -1,8 +1,7 @@ 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.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; @@ -80,6 +79,7 @@ public class SysOperLog extends BaseEntity private String errorMsg; /** 操作时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date operTime; @@ -266,27 +266,4 @@ public class SysOperLog extends BaseEntity { 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 index 7a2cb2bad..a36ed791d 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java @@ -1,6 +1,8 @@ package com.ruoyi.system.domain; -import javax.validation.constraints.*; +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.ruoyi.common.annotation.Excel; @@ -29,8 +31,8 @@ public class SysPost extends BaseEntity private String postName; /** 岗位排序 */ - @Excel(name = "岗位排序", cellType = ColumnType.NUMERIC) - private String postSort; + @Excel(name = "岗位排序") + private Integer postSort; /** 状态(0正常 1停用) */ @Excel(name = "状态", readConverterExp = "0=正常,1=停用") @@ -73,13 +75,13 @@ public class SysPost extends BaseEntity this.postName = postName; } - @NotBlank(message = "显示顺序不能为空") - public String getPostSort() + @NotNull(message = "显示顺序不能为空") + public Integer getPostSort() { return postSort; } - public void setPostSort(String postSort) + public void setPostSort(Integer postSort) { this.postSort = postSort; } @@ -103,7 +105,7 @@ public class SysPost extends BaseEntity { this.flag = flag; } - + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) 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 index c5467bb66..e1495aaa7 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java @@ -1,28 +1,20 @@ 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 +public class SysUserOnline { - private static final long serialVersionUID = 1L; - - /** 用户会话id */ - private String sessionId; + /** 会话编号 */ + private String tokenId; /** 部门名称 */ private String deptName; - /** 登录名称 */ - private String loginName; + /** 用户名称 */ + private String userName; /** 登录IP地址 */ private String ipaddr; @@ -36,26 +28,17 @@ public class SysUserOnline extends BaseEntity /** 操作系统 */ private String os; - /** session创建时间 */ - private Date startTimestamp; + /** 登录时间 */ + private Long loginTime; - /** session最后访问时间 */ - private Date lastAccessTime; - - /** 超时时间,单位为分钟 */ - private Long expireTime; - - /** 在线状态 */ - private OnlineStatus status = OnlineStatus.on_line; - - public String getSessionId() + public String getTokenId() { - return sessionId; + return tokenId; } - public void setSessionId(String sessionId) + public void setTokenId(String tokenId) { - this.sessionId = sessionId; + this.tokenId = tokenId; } public String getDeptName() @@ -68,14 +51,14 @@ public class SysUserOnline extends BaseEntity this.deptName = deptName; } - public String getLoginName() + public String getUserName() { - return loginName; + return userName; } - public void setLoginName(String loginName) + public void setUserName(String userName) { - this.loginName = loginName; + this.userName = userName; } public String getIpaddr() @@ -118,60 +101,13 @@ public class SysUserOnline extends BaseEntity this.os = os; } - public Date getStartTimestamp() + public Long getLoginTime() { - return startTimestamp; + return loginTime; } - public void setStartTimestamp(Date startTimestamp) + public void setLoginTime(Long loginTime) { - 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(); + this.loginTime = loginTime; } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java new file mode 100644 index 000000000..a5d5fdccb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java @@ -0,0 +1,106 @@ +package com.ruoyi.system.domain.vo; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author ruoyi + */ +public class MetaVo +{ + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo() + { + } + + public MetaVo(String title, String icon) + { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, boolean noCache) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + } + + public MetaVo(String title, String icon, String link) + { + this.title = title; + this.icon = icon; + this.link = link; + } + + public MetaVo(String title, String icon, boolean noCache, String link) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) + { + this.link = link; + } + } + + public boolean isNoCache() + { + return noCache; + } + + public void setNoCache(boolean noCache) + { + this.noCache = noCache; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public String getLink() + { + return link; + } + + public void setLink(String link) + { + this.link = link; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java new file mode 100644 index 000000000..afff8c9c5 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java @@ -0,0 +1,148 @@ +package com.ruoyi.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +/** + * 路由配置信息 + * + * @author ruoyi + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouterVo +{ + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List children; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + public boolean getHidden() + { + return hidden; + } + + public void setHidden(boolean hidden) + { + this.hidden = hidden; + } + + public String getRedirect() + { + return redirect; + } + + public void setRedirect(String redirect) + { + this.redirect = redirect; + } + + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public Boolean getAlwaysShow() + { + return alwaysShow; + } + + public void setAlwaysShow(Boolean alwaysShow) + { + this.alwaysShow = alwaysShow; + } + + public MetaVo getMeta() + { + return meta; + } + + public void setMeta(MetaVo meta) + { + this.meta = meta; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} 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 index b50c1a3ed..999995d7e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java @@ -61,16 +61,16 @@ public interface SysConfigMapper /** * 删除参数配置 * - * @param configId 参数主键 + * @param configId 参数ID * @return 结果 */ public int deleteConfigById(Long configId); /** - * 批量删除参数配置 + * 批量删除参数信息 * - * @param configIds 需要删除的数据ID + * @param configIds 需要删除的参数ID * @return 结果 */ - public int deleteConfigByIds(String[] configIds); -} \ No newline at end of file + public int deleteConfigByIds(Long[] configIds); +} 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 index 92514f27f..f3b58474e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java @@ -12,12 +12,53 @@ import com.ruoyi.common.core.domain.entity.SysDept; public interface SysDeptMapper { /** - * 查询下级部门数量 + * 查询部门管理数据 * * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + public List selectChildrenDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在子节点 + * + * @param deptId 部门ID * @return 结果 */ - public int selectDeptCount(SysDept dept); + public int hasChildByDeptId(Long deptId); /** * 查询部门是否存在用户 @@ -28,20 +69,13 @@ public interface SysDeptMapper public int checkDeptExistUser(Long deptId); /** - * 查询部门管理数据 + * 校验部门名称是否唯一 * - * @param dept 部门信息 - * @return 部门信息集合 - */ - public List selectDeptList(SysDept dept); - - /** - * 删除部门管理信息 - * - * @param deptId 部门ID + * @param deptName 部门名称 + * @param parentId 父部门ID * @return 结果 */ - public int deleteDeptById(Long deptId); + public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); /** * 新增部门信息 @@ -59,6 +93,13 @@ public interface SysDeptMapper */ public int updateDept(SysDept dept); + /** + * 修改所在部门正常状态 + * + * @param deptIds 部门ID组 + */ + public void updateDeptStatusNormal(Long[] deptIds); + /** * 修改子元素关系 * @@ -68,50 +109,10 @@ public interface SysDeptMapper 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); + public int deleteDeptById(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 index e9653a25c..92f799e55 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java @@ -61,12 +61,12 @@ public interface SysDictDataMapper public int deleteDictDataById(Long dictCode); /** - * 批量删除字典数据 + * 批量删除字典数据信息 * - * @param ids 需要删除的数据 + * @param dictCodes 需要删除的字典数据ID * @return 结果 */ - public int deleteDictDataByIds(String[] ids); + public int deleteDictDataByIds(Long[] dictCodes); /** * 新增字典数据信息 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 index f8c07b726..132a72ed6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java @@ -50,12 +50,12 @@ public interface SysDictTypeMapper public int deleteDictTypeById(Long dictId); /** - * 批量删除字典类型 + * 批量删除字典类型信息 * - * @param ids 需要删除的数据 + * @param dictIds 需要删除的字典ID * @return 结果 */ - public int deleteDictTypeByIds(Long[] ids); + public int deleteDictTypeByIds(Long[] dictIds); /** * 新增字典类型信息 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 index 0efe3d7e8..480dd9ff6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java @@ -28,10 +28,10 @@ public interface SysLogininforMapper /** * 批量删除系统登录日志 * - * @param ids 需要删除的数据 + * @param infoIds 需要删除的登录日志ID * @return 结果 */ - public int deleteLogininforByIds(String[] ids); + public int deleteLogininforByIds(Long[] infoIds); /** * 清空系统登录日志 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 index 1b7d4bff6..f3e2eb9eb 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java @@ -6,48 +6,33 @@ import com.ruoyi.common.core.domain.entity.SysMenu; /** * 菜单表 数据层 - * + * * @author ruoyi */ public interface SysMenuMapper { /** - * 查询系统所有菜单(含按钮) - * + * 查询系统菜单列表 + * + * @param menu 菜单信息 * @return 菜单列表 */ - public List selectMenuAll(); + public List selectMenuList(SysMenu menu); /** - * 根据用户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); + public List selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuListByUserId(SysMenu menu); /** * 根据角色ID查询权限 @@ -55,59 +40,59 @@ public interface SysMenuMapper * @param roleId 角色ID * @return 权限列表 */ - public List selectPermsByRoleId(Long roleId); + public List selectMenuPermsByRoleId(Long roleId); /** - * 根据角色ID查询菜单 + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + public List selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 * * @param roleId 角色ID - * @return 菜单列表 + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @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); + public List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); /** * 根据菜单ID查询信息 - * + * * @param menuId 菜单ID * @return 菜单信息 */ public SysMenu selectMenuById(Long menuId); /** - * 查询菜单数量 - * - * @param parentId 菜单父ID + * 是否存在菜单子节点 + * + * @param menuId 菜单ID * @return 结果 */ - public int selectCountMenuByParentId(Long parentId); + public int hasChildByMenuId(Long menuId); /** * 新增菜单信息 - * + * * @param menu 菜单信息 * @return 结果 */ @@ -115,15 +100,23 @@ public interface SysMenuMapper /** * 修改菜单信息 - * + * * @param menu 菜单信息 * @return 结果 */ public int updateMenu(SysMenu menu); + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + /** * 校验菜单名称是否唯一 - * + * * @param menuName 菜单名称 * @param parentId 父菜单ID * @return 结果 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 index 1ce1d8cde..cc267ac0c 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java @@ -4,7 +4,7 @@ import java.util.List; import com.ruoyi.system.domain.SysNotice; /** - * 公告 数据层 + * 通知公告表 数据层 * * @author ruoyi */ @@ -45,8 +45,16 @@ public interface SysNoticeMapper /** * 批量删除公告 * - * @param noticeIds 需要删除的数据ID + * @param noticeId 公告ID * @return 结果 */ - public int deleteNoticeByIds(String[] noticeIds); -} \ No newline at end of file + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} 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 index 98ca5d130..3bf69bb8e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java @@ -24,15 +24,15 @@ public interface SysOperLogMapper * @return 操作日志集合 */ public List selectOperLogList(SysOperLog operLog); - + /** * 批量删除系统操作日志 * - * @param ids 需要删除的数据 + * @param operIds 需要删除的操作日志ID * @return 结果 */ - public int deleteOperLogByIds(String[] ids); - + public int deleteOperLogByIds(Long[] operIds); + /** * 查询操作日志详细 * @@ -40,7 +40,7 @@ public interface SysOperLogMapper * @return 操作日志对象 */ public SysOperLog selectOperLogById(Long operId); - + /** * 清空操作日志 */ 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 index a4aab595b..8603d9aea 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java @@ -25,14 +25,6 @@ public interface SysPostMapper */ public List selectPostAll(); - /** - * 根据用户ID查询岗位 - * - * @param userId 用户ID - * @return 岗位列表 - */ - public List selectPostsByUserId(Long userId); - /** * 通过岗位ID查询岗位信息 * @@ -42,12 +34,36 @@ public interface SysPostMapper public SysPost selectPostById(Long postId); /** - * 批量删除岗位信息 + * 根据用户ID获取岗位选择框列表 * - * @param ids 需要删除的数据ID + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 * @return 结果 */ - public int deletePostByIds(Long[] ids); + public List selectPostsByUserName(String userName); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); /** * 修改岗位信息 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 index cf7f08614..58ede18e0 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java @@ -24,7 +24,22 @@ public interface SysRoleMapper * @param userId 用户ID * @return 角色列表 */ - public List selectRolesByUserId(Long userId); + public List selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); /** * 通过角色ID查询角色 @@ -35,20 +50,28 @@ public interface SysRoleMapper public SysRole selectRoleById(Long roleId); /** - * 通过角色ID删除角色 + * 根据用户ID查询角色 * - * @param roleId 角色ID - * @return 结果 + * @param userName 用户名 + * @return 角色列表 */ - public int deleteRoleById(Long roleId); + public List selectRolesByUserName(String userName); /** - * 批量角色用户信息 + * 校验角色名称是否唯一 * - * @param ids 需要删除的数据ID - * @return 结果 + * @param roleName 角色名称 + * @return 角色信息 */ - public int deleteRoleByIds(Long[] ids); + public SysRole checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + public SysRole checkRoleKeyUnique(String roleKey); /** * 修改角色信息 @@ -67,18 +90,18 @@ public interface SysRoleMapper public int insertRole(SysRole role); /** - * 校验角色名称是否唯一 + * 通过角色ID删除角色 * - * @param roleName 角色名称 - * @return 角色信息 + * @param roleId 角色ID + * @return 结果 */ - public SysRole checkRoleNameUnique(String roleName); - + public int deleteRoleById(Long roleId); + /** - * 校验角色权限是否唯一 + * 批量删除角色信息 * - * @param roleKey 角色权限 - * @return 角色信息 + * @param roleIds 需要删除的角色ID + * @return 结果 */ - public SysRole checkRoleKeyUnique(String roleKey); + public int deleteRoleByIds(Long[] roleIds); } 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 index 5ed98f294..23391557d 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java @@ -10,6 +10,14 @@ import com.ruoyi.system.domain.SysRoleMenu; */ public interface SysRoleMenuMapper { + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int checkMenuExistRole(Long menuId); + /** * 通过角色ID删除角色和菜单关联 * @@ -17,7 +25,7 @@ public interface SysRoleMenuMapper * @return 结果 */ public int deleteRoleMenuByRoleId(Long roleId); - + /** * 批量删除角色菜单关联信息 * @@ -25,15 +33,7 @@ public interface SysRoleMenuMapper * @return 结果 */ public int deleteRoleMenu(Long[] ids); - - /** - * 查询菜单使用数量 - * - * @param menuId 菜单ID - * @return 结果 - */ - public int selectCountRoleMenuByMenuId(Long menuId); - + /** * 批量新增角色菜单信息 * 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 index ed57abb47..c86acea45 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -1,6 +1,7 @@ package com.ruoyi.system.mapper; import java.util.List; +import org.apache.ibatis.annotations.Param; import com.ruoyi.common.core.domain.entity.SysUser; /** @@ -40,23 +41,7 @@ public interface SysUserMapper * @param userName 用户名 * @return 用户对象信息 */ - public SysUser selectUserByLoginName(String userName); - - /** - * 通过手机号码查询用户 - * - * @param phoneNumber 手机号码 - * @return 用户对象信息 - */ - public SysUser selectUserByPhoneNumber(String phoneNumber); - - /** - * 通过邮箱查询用户 - * - * @param email 邮箱 - * @return 用户对象信息 - */ - public SysUser selectUserByEmail(String email); + public SysUser selectUserByUserName(String userName); /** * 通过用户ID查询用户 @@ -66,6 +51,40 @@ public interface SysUserMapper */ public SysUser selectUserById(Long userId); + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userName") String userName, @Param("password") String password); + /** * 通过用户ID删除用户 * @@ -77,34 +96,18 @@ public interface SysUserMapper /** * 批量删除用户信息 * - * @param ids 需要删除的数据ID + * @param userIds 需要删除的用户ID * @return 结果 */ - public int deleteUserByIds(Long[] ids); - - /** - * 修改用户信息 - * - * @param user 用户信息 - * @return 结果 - */ - public int updateUser(SysUser user); - - /** - * 新增用户信息 - * - * @param user 用户信息 - * @return 结果 - */ - public int insertUser(SysUser user); + public int deleteUserByIds(Long[] userIds); /** * 校验用户名称是否唯一 * - * @param loginName 登录名称 + * @param userName 用户名称 * @return 结果 */ - public SysUser checkLoginNameUnique(String loginName); + public SysUser checkUserNameUnique(String userName); /** * 校验手机号码是否唯一 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 deleted file mode 100644 index c6bf94cd2..000000000 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java +++ /dev/null @@ -1,52 +0,0 @@ -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 index 877ddddcd..81d6e719c 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java @@ -17,7 +17,7 @@ public interface SysUserPostMapper * @return 结果 */ public int deleteUserPostByUserId(Long userId); - + /** * 通过岗位ID查询岗位使用数量 * @@ -25,7 +25,7 @@ public interface SysUserPostMapper * @return 结果 */ public int countUserPostById(Long postId); - + /** * 批量删除用户和岗位关联 * 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 index 17fa5c827..ad3fe62c1 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java @@ -11,14 +11,6 @@ import com.ruoyi.system.domain.SysUserRole; */ public interface SysUserRoleMapper { - /** - * 通过用户ID查询用户和角色关联 - * - * @param userId 用户ID - * @return 用户和角色关联列表 - */ - public List selectUserRoleByUserId(Long userId); - /** * 通过用户ID删除用户和角色关联 * 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 index ba9a27075..209613784 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java @@ -26,6 +26,13 @@ public interface ISysConfigService */ public String selectConfigByKey(String configKey); + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + public boolean selectCaptchaEnabled(); + /** * 查询参数配置列表 * @@ -51,11 +58,11 @@ public interface ISysConfigService public int updateConfig(SysConfig config); /** - * 批量删除参数配置信息 + * 批量删除参数信息 * - * @param ids 需要删除的数据ID + * @param configIds 需要删除的参数ID */ - public void deleteConfigByIds(String ids); + public void deleteConfigByIds(Long[] configIds); /** * 加载参数缓存数据 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 index 0998732ab..302d7a1f7 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java @@ -1,9 +1,8 @@ package com.ruoyi.system.service; import java.util.List; -import com.ruoyi.common.core.domain.Ztree; +import com.ruoyi.common.core.domain.TreeSelect; import com.ruoyi.common.core.domain.entity.SysDept; -import com.ruoyi.common.core.domain.entity.SysRole; /** * 部门管理 服务层 @@ -21,68 +20,36 @@ public interface ISysDeptService public List selectDeptList(SysDept dept); /** - * 查询部门管理树 + * 查询部门树结构信息 * * @param dept 部门信息 - * @return 所有部门信息 + * @return 部门树信息集合 */ - public List selectDeptTree(SysDept dept); + public List selectDeptTreeList(SysDept dept); /** - * 查询部门管理树(排除下级) + * 构建前端所需要树结构 * - * @param dept 部门信息 - * @return 所有部门信息 + * @param depts 部门列表 + * @return 树结构列表 */ - public List selectDeptTreeExcludeChild(SysDept dept); + public List buildDeptTree(List depts); /** - * 根据角色ID查询菜单 - * - * @param role 角色对象 - * @return 菜单列表 - */ - public List roleDeptTreeData(SysRole role); - - /** - * 根据父部门ID查询下级部门数量 + * 构建前端所需要下拉树结构 * - * @param parentId 父部门ID - * @return 结果 + * @param depts 部门列表 + * @return 下拉树结构列表 */ - public int selectDeptCount(Long parentId); + public List buildDeptTreeSelect(List depts); /** - * 查询部门是否存在用户 + * 根据角色ID查询部门树信息 * - * @param deptId 部门ID - * @return 结果 true 存在 false 不存在 + * @param roleId 角色ID + * @return 选中部门列表 */ - 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); + public List selectDeptListByRoleId(Long roleId); /** * 根据部门ID查询信息 @@ -100,6 +67,22 @@ public interface ISysDeptService */ public int selectNormalChildrenDeptById(Long deptId); + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public boolean hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkDeptExistUser(Long deptId); + /** * 校验部门名称是否唯一 * @@ -114,4 +97,28 @@ public interface ISysDeptService * @param deptId 部门id */ public void checkDeptDataScope(Long deptId); + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(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 index b2575a0c8..9a5cf1909 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java @@ -36,11 +36,11 @@ public interface ISysDictDataService public SysDictData selectDictDataById(Long dictCode); /** - * 批量删除字典数据 + * 批量删除字典数据信息 * - * @param ids 需要删除的数据 + * @param dictCodes 需要删除的字典数据ID */ - public void deleteDictDataByIds(String ids); + public void deleteDictDataByIds(Long[] dictCodes); /** * 新增保存字典数据信息 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 index 50e7e283f..f01a35847 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java @@ -1,7 +1,6 @@ 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; @@ -52,11 +51,11 @@ public interface ISysDictTypeService public SysDictType selectDictTypeByType(String dictType); /** - * 批量删除字典类型 + * 批量删除字典信息 * - * @param ids 需要删除的数据 + * @param dictIds 需要删除的字典ID */ - public void deleteDictTypeByIds(String ids); + public void deleteDictTypeByIds(Long[] dictIds); /** * 加载字典缓存数据 @@ -96,12 +95,4 @@ public interface ISysDictTypeService * @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 index 501551573..0a8320838 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java @@ -28,10 +28,10 @@ public interface ISysLogininforService /** * 批量删除系统登录日志 * - * @param ids 需要删除的数据 + * @param infoIds 需要删除的登录日志ID * @return 结果 */ - public int deleteLogininforByIds(String ids); + public int deleteLogininforByIds(Long[] infoIds); /** * 清空系统登录日志 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 index 58dfd689b..134b9ebd4 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java @@ -1,12 +1,10 @@ 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.TreeSelect; 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.system.domain.vo.RouterVo; /** * 菜单 业务层 @@ -16,15 +14,15 @@ import com.ruoyi.common.core.domain.entity.SysUser; public interface ISysMenuService { /** - * 根据用户ID查询菜单 + * 根据用户查询系统菜单列表 * - * @param user 用户信息 + * @param userId 用户ID * @return 菜单列表 */ - public List selectMenusByUser(SysUser user); + public List selectMenuList(Long userId); /** - * 查询系统菜单列表 + * 根据用户查询系统菜单列表 * * @param menu 菜单信息 * @param userId 用户ID @@ -32,21 +30,13 @@ public interface ISysMenuService */ 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); + public Set selectMenuPermsByUserId(Long userId); /** * 根据角色ID查询权限 @@ -54,40 +44,47 @@ public interface ISysMenuService * @param roleId 角色ID * @return 权限列表 */ - public Set selectPermsByRoleId(Long roleId); + public Set selectMenuPermsByRoleId(Long roleId); /** - * 根据角色ID查询菜单 - * - * @param role 角色对象 - * @param userId 用户ID - * @return 菜单列表 - */ - public List roleMenuTreeData(SysRole role, Long userId); - - /** - * 查询所有菜单信息 + * 根据用户ID查询菜单树信息 * * @param userId 用户ID * @return 菜单列表 */ - public List menuTreeData(Long userId); + public List selectMenuTreeByUserId(Long userId); /** - * 查询系统所有权限 + * 根据角色ID查询菜单树信息 * - * @param userId 用户ID - * @return 权限列表 + * @param roleId 角色ID + * @return 选中菜单列表 */ - public Map selectPermsAll(Long userId); + public List selectMenuListByRoleId(Long roleId); /** - * 删除菜单管理信息 + * 构建前端路由所需要的菜单 * - * @param menuId 菜单ID - * @return 结果 + * @param menus 菜单列表 + * @return 路由列表 */ - public int deleteMenuById(Long menuId); + public List buildMenus(List menus); + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List buildMenuTree(List menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + public List buildMenuTreeSelect(List menus); /** * 根据菜单ID查询信息 @@ -98,20 +95,20 @@ public interface ISysMenuService public SysMenu selectMenuById(Long menuId); /** - * 查询菜单数量 - * - * @param parentId 菜单父ID - * @return 结果 - */ - public int selectCountMenuByParentId(Long parentId); - - /** - * 查询菜单使用数量 + * 是否存在菜单子节点 * * @param menuId 菜单ID - * @return 结果 + * @return 结果 true 存在 false 不存在 */ - public int selectCountRoleMenuByMenuId(Long menuId); + public boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkMenuExistRole(Long menuId); /** * 新增保存菜单信息 @@ -129,6 +126,14 @@ public interface ISysMenuService */ public int updateMenu(SysMenu menu); + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + /** * 校验菜单名称是否唯一 * 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 index abd6d7afc..fb1e420fd 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java @@ -45,8 +45,16 @@ public interface ISysNoticeService /** * 删除公告信息 * - * @param ids 需要删除的数据ID + * @param noticeId 公告ID * @return 结果 */ - public int deleteNoticeByIds(String ids); + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); } 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 index 61efe6020..241f121ae 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java @@ -28,10 +28,10 @@ public interface ISysOperLogService /** * 批量删除系统操作日志 * - * @param ids 需要删除的数据 + * @param operIds 需要删除的操作日志ID * @return 结果 */ - public int deleteOperLogByIds(String ids); + public int deleteOperLogByIds(Long[] operIds); /** * 查询操作日志详细 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 index 5e3dbc8d5..590634b9e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java @@ -14,7 +14,7 @@ public interface ISysPostService * 查询岗位信息集合 * * @param post 岗位信息 - * @return 岗位信息集合 + * @return 岗位列表 */ public List selectPostList(SysPost post); @@ -25,14 +25,6 @@ public interface ISysPostService */ public List selectPostAll(); - /** - * 根据用户ID查询岗位 - * - * @param userId 用户ID - * @return 岗位列表 - */ - public List selectPostsByUserId(Long userId); - /** * 通过岗位ID查询岗位信息 * @@ -42,36 +34,12 @@ public interface ISysPostService public SysPost selectPostById(Long postId); /** - * 批量删除岗位信息 + * 根据用户ID获取岗位选择框列表 * - * @param ids 需要删除的数据ID - * @return 结果 + * @param userId 用户ID + * @return 选中岗位ID列表 */ - 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); + public List selectPostListByUserId(Long userId); /** * 校验岗位名称 @@ -88,4 +56,44 @@ public interface ISysPostService * @return 结果 */ public boolean checkPostCodeUnique(SysPost post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(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 index f7448ee78..ba79ed5d9 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java @@ -24,17 +24,17 @@ public interface ISysRoleService * 根据用户ID查询角色列表 * * @param userId 用户ID - * @return 权限列表 + * @return 角色列表 */ - public Set selectRoleKeys(Long userId); + public List selectRolesByUserId(Long userId); /** * 根据用户ID查询角色权限 * * @param userId 用户ID - * @return 角色列表 + * @return 权限列表 */ - public List selectRolesByUserId(Long userId); + public Set selectRolePermissionByUserId(Long userId); /** * 查询所有角色 @@ -43,6 +43,14 @@ public interface ISysRoleService */ public List selectRoleAll(); + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + /** * 通过角色ID查询角色 * @@ -51,47 +59,6 @@ public interface ISysRoleService */ 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); - /** * 校验角色名称是否唯一 * @@ -131,12 +98,52 @@ public interface ISysRoleService public int countUserRoleByRoleId(Long roleId); /** - * 角色状态修改 + * 新增保存角色信息 * * @param role 角色信息 * @return 结果 */ - public int changeStatus(SysRole role); + public int insertRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRoleStatus(SysRole role); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int authDataScope(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); /** * 取消授权用户角色 @@ -150,10 +157,10 @@ public interface ISysRoleService * 批量取消授权用户角色 * * @param roleId 角色ID - * @param userIds 需要删除的用户数据ID + * @param userIds 需要取消授权的用户数据ID * @return 结果 */ - public int deleteAuthUsers(Long roleId, String userIds); + public int deleteAuthUsers(Long roleId, Long[] userIds); /** * 批量选择授权用户角色 @@ -162,5 +169,5 @@ public interface ISysRoleService * @param userIds 需要删除的用户数据ID * @return 结果 */ - public int insertAuthUsers(Long roleId, String userIds); + public int insertAuthUsers(Long roleId, Long[] 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 index 9c62537f2..12095ff5e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java @@ -1,7 +1,6 @@ package com.ruoyi.system.service; -import java.util.Date; -import java.util.List; +import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.system.domain.SysUserOnline; /** @@ -12,64 +11,38 @@ import com.ruoyi.system.domain.SysUserOnline; public interface ISysUserOnlineService { /** - * 通过会话序号查询信息 + * 通过登录地址查询信息 * - * @param sessionId 会话ID + * @param ipaddr 登录地址 + * @param user 用户信息 * @return 在线用户信息 */ - public SysUserOnline selectOnlineById(String sessionId); + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user); /** - * 通过会话序号删除信息 + * 通过用户名称查询信息 * - * @param sessionId 会话ID + * @param userName 用户名称 + * @param user 用户信息 * @return 在线用户信息 */ - public void deleteOnlineById(String sessionId); + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user); /** - * 通过会话序号删除信息 + * 通过登录地址/用户名称查询信息 * - * @param sessions 会话ID集合 + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 * @return 在线用户信息 */ - public void batchDeleteOnline(List sessions); + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user); /** - * 保存会话信息 + * 设置在线用户信息 * - * @param online 会话信息 + * @param user 用户信息 + * @return 在线用户 */ - 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); + public SysUserOnline loginUserToUserOnline(LoginUser user); } 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 index 58869cd1a..e438b577e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -2,7 +2,6 @@ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.common.core.domain.entity.SysUser; -import com.ruoyi.system.domain.SysUserRole; /** * 用户 业务层 @@ -41,23 +40,7 @@ public interface ISysUserService * @param userName 用户名 * @return 用户对象信息 */ - public SysUser selectUserByLoginName(String userName); - - /** - * 通过手机号码查询用户 - * - * @param phoneNumber 手机号码 - * @return 用户对象信息 - */ - public SysUser selectUserByPhoneNumber(String phoneNumber); - - /** - * 通过邮箱查询用户 - * - * @param email 邮箱 - * @return 用户对象信息 - */ - public SysUser selectUserByEmail(String email); + public SysUser selectUserByUserName(String userName); /** * 通过用户ID查询用户 @@ -68,77 +51,20 @@ public interface ISysUserService public SysUser selectUserById(Long userId); /** - * 通过用户ID查询用户和角色关联 + * 根据用户ID查询用户所属角色组 * - * @param userId 用户ID - * @return 用户和角色关联列表 - */ - public List selectUserRoleByUserId(Long userId); - - /** - * 通过用户ID删除用户 - * - * @param userId 用户ID + * @param userName 用户名 * @return 结果 */ - public int deleteUserById(Long userId); + public String selectUserRoleGroup(String userName); /** - * 批量删除用户信息 + * 根据用户ID查询用户所属岗位组 * - * @param ids 需要删除的数据ID - * @return 结果 - * @throws Exception 异常 - */ - public int deleteUserByIds(String ids); - - /** - * 保存用户信息 - * - * @param user 用户信息 + * @param userName 用户名 * @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); + public String selectUserPostGroup(String userName); /** * 校验用户名称是否唯一 @@ -146,7 +72,7 @@ public interface ISysUserService * @param user 用户信息 * @return 结果 */ - public boolean checkLoginNameUnique(SysUser user); + public boolean checkUserNameUnique(SysUser user); /** * 校验手机号码是否唯一 @@ -179,20 +105,94 @@ public interface ISysUserService public void checkUserDataScope(Long userId); /** - * 根据用户ID查询用户所属角色组 + * 新增用户信息 * - * @param userId 用户ID + * @param user 用户信息 * @return 结果 */ - public String selectUserRoleGroup(Long userId); + public int insertUser(SysUser user); /** - * 根据用户ID查询用户所属岗位组 + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean registerUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserStatus(SysUser user); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserProfile(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public boolean updateUserAvatar(String userName, String avatar); + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetPwd(SysUser user); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(String userName, String password); + + /** + * 通过用户ID删除用户 * * @param userId 用户ID * @return 结果 */ - public String selectUserPostGroup(Long userId); + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); /** * 导入用户数据 @@ -203,12 +203,4 @@ public interface ISysUserService * @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 index 960c5020c..3615b88f5 100644 --- 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 @@ -1,14 +1,17 @@ package com.ruoyi.system.service.impl; +import java.util.Collection; 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.annotation.DataSource; +import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.DataSourceType; 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; @@ -25,6 +28,9 @@ public class SysConfigServiceImpl implements ISysConfigService @Autowired private SysConfigMapper configMapper; + @Autowired + private RedisCache redisCache; + /** * 项目启动时,初始化参数到缓存 */ @@ -41,6 +47,7 @@ public class SysConfigServiceImpl implements ISysConfigService * @return 参数配置信息 */ @Override + @DataSource(DataSourceType.MASTER) public SysConfig selectConfigById(Long configId) { SysConfig config = new SysConfig(); @@ -57,7 +64,7 @@ public class SysConfigServiceImpl implements ISysConfigService @Override public String selectConfigByKey(String configKey) { - String configValue = Convert.toStr(CacheUtils.get(getCacheName(), getCacheKey(configKey))); + String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey))); if (StringUtils.isNotEmpty(configValue)) { return configValue; @@ -67,12 +74,28 @@ public class SysConfigServiceImpl implements ISysConfigService SysConfig retConfig = configMapper.selectConfig(config); if (StringUtils.isNotNull(retConfig)) { - CacheUtils.put(getCacheName(), getCacheKey(configKey), retConfig.getConfigValue()); + redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue()); return retConfig.getConfigValue(); } return StringUtils.EMPTY; } + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + @Override + public boolean selectCaptchaEnabled() + { + String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); + if (StringUtils.isEmpty(captchaEnabled)) + { + return true; + } + return Convert.toBool(captchaEnabled); + } + /** * 查询参数配置列表 * @@ -97,7 +120,7 @@ public class SysConfigServiceImpl implements ISysConfigService int row = configMapper.insertConfig(config); if (row > 0) { - CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); } return row; } @@ -114,26 +137,25 @@ public class SysConfigServiceImpl implements ISysConfigService SysConfig temp = configMapper.selectConfigById(config.getConfigId()); if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) { - CacheUtils.remove(getCacheName(), getCacheKey(temp.getConfigKey())); + redisCache.deleteObject(getCacheKey(temp.getConfigKey())); } int row = configMapper.updateConfig(config); if (row > 0) { - CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); } return row; } /** - * 批量删除参数配置对象 + * 批量删除参数信息 * - * @param ids 需要删除的数据ID + * @param configIds 需要删除的参数ID */ @Override - public void deleteConfigByIds(String ids) + public void deleteConfigByIds(Long[] configIds) { - Long[] configIds = Convert.toLongArray(ids); for (Long configId : configIds) { SysConfig config = selectConfigById(configId); @@ -142,7 +164,7 @@ public class SysConfigServiceImpl implements ISysConfigService throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); } configMapper.deleteConfigById(configId); - CacheUtils.remove(getCacheName(), getCacheKey(config.getConfigKey())); + redisCache.deleteObject(getCacheKey(config.getConfigKey())); } } @@ -155,7 +177,7 @@ public class SysConfigServiceImpl implements ISysConfigService List configsList = configMapper.selectConfigList(new SysConfig()); for (SysConfig config : configsList) { - CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); } } @@ -165,7 +187,8 @@ public class SysConfigServiceImpl implements ISysConfigService @Override public void clearConfigCache() { - CacheUtils.removeAll(getCacheName()); + Collection keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*"); + redisCache.deleteObject(keys); } /** @@ -196,16 +219,6 @@ public class SysConfigServiceImpl implements ISysConfigService return UserConstants.UNIQUE; } - /** - * 获取cache name - * - * @return 缓存名 - */ - private String getCacheName() - { - return Constants.SYS_CONFIG_CACHE; - } - /** * 设置cache key * @@ -214,6 +227,6 @@ public class SysConfigServiceImpl implements ISysConfigService */ private String getCacheKey(String configKey) { - return Constants.SYS_CONFIG_KEY + configKey; + return CacheConstants.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 index adca04c6a..c5f3ce6e1 100644 --- 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 @@ -1,23 +1,24 @@ package com.ruoyi.system.service.impl; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.ArrayUtils; +import java.util.stream.Collectors; 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.TreeSelect; 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.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.system.mapper.SysRoleMapper; import com.ruoyi.system.service.ISysDeptService; /** @@ -31,6 +32,9 @@ public class SysDeptServiceImpl implements ISysDeptService @Autowired private SysDeptMapper deptMapper; + @Autowired + private SysRoleMapper roleMapper; + /** * 查询部门管理数据 * @@ -45,118 +49,106 @@ public class SysDeptServiceImpl implements ISysDeptService } /** - * 查询部门管理树 + * 查询部门树结构信息 * * @param dept 部门信息 - * @return 所有部门信息 + * @return 部门树信息集合 */ @Override - @DataScope(deptAlias = "d") - public List selectDeptTree(SysDept dept) + public List selectDeptTreeList(SysDept dept) { - List deptList = deptMapper.selectDeptList(dept); - List ztrees = initZtree(deptList); - return ztrees; + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + return buildDeptTreeSelect(depts); } /** - * 查询部门管理树(排除下级) + * 构建前端所需要树结构 * - * @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 部门列表 + * @param depts 部门列表 * @return 树结构列表 */ - public List initZtree(List deptList) + @Override + public List buildDeptTree(List depts) { - 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) + List returnList = new ArrayList(); + List tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); + for (SysDept dept : depts) { - if (UserConstants.DEPT_NORMAL.equals(dept.getStatus())) + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(dept.getParentId())) { - 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); + recursionFn(depts, dept); + returnList.add(dept); } } - return ztrees; + if (returnList.isEmpty()) + { + returnList = depts; + } + return returnList; } /** - * 根据父部门ID查询下级部门数量 + * 构建前端所需要下拉树结构 * - * @param parentId 部门ID + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + @Override + public List buildDeptTreeSelect(List depts) + { + List deptTrees = buildDeptTree(depts); + return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List selectDeptListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly()); + } + + /** + * 根据部门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 deptId 部门ID * @return 结果 */ @Override - public int selectDeptCount(Long parentId) + public boolean hasChildByDeptId(Long deptId) { - SysDept dept = new SysDept(); - dept.setParentId(parentId); - return deptMapper.selectDeptCount(dept); + int result = deptMapper.hasChildByDeptId(deptId); + return result > 0; } /** @@ -173,15 +165,41 @@ public class SysDeptServiceImpl implements ISysDeptService } /** - * 删除部门管理信息 + * 校验部门名称是否唯一 * - * @param deptId 部门ID + * @param dept 部门信息 * @return 结果 */ @Override - public int deleteDeptById(Long deptId) + public boolean checkDeptNameUnique(SysDept dept) { - return deptMapper.deleteDeptById(deptId); + 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(SecurityUtils.getUserId())) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + if (StringUtils.isEmpty(depts)) + { + throw new ServiceException("没有权限访问部门数据!"); + } + } } /** @@ -194,7 +212,7 @@ public class SysDeptServiceImpl implements ISysDeptService public int insertDept(SysDept dept) { SysDept info = deptMapper.selectDeptById(dept.getParentId()); - // 如果父节点不为"正常"状态,则不允许新增子节点 + // 如果父节点不为正常状态,则不允许新增子节点 if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) { throw new ServiceException("部门停用,不允许新增"); @@ -210,11 +228,10 @@ public class SysDeptServiceImpl implements ISysDeptService * @return 结果 */ @Override - @Transactional public int updateDept(SysDept dept) { SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); - SysDept oldDept = selectDeptById(dept.getDeptId()); + SysDept oldDept = deptMapper.selectDeptById(dept.getDeptId()); if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept)) { String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); @@ -265,64 +282,57 @@ public class SysDeptServiceImpl implements ISysDeptService } /** - * 根据部门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) + public int deleteDeptById(Long deptId) { - 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; + return deptMapper.deleteDeptById(deptId); } /** - * 校验部门是否有数据权限 - * - * @param deptId 部门id + * 递归列表 */ - @Override - public void checkDeptDataScope(Long deptId) + private void recursionFn(List list, SysDept t) { - if (!SysUser.isAdmin(ShiroUtils.getUserId())) + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysDept tChild : childList) { - SysDept dept = new SysDept(); - dept.setDeptId(deptId); - List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); - if (StringUtils.isEmpty(depts)) + if (hasChild(list, tChild)) { - throw new ServiceException("没有权限访问部门数据!"); + recursionFn(list, tChild); } } } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysDept t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysDept n = (SysDept) it.next(); + if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysDept t) + { + return getChildList(list, t).size() > 0; + } } 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 index 9c3325fb7..de244f8b4 100644 --- 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 @@ -4,7 +4,6 @@ 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; @@ -58,14 +57,13 @@ public class SysDictDataServiceImpl implements ISysDictDataService } /** - * 批量删除字典数据 + * 批量删除字典数据信息 * - * @param ids 需要删除的数据 + * @param dictCodes 需要删除的字典数据ID */ @Override - public void deleteDictDataByIds(String ids) + public void deleteDictDataByIds(Long[] dictCodes) { - Long[] dictCodes = Convert.toLongArray(ids); for (Long dictCode : dictCodes) { SysDictData data = selectDictDataById(dictCode); 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 index b7b2cf10f..b6c4c5650 100644 --- 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 @@ -1,6 +1,5 @@ package com.ruoyi.system.service.impl; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -10,10 +9,8 @@ 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; @@ -115,14 +112,13 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService } /** - * 批量删除字典类型 + * 批量删除字典类型信息 * - * @param ids 需要删除的数据 + * @param dictIds 需要删除的字典ID */ @Override - public void deleteDictTypeByIds(String ids) + public void deleteDictTypeByIds(Long[] dictIds) { - Long[] dictIds = Convert.toLongArray(ids); for (Long dictId : dictIds) { SysDictType dictType = selectDictTypeById(dictId); @@ -224,37 +220,4 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService } 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 index 2ffb61355..a5637aee2 100644 --- 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 @@ -3,7 +3,6 @@ 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; @@ -46,13 +45,13 @@ public class SysLogininforServiceImpl implements ISysLogininforService /** * 批量删除系统登录日志 * - * @param ids 需要删除的数据 + * @param infoIds 需要删除的登录日志ID * @return 结果 */ @Override - public int deleteLogininforByIds(String ids) + public int deleteLogininforByIds(Long[] infoIds) { - return logininforMapper.deleteLogininforByIds(Convert.toStrArray(ids)); + return logininforMapper.deleteLogininforByIds(infoIds); } /** 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 index 26020c2cd..624a379fe 100644 --- 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 @@ -1,23 +1,27 @@ 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 java.util.stream.Collectors; 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.domain.Ztree; +import com.ruoyi.common.core.domain.TreeSelect; 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.SecurityUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.vo.MetaVo; +import com.ruoyi.system.domain.vo.RouterVo; import com.ruoyi.system.mapper.SysMenuMapper; +import com.ruoyi.system.mapper.SysRoleMapper; import com.ruoyi.system.mapper.SysRoleMenuMapper; import com.ruoyi.system.service.ISysMenuService; @@ -34,40 +38,35 @@ public class SysMenuServiceImpl implements ISysMenuService @Autowired private SysMenuMapper menuMapper; + @Autowired + private SysRoleMapper roleMapper; + @Autowired private SysRoleMenuMapper roleMenuMapper; /** - * 根据用户查询菜单 + * 根据用户查询系统菜单列表 * - * @param user 用户信息 + * @param userId 用户ID * @return 菜单列表 */ @Override - public List selectMenusByUser(SysUser user) + public List selectMenuList(Long userId) { - List menus = new LinkedList(); - // 管理员显示所有菜单信息 - if (user.isAdmin()) - { - menus = menuMapper.selectMenuNormalAll(); - } - else - { - menus = menuMapper.selectMenusByUserId(user.getUserId()); - } - return getChildPerms(menus, 0); + return selectMenuList(new SysMenu(), userId); } /** - * 查询菜单集合 + * 查询系统菜单列表 * - * @return 所有菜单信息 + * @param menu 菜单信息 + * @return 菜单列表 */ @Override public List selectMenuList(SysMenu menu, Long userId) { List menuList = null; + // 管理员显示所有菜单信息 if (SysUser.isAdmin(userId)) { menuList = menuMapper.selectMenuList(menu); @@ -80,26 +79,6 @@ public class SysMenuServiceImpl implements ISysMenuService 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查询权限 * @@ -107,9 +86,9 @@ public class SysMenuServiceImpl implements ISysMenuService * @return 权限列表 */ @Override - public Set selectPermsByUserId(Long userId) + public Set selectMenuPermsByUserId(Long userId) { - List perms = menuMapper.selectPermsByUserId(userId); + List perms = menuMapper.selectMenuPermsByUserId(userId); Set permsSet = new HashSet<>(); for (String perm : perms) { @@ -128,9 +107,9 @@ public class SysMenuServiceImpl implements ISysMenuService * @return 权限列表 */ @Override - public Set selectPermsByRoleId(Long roleId) + public Set selectMenuPermsByRoleId(Long roleId) { - List perms = menuMapper.selectPermsByRoleId(roleId); + List perms = menuMapper.selectMenuPermsByRoleId(roleId); Set permsSet = new HashSet<>(); for (String perm : perms) { @@ -143,122 +122,136 @@ public class SysMenuServiceImpl implements ISysMenuService } /** - * 根据角色ID查询菜单 + * 根据用户ID查询菜单 * - * @param role 角色对象 + * @param userId 用户名称 * @return 菜单列表 */ @Override - public List roleMenuTreeData(SysRole role, Long userId) + public List selectMenuTreeByUserId(Long userId) { - Long roleId = role.getRoleId(); - List ztrees = new ArrayList(); - List menuList = selectMenuAll(userId); - if (StringUtils.isNotNull(roleId)) + List menus = null; + if (SecurityUtils.isAdmin(userId)) { - List roleMenuList = menuMapper.selectMenuTree(roleId); - ztrees = initZtree(menuList, roleMenuList, true); + menus = menuMapper.selectMenuTreeAll(); } else { - ztrees = initZtree(menuList, null, true); + menus = menuMapper.selectMenuTreeByUserId(userId); } - return ztrees; + return getChildPerms(menus, 0); } /** - * 查询所有菜单 + * 根据角色ID查询菜单树信息 * - * @return 菜单列表 + * @param roleId 角色ID + * @return 选中菜单列表 */ @Override - public List menuTreeData(Long userId) + public List selectMenuListByRoleId(Long roleId) { - List menuList = selectMenuAll(userId); - List ztrees = initZtree(menuList); - return ztrees; + SysRole role = roleMapper.selectRoleById(roleId); + return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); } /** - * 查询系统所有权限 + * 构建前端路由所需要的菜单 * - * @return 权限列表 + * @param menus 菜单列表 + * @return 路由列表 */ @Override - public LinkedHashMap selectPermsAll(Long userId) + public List buildMenus(List menus) { - LinkedHashMap section = new LinkedHashMap<>(); - List permissions = selectMenuAll(userId); - if (StringUtils.isNotEmpty(permissions)) + List routers = new LinkedList(); + for (SysMenu menu : menus) { - for (SysMenu menu : permissions) + RouterVo router = new RouterVo(); + router.setHidden("1".equals(menu.getVisible())); + router.setName(getRouteName(menu)); + router.setPath(getRouterPath(menu)); + router.setComponent(getComponent(menu)); + router.setQuery(menu.getQuery()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + List cMenus = menu.getChildren(); + if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) { - section.put(menu.getUrl(), MessageFormat.format(PREMISSION_STRING, menu.getPerms())); + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); } + else if (isMenuFrame(menu)) + { + router.setMeta(null); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(StringUtils.capitalize(menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setQuery(menu.getQuery()); + childrenList.add(children); + router.setChildren(childrenList); + } + else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) + { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(StringUtils.capitalize(routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); } - return section; + return routers; } /** - * 对象转菜单树 + * 构建前端所需要树结构 * - * @param menuList 菜单列表 + * @param menus 菜单列表 * @return 树结构列表 */ - public List initZtree(List menuList) + @Override + public List buildMenuTree(List menus) { - 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) + List returnList = new ArrayList(); + List tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + for (Iterator iterator = menus.iterator(); iterator.hasNext();) { - Ztree ztree = new Ztree(); - ztree.setId(menu.getMenuId()); - ztree.setpId(menu.getParentId()); - ztree.setName(transMenuName(menu, permsFlag)); - ztree.setTitle(menu.getMenuName()); - if (isCheck) + SysMenu menu = (SysMenu) iterator.next(); + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(menu.getParentId())) { - ztree.setChecked(roleMenuList.contains(menu.getMenuId() + menu.getPerms())); + recursionFn(menus, menu); + returnList.add(menu); } - ztrees.add(ztree); } - return ztrees; - } - - public String transMenuName(SysMenu menu, boolean permsFlag) - { - StringBuffer sb = new StringBuffer(); - sb.append(menu.getMenuName()); - if (permsFlag) + if (returnList.isEmpty()) { - sb.append("   " + menu.getPerms() + ""); + returnList = menus; } - return sb.toString(); + return returnList; } /** - * 删除菜单管理信息 + * 构建前端所需要下拉树结构 * - * @param menuId 菜单ID - * @return 结果 + * @param menus 菜单列表 + * @return 下拉树结构列表 */ @Override - public int deleteMenuById(Long menuId) + public List buildMenuTreeSelect(List menus) { - return menuMapper.deleteMenuById(menuId); + List menuTrees = buildMenuTree(menus); + return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); } /** @@ -274,15 +267,16 @@ public class SysMenuServiceImpl implements ISysMenuService } /** - * 查询子菜单数量 + * 是否存在菜单子节点 * - * @param parentId 父级菜单ID + * @param menuId 菜单ID * @return 结果 */ @Override - public int selectCountMenuByParentId(Long parentId) + public boolean hasChildByMenuId(Long menuId) { - return menuMapper.selectCountMenuByParentId(parentId); + int result = menuMapper.hasChildByMenuId(menuId); + return result > 0; } /** @@ -292,9 +286,10 @@ public class SysMenuServiceImpl implements ISysMenuService * @return 结果 */ @Override - public int selectCountRoleMenuByMenuId(Long menuId) + public boolean checkMenuExistRole(Long menuId) { - return roleMenuMapper.selectCountRoleMenuByMenuId(menuId); + int result = roleMenuMapper.checkMenuExistRole(menuId); + return result > 0; } /** @@ -321,6 +316,18 @@ public class SysMenuServiceImpl implements ISysMenuService return menuMapper.updateMenu(menu); } + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) + { + return menuMapper.deleteMenuById(menuId); + } + /** * 校验菜单名称是否唯一 * @@ -339,6 +346,109 @@ public class SysMenuServiceImpl implements ISysMenuService return UserConstants.UNIQUE; } + /** + * 获取路由名称 + * + * @param menu 菜单信息 + * @return 路由名称 + */ + public String getRouteName(SysMenu menu) + { + String routerName = StringUtils.capitalize(menu.getPath()); + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame(menu)) + { + routerName = StringUtils.EMPTY; + } + return routerName; + } + + /** + * 获取路由地址 + * + * @param menu 菜单信息 + * @return 路由地址 + */ + public String getRouterPath(SysMenu menu) + { + String routerPath = menu.getPath(); + // 内链打开外网方式 + if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) + { + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) + && UserConstants.NO_FRAME.equals(menu.getIsFrame())) + { + routerPath = "/" + menu.getPath(); + } + // 非外链并且是一级目录(类型为菜单) + else if (isMenuFrame(menu)) + { + routerPath = "/"; + } + return routerPath; + } + + /** + * 获取组件信息 + * + * @param menu 菜单信息 + * @return 组件信息 + */ + public String getComponent(SysMenu menu) + { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) + { + component = menu.getComponent(); + } + else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) + { + component = UserConstants.INNER_LINK; + } + else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) + { + component = UserConstants.PARENT_VIEW; + } + return component; + } + + /** + * 是否为菜单内部跳转 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isMenuFrame(SysMenu menu) + { + return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && menu.getIsFrame().equals(UserConstants.NO_FRAME); + } + + /** + * 是否为内链组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isInnerLink(SysMenu menu) + { + return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); + } + + /** + * 是否为parent_view组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isParentView(SysMenu menu) + { + return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); + } + /** * 根据父节点的ID获取所有子节点 * @@ -365,8 +475,8 @@ public class SysMenuServiceImpl implements ISysMenuService /** * 递归列表 * - * @param list - * @param t + * @param list 分类表 + * @param t 子节点 */ private void recursionFn(List list, SysMenu t) { @@ -407,4 +517,15 @@ public class SysMenuServiceImpl implements ISysMenuService { return getChildList(list, t).size() > 0; } + + /** + * 内链域名特殊字符替换 + * + * @return 替换后的内链域名 + */ + public String innerLinkReplaceEach(String path) + { + return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":" }, + new String[] { "", "", "", "/", "/" }); + } } 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 index 0f4814d0a..8bebd9c69 100644 --- 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 @@ -3,7 +3,6 @@ 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; @@ -12,7 +11,6 @@ import com.ruoyi.system.service.ISysNoticeService; * 公告 服务层实现 * * @author ruoyi - * @date 2018-06-25 */ @Service public class SysNoticeServiceImpl implements ISysNoticeService @@ -71,12 +69,24 @@ public class SysNoticeServiceImpl implements ISysNoticeService /** * 删除公告对象 * - * @param ids 需要删除的数据ID + * @param noticeId 公告ID * @return 结果 */ @Override - public int deleteNoticeByIds(String ids) + public int deleteNoticeById(Long noticeId) { - return noticeMapper.deleteNoticeByIds(Convert.toStrArray(ids)); + return noticeMapper.deleteNoticeById(noticeId); + } + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(Long[] noticeIds) + { + return noticeMapper.deleteNoticeByIds(noticeIds); } } 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 index c79668b2e..785ee1ed7 100644 --- 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 @@ -3,7 +3,6 @@ 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; @@ -45,13 +44,13 @@ public class SysOperLogServiceImpl implements ISysOperLogService /** * 批量删除系统操作日志 * - * @param ids 需要删除的数据 - * @return + * @param operIds 需要删除的操作日志ID + * @return 结果 */ @Override - public int deleteOperLogByIds(String ids) + public int deleteOperLogByIds(Long[] operIds) { - return operLogMapper.deleteOperLogByIds(Convert.toStrArray(ids)); + return operLogMapper.deleteOperLogByIds(operIds); } /** 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 index b005f29f7..57995eb0a 100644 --- 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 @@ -4,7 +4,6 @@ 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; @@ -49,31 +48,6 @@ public class SysPostServiceImpl implements ISysPostService 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查询岗位信息 * @@ -87,60 +61,15 @@ public class SysPostServiceImpl implements ISysPostService } /** - * 批量删除岗位信息 + * 根据用户ID获取岗位选择框列表 * - * @param ids 需要删除的数据ID - * @return 结果 + * @param userId 用户ID + * @return 选中岗位ID列表 */ @Override - public int deletePostByIds(String ids) + public List selectPostListByUserId(Long userId) { - 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); + return postMapper.selectPostListByUserId(userId); } /** @@ -178,4 +107,72 @@ public class SysPostServiceImpl implements ISysPostService } return UserConstants.UNIQUE; } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int countUserPostById(Long postId) + { + return userPostMapper.countUserPostById(postId); + } + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int deletePostById(Long postId) + { + return postMapper.deletePostById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + @Override + public int deletePostByIds(Long[] postIds) + { + 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); + } } 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 index 5c55b93d4..5e0a02dad 100644 --- 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 @@ -12,9 +12,8 @@ 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.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.SysRoleDept; @@ -59,27 +58,6 @@ public class SysRoleServiceImpl implements ISysRoleService 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查询角色 * @@ -89,7 +67,7 @@ public class SysRoleServiceImpl implements ISysRoleService @Override public List selectRolesByUserId(Long userId) { - List userRoles = roleMapper.selectRolesByUserId(userId); + List userRoles = roleMapper.selectRolePermissionByUserId(userId); List roles = selectRoleAll(); for (SysRole role : roles) { @@ -105,6 +83,27 @@ public class SysRoleServiceImpl implements ISysRoleService return roles; } + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRolePermissionByUserId(Long userId) + { + List perms = roleMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + /** * 查询所有角色 * @@ -116,6 +115,18 @@ public class SysRoleServiceImpl implements ISysRoleService return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); } + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List selectRoleListByUserId(Long userId) + { + return roleMapper.selectRoleListByUserId(userId); + } + /** * 通过角色ID查询角色 * @@ -129,48 +140,85 @@ public class SysRoleServiceImpl implements ISysRoleService } /** - * 通过角色ID删除角色 + * 校验角色名称是否唯一 + * + * @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(SecurityUtils.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 - @Transactional - public boolean deleteRoleById(Long roleId) + public int countUserRoleByRoleId(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); + return userRoleMapper.countUserRoleByRoleId(roleId); } /** @@ -205,6 +253,18 @@ public class SysRoleServiceImpl implements ISysRoleService return insertRoleMenu(role); } + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public int updateRoleStatus(SysRole role) + { + return roleMapper.updateRole(role); + } + /** * 修改数据权限信息 * @@ -272,97 +332,47 @@ public class SysRoleServiceImpl implements ISysRoleService } /** - * 校验角色名称是否唯一 - * - * @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查询角色使用数量 + * 通过角色ID删除角色 * * @param roleId 角色ID * @return 结果 */ @Override - public int countUserRoleByRoleId(Long roleId) + @Transactional + public int deleteRoleById(Long roleId) { - return userRoleMapper.countUserRoleByRoleId(roleId); + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(roleId); + return roleMapper.deleteRoleById(roleId); } /** - * 角色状态修改 + * 批量删除角色信息 * - * @param role 角色信息 + * @param roleIds 需要删除的角色ID * @return 结果 */ @Override - public int changeStatus(SysRole role) + @Transactional + public int deleteRoleByIds(Long[] roleIds) { - return roleMapper.updateRole(role); + 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); } /** @@ -381,13 +391,13 @@ public class SysRoleServiceImpl implements ISysRoleService * 批量取消授权用户角色 * * @param roleId 角色ID - * @param userIds 需要删除的用户数据ID + * @param userIds 需要取消授权的用户数据ID * @return 结果 */ @Override - public int deleteAuthUsers(Long roleId, String userIds) + public int deleteAuthUsers(Long roleId, Long[] userIds) { - return userRoleMapper.deleteUserRoleInfos(roleId, Convert.toLongArray(userIds)); + return userRoleMapper.deleteUserRoleInfos(roleId, userIds); } /** @@ -398,12 +408,11 @@ public class SysRoleServiceImpl implements ISysRoleService * @return 结果 */ @Override - public int insertAuthUsers(Long roleId, String userIds) + public int insertAuthUsers(Long roleId, Long[] userIds) { - Long[] users = Convert.toLongArray(userIds); // 新增用户与角色管理 List list = new ArrayList(); - for (Long userId : users) + for (Long userId : userIds) { SysUserRole ur = new SysUserRole(); ur.setUserId(userId); 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 index a71dc17ad..347837bc5 100644 --- 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 @@ -1,19 +1,9 @@ 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.core.domain.model.LoginUser; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.SysUserOnline; -import com.ruoyi.system.mapper.SysUserOnlineMapper; import com.ruoyi.system.service.ISysUserOnlineService; /** @@ -24,117 +14,83 @@ import com.ruoyi.system.service.ISysUserOnlineService; @Service public class SysUserOnlineServiceImpl implements ISysUserOnlineService { - @Autowired - private SysUserOnlineMapper userOnlineDao; - /** - * 通过会话序号查询信息 + * 通过登录地址查询信息 * - * @param sessionId 会话ID + * @param ipaddr 登录地址 + * @param user 用户信息 * @return 在线用户信息 */ @Override - public SysUserOnline selectOnlineById(String sessionId) + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) { - return userOnlineDao.selectOnlineById(sessionId); + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + return loginUserToUserOnline(user); + } + return null; } /** - * 通过会话序号删除信息 + * 通过用户名称查询信息 * - * @param sessionId 会话ID + * @param userName 用户名称 + * @param user 用户信息 * @return 在线用户信息 */ @Override - public void deleteOnlineById(String sessionId) + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user) { - SysUserOnline userOnline = selectOnlineById(sessionId); - if (StringUtils.isNotNull(userOnline)) + if (StringUtils.equals(userName, user.getUsername())) { - userOnlineDao.deleteOnlineById(sessionId); + return loginUserToUserOnline(user); } + return null; } /** - * 通过会话序号删除信息 + * 通过登录地址/用户名称查询信息 * - * @param sessions 会话ID集合 + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 * @return 在线用户信息 */ @Override - public void batchDeleteOnline(List sessions) + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) { - for (String sessionId : sessions) + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) { - SysUserOnline userOnline = selectOnlineById(sessionId); - if (StringUtils.isNotNull(userOnline)) - { - userOnlineDao.deleteOnlineById(sessionId); - } + return loginUserToUserOnline(user); } + return null; } /** - * 保存会话信息 + * 设置在线用户信息 * - * @param online 会话信息 + * @param user 用户信息 + * @return 在线用户 */ @Override - public void saveOnline(SysUserOnline online) + public SysUserOnline loginUserToUserOnline(LoginUser user) { - 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) + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) { - return; + return null; } - 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); + SysUserOnline sysUserOnline = new SysUserOnline(); + sysUserOnline.setTokenId(user.getToken()); + sysUserOnline.setUserName(user.getUsername()); + sysUserOnline.setIpaddr(user.getIpaddr()); + sysUserOnline.setLoginLocation(user.getLoginLocation()); + sysUserOnline.setBrowser(user.getBrowser()); + sysUserOnline.setOs(user.getOs()); + sysUserOnline.setLoginTime(user.getLoginTime()); + if (StringUtils.isNotNull(user.getUser().getDept())) + { + sysUserOnline.setDeptName(user.getUser().getDept().getDeptName()); + } + return sysUserOnline; } } 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 index 3c7970c85..09f3fb934 100644 --- 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 @@ -14,12 +14,10 @@ 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.SecurityUtils; 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; @@ -52,10 +50,10 @@ public class SysUserServiceImpl implements ISysUserService private SysPostMapper postMapper; @Autowired - private SysUserPostMapper userPostMapper; + private SysUserRoleMapper userRoleMapper; @Autowired - private SysUserRoleMapper userRoleMapper; + private SysUserPostMapper userPostMapper; @Autowired private ISysConfigService configService; @@ -109,33 +107,9 @@ public class SysUserServiceImpl implements ISysUserService * @return 用户对象信息 */ @Override - public SysUser selectUserByLoginName(String userName) + public SysUser selectUserByUserName(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); + return userMapper.selectUserByUserName(userName); } /** @@ -151,199 +125,37 @@ public class SysUserServiceImpl implements ISysUserService } /** - * 通过用户ID查询用户和角色关联 + * 查询用户所属角色组 * - * @param userId 用户ID - * @return 用户和角色关联列表 - */ - @Override - public List selectUserRoleByUserId(Long userId) - { - return userRoleMapper.selectUserRoleByUserId(userId); - } - - /** - * 通过用户ID删除用户 - * - * @param userId 用户ID + * @param userName 用户名 * @return 结果 */ @Override - @Transactional - public int deleteUserById(Long userId) + public String selectUserRoleGroup(String userName) { - // 删除用户与角色关联 - 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) + List list = roleMapper.selectRolesByUserName(userName); + if (CollectionUtils.isEmpty(list)) { - checkUserAllowed(new SysUser(userId)); - checkUserDataScope(userId); + return StringUtils.EMPTY; } - // 删除用户与角色关联 - userRoleMapper.deleteUserRole(userIds); - // 删除用户与岗位关联 - userPostMapper.deleteUserPost(userIds); - return userMapper.deleteUserByIds(userIds); + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); } /** - * 新增保存用户信息 + * 查询用户所属岗位组 * - * @param user 用户信息 + * @param userName 用户名 * @return 结果 */ @Override - @Transactional - public int insertUser(SysUser user) + public String selectUserPostGroup(String userName) { - // 新增用户信息 - 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 = postMapper.selectPostsByUserName(userName); + if (CollectionUtils.isEmpty(list)) { - // 新增用户与角色管理 - 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); - } + return StringUtils.EMPTY; } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); } /** @@ -353,10 +165,10 @@ public class SysUserServiceImpl implements ISysUserService * @return 结果 */ @Override - public boolean checkLoginNameUnique(SysUser user) + public boolean checkUserNameUnique(SysUser user) { Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); - SysUser info = userMapper.checkLoginNameUnique(user.getLoginName()); + SysUser info = userMapper.checkUserNameUnique(user.getUserName()); if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { return UserConstants.NOT_UNIQUE; @@ -422,7 +234,7 @@ public class SysUserServiceImpl implements ISysUserService @Override public void checkUserDataScope(Long userId) { - if (!SysUser.isAdmin(ShiroUtils.getUserId())) + if (!SysUser.isAdmin(SecurityUtils.getUserId())) { SysUser user = new SysUser(); user.setUserId(userId); @@ -435,37 +247,227 @@ public class SysUserServiceImpl implements ISysUserService } /** - * 查询用户所属角色组 + * 新增保存用户信息 * - * @param userId 用户ID + * @param user 用户信息 * @return 结果 */ @Override - public String selectUserRoleGroup(Long userId) + @Transactional + public int insertUser(SysUser user) { - List list = roleMapper.selectRolesByUserId(userId); - if (CollectionUtils.isEmpty(list)) - { - return StringUtils.EMPTY; - } - return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + // 新增用户信息 + int rows = userMapper.insertUser(user); + // 新增用户岗位关联 + insertUserPost(user); + // 新增用户与角色管理 + insertUserRole(user); + return rows; } /** - * 查询用户所属岗位组 + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean registerUser(SysUser user) + { + return userMapper.insertUser(user) > 0; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int updateUser(SysUser user) + { + Long userId = user.getUserId(); + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 + insertUserRole(user); + // 删除用户与岗位关联 + userPostMapper.deleteUserPostByUserId(userId); + // 新增用户与岗位管理 + insertUserPost(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 updateUserStatus(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + @Override + public boolean updateUserAvatar(String userName, String avatar) + { + return userMapper.updateUserAvatar(userName, avatar) > 0; + } + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetPwd(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(String userName, String password) + { + return userMapper.resetUserPwd(userName, password); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRole(SysUser user) + { + this.insertUserRole(user.getUserId(), user.getRoleIds()); + } + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + */ + public void insertUserPost(SysUser user) + { + Long[] posts = user.getPostIds(); + if (StringUtils.isNotEmpty(posts)) + { + // 新增用户与岗位管理 + List list = new ArrayList(posts.length); + for (Long postId : posts) + { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + list.add(up); + } + userPostMapper.batchUserPost(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserRole(Long userId, Long[] roleIds) + { + if (StringUtils.isNotEmpty(roleIds)) + { + // 新增用户与角色管理 + List list = new ArrayList(roleIds.length); + for (Long roleId : roleIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + userRoleMapper.batchUserRole(list); + } + } + + /** + * 通过用户ID删除用户 * * @param userId 用户ID * @return 结果 */ @Override - public String selectUserPostGroup(Long userId) + @Transactional + public int deleteUserById(Long userId) { - List list = postMapper.selectPostsByUserId(userId); - if (CollectionUtils.isEmpty(list)) + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 删除用户与岗位表 + userPostMapper.deleteUserPostByUserId(userId); + return userMapper.deleteUserById(userId); + } + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserByIds(Long[] userIds) + { + for (Long userId : userIds) { - return StringUtils.EMPTY; + checkUserAllowed(new SysUser(userId)); + checkUserDataScope(userId); } - return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + // 删除用户与角色关联 + userRoleMapper.deleteUserRole(userIds); + // 删除用户与岗位关联 + userPostMapper.deleteUserPost(userIds); + return userMapper.deleteUserByIds(userIds); } /** @@ -493,15 +495,15 @@ public class SysUserServiceImpl implements ISysUserService try { // 验证是否存在这个用户 - SysUser u = userMapper.selectUserByLoginName(user.getLoginName()); + SysUser u = userMapper.selectUserByUserName(user.getUserName()); if (StringUtils.isNull(u)) { BeanValidators.validateWithException(validator, user); - user.setPassword(Md5Utils.hash(user.getLoginName() + password)); + user.setPassword(SecurityUtils.encryptPassword(password)); user.setCreateBy(operName); userMapper.insertUser(user); successNum++; - successMsg.append("
    " + successNum + "、账号 " + user.getLoginName() + " 导入成功"); + successMsg.append("
    " + successNum + "、账号 " + user.getUserName() + " 导入成功"); } else if (isUpdateSupport) { @@ -512,18 +514,18 @@ public class SysUserServiceImpl implements ISysUserService user.setUpdateBy(operName); userMapper.updateUser(user); successNum++; - successMsg.append("
    " + successNum + "、账号 " + user.getLoginName() + " 更新成功"); + successMsg.append("
    " + successNum + "、账号 " + user.getUserName() + " 更新成功"); } else { failureNum++; - failureMsg.append("
    " + failureNum + "、账号 " + user.getLoginName() + " 已存在"); + failureMsg.append("
    " + failureNum + "、账号 " + user.getUserName() + " 已存在"); } } catch (Exception e) { failureNum++; - String msg = "
    " + failureNum + "、账号 " + user.getLoginName() + " 导入失败:"; + String msg = "
    " + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; failureMsg.append(msg + e.getMessage()); log.error(msg, e); } @@ -539,16 +541,4 @@ public class SysUserServiceImpl implements ISysUserService } 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 index 5ea755a69..c21169278 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -60,10 +60,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + + where config_id = #{configId} + + - select concat(d.dept_id, d.dept_name) as dept_name - from sys_dept d - left join sys_role_dept rd on d.dept_id = rd.dept_id - where d.del_flag = '0' and rd.role_id = #{roleId} - order by d.parent_id, d.order_num - - - - + select d.dept_id + from sys_dept d + left join sys_role_dept rd on d.dept_id = rd.dept_id + where rd.role_id = #{roleId} + + and d.dept_id not in (select d.parent_id from sys_dept d inner join sys_role_dept rd on d.dept_id = rd.dept_id and rd.role_id = #{roleId}) + + order by d.parent_id, d.order_num - - - - - - select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, (select dept_name from sys_dept where dept_id = d.parent_id) parent_name from sys_dept d where d.dept_id = #{deptId} - + + + + + @@ -86,7 +82,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select count(*) from sys_dept where status = 0 and del_flag = '0' and find_in_set(#{deptId}, ancestors) - + + + insert into sys_dept( dept_id, parent_id, @@ -142,17 +143,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" separator="," open="(" close=")"> #{item.deptId} - - - - update sys_dept set del_flag = '2' where dept_id = #{deptId} - - + + update sys_dept set status = '0' where dept_id in #{deptId} + + + update sys_dept set del_flag = '2' where dept_id = #{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 index aebd9b4f6..75d80a157 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -38,6 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND status = #{status} + order by dict_sort asc - select info_id,login_name,ipaddr,login_location,browser,os,status,msg,login_time from sys_logininfor + select info_id, user_name, ipaddr, login_location, browser, os, status, msg, login_time from sys_logininfor AND ipaddr like concat('%', #{ipaddr}, '%') @@ -30,8 +30,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND status = #{status} - - AND login_name like concat('%', #{loginName}, '%') + + AND user_name like concat('%', #{userName}, '%') AND login_time >= #{params.beginTime} @@ -40,9 +40,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND login_time <= #{params.endTime} + order by info_id desc - + delete from sys_logininfor where info_id in #{infoId} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml index be4ef1a43..e90f6baf0 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -10,11 +10,14 @@ - - + + + + + - + @@ -25,67 +28,11 @@ - 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 + select menu_id, menu_name, parent_id, order_num, path, component, `query`, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time from sys_menu - - - - - - - - - - - - - - - - @@ -94,60 +41,114 @@ AND visible = #{visible} + + AND status = #{status} + order by parent_id, order_num - + + + + + + + + + - - delete from sys_menu where menu_id = #{menuId} or parent_id = #{menuId} - - + + + + - - + select count(1) from sys_menu where parent_id = #{menuId} - + - + update sys_menu menu_name = #{menuName}, - parent_id = #{parentId}, + parent_id = #{parentId}, order_num = #{orderNum}, - url = #{url}, - target = #{target}, + path = #{path}, + component = #{component}, + `query` = #{query}, + is_frame = #{isFrame}, + is_cache = #{isCache}, menu_type = #{menuType}, visible = #{visible}, - is_refresh = #{isRefresh}, + status = #{status}, perms = #{perms}, icon = #{icon}, - remark = #{remark}, + remark = #{remark}, update_by = #{updateBy}, update_time = sysdate() @@ -160,11 +161,14 @@ parent_id, menu_name, order_num, - url, - target, + path, + component, + `query`, + is_frame, + is_cache, menu_type, visible, - is_refresh, + status, perms, icon, remark, @@ -175,11 +179,14 @@ #{parentId}, #{menuName}, #{orderNum}, - #{url}, - #{target}, + #{path}, + #{component}, + #{query}, + #{isFrame}, + #{isCache}, #{menuType}, #{visible}, - #{isRefresh}, + #{status}, #{perms}, #{icon}, #{remark}, @@ -187,5 +194,9 @@ sysdate() ) + + + delete from sys_menu where menu_id = #{menuId} + \ 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 index c2037c9ee..6915a1482 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -75,7 +75,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where notice_id = #{noticeId} - + + delete from sys_notice where notice_id = #{noticeId} + + + delete from sys_notice where notice_id in #{noticeId} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml index ed050e86c..0c3fb691f 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -65,9 +65,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND oper_time <= #{params.endTime} + order by oper_id desc - + delete from sys_oper_log where oper_id in #{operId} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml index 7aaa5a6a0..faefb2f07 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -41,19 +41,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - + + + + - - delete from sys_post where post_id in - - #{postId} - - - - + update sys_post post_code = #{postCode}, post_name = #{postName}, - post_sort = #{postSort}, + post_sort = #{postSort}, status = #{status}, remark = #{remark}, update_by = #{updateBy}, @@ -90,7 +91,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" post_id, post_code, post_name, - post_sort, + post_sort, status, remark, create_by, @@ -99,12 +100,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{postId}, #{postCode}, #{postName}, - #{postSort}, + #{postSort}, #{status}, #{remark}, #{createBy}, sysdate() ) + + + delete from sys_post where post_id = #{postId} + + + + delete from sys_post where post_id in + + #{postId} + + \ 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 index 7d2565042..ab601e4b5 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -5,22 +5,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - - - - - - - - - - + + + + + + + + + + + + + + - - select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, 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 @@ -28,13 +30,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 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 - - - + where r.del_flag = '0' AND r.role_id = #{roleId} @@ -48,9 +45,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND r.role_key like concat('%', #{roleKey}, '%') - - AND r.data_scope = #{dataScope} - and date_format(r.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') @@ -59,16 +53,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ${params.dataScope} + order by r.role_sort - - + WHERE r.del_flag = '0' and ur.user_id = #{userId} + + + + + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + menu_check_strictly, + dept_check_strictly, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{menuCheckStrictly}, + #{deptCheckStrictly}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + menu_check_strictly = #{menuCheckStrictly}, + dept_check_strictly = #{deptCheckStrictly}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where role_id = #{roleId} + + update sys_role set del_flag = '2' where role_id = #{roleId} @@ -92,43 +149,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - 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 index f509d207e..e75bb1744 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -8,15 +8,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + delete from sys_role_menu where role_id=#{roleId} - - delete from sys_role_menu where role_id in diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index c7bfaf69f..eda0be227 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -4,70 +4,67 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - 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_by, u.create_time, u.update_by, u.update_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 + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, 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 - - + select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u left join sys_dept d on u.dept_id = d.dept_id where u.del_flag = '0' AND u.user_id = #{userId} - - AND u.login_name like concat('%', #{loginName}, '%') + + AND u.user_name like concat('%', #{userName}, '%') AND u.status = #{status} @@ -82,21 +79,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') - AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE FIND_IN_SET (#{deptId},ancestors) )) + AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) )) ${params.dataScope} - - where u.login_name = #{userName} and u.del_flag = '0' - - - - - - - - - - - + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + user_name = #{userName}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set status = #{status} where user_id = #{userId} + + + + update sys_user set avatar = #{avatar} where user_name = #{userName} + + + + update sys_user set password = #{password} where user_name = #{userName} + + update sys_user set del_flag = '2' where user_id = #{userId} @@ -165,67 +217,5 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{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 deleted file mode 100644 index 92f684358..000000000 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - 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/SysUserRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml index b091ba757..95e07adb1 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -8,17 +8,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - delete from sys_user_role where user_id = #{userId} + delete from sys_user_role where user_id=#{userId} diff --git a/ruoyi-ui/.editorconfig b/ruoyi-ui/.editorconfig new file mode 100644 index 000000000..7034f9bf3 --- /dev/null +++ b/ruoyi-ui/.editorconfig @@ -0,0 +1,22 @@ +# 告诉EditorConfig插件,这是根文件,不用继续往上查找 +root = true + +# 匹配全部文件 +[*] +# 设置字符集 +charset = utf-8 +# 缩进风格,可选space、tab +indent_style = space +# 缩进的空格数 +indent_size = 2 +# 结尾换行符,可选lf、cr、crlf +end_of_line = lf +# 在文件结尾插入新行 +insert_final_newline = true +# 删除一行中的前后空格 +trim_trailing_whitespace = true + +# 匹配md结尾的文件 +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development new file mode 100644 index 000000000..302ecd1ab --- /dev/null +++ b/ruoyi-ui/.env.development @@ -0,0 +1,11 @@ +# 页面标题 +VUE_APP_TITLE = 若依管理系统 + +# 开发环境配置 +ENV = 'development' + +# 若依管理系统/开发环境 +VUE_APP_BASE_API = '/dev-api' + +# 路由懒加载 +VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production new file mode 100644 index 000000000..b4893b0d9 --- /dev/null +++ b/ruoyi-ui/.env.production @@ -0,0 +1,8 @@ +# 页面标题 +VUE_APP_TITLE = 若依管理系统 + +# 生产环境配置 +ENV = 'production' + +# 若依管理系统/生产环境 +VUE_APP_BASE_API = '/prod-api' diff --git a/ruoyi-ui/.env.staging b/ruoyi-ui/.env.staging new file mode 100644 index 000000000..361859f63 --- /dev/null +++ b/ruoyi-ui/.env.staging @@ -0,0 +1,10 @@ +# 页面标题 +VUE_APP_TITLE = 若依管理系统 + +NODE_ENV = production + +# 测试环境配置 +ENV = 'staging' + +# 若依管理系统/测试环境 +VUE_APP_BASE_API = '/stage-api' diff --git a/ruoyi-ui/.eslintignore b/ruoyi-ui/.eslintignore new file mode 100644 index 000000000..89be6f659 --- /dev/null +++ b/ruoyi-ui/.eslintignore @@ -0,0 +1,10 @@ +# 忽略build目录下类型为js的文件的语法检查 +build/*.js +# 忽略src/assets目录下文件的语法检查 +src/assets +# 忽略public目录下文件的语法检查 +public +# 忽略当前目录下为js的文件的语法检查 +*.js +# 忽略当前目录下为vue的文件的语法检查 +*.vue \ No newline at end of file diff --git a/ruoyi-ui/.eslintrc.js b/ruoyi-ui/.eslintrc.js new file mode 100644 index 000000000..82bbdeea6 --- /dev/null +++ b/ruoyi-ui/.eslintrc.js @@ -0,0 +1,199 @@ +// ESlint 检查配置 +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint', + sourceType: 'module' + }, + env: { + browser: true, + node: true, + es6: true, + }, + extends: ['plugin:vue/recommended', 'eslint:recommended'], + + // add your custom rules here + //it is base on https://github.com/vuejs/eslint-config-vue + rules: { + "vue/max-attributes-per-line": [2, { + "singleline": 10, + "multiline": { + "max": 1, + "allowFirstLine": false + } + }], + "vue/singleline-html-element-content-newline": "off", + "vue/multiline-html-element-content-newline":"off", + "vue/name-property-casing": ["error", "PascalCase"], + "vue/no-v-html": "off", + 'accessor-pairs': 2, + 'arrow-spacing': [2, { + 'before': true, + 'after': true + }], + 'block-spacing': [2, 'always'], + 'brace-style': [2, '1tbs', { + 'allowSingleLine': true + }], + 'camelcase': [0, { + 'properties': 'always' + }], + 'comma-dangle': [2, 'never'], + 'comma-spacing': [2, { + 'before': false, + 'after': true + }], + 'comma-style': [2, 'last'], + 'constructor-super': 2, + 'curly': [2, 'multi-line'], + 'dot-location': [2, 'property'], + 'eol-last': 2, + 'eqeqeq': ["error", "always", {"null": "ignore"}], + 'generator-star-spacing': [2, { + 'before': true, + 'after': true + }], + 'handle-callback-err': [2, '^(err|error)$'], + 'indent': [2, 2, { + 'SwitchCase': 1 + }], + 'jsx-quotes': [2, 'prefer-single'], + 'key-spacing': [2, { + 'beforeColon': false, + 'afterColon': true + }], + 'keyword-spacing': [2, { + 'before': true, + 'after': true + }], + 'new-cap': [2, { + 'newIsCap': true, + 'capIsNew': false + }], + 'new-parens': 2, + 'no-array-constructor': 2, + 'no-caller': 2, + 'no-console': 'off', + 'no-class-assign': 2, + 'no-cond-assign': 2, + 'no-const-assign': 2, + 'no-control-regex': 0, + 'no-delete-var': 2, + 'no-dupe-args': 2, + 'no-dupe-class-members': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-empty-character-class': 2, + 'no-empty-pattern': 2, + 'no-eval': 2, + 'no-ex-assign': 2, + 'no-extend-native': 2, + 'no-extra-bind': 2, + 'no-extra-boolean-cast': 2, + 'no-extra-parens': [2, 'functions'], + 'no-fallthrough': 2, + 'no-floating-decimal': 2, + 'no-func-assign': 2, + 'no-implied-eval': 2, + 'no-inner-declarations': [2, 'functions'], + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 2, + 'no-iterator': 2, + 'no-label-var': 2, + 'no-labels': [2, { + 'allowLoop': false, + 'allowSwitch': false + }], + 'no-lone-blocks': 2, + 'no-mixed-spaces-and-tabs': 2, + 'no-multi-spaces': 2, + 'no-multi-str': 2, + 'no-multiple-empty-lines': [2, { + 'max': 1 + }], + 'no-native-reassign': 2, + 'no-negated-in-lhs': 2, + 'no-new-object': 2, + 'no-new-require': 2, + 'no-new-symbol': 2, + 'no-new-wrappers': 2, + 'no-obj-calls': 2, + 'no-octal': 2, + 'no-octal-escape': 2, + 'no-path-concat': 2, + 'no-proto': 2, + 'no-redeclare': 2, + 'no-regex-spaces': 2, + 'no-return-assign': [2, 'except-parens'], + 'no-self-assign': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-shadow-restricted-names': 2, + 'no-spaced-func': 2, + 'no-sparse-arrays': 2, + 'no-this-before-super': 2, + 'no-throw-literal': 2, + 'no-trailing-spaces': 2, + 'no-undef': 2, + 'no-undef-init': 2, + 'no-unexpected-multiline': 2, + 'no-unmodified-loop-condition': 2, + 'no-unneeded-ternary': [2, { + 'defaultAssignment': false + }], + 'no-unreachable': 2, + 'no-unsafe-finally': 2, + 'no-unused-vars': [2, { + 'vars': 'all', + 'args': 'none' + }], + 'no-useless-call': 2, + 'no-useless-computed-key': 2, + 'no-useless-constructor': 2, + 'no-useless-escape': 0, + 'no-whitespace-before-property': 2, + 'no-with': 2, + 'one-var': [2, { + 'initialized': 'never' + }], + 'operator-linebreak': [2, 'after', { + 'overrides': { + '?': 'before', + ':': 'before' + } + }], + 'padded-blocks': [2, 'never'], + 'quotes': [2, 'single', { + 'avoidEscape': true, + 'allowTemplateLiterals': true + }], + 'semi': [2, 'never'], + 'semi-spacing': [2, { + 'before': false, + 'after': true + }], + 'space-before-blocks': [2, 'always'], + 'space-before-function-paren': [2, 'never'], + 'space-in-parens': [2, 'never'], + 'space-infix-ops': 2, + 'space-unary-ops': [2, { + 'words': true, + 'nonwords': false + }], + 'spaced-comment': [2, 'always', { + 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] + }], + 'template-curly-spacing': [2, 'never'], + 'use-isnan': 2, + 'valid-typeof': 2, + 'wrap-iife': [2, 'any'], + 'yield-star-spacing': [2, 'both'], + 'yoda': [2, 'never'], + 'prefer-const': 2, + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, + 'object-curly-spacing': [2, 'always', { + objectsInObjects: false + }], + 'array-bracket-spacing': [2, 'never'] + } +} diff --git a/ruoyi-ui/README.md b/ruoyi-ui/README.md new file mode 100644 index 000000000..00c0ab84f --- /dev/null +++ b/ruoyi-ui/README.md @@ -0,0 +1,30 @@ +## 开发 + +```bash +# 克隆项目 +git clone https://gitee.com/y_project/RuoYi-Vue + +# 进入项目目录 +cd ruoyi-ui + +# 安装依赖 +npm install + +# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 +npm install --registry=https://registry.npmmirror.com + +# 启动服务 +npm run dev +``` + +浏览器访问 http://localhost:80 + +## 发布 + +```bash +# 构建测试环境 +npm run build:stage + +# 构建生产环境 +npm run build:prod +``` \ No newline at end of file diff --git a/ruoyi-ui/babel.config.js b/ruoyi-ui/babel.config.js new file mode 100644 index 000000000..c8267b2dd --- /dev/null +++ b/ruoyi-ui/babel.config.js @@ -0,0 +1,13 @@ +module.exports = { + presets: [ + // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app + '@vue/cli-plugin-babel/preset' + ], + 'env': { + 'development': { + // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). + // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. + 'plugins': ['dynamic-import-node'] + } + } +} \ No newline at end of file diff --git a/ruoyi-ui/bin/build.bat b/ruoyi-ui/bin/build.bat new file mode 100644 index 000000000..dda590d22 --- /dev/null +++ b/ruoyi-ui/bin/build.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅdistļ +echo. + +%~d0 +cd %~dp0 + +cd .. +npm run build:prod + +pause \ No newline at end of file diff --git a/ruoyi-ui/bin/package.bat b/ruoyi-ui/bin/package.bat new file mode 100644 index 000000000..0e5bc0fb5 --- /dev/null +++ b/ruoyi-ui/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] װWeḅnode_modulesļ +echo. + +%~d0 +cd %~dp0 + +cd .. +npm install --registry=https://registry.npmmirror.com + +pause \ No newline at end of file diff --git a/ruoyi-ui/bin/run-web.bat b/ruoyi-ui/bin/run-web.bat new file mode 100644 index 000000000..d30deae79 --- /dev/null +++ b/ruoyi-ui/bin/run-web.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] ʹ Vue CLI Web ̡ +echo. + +%~d0 +cd %~dp0 + +cd .. +npm run dev + +pause \ No newline at end of file diff --git a/ruoyi-ui/build/index.js b/ruoyi-ui/build/index.js new file mode 100644 index 000000000..0c57de2aa --- /dev/null +++ b/ruoyi-ui/build/index.js @@ -0,0 +1,35 @@ +const { run } = require('runjs') +const chalk = require('chalk') +const config = require('../vue.config.js') +const rawArgv = process.argv.slice(2) +const args = rawArgv.join(' ') + +if (process.env.npm_config_preview || rawArgv.includes('--preview')) { + const report = rawArgv.includes('--report') + + run(`vue-cli-service build ${args}`) + + const port = 9526 + const publicPath = config.publicPath + + var connect = require('connect') + var serveStatic = require('serve-static') + const app = connect() + + app.use( + publicPath, + serveStatic('./dist', { + index: ['index.html', '/'] + }) + ) + + app.listen(port, function () { + console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) + if (report) { + console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) + } + + }) +} else { + run(`vue-cli-service build ${args}`) +} diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json new file mode 100644 index 000000000..2e4b91e9d --- /dev/null +++ b/ruoyi-ui/package.json @@ -0,0 +1,90 @@ +{ + "name": "ruoyi", + "version": "3.8.7", + "description": "若依管理系统", + "author": "若依", + "license": "MIT", + "scripts": { + "dev": "vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "preview": "node build/index.js --preview", + "lint": "eslint --ext .js,.vue src" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "src/**/*.{js,vue}": [ + "eslint --fix", + "git add" + ] + }, + "keywords": [ + "vue", + "admin", + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" + ], + "repository": { + "type": "git", + "url": "https://gitee.com/y_project/RuoYi-Vue.git" + }, + "dependencies": { + "@riophae/vue-treeselect": "0.4.0", + "axios": "0.24.0", + "clipboard": "2.0.8", + "core-js": "3.25.3", + "echarts": "5.4.0", + "element-ui": "2.15.14", + "file-saver": "2.0.5", + "fuse.js": "6.4.3", + "highlight.js": "9.18.5", + "js-beautify": "1.13.0", + "js-cookie": "3.0.1", + "jsencrypt": "3.0.0-rc.1", + "nprogress": "0.2.0", + "quill": "1.3.7", + "screenfull": "5.0.2", + "sortablejs": "1.10.2", + "vue": "2.6.12", + "vue-count-to": "1.0.13", + "vue-cropper": "0.5.5", + "vue-meta": "2.4.0", + "vue-router": "3.4.9", + "vuedraggable": "2.24.3", + "vuex": "3.6.0" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "4.4.6", + "@vue/cli-plugin-eslint": "4.4.6", + "@vue/cli-service": "4.4.6", + "babel-eslint": "10.1.0", + "babel-plugin-dynamic-import-node": "2.3.3", + "chalk": "4.1.0", + "compression-webpack-plugin": "6.1.2", + "connect": "3.6.6", + "eslint": "7.15.0", + "eslint-plugin-vue": "7.2.0", + "lint-staged": "10.5.3", + "runjs": "4.4.2", + "sass": "1.32.13", + "sass-loader": "10.1.1", + "script-ext-html-webpack-plugin": "2.1.5", + "svg-sprite-loader": "5.1.1", + "vue-template-compiler": "2.6.12" + }, + "engines": { + "node": ">=8.9", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions" + ] +} diff --git a/ruoyi-ui/public/favicon.ico b/ruoyi-ui/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e26376026420542212ed58d90d0ed34f554fa4ae GIT binary patch literal 5663 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 - - - - 请升级您的浏览器 - - - - - - -

    请升级您的浏览器,以便我们更好的为您提供服务!

    -

    您正在使用 Internet Explorer 的早期版本(IE9以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。

    -
    -

    请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束

    -

    自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明

    -
    -

    您可以选择更先进的浏览器

    -

    推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。

    - -
    - + + + + + + 请升级您的浏览器 + + + + + + +

    请升级您的浏览器,以便我们更好的为您提供服务!

    +

    您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。

    +
    +

    请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束

    +

    自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明

    +
    +

    您可以选择更先进的浏览器

    +

    推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。

    + +
    + \ No newline at end of file diff --git a/ruoyi-ui/public/index.html b/ruoyi-ui/public/index.html new file mode 100644 index 000000000..925455caf --- /dev/null +++ b/ruoyi-ui/public/index.html @@ -0,0 +1,208 @@ + + + + + + + + + <%= webpackConfig.name %> + + + + +
    +
    +
    +
    +
    +
    正在加载系统资源,请耐心等待
    +
    +
    + + diff --git a/ruoyi-ui/public/robots.txt b/ruoyi-ui/public/robots.txt new file mode 100644 index 000000000..77470cb39 --- /dev/null +++ b/ruoyi-ui/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/ruoyi-ui/src/App.vue b/ruoyi-ui/src/App.vue new file mode 100644 index 000000000..b92ea3792 --- /dev/null +++ b/ruoyi-ui/src/App.vue @@ -0,0 +1,28 @@ + + + + diff --git a/ruoyi-ui/src/api/login.js b/ruoyi-ui/src/api/login.js new file mode 100644 index 000000000..7b7388fde --- /dev/null +++ b/ruoyi-ui/src/api/login.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 登录方法 +export function login(username, password, code, uuid) { + const data = { + username, + password, + code, + uuid + } + return request({ + url: '/login', + headers: { + isToken: false, + repeatSubmit: false + }, + method: 'post', + data: data + }) +} + +// 注册方法 +export function register(data) { + return request({ + url: '/register', + headers: { + isToken: false + }, + method: 'post', + data: data + }) +} + +// 获取用户详细信息 +export function getInfo() { + return request({ + url: '/getInfo', + method: 'get' + }) +} + +// 退出方法 +export function logout() { + return request({ + url: '/logout', + method: 'post' + }) +} + +// 获取验证码 +export function getCodeImg() { + return request({ + url: '/captchaImage', + headers: { + isToken: false + }, + method: 'get', + timeout: 20000 + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/menu.js b/ruoyi-ui/src/api/menu.js new file mode 100644 index 000000000..faef101c4 --- /dev/null +++ b/ruoyi-ui/src/api/menu.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取路由 +export const getRouters = () => { + return request({ + url: '/getRouters', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/monitor/cache.js b/ruoyi-ui/src/api/monitor/cache.js new file mode 100644 index 000000000..72c5f6a3e --- /dev/null +++ b/ruoyi-ui/src/api/monitor/cache.js @@ -0,0 +1,57 @@ +import request from '@/utils/request' + +// 查询缓存详细 +export function getCache() { + return request({ + url: '/monitor/cache', + method: 'get' + }) +} + +// 查询缓存名称列表 +export function listCacheName() { + return request({ + url: '/monitor/cache/getNames', + method: 'get' + }) +} + +// 查询缓存键名列表 +export function listCacheKey(cacheName) { + return request({ + url: '/monitor/cache/getKeys/' + cacheName, + method: 'get' + }) +} + +// 查询缓存内容 +export function getCacheValue(cacheName, cacheKey) { + return request({ + url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, + method: 'get' + }) +} + +// 清理指定名称缓存 +export function clearCacheName(cacheName) { + return request({ + url: '/monitor/cache/clearCacheName/' + cacheName, + method: 'delete' + }) +} + +// 清理指定键名缓存 +export function clearCacheKey(cacheKey) { + return request({ + url: '/monitor/cache/clearCacheKey/' + cacheKey, + method: 'delete' + }) +} + +// 清理全部缓存 +export function clearCacheAll() { + return request({ + url: '/monitor/cache/clearCacheAll', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/job.js b/ruoyi-ui/src/api/monitor/job.js new file mode 100644 index 000000000..38155693a --- /dev/null +++ b/ruoyi-ui/src/api/monitor/job.js @@ -0,0 +1,71 @@ +import request from '@/utils/request' + +// 查询定时任务调度列表 +export function listJob(query) { + return request({ + url: '/monitor/job/list', + method: 'get', + params: query + }) +} + +// 查询定时任务调度详细 +export function getJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'get' + }) +} + +// 新增定时任务调度 +export function addJob(data) { + return request({ + url: '/monitor/job', + method: 'post', + data: data + }) +} + +// 修改定时任务调度 +export function updateJob(data) { + return request({ + url: '/monitor/job', + method: 'put', + data: data + }) +} + +// 删除定时任务调度 +export function delJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'delete' + }) +} + +// 任务状态修改 +export function changeJobStatus(jobId, status) { + const data = { + jobId, + status + } + return request({ + url: '/monitor/job/changeStatus', + method: 'put', + data: data + }) +} + + +// 定时任务立即执行一次 +export function runJob(jobId, jobGroup) { + const data = { + jobId, + jobGroup + } + return request({ + url: '/monitor/job/run', + method: 'put', + data: data + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/monitor/jobLog.js b/ruoyi-ui/src/api/monitor/jobLog.js new file mode 100644 index 000000000..6e0be6166 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/jobLog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询调度日志列表 +export function listJobLog(query) { + return request({ + url: '/monitor/jobLog/list', + method: 'get', + params: query + }) +} + +// 删除调度日志 +export function delJobLog(jobLogId) { + return request({ + url: '/monitor/jobLog/' + jobLogId, + method: 'delete' + }) +} + +// 清空调度日志 +export function cleanJobLog() { + return request({ + url: '/monitor/jobLog/clean', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/logininfor.js b/ruoyi-ui/src/api/monitor/logininfor.js new file mode 100644 index 000000000..4d112b78a --- /dev/null +++ b/ruoyi-ui/src/api/monitor/logininfor.js @@ -0,0 +1,34 @@ +import request from '@/utils/request' + +// 查询登录日志列表 +export function list(query) { + return request({ + url: '/monitor/logininfor/list', + method: 'get', + params: query + }) +} + +// 删除登录日志 +export function delLogininfor(infoId) { + return request({ + url: '/monitor/logininfor/' + infoId, + method: 'delete' + }) +} + +// 解锁用户登录状态 +export function unlockLogininfor(userName) { + return request({ + url: '/monitor/logininfor/unlock/' + userName, + method: 'get' + }) +} + +// 清空登录日志 +export function cleanLogininfor() { + return request({ + url: '/monitor/logininfor/clean', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/online.js b/ruoyi-ui/src/api/monitor/online.js new file mode 100644 index 000000000..bd2213780 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/online.js @@ -0,0 +1,18 @@ +import request from '@/utils/request' + +// 查询在线用户列表 +export function list(query) { + return request({ + url: '/monitor/online/list', + method: 'get', + params: query + }) +} + +// 强退用户 +export function forceLogout(tokenId) { + return request({ + url: '/monitor/online/' + tokenId, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/operlog.js b/ruoyi-ui/src/api/monitor/operlog.js new file mode 100644 index 000000000..a04bca840 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/operlog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询操作日志列表 +export function list(query) { + return request({ + url: '/monitor/operlog/list', + method: 'get', + params: query + }) +} + +// 删除操作日志 +export function delOperlog(operId) { + return request({ + url: '/monitor/operlog/' + operId, + method: 'delete' + }) +} + +// 清空操作日志 +export function cleanOperlog() { + return request({ + url: '/monitor/operlog/clean', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/server.js b/ruoyi-ui/src/api/monitor/server.js new file mode 100644 index 000000000..e1f9ca214 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/server.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取服务信息 +export function getServer() { + return request({ + url: '/monitor/server', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/config.js b/ruoyi-ui/src/api/system/config.js new file mode 100644 index 000000000..a404d8254 --- /dev/null +++ b/ruoyi-ui/src/api/system/config.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询参数列表 +export function listConfig(query) { + return request({ + url: '/system/config/list', + method: 'get', + params: query + }) +} + +// 查询参数详细 +export function getConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'get' + }) +} + +// 根据参数键名查询参数值 +export function getConfigKey(configKey) { + return request({ + url: '/system/config/configKey/' + configKey, + method: 'get' + }) +} + +// 新增参数配置 +export function addConfig(data) { + return request({ + url: '/system/config', + method: 'post', + data: data + }) +} + +// 修改参数配置 +export function updateConfig(data) { + return request({ + url: '/system/config', + method: 'put', + data: data + }) +} + +// 删除参数配置 +export function delConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'delete' + }) +} + +// 刷新参数缓存 +export function refreshCache() { + return request({ + url: '/system/config/refreshCache', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/system/dept.js b/ruoyi-ui/src/api/system/dept.js new file mode 100644 index 000000000..fc943cd41 --- /dev/null +++ b/ruoyi-ui/src/api/system/dept.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询部门列表 +export function listDept(query) { + return request({ + url: '/system/dept/list', + method: 'get', + params: query + }) +} + +// 查询部门列表(排除节点) +export function listDeptExcludeChild(deptId) { + return request({ + url: '/system/dept/list/exclude/' + deptId, + method: 'get' + }) +} + +// 查询部门详细 +export function getDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'get' + }) +} + +// 新增部门 +export function addDept(data) { + return request({ + url: '/system/dept', + method: 'post', + data: data + }) +} + +// 修改部门 +export function updateDept(data) { + return request({ + url: '/system/dept', + method: 'put', + data: data + }) +} + +// 删除部门 +export function delDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/dict/data.js b/ruoyi-ui/src/api/system/dict/data.js new file mode 100644 index 000000000..6c9eb79b4 --- /dev/null +++ b/ruoyi-ui/src/api/system/dict/data.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询字典数据列表 +export function listData(query) { + return request({ + url: '/system/dict/data/list', + method: 'get', + params: query + }) +} + +// 查询字典数据详细 +export function getData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'get' + }) +} + +// 根据字典类型查询字典数据信息 +export function getDicts(dictType) { + return request({ + url: '/system/dict/data/type/' + dictType, + method: 'get' + }) +} + +// 新增字典数据 +export function addData(data) { + return request({ + url: '/system/dict/data', + method: 'post', + data: data + }) +} + +// 修改字典数据 +export function updateData(data) { + return request({ + url: '/system/dict/data', + method: 'put', + data: data + }) +} + +// 删除字典数据 +export function delData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/system/dict/type.js b/ruoyi-ui/src/api/system/dict/type.js new file mode 100644 index 000000000..a7a6e01fc --- /dev/null +++ b/ruoyi-ui/src/api/system/dict/type.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询字典类型列表 +export function listType(query) { + return request({ + url: '/system/dict/type/list', + method: 'get', + params: query + }) +} + +// 查询字典类型详细 +export function getType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'get' + }) +} + +// 新增字典类型 +export function addType(data) { + return request({ + url: '/system/dict/type', + method: 'post', + data: data + }) +} + +// 修改字典类型 +export function updateType(data) { + return request({ + url: '/system/dict/type', + method: 'put', + data: data + }) +} + +// 删除字典类型 +export function delType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'delete' + }) +} + +// 刷新字典缓存 +export function refreshCache() { + return request({ + url: '/system/dict/type/refreshCache', + method: 'delete' + }) +} + +// 获取字典选择框列表 +export function optionselect() { + return request({ + url: '/system/dict/type/optionselect', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/menu.js b/ruoyi-ui/src/api/system/menu.js new file mode 100644 index 000000000..f6415c656 --- /dev/null +++ b/ruoyi-ui/src/api/system/menu.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询菜单列表 +export function listMenu(query) { + return request({ + url: '/system/menu/list', + method: 'get', + params: query + }) +} + +// 查询菜单详细 +export function getMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'get' + }) +} + +// 查询菜单下拉树结构 +export function treeselect() { + return request({ + url: '/system/menu/treeselect', + method: 'get' + }) +} + +// 根据角色ID查询菜单下拉树结构 +export function roleMenuTreeselect(roleId) { + return request({ + url: '/system/menu/roleMenuTreeselect/' + roleId, + method: 'get' + }) +} + +// 新增菜单 +export function addMenu(data) { + return request({ + url: '/system/menu', + method: 'post', + data: data + }) +} + +// 修改菜单 +export function updateMenu(data) { + return request({ + url: '/system/menu', + method: 'put', + data: data + }) +} + +// 删除菜单 +export function delMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/notice.js b/ruoyi-ui/src/api/system/notice.js new file mode 100644 index 000000000..c274ea5ba --- /dev/null +++ b/ruoyi-ui/src/api/system/notice.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询公告列表 +export function listNotice(query) { + return request({ + url: '/system/notice/list', + method: 'get', + params: query + }) +} + +// 查询公告详细 +export function getNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'get' + }) +} + +// 新增公告 +export function addNotice(data) { + return request({ + url: '/system/notice', + method: 'post', + data: data + }) +} + +// 修改公告 +export function updateNotice(data) { + return request({ + url: '/system/notice', + method: 'put', + data: data + }) +} + +// 删除公告 +export function delNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/post.js b/ruoyi-ui/src/api/system/post.js new file mode 100644 index 000000000..1a8e9ca04 --- /dev/null +++ b/ruoyi-ui/src/api/system/post.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询岗位列表 +export function listPost(query) { + return request({ + url: '/system/post/list', + method: 'get', + params: query + }) +} + +// 查询岗位详细 +export function getPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'get' + }) +} + +// 新增岗位 +export function addPost(data) { + return request({ + url: '/system/post', + method: 'post', + data: data + }) +} + +// 修改岗位 +export function updatePost(data) { + return request({ + url: '/system/post', + method: 'put', + data: data + }) +} + +// 删除岗位 +export function delPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/system/role.js b/ruoyi-ui/src/api/system/role.js new file mode 100644 index 000000000..f13e6f404 --- /dev/null +++ b/ruoyi-ui/src/api/system/role.js @@ -0,0 +1,119 @@ +import request from '@/utils/request' + +// 查询角色列表 +export function listRole(query) { + return request({ + url: '/system/role/list', + method: 'get', + params: query + }) +} + +// 查询角色详细 +export function getRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'get' + }) +} + +// 新增角色 +export function addRole(data) { + return request({ + url: '/system/role', + method: 'post', + data: data + }) +} + +// 修改角色 +export function updateRole(data) { + return request({ + url: '/system/role', + method: 'put', + data: data + }) +} + +// 角色数据权限 +export function dataScope(data) { + return request({ + url: '/system/role/dataScope', + method: 'put', + data: data + }) +} + +// 角色状态修改 +export function changeRoleStatus(roleId, status) { + const data = { + roleId, + status + } + return request({ + url: '/system/role/changeStatus', + method: 'put', + data: data + }) +} + +// 删除角色 +export function delRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'delete' + }) +} + +// 查询角色已授权用户列表 +export function allocatedUserList(query) { + return request({ + url: '/system/role/authUser/allocatedList', + method: 'get', + params: query + }) +} + +// 查询角色未授权用户列表 +export function unallocatedUserList(query) { + return request({ + url: '/system/role/authUser/unallocatedList', + method: 'get', + params: query + }) +} + +// 取消用户授权角色 +export function authUserCancel(data) { + return request({ + url: '/system/role/authUser/cancel', + method: 'put', + data: data + }) +} + +// 批量取消用户授权角色 +export function authUserCancelAll(data) { + return request({ + url: '/system/role/authUser/cancelAll', + method: 'put', + params: data + }) +} + +// 授权用户选择 +export function authUserSelectAll(data) { + return request({ + url: '/system/role/authUser/selectAll', + method: 'put', + params: data + }) +} + +// 根据角色ID查询部门树结构 +export function deptTreeSelect(roleId) { + return request({ + url: '/system/role/deptTree/' + roleId, + method: 'get' + }) +} diff --git a/ruoyi-ui/src/api/system/user.js b/ruoyi-ui/src/api/system/user.js new file mode 100644 index 000000000..f2f76ef9f --- /dev/null +++ b/ruoyi-ui/src/api/system/user.js @@ -0,0 +1,135 @@ +import request from '@/utils/request' +import { parseStrEmpty } from "@/utils/ruoyi"; + +// 查询用户列表 +export function listUser(query) { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }) +} + +// 查询用户详细 +export function getUser(userId) { + return request({ + url: '/system/user/' + parseStrEmpty(userId), + method: 'get' + }) +} + +// 新增用户 +export function addUser(data) { + return request({ + url: '/system/user', + method: 'post', + data: data + }) +} + +// 修改用户 +export function updateUser(data) { + return request({ + url: '/system/user', + method: 'put', + data: data + }) +} + +// 删除用户 +export function delUser(userId) { + return request({ + url: '/system/user/' + userId, + method: 'delete' + }) +} + +// 用户密码重置 +export function resetUserPwd(userId, password) { + const data = { + userId, + password + } + return request({ + url: '/system/user/resetPwd', + method: 'put', + data: data + }) +} + +// 用户状态修改 +export function changeUserStatus(userId, status) { + const data = { + userId, + status + } + return request({ + url: '/system/user/changeStatus', + method: 'put', + data: data + }) +} + +// 查询用户个人信息 +export function getUserProfile() { + return request({ + url: '/system/user/profile', + method: 'get' + }) +} + +// 修改用户个人信息 +export function updateUserProfile(data) { + return request({ + url: '/system/user/profile', + method: 'put', + data: data + }) +} + +// 用户密码重置 +export function updateUserPwd(oldPassword, newPassword) { + const data = { + oldPassword, + newPassword + } + return request({ + url: '/system/user/profile/updatePwd', + method: 'put', + params: data + }) +} + +// 用户头像上传 +export function uploadAvatar(data) { + return request({ + url: '/system/user/profile/avatar', + method: 'post', + data: data + }) +} + +// 查询授权角色 +export function getAuthRole(userId) { + return request({ + url: '/system/user/authRole/' + userId, + method: 'get' + }) +} + +// 保存授权角色 +export function updateAuthRole(data) { + return request({ + url: '/system/user/authRole', + method: 'put', + params: data + }) +} + +// 查询部门下拉树结构 +export function deptTreeSelect() { + return request({ + url: '/system/user/deptTree', + method: 'get' + }) +} diff --git a/ruoyi-ui/src/api/tool/gen.js b/ruoyi-ui/src/api/tool/gen.js new file mode 100644 index 000000000..207567727 --- /dev/null +++ b/ruoyi-ui/src/api/tool/gen.js @@ -0,0 +1,85 @@ +import request from '@/utils/request' + +// 查询生成表数据 +export function listTable(query) { + return request({ + url: '/tool/gen/list', + method: 'get', + params: query + }) +} +// 查询db数据库列表 +export function listDbTable(query) { + return request({ + url: '/tool/gen/db/list', + method: 'get', + params: query + }) +} + +// 查询表详细信息 +export function getGenTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'get' + }) +} + +// 修改代码生成信息 +export function updateGenTable(data) { + return request({ + url: '/tool/gen', + method: 'put', + data: data + }) +} + +// 导入表 +export function importTable(data) { + return request({ + url: '/tool/gen/importTable', + method: 'post', + params: data + }) +} + +// 创建表 +export function createTable(data) { + return request({ + url: '/tool/gen/createTable', + method: 'post', + params: data + }) +} + +// 预览生成代码 +export function previewTable(tableId) { + return request({ + url: '/tool/gen/preview/' + tableId, + method: 'get' + }) +} + +// 删除表数据 +export function delTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'delete' + }) +} + +// 生成代码(自定义路径) +export function genCode(tableName) { + return request({ + url: '/tool/gen/genCode/' + tableName, + method: 'get' + }) +} + +// 同步数据库 +export function synchDb(tableName) { + return request({ + url: '/tool/gen/synchDb/' + tableName, + method: 'get' + }) +} diff --git a/ruoyi-ui/src/assets/401_images/401.gif b/ruoyi-ui/src/assets/401_images/401.gif new file mode 100644 index 0000000000000000000000000000000000000000..cd6e0d9433421b3f29d0ec0c40f755e354728000 GIT binary patch literal 164227 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⋘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 GY5fOuZZib{ literal 0 HcmV?d00001 diff --git a/ruoyi-ui/src/assets/404_images/404.png b/ruoyi-ui/src/assets/404_images/404.png new file mode 100644 index 0000000000000000000000000000000000000000..3d8e2305cc973ad2121403aee4bf08728f76c461 GIT binary patch literal 98071 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 literal 0 HcmV?d00001 diff --git a/ruoyi-ui/src/assets/404_images/404_cloud.png b/ruoyi-ui/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..c6281d09013e0a2c5f8e699a0a6038d9480291e5 GIT binary patch literal 4766 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 literal 0 HcmV?d00001 diff --git a/ruoyi-ui/src/assets/icons/index.js b/ruoyi-ui/src/assets/icons/index.js new file mode 100644 index 000000000..2c6b309c9 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/index.js @@ -0,0 +1,9 @@ +import Vue from 'vue' +import SvgIcon from '@/components/SvgIcon'// svg component + +// register globally +Vue.component('svg-icon', SvgIcon) + +const req = require.context('./svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys().map(requireContext) +requireAll(req) diff --git a/ruoyi-ui/src/assets/icons/svg/404.svg b/ruoyi-ui/src/assets/icons/svg/404.svg new file mode 100644 index 000000000..6df50190a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/bug.svg b/ruoyi-ui/src/assets/icons/svg/bug.svg new file mode 100644 index 000000000..05a150dc3 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/build.svg b/ruoyi-ui/src/assets/icons/svg/build.svg new file mode 100644 index 000000000..97c468863 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/build.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/button.svg b/ruoyi-ui/src/assets/icons/svg/button.svg new file mode 100644 index 000000000..904fddc85 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/button.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/cascader.svg b/ruoyi-ui/src/assets/icons/svg/cascader.svg new file mode 100644 index 000000000..e256024f9 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/cascader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/chart.svg b/ruoyi-ui/src/assets/icons/svg/chart.svg new file mode 100644 index 000000000..27728fb0b --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/checkbox.svg b/ruoyi-ui/src/assets/icons/svg/checkbox.svg new file mode 100644 index 000000000..013fd3a27 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/checkbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/clipboard.svg b/ruoyi-ui/src/assets/icons/svg/clipboard.svg new file mode 100644 index 000000000..90923ff62 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/code.svg b/ruoyi-ui/src/assets/icons/svg/code.svg new file mode 100644 index 000000000..5f9c5abd5 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/color.svg b/ruoyi-ui/src/assets/icons/svg/color.svg new file mode 100644 index 000000000..44a81aab1 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/component.svg b/ruoyi-ui/src/assets/icons/svg/component.svg new file mode 100644 index 000000000..29c345809 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/component.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/dashboard.svg b/ruoyi-ui/src/assets/icons/svg/dashboard.svg new file mode 100644 index 000000000..5317d3702 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/date-range.svg b/ruoyi-ui/src/assets/icons/svg/date-range.svg new file mode 100644 index 000000000..fda571e70 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/date-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/date.svg b/ruoyi-ui/src/assets/icons/svg/date.svg new file mode 100644 index 000000000..52dc73eec --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/date.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/dict.svg b/ruoyi-ui/src/assets/icons/svg/dict.svg new file mode 100644 index 000000000..484937730 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/dict.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/documentation.svg b/ruoyi-ui/src/assets/icons/svg/documentation.svg new file mode 100644 index 000000000..704312289 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/documentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/download.svg b/ruoyi-ui/src/assets/icons/svg/download.svg new file mode 100644 index 000000000..c89695134 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/drag.svg b/ruoyi-ui/src/assets/icons/svg/drag.svg new file mode 100644 index 000000000..4185d3cee --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/druid.svg b/ruoyi-ui/src/assets/icons/svg/druid.svg new file mode 100644 index 000000000..a2b4b4ed2 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/druid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/edit.svg b/ruoyi-ui/src/assets/icons/svg/edit.svg new file mode 100644 index 000000000..d26101f29 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/education.svg b/ruoyi-ui/src/assets/icons/svg/education.svg new file mode 100644 index 000000000..7bfb01d18 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/education.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/email.svg b/ruoyi-ui/src/assets/icons/svg/email.svg new file mode 100644 index 000000000..74d25e21a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/example.svg b/ruoyi-ui/src/assets/icons/svg/example.svg new file mode 100644 index 000000000..46f42b532 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/excel.svg b/ruoyi-ui/src/assets/icons/svg/excel.svg new file mode 100644 index 000000000..74d97b802 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg b/ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg new file mode 100644 index 000000000..485c128b6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/eye-open.svg b/ruoyi-ui/src/assets/icons/svg/eye-open.svg new file mode 100644 index 000000000..88dcc98e6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/eye.svg b/ruoyi-ui/src/assets/icons/svg/eye.svg new file mode 100644 index 000000000..16ed2d872 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/form.svg b/ruoyi-ui/src/assets/icons/svg/form.svg new file mode 100644 index 000000000..dcbaa185a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/form.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/fullscreen.svg b/ruoyi-ui/src/assets/icons/svg/fullscreen.svg new file mode 100644 index 000000000..0e86b6fa8 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/github.svg b/ruoyi-ui/src/assets/icons/svg/github.svg new file mode 100644 index 000000000..db0a0d430 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/guide.svg b/ruoyi-ui/src/assets/icons/svg/guide.svg new file mode 100644 index 000000000..b27100179 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/icon.svg b/ruoyi-ui/src/assets/icons/svg/icon.svg new file mode 100644 index 000000000..82be8eeed --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/input.svg b/ruoyi-ui/src/assets/icons/svg/input.svg new file mode 100644 index 000000000..ab91381e6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/input.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/international.svg b/ruoyi-ui/src/assets/icons/svg/international.svg new file mode 100644 index 000000000..e9b56eee2 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/international.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/job.svg b/ruoyi-ui/src/assets/icons/svg/job.svg new file mode 100644 index 000000000..2a93a2519 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/job.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/language.svg b/ruoyi-ui/src/assets/icons/svg/language.svg new file mode 100644 index 000000000..0082b577a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/link.svg b/ruoyi-ui/src/assets/icons/svg/link.svg new file mode 100644 index 000000000..48197ba4d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/list.svg b/ruoyi-ui/src/assets/icons/svg/list.svg new file mode 100644 index 000000000..20259eddb --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/lock.svg b/ruoyi-ui/src/assets/icons/svg/lock.svg new file mode 100644 index 000000000..74fee543d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/log.svg b/ruoyi-ui/src/assets/icons/svg/log.svg new file mode 100644 index 000000000..d879d33b6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/logininfor.svg b/ruoyi-ui/src/assets/icons/svg/logininfor.svg new file mode 100644 index 000000000..267f84474 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/logininfor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/message.svg b/ruoyi-ui/src/assets/icons/svg/message.svg new file mode 100644 index 000000000..14ca81728 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/money.svg b/ruoyi-ui/src/assets/icons/svg/money.svg new file mode 100644 index 000000000..c1580de10 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/monitor.svg b/ruoyi-ui/src/assets/icons/svg/monitor.svg new file mode 100644 index 000000000..bc308cb0f --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/monitor.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/nested.svg b/ruoyi-ui/src/assets/icons/svg/nested.svg new file mode 100644 index 000000000..06713a86c --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/number.svg b/ruoyi-ui/src/assets/icons/svg/number.svg new file mode 100644 index 000000000..ad5ce9af2 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/online.svg b/ruoyi-ui/src/assets/icons/svg/online.svg new file mode 100644 index 000000000..330a20293 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/online.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/password.svg b/ruoyi-ui/src/assets/icons/svg/password.svg new file mode 100644 index 000000000..6c64defe3 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/pdf.svg b/ruoyi-ui/src/assets/icons/svg/pdf.svg new file mode 100644 index 000000000..957aa0cc3 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/people.svg b/ruoyi-ui/src/assets/icons/svg/people.svg new file mode 100644 index 000000000..2bd54aeb7 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/peoples.svg b/ruoyi-ui/src/assets/icons/svg/peoples.svg new file mode 100644 index 000000000..aab852e52 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/phone.svg b/ruoyi-ui/src/assets/icons/svg/phone.svg new file mode 100644 index 000000000..ab8e8c4e5 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/post.svg b/ruoyi-ui/src/assets/icons/svg/post.svg new file mode 100644 index 000000000..2922c613b --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/post.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/qq.svg b/ruoyi-ui/src/assets/icons/svg/qq.svg new file mode 100644 index 000000000..ee13d4ec2 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/qq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/question.svg b/ruoyi-ui/src/assets/icons/svg/question.svg new file mode 100644 index 000000000..cf75bd4be --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/radio.svg b/ruoyi-ui/src/assets/icons/svg/radio.svg new file mode 100644 index 000000000..0cde34521 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/rate.svg b/ruoyi-ui/src/assets/icons/svg/rate.svg new file mode 100644 index 000000000..aa3b14d7d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/redis-list.svg b/ruoyi-ui/src/assets/icons/svg/redis-list.svg new file mode 100644 index 000000000..98a15b2a6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/redis-list.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/redis.svg b/ruoyi-ui/src/assets/icons/svg/redis.svg new file mode 100644 index 000000000..2f1d62dfc --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/row.svg b/ruoyi-ui/src/assets/icons/svg/row.svg new file mode 100644 index 000000000..078099222 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/row.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/search.svg b/ruoyi-ui/src/assets/icons/svg/search.svg new file mode 100644 index 000000000..84233ddaa --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/select.svg b/ruoyi-ui/src/assets/icons/svg/select.svg new file mode 100644 index 000000000..d6283828b --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/select.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/server.svg b/ruoyi-ui/src/assets/icons/svg/server.svg new file mode 100644 index 000000000..eb287e36c --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/shopping.svg b/ruoyi-ui/src/assets/icons/svg/shopping.svg new file mode 100644 index 000000000..87513e7c5 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/size.svg b/ruoyi-ui/src/assets/icons/svg/size.svg new file mode 100644 index 000000000..ddb25b8d5 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/skill.svg b/ruoyi-ui/src/assets/icons/svg/skill.svg new file mode 100644 index 000000000..a3b731218 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/slider.svg b/ruoyi-ui/src/assets/icons/svg/slider.svg new file mode 100644 index 000000000..fbe4f39f0 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/slider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/star.svg b/ruoyi-ui/src/assets/icons/svg/star.svg new file mode 100644 index 000000000..6cf86e66a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/swagger.svg b/ruoyi-ui/src/assets/icons/svg/swagger.svg new file mode 100644 index 000000000..05d4e7bce --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/swagger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/switch.svg b/ruoyi-ui/src/assets/icons/svg/switch.svg new file mode 100644 index 000000000..0ba61e38d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/switch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/system.svg b/ruoyi-ui/src/assets/icons/svg/system.svg new file mode 100644 index 000000000..5992593e0 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/system.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tab.svg b/ruoyi-ui/src/assets/icons/svg/tab.svg new file mode 100644 index 000000000..b4b48e480 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/table.svg b/ruoyi-ui/src/assets/icons/svg/table.svg new file mode 100644 index 000000000..0e3dc9dea --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/textarea.svg b/ruoyi-ui/src/assets/icons/svg/textarea.svg new file mode 100644 index 000000000..2709f292e --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/textarea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/theme.svg b/ruoyi-ui/src/assets/icons/svg/theme.svg new file mode 100644 index 000000000..5982a2f78 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/time-range.svg b/ruoyi-ui/src/assets/icons/svg/time-range.svg new file mode 100644 index 000000000..13c1202bd --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/time-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/time.svg b/ruoyi-ui/src/assets/icons/svg/time.svg new file mode 100644 index 000000000..b376e32a6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tool.svg b/ruoyi-ui/src/assets/icons/svg/tool.svg new file mode 100644 index 000000000..48e0e3573 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tree-table.svg b/ruoyi-ui/src/assets/icons/svg/tree-table.svg new file mode 100644 index 000000000..8aafdb829 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tree-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tree.svg b/ruoyi-ui/src/assets/icons/svg/tree.svg new file mode 100644 index 000000000..dd4b7dd22 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/upload.svg b/ruoyi-ui/src/assets/icons/svg/upload.svg new file mode 100644 index 000000000..bae49c0a5 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/user.svg b/ruoyi-ui/src/assets/icons/svg/user.svg new file mode 100644 index 000000000..0ba0716a6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/validCode.svg b/ruoyi-ui/src/assets/icons/svg/validCode.svg new file mode 100644 index 000000000..cfb10214c --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/validCode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/wechat.svg b/ruoyi-ui/src/assets/icons/svg/wechat.svg new file mode 100644 index 000000000..c586e5511 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/wechat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/zip.svg b/ruoyi-ui/src/assets/icons/svg/zip.svg new file mode 100644 index 000000000..f806fc482 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svgo.yml b/ruoyi-ui/src/assets/icons/svgo.yml new file mode 100644 index 000000000..d11906aec --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svgo.yml @@ -0,0 +1,22 @@ +# replace default config + +# multipass: true +# full: true + +plugins: + + # - name + # + # or: + # - name: false + # - name: true + # + # or: + # - name: + # param1: 1 + # param2: 2 + +- removeAttrs: + attrs: + - 'fill' + - 'fill-rule' diff --git a/ruoyi-ui/src/assets/images/dark.svg b/ruoyi-ui/src/assets/images/dark.svg new file mode 100644 index 000000000..f646bd7ea --- /dev/null +++ b/ruoyi-ui/src/assets/images/dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/images/light.svg b/ruoyi-ui/src/assets/images/light.svg new file mode 100644 index 000000000..ab7cc088f --- /dev/null +++ b/ruoyi-ui/src/assets/images/light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-ui/src/assets/images/login-background.jpg b/ruoyi-ui/src/assets/images/login-background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a89eb8291d5cb7d9f37ec4f275deab911c9e28e GIT binary patch literal 521275 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¥}>uauIoY!`8n(7niSl_m2d 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

    LZs5vs<4*dn)6_orH;Y;`bzi*q=#N#x6 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

    8A%Us3k< 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𝔫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

    &!CIz1Ns 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⩛TtXwH2N8q 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

    Wf@+FD?DGvA4he?g#aN51zy(O$Xy! z?jQSAXPqX%mP6S3SLHaVl82{yT@XXaJz?PDS^57ppW4ws_W0NFQGq6&Hch(d=d;_| zi)DM`ZvT;Ip1Fs+)X z8DN!f%pO77Lk90ma73z+?Q#k0H}p(X=TbRtt@d49rvzT+-F_5&&WLDDn8M0x%~L$U zYg5np)c@a3#$o!otXN(?xCdzl-?0{W5P1AgfA@;>!QXTdUWSYZex>@1 z<9YrAgevsMM(U&r+ED~sXD@7CK7aacxdHsbOQ|9=fB!71e=2F*07)}t5P}ywsC&vA zg`S^0$35-M3@bHtmG2cIx&1k&U)l_1`1%4O5YA6qK|`qSXcQ7i-JwY|q$q#rLBkcD}H{`XZkG ztUILS0y`?WFs0f~m#(3gE)jfw)^8yx14;{K5|abh>KyOsYP&r~k?6d&KWGL>z;$>w zT`R}_uO}epI5c`1>zVOROWgGgZ6CjRp^+1Hq_wpG^d5D4(J=Z5QEP4TmKxLZ?YPDm{MoYDT3~2{qlIW z*KA<_Si(*6HMeI`*ncks^;$=*K4jW*aU;@!juX~R&^;tLGz<)$d|KvYFzg210-u_k-f5XAI{;3Zg8c!=X>F2i|&t#iZ z+}@_4E?z=khDrn;y0!K$3_Fw6OC-IuUgP&@E^OV9^<&y!9)C@K(mC72;ErM>N{w8) zb!huK$69ziU3`bNTdaO1d)6ugvwege>vUIxIN_LPY;6AvQ;qFm^BenTYZl0gL0wWszr%dZiQ7$^3 zLi!)JDO%GRT^noCe5krsF*w~h6YhTB-u}%0YyxbH7VD0BOTlN{={J%H0g%gwIWVVD zF+IWPt_ogFZ+!>!Rs69?1dOVY(fV@t?=(73-Z;P10uPVlwz;g2y6#%j`1m>-?iGKj zX}b2z0Np|d;TeFnx3=TsK%3hvI9j&^v@w88Q#J-c)Lf-jFlj4OP(cfq>1RCNqn4ju zYV7@rMYGnQ9_Fo`W@z`%=WSv>RTr*DT59{6A9Kcx(bq^Nb8F}}R@#jMPEQUt*p7ds z87TeoXK8KD0GSnAI`h7BImF(R*Xp1QwY&7cax03y1rgAe4WgOseL-qSi)dZ52dJe8 zfk1>4o7q+W!53$LJjI23J+?o;u1DnK`XEVOYc4U&(GQ1vB188ez5}8O#*`Fi#cg_Z zyaM`TOvp<7Ckr*;Q`=42uOGj`@*G>m&FH}L@z)7j&*&HSqmF00+JCRhI6k8}e-mlF zU5@HSh%cfE%ce7w2)y*(dDmFs$3fc`#EM> z$?FZAbK5y=>o93!5(DrwvG7MA@HG3ih|MAfm`8?m_jSG_+V291x?m%eOoGI4_h9DP_!cal_}EB#?DS>~2W z?r5Rdkm^b_!31@N1Zla_Pp^iLHM+aPJq?g4Ms{Y(p=49)TXcaQinTdWO$Yy4je%iP z7jE034^U`&l)vd@rIv3pjrMjZy#;Nwd*vpqW7bOKTCwr&#q~dBD%rYgGwF=996VLC z+>0wuF2|ihafMOuWg4ldNjwY6H)gQ&JM(TEf&Y3V0Vzz&*P|F!`o4(fkI@(%paVabT}3o&rJXYzihVb-w7lNBJb$ZH+A8mo;ee z$95>GTr9x6p7vWIGQfR4#{ntOoB~?>jgsR$Jd!{CULFl`LFZ9MKVk6rp~y_rM5kbdl+RTy!@w|w`d_MC-Xz*_C^mDWUSl>LwqIMe zI6Pik+FinL&BX5~DW-%o}_k2nV+;NzeWNCeC;r>-0l6B3UoD;cSNdomL5t7Ce-xOvNx^%{O~Bu=KccF zTV!W^@gCJt6scHvZ|MH>`gr^1JPn4;S|QrBSx}9Y@%S$V;F6{DTU85*u+5vHDXjdh-3ASihp&a)I2%U(kj4# z*{o{KT7y<>Mh4>BCcB)bSCu?PmQgLk}8kt^NhIcpE4-aYK&O}f#s{59=ys+vCCHjrNkF6jrOsMtue4;9uz#j){9E?5GXV{xF{{;x2<38=nMoc6^ArakExGwSjpSu)EnO5Ppma* z?-UyS1MS9X${-%FYC1L8BxO&SEQGEeu&z2QroTMa`WpcDs^ykGm!BaUzut zSh4z?gZUcO<;~s>+oKfr51EGlOvLk0{ajS%Efl5~Lur#)gyh|nN?MUUJ%^5Tv$j$@ zAMow3A+g5{JMvbqTT5A5v@jRl!CTo|Q=dCA(lvG+6kEA>J6}F68F+n5&bK0_s3`GM0d2&DuhdLz+06O1!MGmj zuCI29V18wW_Y|LMb#!0(=Gskx_XWNzrnSkAqe2})l~}9{JHhSryIbmG70+*DS0UQ& zC*;d<)5sofMr8gwPU{eQdTh~oBDY7BvV~FZ#!Z&V8$89#%DEz8cN_aSK1k+G`378n z8gLr8-SUJ-9dA^s2-?%m`v0t|!2_4Mdh0kbq*Dv=t*4R3Z4wju6l=j?c%>bWc$wjl z4fbiOL|wMDFBLk8io)-}0?T^-1I(R8dPYFKq;;TLWu>J{Clqj;+(UR=K?TPqkQ&&o zLrMU1$^LkkyWlqvQgEVeHwnLYao4fXd<>i1))q2R2g)r4r>>ml9EhE}{`^ZVQ-o8b zDjy!(-CN9F^~BA zk~!!nNaK8^F8V3-TB(nZdbEhzxIm7ZSk!ebkRXq1 zHS9N`+V8T!a|r^VpiG46vRExWQzzl=dD|br?m?P7kru;}-!EBvCLJJT^^|4Gp6+_> z?~Q<-&mx9wN=|Vtw83Q}R}d-|n3;l&%;s7t^cu%c3x{v>0v0|qPp5we>Ck_S1ZU|A zZ|mI>b785aF038TY|67#Eh#k$3tRK&*H0p`V#PS-X=7=&j4+HH&Qi* zo;_Rmm!>#w`#1yFhLZOvGV$V_Ib&5iJeouwbiT5>VBJ zQ$ZV*rv{MDvJBT%920XP5xQ1#4SzyUBMmt2S!q1ph2JhJ{;tB<#KQimG~f0S8;{E2 z6rLa(Dv*W70CD=Z#A-vvs*}2}2H%8FFg{vEtRxFHa2MFqd^-Z9vC@k z5%&qw34B6{sGV9i&)!S>*T$IJTM!fqLGU^e7vj{X#Cs0xEma;Mh8&9<$_@d_oU>sG+^Qb%$lho zy#!tKO=g1>yqAoIdaOBl(&=6MyeVrNyJbUExNDZDSXNct4ixZmw}%kVH>_Oi0_1BR zCo@j*Q!cT~ng(Oas1{3;I%!ujX#F@NdO1~oj}lvN9r7_{Z{`a+W);cDwcp#iBVEb) zQstJDBf5%1wD|m9wAp3kkHffhzR~GTSyb?V=e~eS1fsd}yS&#G7c0XsW;^R|XtV_y zre)i}Yn%3pcGx4h=Arf3(kPj}?8TlN`K5&4 z1y-7RbY)ZIS8#z%b$!JsPC59UMg9VsfgI(%$39HEi^_*wvmfQVq|*fgXhbuNQyO3T zI8?>=(}AT6D?K#Dj*fo$cm=3V&=SfBOXn9?^)q*`Z3 zc7A7RF>eScV*)-enQ;Qp0EuK6T}T=i`bo)NJFol=O`-&`aJ{w7<{jR~tHt)6eF9%R z5~_fND8v5Y(#?1=$=6Q+!l62Q{tCMi01zKshjw<&G`sZI6qC3vZ-AgPNeoahO z0>IC0aoWNN>n;?F-u|Nn1SeN|c9gfv<6mjKyrIWo47M@wMrM6tetpaC;Ugg&bxq#S zA8FexShG60wGuGR9$@+W)sKelnL3CNeO(x(jR6YIVueW|Va^jixuA-NAvNMG&%)3c z+?#6j5)6TR$m(jED#4N)LT-7L!i#26eE(+*9L(M0iq-6D>BDDud93jbs<83nX|{#e zwkBPj2Z1BLhPeq6G6uJP6T=!<&_-WEKD2e}Q-WVn#H(do!8`n^4wxrM&?2EcY=U{Qf_Cyb zhD#T8>r#GpZ;(yKKUq<$!8-xd4V`QwC4I^H(HO@qVh;n2loOWzW>-~?E)!>pVtR#@ z(lxwRR6-qYExrST9b^1;j-5t1hgO=qC=9LqfjNSA8N&{?f6 zU+JpPFv7o30za!)=Kyk~_W_X)RAiqzcoxm_ZDy+Tco=WN;oOb&+_YKxC|WoX;JE~_ zCxn93C1a|YNe>?zSkmu#Z z;Sj!rUExBbO)j0)D+97!RGo)uED5W4qbaAQ6hCL|K*|yxZF?x>GT+>idw7VP%S720 z_#uiA6zOtvWs=GVR{87MoL$!hP5AWdxbA-^(Yy{=4fVwvRp#hv>5oMAjvc(bc5Noc zC*^w#B>P)jT`;6k8l_K#U?`kahs_?@o5OC)zh3jTF+w>2?<~x6Tn#8C2SP0!7sU}{IEO806pmFxZ*LNg_-zyG*IyxFPZz%P6JuAqTAJSBq072k;lGM2v|mUr~Vc$!M#OnWRG za10^O98&C|%r+c|KWVm6q?=m*;&eu|6afi)%ljWOS4?MrZ+=c|N>@`b)U1L>7m=|r z7C(C%8y&_nI%YRrdwtGa`0TJ{`uPp}>`ip{1GEQTe!CSjoz<*BghZc$8ivz=4%O0Z zPm)g-SY`o{xeavcX51^yb03ye(8)usa^K9njWIR@^q?|Iw^}SCB*2Q51+u4~dsY-& z`+)=>+n4uYNNEg8K5ETJirU-Y6g>Y9@p`Jm;{|l2pn{d}@h3S}*&7DHJ(b~w_&h_} zm)VBPc=dKA4{1iKVi>o$DBmk-;rCLFW-{FeXCi@dHr_;l zzHJRN)$V+MVOz*7yrLE!+{C{F-QQTRv^Rz5+L1==Qu|x0V!z$7D}vjRIyNQv#!UhC*q-Ket)Z^liW`W?{(Qc;x~$a zDvKCTd9+cY8|mDnDO8jQi3;68A7qYtBu*x_^RUh4R?O3 zB+(gyI3QVVXC-g@kMp&O zF5I!FtB#*fW{?UrJZIAvF|oTrsJeH~xbco6|dxqrAl7IycgDpOY(2CNU}|lsq9*az(busT zyi0y!Jnmk!WPr0IPb!>&M>I6tu1DLmMcHQBKn7H;G0n|WwWoWnU{m;$cS=PGO56^s zxA=~S+*tD;z~=AxH4XD54A!YOx{*Sn6V7V7(~*J_>FCVk-XSl*xLT%cECk5}NPtce z+xIk{vF?y*GyW)`4aLyTEx|?pXbu0ka;0sOmv+}eBYE_u2%947#Y+$M(5{56S*s5t zJGH=z+%n=Y5?1Nui{G47i3wRqAcqQprcIYwGqt4i2la$*d6W%L846xg!!_!zgk>Nd zi1oihMjBM1Bd0RznahLJl~)_Crn8|fRdt<#JHu3y(tiW~h{nu1z}7*UjHK9HG#zwr ziqyu<=4bgVF6g6~Bg~qPB`t@?2eq*8=;V`4TjIz}GFm~?P{f~)gNAs;>-7i3FBQ_& zax4YZW~(2J_}a%v3wjB*+QNZ((}q4%JCjY9`P>ABwj?sn4#@9BIRS}g8u*k|Dhw4| zZqTNQ={?8^)5(k~d14gf@56fd;mt61qjV#C@ZpuU0 z@RhXXbB5}xpQ{xsVGi8#BwOp(xllUW7ib0(Zm29z-Kyf@(P=C<6oPDLY;&Z~knJmI z_N@+mj$IT>>O=E7Z_-6tc-YT9;QcFJXwEVW7DxkzpZCa^vSrI7=AH#vD16U0zhZqs z4PY}73C9hKVW|eaV`^AKkdi1_#}cWI#EEjd-FNSsM19Em=$6N9hS99w2jvl{9Me{; zZZ&46P#oZkZ7LnFo%xaa(f9tj89Xy7%~q@dYTp$Hg)S^%CF{~F@<(v=xPOPIi~IxF z5i0m^;>ZngL~4oJQNeAx2?!p}2mwJrOMb)YGL%sT4gZFpd|Bdt>dD})&+*Jy-CO9a z`m!O}K5T@cH>JQsM2ycKKvE__f%_uq^5wDV`p?|*IwseCw39!^s|{1D1&J(`t%P5C zvE_C3^Tu2=+L_PfU_?}YM4Nzx6O+rz;Z{0^3I-3oyz=Zs^bK7HR8VFgRrGKS*&r3X znS;{Otky(cr+ZC#J8)qZv|oOU?-qkNb!FwXNAatw8V{%zCmKxbxF^|zYI0Ii&LPXu z6OKiwYC^H~XAi~dQJpOTz)>}OKzuRd~*4^?+@Oy$Cs zj?et-Xa~8m_tn&bMz|>yKEx|XwMLy~5xMo2{3gx@Fp(D#6B)@b*O*ro@KE8-3Aec&(!>k%F3r^w|^rlMDRpn`hXx4@r$#?F6G^Lf#Y$%6{s zKn16}L(6501x`?|)Mx~G&KoY56mH1ZJ-|qjVXxXRD=!|iA6>r7KiGw2p{`!IMlG(p zy(B6dFP?i89yUC{Ok92*D!z%H{FE>cM;I+Ay=+UOJxTB%!Y>b8xg3VAg~~9t#U){P|`q^MVz0LBGS#7 z2#F>wQd^hpA3sJ*UCRB{CG;%|qRvH${ggz)=K1MIQD7+d)l=vB41o5t3#+w@j-H$! zR$5I(31yT1rTbAY)@I?o8Ay+=ByrQZ%_#bQ-1ST0%!q1~m8<0>6CcIJb=rNp*K$4Z z_tisby|KPdiQaNRX6@R~*FjGsQPd_BqJ`)$SE&zso3`9924x^)oPwqW(b$Dfargks_MWN+5FKM3K5jn84}bIGyyeL%UHD? zoMj9YI*Io)amUxQB{UzJLT@}1uD&EuTJ%Qf^A~4P)0dXH`c&R_o^zCOcP-lUH}mo* zp|Z8tIL?TW3L8V3Jjt<973Kk6nOCf$r|bmU3x-~q#;mH%gZ}^`kS!}ZgSZh0OYlb{ zL%$de0zD>;W|A@Q#vLw&rvVQF{3)GQ@o%RKQ<(-UMbffDd5wgknEvh3xO0sc&c3oJV4Wa4V^E@YCpv@PBtrOIwRESSpA>l=flp ze}J3L4TOg$o`L08JC50_6JJLee<1OA)(Ti^s)eTOus+Z$(QWSRIT4uu!D_`O=qSzV zK9sjR3D?dV3`p>cZg&k^z=$yP`S7$PowD7(kM(pQiA5e!_s2iOK0u3v(Kfqjp%3EN3Q~Y&UoCzT=u$!m1|Mr z?oxJp!*I`;>9jg@^^5Dc*M6GRD!<8?*Fz#gWeqndaIghSSPh zVGjY=Fl3&#Oxw1Ol+K*LCvDZ0?OD+%KfA=iHsDWlT_^<9NqrF3I0|kRl5p{+BF=%j7gEVov6-pNN z9So`CUA|1M1h+0rR60~--+KqKfce<5UddU1!CpHN)qPf`8c#Dw;1*yg*dMlng-CRq zqMK>34LU^b>IZVVnR!+wU-s3-^LUIfD=?71K)JUUOBp+%_w?Q6w%NqSTal=obI1li zr=X3sH2TSI3QeZ^MYk2KNxT^dlg=bapQb)57VF==?9aTPusG??Y6Nro4?rs2+}nKg zL)yiV34k58nkenNdr4qEo4N%p+ZaQA{p^Ub1xG**cjDlE3~Jxj2nt zTAVwwISDPwC}%}dbnhCn(E-6{mDz`sgE=KnX7$vkqNFXEgmnMfC^LMKk$cPIgLRgj z9W6{O>wbpWwH<;^`-#+P=EmqR|~Un-9MITbTO+;{~Y=s%0KU_*v#zn0)JNqTJTH$*)Zj zRW(pD`ahDwe6^A}lqYFW*2ISUW|5I?)i2z1E%Qt_Y<;-z?TF#Eyn-)!^ARM{v}X#K z+~x4f-r*gs^u4=>=+`Ezo0!Xz1MHT{t+MgPsOhcgRlJ=NM^?RV`2ygQ|Abs>9<;?G zsjcGNk$O~{rB{O(^b|4x;3a5&E_~HxI6dnumu$pjsozs0CD@6TWvQ^LO(hEd$}T!o zZ*)p_uYj=_4_lgh&Do*=lVt!shNbT%ftS56KV+eZCBlJYnTMX$3QH5-Kn!f>dw=Ag&&M}LaQxw z4=#C@FG-g_Gs8p8T$Mb^QQfc_FCF8xch9Ywh}$QrP(8E6B1xZADW3T;TN7--Pf~^K zW4%EW`H=|+9=Lvl3B#uFFT9c6q;&DS;-3c7FA#Dm>|>rpn(y4+4wZJ^*?326W3hn( zRe6K1a1ziim%fMN(>h`Q=>Bo`w(}~OjaORCFa?|w{h(ezi0Q`p{x%W~GH6@;wPkhq z)WzspjyCzbd=oL>H0NEFZ01f{u<*jVLi%F3-&pE0vv+nM*|M}27-fA#YIEAv&NnnR z!}v=AhEKXFXsa44_Uh{BxAZ!_1P6b_{VMz^{r}6c6z!~_Qyl2??k+^@P2AM^g>>`r zn8prgwz}l%pqH&Y4HeNb`juyJ;L-fjTlr5A976#mj(CR!bvM$_(ZA2VM98PGA!>B0 z-SX$05;(`uQNGBiE9fsTE%6n(<+X$V!X<@H>gDAffnz)o(!x_(!uGWm^5{w%nFhHM z2FM_5kWp~|Z$apDJovh}D%Y}&VuHw3p9_<3nSlR;BPq(m9{v{E9Gm~Yuv%{5;7@zn z6@*s}%0vL|_grA9TjxyOWcrhDU7nGvmw(HJ(f1$26JKI~pCgIydPMPGUTpC}ni@6E zyI)K;=BQfcwzU+3yJbTavjf8Tqs-#lCxWaQhs|& zM|*t#0^iick#Q)-{5rfQdqnSI`u?P!lU>_1fvb$0)#z5)Rhur^B4-67V*F1fIIDsz zfYjlx%a64=Vc6(vIr8aimKCS+(|3-_r&;@Kl^o=JMTy#!jy8s@cB>j{lMu;u>4{AOLYqYgh}RK~rq-Z6Y91zHbz~SkPDy&~onhus{cP_I`G89;1wPH( znLB(0fNwk#w03Q6Qs$daT3$+05)&Eb9#Sk4&cXVCsZx2Iq`gw@BHwav`RX5;1SWon zaO+?Bpd7JR@FB5o=AE)V_6}}rfD^i*@3}_-=tSsNgIef=tl?m)P)|C*lpcMhbIiM% z=eN5%ZKFTO9(UL*Zl(u-tP4AY!YI2|#oajhKFQ{)JLiC_3y+mzRCeB-TEcEE@AB_9 zYHFMxmt-g7C0;*D=Q1iFf^LDFPiXY~td!_wCT4QhOP)E{KHdzt8z`QLnsb1AA{go4 z#y(}BWk(xxy+MwbRcotfubDpetnqyaN{Q#T#C^C>6w;?Lp~}z&PXw>)mKD(M##3X+ ziK6k{l7oTnA!U@E4rQSI?(YUgbl)aonzGbN*QVb6VG=6$lX2pbmDYL^5)Jpm$^FuG zcszLC$x;xi@Z`xQ?k8459n8;r&FQG6eSG!jR2Te49ycJR^i%15ZC_Ilq+*S8E_ECk zt7Zcuo*BsvwJke*IT6oD$WwbSnh{Zew|?I2Ma(&~1A0jJXR(e_Af$g@D&?oBjr^Wq zmC<1+_#_LXkV&SmD=vkmXXQw?Qi8l?70vI2w z3_7)G@|v3VV7eTaAK^J^S(n^Der!jlnx4`)l#Wtr$?~6Qj2ygWlyDIjAv|8Q?8SWF zGrLe)XDUm#T3_+1g-%o9vX!BPmYGhTLzYZBHg#Au40)AZ^gi{_Ep1nZwcX!eD%iyI z0Nmgbz5`)O^tDG~?#_qc#`?Dz*lx9=X0|Dcgl4JK_DR&C(78VzK0t1(S+_q6$b;5eqsHQnn(0G z%7MdP8NPVAT|X!YD6f7%MN8ogsQ9fLso#2mt;YSKB!O?p;A&Lt^!pmiR?V~C8^aIy5s9P%46}BMX;Eazo|vry#3x&_o)c`|Iu-)mzULSU z!WDd-tf{13s!CxOQ8J7)S+p$4clO{a@B>e_0j{ISbwpS<=m91SRriI}Hx^`H!G4Cp(9r5!CLh?(mf2O=+oU zuZ*e{;?i~AjOG@@Jz~q92exmH1q1Y8i%yfd@6uxYju35!n>lT8_3LEr)V2iRF*uL> zryHRDWF1Cbm8Bjwcc-zYHo*Rd9qgCVU2(6PqOOV(S#k=*q{ydT{*YOm!Cp0K#Zmre zgD4(cS3ZFDlMa;MCGLZ2v@i?GlOr<^s8V#^TQcK7l=@Mf{O5YHUNm8Pkj6+@>ue^P zv|gUcmzC~SS&t*hr|rJ?^s9!8r%H*EDi<8p0`$%Bhk#?DtEt3jJvycuCEZ5jQs4s1 zZml^aF^G%qicW(y(p^_F&7>lm)JDrirD5h3a`|N{J@(HETGtxJm(l&MbU-{J%d^DP zK-tcI%0^RcsOqB-&g?P|$uQ?uYiwfT&g#IQLRip|b1q%csGI)$XDQ{UT6SDoT68=L z!zO~QS7PPD5-Jg~WmCf4YYDY78P(N0CB5=6aYYkK&m($xD)6GUa^j3_Nfe^>XF{@+SIdJHphl7OE4WvK3Kvr3nTKLoR zhes>1uEP0Ff&ZiEyrbEE|27_**dt161hHb&h!(X8N$kB_yFy#E^ovrPQZsgHZ?RX= zR;fK|MbN6MSwx3j6u&3W-#I7e-J|Kr~fVwt^hkInmr!S5g9f zz)PFI9?YU55DqN_iH3-#kN65aZ>^AXK{vbFJd_JF16l=%(Nqss+p?rp9wQ!dF%mWvv}c zDSG0JT>y$C^MY_nVjbjrK>LK{RLAkIu8FG`t;JLyjqD2=E*CC|5@?F5qUEx8Md0gS zQws?V&$zc%0<1p%D1T|WvAdLm>x7(xf1yvZ>gPdOZmjPe6TGu+Hnh8-CCk4p#u@X6 zj3U7leYTX<+h!Oe3c@RI8^em$=#3K(5zE%OHAowkRuJ1DpraJ}BV}IP^ZCudQyX7V z$JgVFx7#t9V>VMeYIso4nD+=vFmg76yR$3e$+9d^?L>MrIbiJQh-9=R{ph!6>L*Dc z*Gx1WtE?cVk9Xs~qQQa4M&sz87ZcyU+6Hg_C$irB)gneo_qK>6GZSIcrxjx$ZOx$s z(orzjf(ql87JHB7w!Q`}4-I@H?g&)46GP4ePDb=DCqu+0;ixyQ?~MYnBop$@&L&n+ zI;J3?GG{hIS~FGbhj}lapDAAV_I?iB(^HxU4);G0BHk(|RqsZ)nYVwQ_4Ofli&};p za5b$BJ^c|%pX&0BYTnbv;wKEG6NL>+R9ZErXkwEhG%;RwzK|yL%^7z5rE#h)H~8lF zX#D2P>A_CbXy26ReRCJ-8rGSAX8)YAEyBxeJfQ`aFDun0DAq$UA(>C)RUB&wCQ_g2 z`b{Wm#d_I&*ls9KqgB6ejUCuZCFYynx1M>Aa{+bzntv=|@7%j% z193+7zANnYb1mPE8WEt54nAk|4z@j?tEhYbhmr4&&gSi^-(ap-z!nPdUjX1og{2Cq z@y>T$KhxM=;b)5LzP?X;o_=^@Y|!aTG6$E18sK;oUPs(vV&tNe(FQpv2M=P@JUz@R z?X5*)EgrqgOA@c=a7$R6n-)|YnJ$0&ocpSMT0X7+ZEQ3`gQI()1F)-+ClVKMuYTwr zuBxa?DTY_=X}cejjJc8;acZfy3V68biQG>0aA{diGY4k{{0wg4W~!eY#HE4+ACR-G zmSsU$#7v9Cf_4e9FU6Xg8*)42OsfH7rbzJ06upnQ*S=mB5A?AP)K3i-E^2*~lA4nq zPMic}9M>6KFun48T9;rNTWdF_O2pPzFKUFGsL>zpt-#BI*RnZ6vkY?SQ2)y%hHajV@*#~Cs5PN664kJ~St#m${Vl@K4?`v+kTEhOmwABI+ zZ#(N~xGM&;DY|{fEtYer(K320S&M3rX#xe%N}Wb%bTZ0xr7k%3h1F2L37hmb zh_9HhbC8Evv9wrk!oJi(s^cA^7>(9d@+*qWDf7iW42I`MnrulH^@zBaX6h~SW-V>x zPF*OURT72K42u#3$XAqObk9@gAd#oR8@^Zhyr{4PAdB-@h%?AVk#@=ps~dCFl9lK^ zLzq+;4a{EM9frOfL8i$YWC)xRDzFh$-_&#(XL*PPzyrvII~QfI25+@(+DEqbjFnb$ zxfyL1^$THd6Bb_*ir)((YG(w`u(qT=0FJ_D)RVz8XV(BZ7J}8*VZgl(_?jj&|HRCkEyfF(0+6R3x!?+F^|H zURKnjPe_X^>m9YPsyAvbNb!B)>Q9Vm?@jW{R|*0fW*2c!d! zNI5uQh+TG$YEo`zGV9#G3@wvbi~c(>w$H3{?#|S?)&V7KPcS>){`8`8%A4+W)Be#L zeK{DKS>VqnT=LWuI>DQIwcOng|SMmrDRif4nZ+>S-6L*Ls8X9aw9s<$4Sv zj}A!($bx0Qb{h*W%VMjX&;2AJBV{>YlKq&lgE|11DIzOlXL8*o+-2kJFcsDC9~?mJ+E}lkMR8!lQ0IXa`F~SYg1a6# z-zS@J$7E?@lP@yLpZMQvrPLy~G_$yUOPGQ$_g9hbgjIs_$YhQY`X3?y%7}H4DLI3&u+1NXR?k*1xN?q^`$iFZ56j1tuMFT*>U?7BFF^yaku5pbgaF&*7h zQK7>+&zfO~%gYjiu>_R_nL=cYQVpDax~%6(_e^9^4B zx;vt%mi))4#cpvldUuFKLW2C;9`h{uF#9pq+rR&{n3+AA)fOf^866 zMJVA^NKRHe`coaRTJUVPIl6hn^eo)Z_X}KElbz{_K8kBn-5`1-N~g$-x`Nz|} zPVGNB$`{Kasr)u}6?V8S3G#Flq#mVJ7I`-jPPECUGJZm1nEE#rMLj^Y8T#_|@{8vx z<_Q-}{U`VGGMUx-rqNCxNeE024$$zJAxl5prmVM=a>@)pGgaY(6--YJJN`jC2^MJn1>)FA@ zs!46NxEPQxr%Q>?M%)PpFw^g#yR%)aiz-c@w58j|Eio`^DO|uh-hFf=D4Cgld(!R7 z-~eZ1hRA;z?R!^JHL~U6O1+z3Nk(db9QV2LP2OI|rtXM${fV}6&CzA*6U$5?L`plr zLE`-KEBfl;L+0+&h8Cl9mOS9tfsE<7TJPsQeqLcbWoOUl8m1fnq$A94Q(cD72k(>S z=gKeR3tof=ZWA?YZr|vfxq7V9)AYy1pZbKf>$6(a1qfn>aIa~tvP5VSzpuO{u&il+ za|#kos*ul~-)5G;X{}%xoPpJuK|fAAH@<~US4m#qYkY1|^4F{S>i*`&0u(B7!tT}c zk=wDep}Uvr;(0SKFPYU^DK}i~=M*#6crgZ~j2@XQ4F?(=U;O*qANpCw26*lC!a3r9 z3*`^}5VPhs59Gf-m&vFf-E98CWNpotC8ZXaG0)1S*n39+qz^BnJy&Q=*|st*KZQ)K?WM&+mocxoAN zCb23QOVE0gA@(P`3@K#?ea$xTc5EHe$d5NwKLdIq`8JWg{CgCja);?nwc{glIxu)IH^*?~)QY(pu(V_=p_e1sApng|P%KY@IC}M4oZ`RZpN*ns0 zw*QicO|q&KVh;39*RyXqQaL=h-x_-JI%O@pR?`#!%x;ci%r&%IVzIG`wQ&E&Zzv_bTio z29hVF5Sc2)w2aAMR#UkbQ&l6WqY9r1M8O?u*@w!`=r=tHXU}Jf*!tUm;XU>Zvi&6Q zqL}dlwZzM}!}38x^%x&IVxi;{_-jsgU-q*boB#e2^@tQ7Cshv}jPY2Z;jjW}{QXIN*ViOBXpDKds*F2=Lhi*Z%L0l58ZW6v_GNVpIk{<>(c?w2clAc(T;Zv1l$ zH#9w%+*GuZ>E9tu+p7@lF3{uST(m=rmq16w{LrdyD$*p!$lu-Y$G)w_%$b7?zKJ!$ zza%w$mPz|3*wn*MAe#A#ruv$8f=CmskA;HZ^z`tvwk_TBw6BBi^$pEOm)s6gJ1i9S z_pDFsUdfP=2!P_s4@H-EZdycuw5$I=c%SBQm;Z!9L%>^WUdQ+T{;u+nhBNYB&oY83 zNg`$Pr4o8b+piXk(mvDFeymP>Y*QIc@)68sRV7FE`Bnav?GHCXlQXw0FxwV7GhtKe zY8Rh+9(65KgtGaGWdFlf!lP>1RB0S(H;N)w!q0v`D{DyCAQy@3f4PNC`jn*vp+Z~a zL-(o1yELq;okr{*yboP^=vBvkr%tHpgb*bBJtpFGp;zPdKM5|{AXm%Xz3su1>MxIl z0dEr0lV-vWVaDnaxkaId5BATE3p0oFVP&a3ZlLx#^U?eL2Sj9Kj(jAK+JBcZ*WbT3 z8}oni(z7i45WLI$t>O)bF5ddU&WFY~`C{h9toed-XQzT=iZ7`)MSZQt^zNq>xqF`f z9S`Hfq5lJDyM+IC>~eXefa6}9Z>X2O1*&>URat{N5fN0oAY}hjwKe<6RH$UdIj;oL zBl}9^p>Z9%t)=%_a$H7skhcX1BTF}y-BmZNTnvgwLpI_fRF5BUj|5O>Oi1bN75I>M z<^Z{)Dos&GA~Y})GdTHFi$m#=iwIuOA|2k%IyAyf7vtB^(<+-sZt9_54?eh#AdLv* zT>p;NPq~D&aMzXsa;9hy$dl@fYH-ld?FQ9P{SIO&NDrOdOg%g(x;5U6fovo1{aSQ; zkprZkd~SORwz8c#6x@Ey-~Kw+Hdg!**CGYk2WOKFKTp2csJ;K~p-=y&?uE5;rI%T` zJ&;Y|T+>iMmidq0!zktx{ z+3@vv_FK9^)wcbDi~ttE7jRX2(RJhW_&*k{1?Cq6*G(r6D$?Hczx;@-^chia*W7F> zlIjcCkZq)d=x4lRalb~D{{T-szs3a@Dtn?6^h zNEPdCYcTNktcnB3)ii1rQzfVJ8&-XtH=j4}vY?#T8Zpx}7nXOSeA5Hv*IjuZ1nVnO zChc~PCkvENPd9{(0X%DesOA?2s6W%UHd7i?{K8T}abNfKa&$5UB*L@z>p%H&3_O&K zH2AqwZ^)MoTaQZ|DG}=cX?h=P6sm4K;5tik->?bZy_{%x9QgL_%7vYM?5oF!m%V4t zzjBsZvFfI64=$d%r%f~rqg=~0jwg{P)FILI)2uM7WkCzEf^?bH0I; zC_lxXZXXwEj;!Ee+M{Zcr-94kOWD~B6kP>o1rMfTCK?qRoMdO(KY^m$N`WNX)XB<+ z^qrRm0)LkJ+{rg0Yt4Dnd#{DfxViJPDSJC#4r#*Xh(SF0AU9peTtJ|w@W;S-i-~Y2 z!P3oZgI+T^^z1wxY z(21jabu(2x>vzRVb1jNQ4QoyEHeqFnb?T^Jb~cl-gH&e-NJl)T1s8Af^9km3Q`w8n zVYX@O&R#5I1EnphazCBOT79PHFXh4SFnUehM^v9?IZzS3Gn|R@VzAN~REzunO9D#7JbjWA~|W zhrbB$6E9w05m&4c`8v91Xs|Vr%7+5446acNJ0rVmdT5bRl?VvG$1QR#QDu*&?k~%b z4RuiU)0k+6qg`VG?gxqPsVdY_OFBV}po3w6cGXZ!=6TPDqB%9(6BE-zP|41a3}c~u z>e`V&bdmko5W_K`s2dTJJ;2d=EAjp)MK?EjR@eTDQNVyN>Q|eJGdZ}swy^O=#$sFx z992pO3ERCcH)(zYL--L%%loL!>?>R9qFYz2lz;!a%9B4Qd+uzT?s1pf)y-UfJf`g> z@u^+DeVEHH;TVl%(lQUXbtC)SP0OjS!n<0nlV%nxuBp<|YzS`v*%Y_9cpA7wG9UG; z>4}^CNiTT6Vfx?q4iVZOsE-f4RB2B}&0<2h-nDRg*5EX9^(q4Jd|t|=F)BZfhw2DU zUPDI~a#C9>{p5smJfrkl=H9H7uj)EF%YMR6xY_Y@T5TusO8wOvPuGW7h+kp?`{{mz zu|W}rtPuDCslE2wG&CB zX{>qudXgUnQ$%luB!_HV-%3ExT!In@DCPB);KDd0LZxQSiFU)1;1ykFy1L+>61?dS z?uyNEQ4;0?lvrR?wk;&Bs=+N_Uc*T)^ChAzaO}TS3P4?*lA3cFJUS$+c=*~4ZCQG zs_G1UX&`DINq&QQ@r21ov&NtBSKCxgYTXmc4fPT8&$mz+|lah`dSN<&X~ z@1?K~t9(A8QTtFSxRQLD&ub+mR4BcJcSKyrSoiil{YjcLe?zGwXdKMBH!!;JA^Tq_ zYrD_1|E6knil`C*2B0j9-Bsdpwi-4Nh+nc@%%~Rr$zS!KF;uEcT6U2CUi@lnT0_(I zt!6WcK1)8esB^vtY2=X4n1PMEzRO0uax-;O^0^g|o6$i)3WokHk}Zl-yT3UW$m&63 z|LUe3Br=5dyX7_2%4dF69Vi*r|~;g|FkdsR4f10-wXh*WooIpHjIFtDDgIzPy~>GEqrjS~X@E zN(9-|;H^b!>L6@FHY!#CI*vwI95V?Mrl&(u{vg$HZb^W8_!qOmYq&UAMWcPSkSE->h_r-)F2$-3$*^$Wc}u6KB1&4o85G#(m7!`5MWPEx1vq%|O7P>~%BYhJ9z0X= zts=?fB60o@&q!^2wNo_C4j4yGjB(lpm9+`{G8io?*cekCjHbO3*4?8NeJ1_AyOKgQ z@rH#;_r>$BxCwwe*oQ^&G%Y(^`2-0~TBVD7mtkyZZiUM*D~qq@A5ao5Pu%0y?}>i7 za)e>5EQe9Qx=1_a$~NRcMD<>*T(V{BYa>ck1apgN8no4v&IZw!r?m>12iGogX$h{F^{~+HZcj(vv?(Tzv%Rhg`IX zF3XwH1CnR~`aBs)lGH#D2w)TUq0jQ2!HwKO>mNt+Rx@)5dbXFZ#i@YXCI9lvs*=D! zyX#p`B{AoF*+L;g9h7}Eu(em@v!mqZ`fl@AVXP-RmrL(wr@qd_F z(BdFWVvcP{nSgTX2*Kz<>eEr#`d=i9m-lWuKjb!L?zc)2ZdXGc@k#C(?XS%}>BfN9 z%v@6R^&TIx4rn(vIPAJmV$6+_+Aqe>4($fGeO|L0m}JY3Q2e{tt#zwHZUrv{i{~+{ z;0m)c!Pl}XX|#&A|sZR*z6RuLU79n+d-79_7#ma|wEh%&msm?xTotD613_MAL^ zM4}_!%HCLs-zx^fMRo}{VSZDeA6HIk=hn-xS2UvCzPkhJ8nWsiNyKNY=tWmGSmab~ zTPl=QWsvv;A9fK9MZk>#+Scc+S`;+`v5S>cG+n8Zj8?=j^$ulmXW&AK4{e3{6FF7D zo_|N4{gdc7s%maOq)1^dlLBa1i-h-rPo2exfSTzR(7I?1gt{&}xIu;Hf5>bs*L|iv zg+b#|HPr%f7>;E3faMS6Op4ZSKJ~*LmiGh_82L=Z9;)BF?WZIvL9Wy-Lnk3!Ll})t z@EL8zKR*(&{K{13N>%udz(_a&PZTo6md6Nj%Z8#waP`{kmAHuYtcvmGX~oi7 zZ2RWUR;-|~geuhK*7N7@(F21AJoF?4tv8KY7vFihq{9VE!&Bi;ZaE4(SPBRhX6)@~ zXr2Ot4RRtLLu2P$w{a!b)AU@N#J&vja}k!q$!1kk)qZ4X&Ju+*B;){Fv-iR0X?|Mss&e~Dkedx6tukbfUBDnm#f^=o zdel_yp-~-PiY;7xK1fV~o-m36{OB%GKIUrB>{EM-93}W1l;Xr*W#-x?OT;7 zDgFWKy!OB3tqd*QeC54$PbJ{I29K#m?w?o{F+{o>=9OlZTZLfe-QAKJgCwC{EzB$W zScgfb(Jg*&06E9Dm6U0Tr3ri6(=*B`vt&rtq|I!ov%PAd*+;Hu^I&GBhS2KE>S2w~ zUsMVv6@I3^aWE z%=CencnXuYzM09JF=z>I`LXss0ELalso-^l9s!mq`g%1cLoCKgUr9MXzRbvJ$q&Y) zo915B&6+Atc77Z4JHF<_+L+8t5kZyS71#hXWw!!k<>Ks*{S;$9!e%Xqpp@ekg1f2D zy?P*!?`o#ft#HVUBL-)H#cs^i1u&uH}De;gD; ztQKqEp1%Nw;@(VHSdudGtb7Vxqq33o!2W|#XRg<;Sd=fGf9?PA3IYjXdY}Y4qDuJE zcd)CO5K?QzR?~IhL=C#}ysZ5s52$8mLQldu8 zM0I-)Z`yV$zGH#CcMkHTv)iicbg?hZVZcB6kuM)e3vC^hln}yWuJqow(i~#|trLdu zU)`@e4c8Lbkmc;Vr#J>FiR@z!{uu#zW_K|+~iGNB%i}$83Sc+P4&3rnZ zRVf~Fw5#+LU=^a#GlReMiHnJjYtM$<5?GXy0b}f3yB=(t2}gNT>!2ro zu;#mIsEFIG3OJPjhN5hK;F3R0B!^6T7*T|*D(b?47nOjix!-Zg?WurY)9rKfVb}{KGXmHr9 zK9n+hL9p8$Il+`M8FW?QM!|edw5xddYJIr=xYBMW^B%m36WqLzK5S0CF(?iE)4ptk%VW6Z^I})2?7>`i>2-%g{wf_57 zVayo*Ok4>7+!t{2?IW_NiwcJ@#pPq_g#dE0L_mwUdG&sj$qLj~5|2OMGKy`R1(qWC zj;#{8h#5o!zUDRpsWvsk2b!nqgf7xFm~T{X6KU1w<)|oxa44ra0VS(nd%c$=ymQo* zaL*`xS%OGcq7XN)Rr)C2-k6Rd+)lfLI?WiD_eDY5OMNjudbi-IB^7OL6GmlT?s8_j zNC3bGN~tEBKi9qsg!P5|h-J;C4D0bNs(FnmU|6%m33prA?eaioPS{fg`eic|6*bteiTOA?{f1qQX$2LCjZk0B+DMTFfs|3GL#{a?uhTPpPx6w6)Nxk>9>AJ^i91;sF6y6 z6SM&Z0AypiNg*g*I#Y>!q*`J?;?y2wjR3NOO%ut3*TUbL{-5LA7`ShAzQm)vr!p;z z(Jl+!B5D}4)xn;hA4OvBP{p{8qKvj|1xHyH9}5}_=a5>WGYyzmoQue&FVYf;-5n(i z@W8fWvj1^GqAT?AR@qxQee8Z59OGDX4^ZjRs#FFp)G~ojpmD#gY`A8UUc}siUp|Qq zC1k7%6e}JuHwzZoJJMGQ4c=>6m;d*aQ27v>)|UJ-+avi?{XE05XSWH(_BW3%)mcZA zHeAezh5;2%tw8(k-PgG+Qxq1laxN6A2-P|AV$cx6eueCgLDtVVw@Le=*`QrTznwh3Lx;FMU7mA0fe zN0vdAPMhUId1wc-gu%SuS=VEg&A8#bKKFsS@peE`I%>+GP;y~`r%B|$i~F-){(Ws` zHC)sbmNp^?2`O~UNGY9Eow%v7T9AMLEVs}AGGpP~Jt~Db-#zoP(mExzQZ;yEX$3ZyODJ+!#NT-6Y*J(aQKjozqSv^JDBwJ>%g68a{n zj}LD+q?1M&E$eOw+?Lt^=8 z+7@bOG(huUjJu^dI)q87j1q6dW>F=YVUJ|8UN_M5O3%ia*_)IZGF~*e{sdp3750?$ zYmbywAXZjk2`mtg<0_;z_7?4D1!Y@Nn35E1hq8k1X01nXaUZCT|zOX&cel45S2VfHySX7WdNGYsr(1Xc9JkRcc2l@ z-mo;&Iya-f#Z`U75>ADlhMJw}WFMk4FdP_L?=NYYq+-XbTfefHb@?JWDheWvCHrIj z&jm;4>L#dPrU22$5ZNdPc*2_sn=FVqauQY2h!*pm*LdiS$UKqn z+P1v;{o(>U+h;mg(%>C@&Xh4AG6M{3l_;_O^A45_|4Vk{{$8LBBoY^?lZbrjM{*oW zSW!Odz6!j@1qRZJt!Iq8Lr&u2J6HeB-e>lJ&0ADFpzW?+(gtPF;pb>A-`bvK4=wq1L>Tk|jh0g-bEXHFyvi|>sAZy$fOtK9*8QeIPd1fq z){m7G1~0kX`xnalWgQyErvD^dG0FW8w=T?{h1AI|O;{FHtmh@)+$y2TpX6-~qWA!NA7ZmR2f{xEaP|~le z+bNCJ>P(LUqg^iEKS3x5G_i_U?hRZ^Rl#2ItWswN3&TuT{aLxGm%eA7_jXX5v9?vA zns{N#;l&5{{mdnoEA>n$+o*>AJl3W?-L$)T@MjzeNmYM%8(Z&y=4Y(oDk!6>k8%Y88zA|j^)48eEj6+=iX;2N6p zeDjAlEuDPbr@O<~|5K8>h~(6QRZ!AO;Q~sl2U&B?muOqxM&E!8i0ZPLdX?5NM-1rd z?$(%L=?rnI<7X9m6Ru_TweWfod}-crXJo??S`t6i+a#uvS{5tQ;P)6`H~f~y>^Az` zr*^dZu(ni5BmAz)ext0O-j$AiAf3!IOuxiJ8((jT!KaToY?N&s+E&Xcz4K~nenS{R zydDH)Dy634yv0V{t)q!HJt`?SKs<`)F6wEc804`0vwPm@=;KEzLvKlM&Fif7t%}vG zGS1!0uhRT;7M}Wmuwn!%z-CMZyR%*-OkpW)7%w@1|684(2lX)&avbmLW^eBwrQ3A_ zHuP%FEkD{XAOm`)3c}ukYhCEmKvbhe8rH*VwIdUwiz!Nh{sW>S^7kPSXPT6XcQlM2 zWlEloEG(XW9T9)&>~C<HDa(wZyb!3-&Y%Akn41naM0ZU0TfN@QtG4P=$7z$;6` zSy})Gi*@CTzBm!i8}zLj3fUM%cXac;x_|VG-j$bEWW1;-f^>fD#}?8dKxNx+b!$zf zn$KnN<@%G*Nq(+KWv|Vew});NI=&}Q?7TaVoB95^AgTCf_K#I|1`*u(x@ceRB(OjA z_LU4>?x&(2e#XJN8b%Iuvm0v)ugo)PDohYvOt|LS?-kiME)`~>2Uo?e)#M{(D*b@8 zf`N%xHxCz?gbdlA%Gf1lRbnr=tuC{50$)d>KhtWOb#7)}cW-}>t;^w)!tGxk{0KPO z(@|BIfZLRha_Zu&4lW^?=LdU=Bg)U96)htV%!Yd_%eK$M*QT!PnCGXxl zXGl#=a&^EnZ8^6-QX;(58zK?*BbHK&>ml*nzjTr>BLf9w{oywsyFgNL@$VB7be=YX{%`8M7E|r}GRfZLn*qgpU+)XERmO0r{De_X%(I zcZW9>Fmi3LdG(IUb!`bai$8Gag$y0{|UWH-sw~u)M*JE-`fqKqF6AW3;O;^g?G` zEXQMNiD?Fq1?0jkAn_Rj$|gI?;4&VY`Gk4$L$N6uOI3Ae2sMQ{C!<=?54IM{yd?zh zIePNREz?w<(PhpOv-PhqXA*;ZtC`=-WJN5ygyMzoTIKwP#K;N79 zhJ6u>jB`cTz-vZF*d!DvMl8L`;z}%ka8gdN&2$22oZXFH7{bz-6J4j*mqSFYy?#PA zi=Bu`R%DCY(DBcV_ceUofqiEvu+^qpIUUriH`g2*%`c8H+&zwtlkTG^8!;^OG&sI< zLuBud&;I~y?2A84)Q#1KOa&c)K?czXeRflAGds-hx#38k=`)0};_UNZ9f^uWHtky( zB17U(_4$>m(!%?e>aT6p1Sh8#KPNo=#Iy-OoeK*6P@b_Y@so$s9`cl^CuObr2g$3U zuJ3gNin0rpGYr!v8Gs}ZF*cqiI~%jPS<~_BOgvRnVD7O8bboZ94aEoZG5seveYi7` ztE1kr>Yl!5WaY2+g!Rh~nWHnc!56F0mE6rBI{HWp&##pq?3w%}<)`MtsS|l-4E>As zrmOUpKi#wf9_%1JqBw_=%qzd60P?i$zb6D?^K}(eTA@>~Qn0pWfBsTLJf4Zpyy+Y1PZO;}@SESkIStB$3U7SE20>FQ`;;siXkfEI=sJ#YK?8gN0pc zgqgk__&>QCvGyaI%iTR(t0#sVjiDm&bLc`6)l7!;b7WU3$vlAe&x}-8bh0Dm_aBy( z^M$M7DL$@86otB32PQ+u9W5f+B%x;WDN=DNz~D3(1vUmgCyl+8^%(`joNxrTSW|Nt zRFQ!|u0I{Xbnl)p(qloEqi)e~yL{u^6^$5XTKcs;gfR^F}reN1sfZRNY^ z^UJp;+M=)c7OzqZB}osEA5lDPG{h9$9l$E5%dL^^;-WjKL{O=^A2OoO< zCa$HXl}U!SHr%Qnz6vR5Dv(e@4aEu2c&K+^EsMIG9GqDg7+6G9N+0i64xG00Qv6)EtTYQuGBf0?)UJ8C+}3PHv7IV&@^`((3j1n)0h8X|g4CVaR=#?#xVx!1 zH~b^9q!zDb6_?D$r4Y$qv{SK5%%!`$d2oG8Vcw=d(xKr0CFk4UYkt+G(8z_6kkGj~ z0q21DaJZFu@Rg;ztA2`F%qP5F1@>*Q{IEs=>)789-x;XbjpoMlH?Er|8pMj(vvaZm z*V2T9(KPMD5rO;%9tY#C`ojG%6J(jN?SSV=bw4bOR&^m z{78tCKOC_qmxt%wH9GvzpnCN{KQ9VqJUtuH7GXNKG8~D7L~R{uYT$H*AZ?^{pn&+5 z)$uwVI0$ zL-qGgdTxFDQJWo2qr~O%ZbZZYbJX>6lAwUyg?wHt??3OHrP$hmbiVu^43jv1g@0j!VR~Jd4-5p_`DN$=gE^wd?Dp-QN8r9~btBo>}tr;INYQ>ulNb`kN~72C`6+HbZS=+c2aeXZ5~&0BYcc

    Acrih5KDW9ugPSk3y-@mXy^-HnY=Jp77llly?k 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 div + > .el-submenu + > .el-submenu__title + .el-submenu__icon-arrow { + display: none; +} \ No newline at end of file diff --git a/ruoyi-ui/src/assets/styles/element-variables.scss b/ruoyi-ui/src/assets/styles/element-variables.scss new file mode 100644 index 000000000..1615ff289 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/element-variables.scss @@ -0,0 +1,31 @@ +/** +* I think element-ui's default theme color is too light for long-term use. +* So I modified the default color and you can modify it to your liking. +**/ + +/* theme color */ +$--color-primary: #1890ff; +$--color-success: #13ce66; +$--color-warning: #ffba00; +$--color-danger: #ff4949; +// $--color-info: #1E1E1E; + +$--button-font-weight: 400; + +// $--color-text-regular: #1f2d3d; + +$--border-color-light: #dfe4ed; +$--border-color-lighter: #e6ebf5; + +$--table-border: 1px solid #dfe6ec; + +/* icon font path, required */ +$--font-path: '~element-ui/lib/theme-chalk/fonts'; + +@import "~element-ui/packages/theme-chalk/src/index"; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + theme: $--color-primary; +} diff --git a/ruoyi-ui/src/assets/styles/index.scss b/ruoyi-ui/src/assets/styles/index.scss new file mode 100644 index 000000000..2f3b9ef9c --- /dev/null +++ b/ruoyi-ui/src/assets/styles/index.scss @@ -0,0 +1,182 @@ +@import './variables.scss'; +@import './mixin.scss'; +@import './transition.scss'; +@import './element-ui.scss'; +@import './sidebar.scss'; +@import './btn.scss'; + +body { + height: 100%; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +.no-padding { + padding: 0px !important; +} + +.padding-content { + padding: 4px 0; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.pr-5 { + padding-right: 5px; +} + +.pl-5 { + padding-left: 5px; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.inlineBlock { + display: block; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +} + +aside { + background: #eef1f6; + padding: 8px 24px; + margin-bottom: 20px; + border-radius: 2px; + display: block; + line-height: 32px; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + color: #2c3e50; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + a { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } + } +} + +//main-container全局样式 +.app-container { + padding: 20px; +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +.pagination-container { + margin-top: 30px; +} + +.text-center { + text-align: center +} + +.sub-navbar { + height: 50px; + line-height: 50px; + position: relative; + width: 100%; + text-align: right; + padding-right: 20px; + transition: 600ms ease position; + background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); + + .subtitle { + font-size: 20px; + color: #fff; + } + + &.draft { + background: #d0d0d0; + } + + &.deleted { + background: #d0d0d0; + } +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } +} + +.filter-container { + padding-bottom: 10px; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: 10px; + } +} diff --git a/ruoyi-ui/src/assets/styles/mixin.scss b/ruoyi-ui/src/assets/styles/mixin.scss new file mode 100644 index 000000000..06fa06125 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/mixin.scss @@ -0,0 +1,66 @@ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} + +@mixin pct($pct) { + width: #{$pct}; + position: relative; + margin: 0 auto; +} + +@mixin triangle($width, $height, $color, $direction) { + $width: $width/2; + $color-border-style: $height solid $color; + $transparent-border-style: $width solid transparent; + height: 0; + width: 0; + + @if $direction==up { + border-bottom: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==right { + border-left: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } + + @else if $direction==down { + border-top: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==left { + border-right: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } +} diff --git a/ruoyi-ui/src/assets/styles/ruoyi.scss b/ruoyi-ui/src/assets/styles/ruoyi.scss new file mode 100644 index 000000000..4e298744c --- /dev/null +++ b/ruoyi-ui/src/assets/styles/ruoyi.scss @@ -0,0 +1,291 @@ +/** +* 通用css样式布局处理 +* Copyright (c) 2019 ruoyi +*/ + +/** 基础通用 **/ +.pt5 { + padding-top: 5px; +} + +.pr5 { + padding-right: 5px; +} + +.pb5 { + padding-bottom: 5px; +} + +.mt5 { + margin-top: 5px; +} + +.mr5 { + margin-right: 5px; +} + +.mb5 { + margin-bottom: 5px; +} + +.mb8 { + margin-bottom: 8px; +} + +.ml5 { + margin-left: 5px; +} + +.mt10 { + margin-top: 10px; +} + +.mr10 { + margin-right: 10px; +} + +.mb10 { + margin-bottom: 10px; +} +.ml10 { + margin-left: 10px; +} + +.mt20 { + margin-top: 20px; +} + +.mr20 { + margin-right: 20px; +} + +.mb20 { + margin-bottom: 20px; +} +.ml20 { + margin-left: 20px; +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.el-message-box__status + .el-message-box__message{ + word-break: break-word; +} + +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + .el-table__header-wrapper, .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #f8f8f9; + color: #515a6e; + height: 40px; + font-size: 13px; + } + } + + .el-table__body-wrapper { + .el-button [class*="el-icon-"] + span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size: 15px; + color: #6379bb; + border-bottom: 1px solid #ddd; + margin: 8px 10px 25px 10px; + padding-bottom: 5px +} + +/** 表格布局 **/ +.pagination-container { + position: relative; + height: 25px; + margin-bottom: 10px; + margin-top: 15px; + padding: 10px 20px !important; +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid #e5e6e7; + background: #FFFFFF none; + border-radius: 4px; +} + +.pagination-container .el-pagination { + right: 0; + position: absolute; +} + +@media (max-width: 768px) { + .pagination-container .el-pagination > .el-pagination__jump { + display: none !important; + } + .pagination-container .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-table .fixed-width .el-button--mini { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link,.el-table .el-dropdown-selfdefine { + cursor: pointer; + margin-left: 5px; +} + +.el-table .el-dropdown, .el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content > .el-checkbox { + margin-right: 8px; +} + +.list-group-striped > .list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 7px; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px; +} + +.card-box { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48D1CC; + border-color: #48D1CC; + color: #FFFFFF; +} + +.el-button--cyan { + background-color: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: relative; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost { + opacity: .8; + color: #fff !important; + background: #42b983 !important; +} + +.top-right-btn { + position: relative; + float: right; +} diff --git a/ruoyi-ui/src/assets/styles/sidebar.scss b/ruoyi-ui/src/assets/styles/sidebar.scss new file mode 100644 index 000000000..abe5b6317 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/sidebar.scss @@ -0,0 +1,227 @@ +#app { + + .main-container { + height: 100%; + transition: margin-left .28s; + margin-left: $base-sidebar-width; + position: relative; + } + + .sidebarHide { + margin-left: 0!important; + } + + .sidebar-container { + -webkit-transition: width .28s; + transition: width 0.28s; + width: $base-sidebar-width !important; + background-color: $base-menu-background; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); + box-shadow: 2px 0 6px rgba(0,21,41,.35); + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + .el-menu-item, .el-submenu__title { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } + + // menu hover + .submenu-title-noDropdown, + .el-submenu__title { + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .is-active > .el-submenu__title { + color: $base-menu-color-active !important; + } + + & .nest-menu .el-submenu>.el-submenu__title, + & .el-submenu .el-menu-item { + min-width: $base-sidebar-width !important; + + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .nest-menu .el-submenu>.el-submenu__title, + & .theme-dark .el-submenu .el-menu-item { + background-color: $base-sub-menu-background !important; + + &:hover { + background-color: $base-sub-menu-hover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .submenu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-submenu { + overflow: hidden; + + &>.el-submenu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + } + } + + .el-menu--collapse { + .el-submenu { + &>.el-submenu__title { + &>span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-submenu { + min-width: $base-sidebar-width !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform .28s; + width: $base-sidebar-width !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$base-sidebar-width, 0, 0); + } + } + } + + .withoutAnimation { + + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + &>.el-menu { + .svg-icon { + margin-right: 16px; + } + } + + .nest-menu .el-submenu>.el-submenu__title, + .el-menu-item { + &:hover { + // you can use $subMenuHover + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + // the scroll bar appears when the subMenu is too long + >.el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/ruoyi-ui/src/assets/styles/transition.scss b/ruoyi-ui/src/assets/styles/transition.scss new file mode 100644 index 000000000..073f8c6ce --- /dev/null +++ b/ruoyi-ui/src/assets/styles/transition.scss @@ -0,0 +1,49 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform--move, +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all .5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all .5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all .5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/ruoyi-ui/src/assets/styles/variables.scss b/ruoyi-ui/src/assets/styles/variables.scss new file mode 100644 index 000000000..34484d47e --- /dev/null +++ b/ruoyi-ui/src/assets/styles/variables.scss @@ -0,0 +1,54 @@ +// base color +$blue:#324157; +$light-blue:#3A71A8; +$red:#C03639; +$pink: #E65D6E; +$green: #30B08F; +$tiffany: #4AB7BD; +$yellow:#FEC171; +$panGreen: #30B08F; + +// 默认菜单主题风格 +$base-menu-color:#bfcbd9; +$base-menu-color-active:#f4f4f5; +$base-menu-background:#304156; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#1f2d3d; +$base-sub-menu-hover:#001528; + +// 自定义暗色菜单风格 +/** +$base-menu-color:hsla(0,0%,100%,.65); +$base-menu-color-active:#fff; +$base-menu-background:#001529; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#000c17; +$base-sub-menu-hover:#001528; +*/ + +$base-sidebar-width: 200px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuColor: $base-menu-color; + menuLightColor: $base-menu-light-color; + menuColorActive: $base-menu-color-active; + menuBackground: $base-menu-background; + menuLightBackground: $base-menu-light-background; + subMenuBackground: $base-sub-menu-background; + subMenuHover: $base-sub-menu-hover; + sideBarWidth: $base-sidebar-width; + logoTitleColor: $base-logo-title-color; + logoLightTitleColor: $base-logo-light-title-color +} diff --git a/ruoyi-ui/src/components/Breadcrumb/index.vue b/ruoyi-ui/src/components/Breadcrumb/index.vue new file mode 100644 index 000000000..1696f5471 --- /dev/null +++ b/ruoyi-ui/src/components/Breadcrumb/index.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/ruoyi-ui/src/components/Crontab/day.vue b/ruoyi-ui/src/components/Crontab/day.vue new file mode 100644 index 000000000..fe3eaf0c4 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/day.vue @@ -0,0 +1,161 @@ + + + diff --git a/ruoyi-ui/src/components/Crontab/hour.vue b/ruoyi-ui/src/components/Crontab/hour.vue new file mode 100644 index 000000000..4b1f1fcdb --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/hour.vue @@ -0,0 +1,114 @@ + + + diff --git a/ruoyi-ui/src/components/Crontab/index.vue b/ruoyi-ui/src/components/Crontab/index.vue new file mode 100644 index 000000000..3963df28e --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/index.vue @@ -0,0 +1,430 @@ + + + + diff --git a/ruoyi-ui/src/components/Crontab/min.vue b/ruoyi-ui/src/components/Crontab/min.vue new file mode 100644 index 000000000..43cab900d --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/min.vue @@ -0,0 +1,116 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/components/Crontab/month.vue b/ruoyi-ui/src/components/Crontab/month.vue new file mode 100644 index 000000000..fd0ac384f --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/month.vue @@ -0,0 +1,114 @@ + + + diff --git a/ruoyi-ui/src/components/Crontab/result.vue b/ruoyi-ui/src/components/Crontab/result.vue new file mode 100644 index 000000000..aea6e0e46 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/result.vue @@ -0,0 +1,559 @@ + + + diff --git a/ruoyi-ui/src/components/Crontab/second.vue b/ruoyi-ui/src/components/Crontab/second.vue new file mode 100644 index 000000000..e7b776171 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/second.vue @@ -0,0 +1,117 @@ + + + diff --git a/ruoyi-ui/src/components/Crontab/week.vue b/ruoyi-ui/src/components/Crontab/week.vue new file mode 100644 index 000000000..1cec700e8 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/week.vue @@ -0,0 +1,202 @@ + + + diff --git a/ruoyi-ui/src/components/Crontab/year.vue b/ruoyi-ui/src/components/Crontab/year.vue new file mode 100644 index 000000000..5487a6c7f --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/year.vue @@ -0,0 +1,131 @@ + + + diff --git a/ruoyi-ui/src/components/DictData/index.js b/ruoyi-ui/src/components/DictData/index.js new file mode 100644 index 000000000..7b85d4aaa --- /dev/null +++ b/ruoyi-ui/src/components/DictData/index.js @@ -0,0 +1,49 @@ +import Vue from 'vue' +import store from '@/store' +import DataDict from '@/utils/dict' +import { getDicts as getDicts } from '@/api/system/dict/data' + +function searchDictByKey(dict, key) { + if (key == null && key == "") { + return null + } + try { + for (let i = 0; i < dict.length; i++) { + if (dict[i].key == key) { + return dict[i].value + } + } + } catch (e) { + return null + } +} + +function install() { + Vue.use(DataDict, { + metas: { + '*': { + labelField: 'dictLabel', + valueField: 'dictValue', + request(dictMeta) { + const storeDict = searchDictByKey(store.getters.dict, dictMeta.type) + if (storeDict) { + return new Promise(resolve => { resolve(storeDict) }) + } else { + return new Promise((resolve, reject) => { + getDicts(dictMeta.type).then(res => { + store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data }) + resolve(res.data) + }).catch(error => { + reject(error) + }) + }) + } + }, + }, + }, + }) +} + +export default { + install, +} \ No newline at end of file diff --git a/ruoyi-ui/src/components/DictTag/index.vue b/ruoyi-ui/src/components/DictTag/index.vue new file mode 100644 index 000000000..6b5b230f5 --- /dev/null +++ b/ruoyi-ui/src/components/DictTag/index.vue @@ -0,0 +1,89 @@ + + + + diff --git a/ruoyi-ui/src/components/Editor/index.vue b/ruoyi-ui/src/components/Editor/index.vue new file mode 100644 index 000000000..8981d7635 --- /dev/null +++ b/ruoyi-ui/src/components/Editor/index.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/ruoyi-ui/src/components/FileUpload/index.vue b/ruoyi-ui/src/components/FileUpload/index.vue new file mode 100644 index 000000000..c7f6b0af0 --- /dev/null +++ b/ruoyi-ui/src/components/FileUpload/index.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/ruoyi-ui/src/components/Hamburger/index.vue b/ruoyi-ui/src/components/Hamburger/index.vue new file mode 100644 index 000000000..368b00215 --- /dev/null +++ b/ruoyi-ui/src/components/Hamburger/index.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/ruoyi-ui/src/components/HeaderSearch/index.vue b/ruoyi-ui/src/components/HeaderSearch/index.vue new file mode 100644 index 000000000..7d6780be9 --- /dev/null +++ b/ruoyi-ui/src/components/HeaderSearch/index.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/ruoyi-ui/src/components/IconSelect/index.vue b/ruoyi-ui/src/components/IconSelect/index.vue new file mode 100644 index 000000000..8dadc028a --- /dev/null +++ b/ruoyi-ui/src/components/IconSelect/index.vue @@ -0,0 +1,104 @@ + + + + + + diff --git a/ruoyi-ui/src/components/IconSelect/requireIcons.js b/ruoyi-ui/src/components/IconSelect/requireIcons.js new file mode 100644 index 000000000..99e5c54cc --- /dev/null +++ b/ruoyi-ui/src/components/IconSelect/requireIcons.js @@ -0,0 +1,11 @@ + +const req = require.context('../../assets/icons/svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys() + +const re = /\.\/(.*)\.svg/ + +const icons = requireAll(req).map(i => { + return i.match(re)[1] +}) + +export default icons diff --git a/ruoyi-ui/src/components/ImagePreview/index.vue b/ruoyi-ui/src/components/ImagePreview/index.vue new file mode 100644 index 000000000..3c770c703 --- /dev/null +++ b/ruoyi-ui/src/components/ImagePreview/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/ruoyi-ui/src/components/ImageUpload/index.vue b/ruoyi-ui/src/components/ImageUpload/index.vue new file mode 100644 index 000000000..2e64c9b8f --- /dev/null +++ b/ruoyi-ui/src/components/ImageUpload/index.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/ruoyi-ui/src/components/Pagination/index.vue b/ruoyi-ui/src/components/Pagination/index.vue new file mode 100644 index 000000000..56f5a6b9d --- /dev/null +++ b/ruoyi-ui/src/components/Pagination/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/ruoyi-ui/src/components/PanThumb/index.vue b/ruoyi-ui/src/components/PanThumb/index.vue new file mode 100644 index 000000000..1bcf41709 --- /dev/null +++ b/ruoyi-ui/src/components/PanThumb/index.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/ruoyi-ui/src/components/ParentView/index.vue b/ruoyi-ui/src/components/ParentView/index.vue new file mode 100644 index 000000000..7bf614897 --- /dev/null +++ b/ruoyi-ui/src/components/ParentView/index.vue @@ -0,0 +1,3 @@ + diff --git a/ruoyi-ui/src/components/RightPanel/index.vue b/ruoyi-ui/src/components/RightPanel/index.vue new file mode 100644 index 000000000..5abeecb00 --- /dev/null +++ b/ruoyi-ui/src/components/RightPanel/index.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/ruoyi-ui/src/components/RightToolbar/index.vue b/ruoyi-ui/src/components/RightToolbar/index.vue new file mode 100644 index 000000000..67da29300 --- /dev/null +++ b/ruoyi-ui/src/components/RightToolbar/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/ruoyi-ui/src/components/RuoYi/Doc/index.vue b/ruoyi-ui/src/components/RuoYi/Doc/index.vue new file mode 100644 index 000000000..75fa86410 --- /dev/null +++ b/ruoyi-ui/src/components/RuoYi/Doc/index.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/components/RuoYi/Git/index.vue b/ruoyi-ui/src/components/RuoYi/Git/index.vue new file mode 100644 index 000000000..bdafbaefa --- /dev/null +++ b/ruoyi-ui/src/components/RuoYi/Git/index.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/components/Screenfull/index.vue b/ruoyi-ui/src/components/Screenfull/index.vue new file mode 100644 index 000000000..d4e539c26 --- /dev/null +++ b/ruoyi-ui/src/components/Screenfull/index.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/ruoyi-ui/src/components/SizeSelect/index.vue b/ruoyi-ui/src/components/SizeSelect/index.vue new file mode 100644 index 000000000..069b5de9b --- /dev/null +++ b/ruoyi-ui/src/components/SizeSelect/index.vue @@ -0,0 +1,56 @@ + + + diff --git a/ruoyi-ui/src/components/SvgIcon/index.vue b/ruoyi-ui/src/components/SvgIcon/index.vue new file mode 100644 index 000000000..e4bf5ade1 --- /dev/null +++ b/ruoyi-ui/src/components/SvgIcon/index.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/ruoyi-ui/src/components/ThemePicker/index.vue b/ruoyi-ui/src/components/ThemePicker/index.vue new file mode 100644 index 000000000..1714e1f39 --- /dev/null +++ b/ruoyi-ui/src/components/ThemePicker/index.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/ruoyi-ui/src/components/TopNav/index.vue b/ruoyi-ui/src/components/TopNav/index.vue new file mode 100644 index 000000000..86a91c4cf --- /dev/null +++ b/ruoyi-ui/src/components/TopNav/index.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/ruoyi-ui/src/components/iFrame/index.vue b/ruoyi-ui/src/components/iFrame/index.vue new file mode 100644 index 000000000..426857fb1 --- /dev/null +++ b/ruoyi-ui/src/components/iFrame/index.vue @@ -0,0 +1,36 @@ + + + diff --git a/ruoyi-ui/src/layout/components/Navbar.vue b/ruoyi-ui/src/layout/components/Navbar.vue new file mode 100644 index 000000000..466cd981e --- /dev/null +++ b/ruoyi-ui/src/layout/components/Navbar.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/ruoyi-ui/src/layout/components/Settings/index.vue b/ruoyi-ui/src/layout/components/Settings/index.vue new file mode 100644 index 000000000..bb3c9ce90 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Settings/index.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js b/ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js new file mode 100644 index 000000000..682372699 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js @@ -0,0 +1,25 @@ +export default { + computed: { + device() { + return this.$store.state.app.device + } + }, + mounted() { + // In order to fix the click on menu on the ios device will trigger the mouseleave bug + this.fixBugIniOS() + }, + methods: { + fixBugIniOS() { + const $subMenu = this.$refs.subMenu + if ($subMenu) { + const handleMouseleave = $subMenu.handleMouseleave + $subMenu.handleMouseleave = (e) => { + if (this.device === 'mobile') { + return + } + handleMouseleave(e) + } + } + } + } +} diff --git a/ruoyi-ui/src/layout/components/Sidebar/Item.vue b/ruoyi-ui/src/layout/components/Sidebar/Item.vue new file mode 100644 index 000000000..be3285dfb --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/Item.vue @@ -0,0 +1,33 @@ + diff --git a/ruoyi-ui/src/layout/components/Sidebar/Link.vue b/ruoyi-ui/src/layout/components/Sidebar/Link.vue new file mode 100644 index 000000000..8b0bc93bd --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,43 @@ + + + diff --git a/ruoyi-ui/src/layout/components/Sidebar/Logo.vue b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 000000000..2774cc836 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue b/ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 000000000..82ba40786 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,100 @@ + + + diff --git a/ruoyi-ui/src/layout/components/Sidebar/index.vue b/ruoyi-ui/src/layout/components/Sidebar/index.vue new file mode 100644 index 000000000..51d0839f3 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue b/ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 000000000..bb753a124 --- /dev/null +++ b/ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/ruoyi-ui/src/layout/components/TagsView/index.vue b/ruoyi-ui/src/layout/components/TagsView/index.vue new file mode 100644 index 000000000..96585a5b1 --- /dev/null +++ b/ruoyi-ui/src/layout/components/TagsView/index.vue @@ -0,0 +1,332 @@ + + + + + + + diff --git a/ruoyi-ui/src/layout/components/index.js b/ruoyi-ui/src/layout/components/index.js new file mode 100644 index 000000000..104bd3ac6 --- /dev/null +++ b/ruoyi-ui/src/layout/components/index.js @@ -0,0 +1,5 @@ +export { default as AppMain } from './AppMain' +export { default as Navbar } from './Navbar' +export { default as Settings } from './Settings' +export { default as Sidebar } from './Sidebar/index.vue' +export { default as TagsView } from './TagsView/index.vue' diff --git a/ruoyi-ui/src/layout/index.vue b/ruoyi-ui/src/layout/index.vue new file mode 100644 index 000000000..dba4393da --- /dev/null +++ b/ruoyi-ui/src/layout/index.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/ruoyi-ui/src/layout/mixin/ResizeHandler.js b/ruoyi-ui/src/layout/mixin/ResizeHandler.js new file mode 100644 index 000000000..e8d0df8c2 --- /dev/null +++ b/ruoyi-ui/src/layout/mixin/ResizeHandler.js @@ -0,0 +1,45 @@ +import store from '@/store' + +const { body } = document +const WIDTH = 992 // refer to Bootstrap's responsive design + +export default { + watch: { + $route(route) { + if (this.device === 'mobile' && this.sidebar.opened) { + store.dispatch('app/closeSideBar', { withoutAnimation: false }) + } + } + }, + beforeMount() { + window.addEventListener('resize', this.$_resizeHandler) + }, + beforeDestroy() { + window.removeEventListener('resize', this.$_resizeHandler) + }, + mounted() { + const isMobile = this.$_isMobile() + if (isMobile) { + store.dispatch('app/toggleDevice', 'mobile') + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_isMobile() { + const rect = body.getBoundingClientRect() + return rect.width - 1 < WIDTH + }, + $_resizeHandler() { + if (!document.hidden) { + const isMobile = this.$_isMobile() + store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') + + if (isMobile) { + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + } + } + } +} diff --git a/ruoyi-ui/src/main.js b/ruoyi-ui/src/main.js new file mode 100644 index 000000000..13c6cf290 --- /dev/null +++ b/ruoyi-ui/src/main.js @@ -0,0 +1,86 @@ +import Vue from 'vue' + +import Cookies from 'js-cookie' + +import Element from 'element-ui' +import './assets/styles/element-variables.scss' + +import '@/assets/styles/index.scss' // global css +import '@/assets/styles/ruoyi.scss' // ruoyi css +import App from './App' +import store from './store' +import router from './router' +import directive from './directive' // directive +import plugins from './plugins' // plugins +import { download } from '@/utils/request' + +import './assets/icons' // icon +import './permission' // permission control +import { getDicts } from "@/api/system/dict/data"; +import { getConfigKey } from "@/api/system/config"; +import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi"; +// 分页组件 +import Pagination from "@/components/Pagination"; +// 自定义表格工具组件 +import RightToolbar from "@/components/RightToolbar" +// 富文本组件 +import Editor from "@/components/Editor" +// 文件上传组件 +import FileUpload from "@/components/FileUpload" +// 图片上传组件 +import ImageUpload from "@/components/ImageUpload" +// 图片预览组件 +import ImagePreview from "@/components/ImagePreview" +// 字典标签组件 +import DictTag from '@/components/DictTag' +// 头部标签组件 +import VueMeta from 'vue-meta' +// 字典数据组件 +import DictData from '@/components/DictData' + +// 全局方法挂载 +Vue.prototype.getDicts = getDicts +Vue.prototype.getConfigKey = getConfigKey +Vue.prototype.parseTime = parseTime +Vue.prototype.resetForm = resetForm +Vue.prototype.addDateRange = addDateRange +Vue.prototype.selectDictLabel = selectDictLabel +Vue.prototype.selectDictLabels = selectDictLabels +Vue.prototype.download = download +Vue.prototype.handleTree = handleTree + +// 全局组件挂载 +Vue.component('DictTag', DictTag) +Vue.component('Pagination', Pagination) +Vue.component('RightToolbar', RightToolbar) +Vue.component('Editor', Editor) +Vue.component('FileUpload', FileUpload) +Vue.component('ImageUpload', ImageUpload) +Vue.component('ImagePreview', ImagePreview) + +Vue.use(directive) +Vue.use(plugins) +Vue.use(VueMeta) +DictData.install() + +/** + * If you don't want to use mock-server + * you want to use MockJs for mock api + * you can execute: mockXHR() + * + * Currently MockJs will be used in the production environment, + * please remove it before going online! ! ! + */ + +Vue.use(Element, { + size: Cookies.get('size') || 'medium' // set element-ui default size +}) + +Vue.config.productionTip = false + +new Vue({ + el: '#app', + router, + store, + render: h => h(App) +}) diff --git a/ruoyi-ui/src/permission.js b/ruoyi-ui/src/permission.js new file mode 100644 index 000000000..c56897902 --- /dev/null +++ b/ruoyi-ui/src/permission.js @@ -0,0 +1,58 @@ +import router from './router' +import store from './store' +import { Message } from 'element-ui' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import { getToken } from '@/utils/auth' +import { isRelogin } from '@/utils/request' + +NProgress.configure({ showSpinner: false }) + +const whiteList = ['/login', '/register'] + +router.beforeEach((to, from, next) => { + NProgress.start() + if (getToken()) { + to.meta.title && store.dispatch('settings/setTitle', to.meta.title) + /* has token*/ + if (to.path === '/login') { + next({ path: '/' }) + NProgress.done() + } else if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + if (store.getters.roles.length === 0) { + isRelogin.show = true + // 判断当前用户是否已拉取完user_info信息 + store.dispatch('GetInfo').then(() => { + isRelogin.show = false + store.dispatch('GenerateRoutes').then(accessRoutes => { + // 根据roles权限生成可访问的路由表 + router.addRoutes(accessRoutes) // 动态添加可访问路由表 + next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + }) + }).catch(err => { + store.dispatch('LogOut').then(() => { + Message.error(err) + next({ path: '/' }) + }) + }) + } else { + next() + } + } + } else { + // 没有token + if (whiteList.indexOf(to.path) !== -1) { + // 在免登录白名单,直接进入 + next() + } else { + next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页 + NProgress.done() + } + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/ruoyi-ui/src/plugins/auth.js b/ruoyi-ui/src/plugins/auth.js new file mode 100644 index 000000000..6c6bc2428 --- /dev/null +++ b/ruoyi-ui/src/plugins/auth.js @@ -0,0 +1,60 @@ +import store from '@/store' + +function authPermission(permission) { + const all_permission = "*:*:*"; + const permissions = store.getters && store.getters.permissions + if (permission && permission.length > 0) { + return permissions.some(v => { + return all_permission === v || v === permission + }) + } else { + return false + } +} + +function authRole(role) { + const super_admin = "admin"; + const roles = store.getters && store.getters.roles + if (role && role.length > 0) { + return roles.some(v => { + return super_admin === v || v === role + }) + } else { + return false + } +} + +export default { + // 验证用户是否具备某权限 + hasPermi(permission) { + return authPermission(permission); + }, + // 验证用户是否含有指定权限,只需包含其中一个 + hasPermiOr(permissions) { + return permissions.some(item => { + return authPermission(item) + }) + }, + // 验证用户是否含有指定权限,必须全部拥有 + hasPermiAnd(permissions) { + return permissions.every(item => { + return authPermission(item) + }) + }, + // 验证用户是否具备某角色 + hasRole(role) { + return authRole(role); + }, + // 验证用户是否含有指定角色,只需包含其中一个 + hasRoleOr(roles) { + return roles.some(item => { + return authRole(item) + }) + }, + // 验证用户是否含有指定角色,必须全部拥有 + hasRoleAnd(roles) { + return roles.every(item => { + return authRole(item) + }) + } +} diff --git a/ruoyi-ui/src/plugins/cache.js b/ruoyi-ui/src/plugins/cache.js new file mode 100644 index 000000000..6b5c00b9e --- /dev/null +++ b/ruoyi-ui/src/plugins/cache.js @@ -0,0 +1,77 @@ +const sessionCache = { + set (key, value) { + if (!sessionStorage) { + return + } + if (key != null && value != null) { + sessionStorage.setItem(key, value) + } + }, + get (key) { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + return sessionStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + sessionStorage.removeItem(key); + } +} +const localCache = { + set (key, value) { + if (!localStorage) { + return + } + if (key != null && value != null) { + localStorage.setItem(key, value) + } + }, + get (key) { + if (!localStorage) { + return null + } + if (key == null) { + return null + } + return localStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + localStorage.removeItem(key); + } +} + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, + /** + * 本地缓存 + */ + local: localCache +} diff --git a/ruoyi-ui/src/plugins/download.js b/ruoyi-ui/src/plugins/download.js new file mode 100644 index 000000000..42acd006f --- /dev/null +++ b/ruoyi-ui/src/plugins/download.js @@ -0,0 +1,79 @@ +import axios from 'axios' +import {Loading, Message} from 'element-ui' +import { saveAs } from 'file-saver' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate } from "@/utils/ruoyi"; + +const baseURL = process.env.VUE_APP_BASE_API +let downloadLoadingInstance; + +export default { + name(name, isDelete = true) { + var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + resource(resource) { + var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource); + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + zip(url, name) { + var url = baseURL + url + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/zip' }) + this.saveAs(blob, name) + } else { + this.printErrMsg(res.data); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) + }, + saveAs(text, name, opts) { + saveAs(text, name, opts); + }, + async printErrMsg(data) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } +} + diff --git a/ruoyi-ui/src/plugins/index.js b/ruoyi-ui/src/plugins/index.js new file mode 100644 index 000000000..d000f2dfa --- /dev/null +++ b/ruoyi-ui/src/plugins/index.js @@ -0,0 +1,20 @@ +import tab from './tab' +import auth from './auth' +import cache from './cache' +import modal from './modal' +import download from './download' + +export default { + install(Vue) { + // 页签操作 + Vue.prototype.$tab = tab + // 认证对象 + Vue.prototype.$auth = auth + // 缓存对象 + Vue.prototype.$cache = cache + // 模态框对象 + Vue.prototype.$modal = modal + // 下载文件 + Vue.prototype.$download = download + } +} diff --git a/ruoyi-ui/src/plugins/modal.js b/ruoyi-ui/src/plugins/modal.js new file mode 100644 index 000000000..b37ca1457 --- /dev/null +++ b/ruoyi-ui/src/plugins/modal.js @@ -0,0 +1,83 @@ +import { Message, MessageBox, Notification, Loading } from 'element-ui' + +let loadingInstance; + +export default { + // 消息提示 + msg(content) { + Message.info(content) + }, + // 错误消息 + msgError(content) { + Message.error(content) + }, + // 成功消息 + msgSuccess(content) { + Message.success(content) + }, + // 警告消息 + msgWarning(content) { + Message.warning(content) + }, + // 弹出提示 + alert(content) { + MessageBox.alert(content, "系统提示") + }, + // 错误提示 + alertError(content) { + MessageBox.alert(content, "系统提示", { type: 'error' }) + }, + // 成功提示 + alertSuccess(content) { + MessageBox.alert(content, "系统提示", { type: 'success' }) + }, + // 警告提示 + alertWarning(content) { + MessageBox.alert(content, "系统提示", { type: 'warning' }) + }, + // 通知提示 + notify(content) { + Notification.info(content) + }, + // 错误通知 + notifyError(content) { + Notification.error(content); + }, + // 成功通知 + notifySuccess(content) { + Notification.success(content) + }, + // 警告通知 + notifyWarning(content) { + Notification.warning(content) + }, + // 确认窗体 + confirm(content) { + return MessageBox.confirm(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 提交内容 + prompt(content) { + return MessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 打开遮罩层 + loading(content) { + loadingInstance = Loading.service({ + lock: true, + text: content, + spinner: "el-icon-loading", + background: "rgba(0, 0, 0, 0.7)", + }) + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close(); + } +} diff --git a/ruoyi-ui/src/plugins/tab.js b/ruoyi-ui/src/plugins/tab.js new file mode 100644 index 000000000..fcde4197f --- /dev/null +++ b/ruoyi-ui/src/plugins/tab.js @@ -0,0 +1,71 @@ +import store from '@/store' +import router from '@/router'; + +export default { + // 刷新当前tab页签 + refreshPage(obj) { + const { path, query, matched } = router.currentRoute; + if (obj === undefined) { + matched.forEach((m) => { + if (m.components && m.components.default && m.components.default.name) { + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { + obj = { name: m.components.default.name, path: path, query: query }; + } + } + }); + } + return store.dispatch('tagsView/delCachedView', obj).then(() => { + const { path, query } = obj + router.replace({ + path: '/redirect' + path, + query: query + }) + }) + }, + // 关闭当前tab页签,打开新页签 + closeOpenPage(obj) { + store.dispatch("tagsView/delView", router.currentRoute); + if (obj !== undefined) { + return router.push(obj); + } + }, + // 关闭指定tab页签 + closePage(obj) { + if (obj === undefined) { + return store.dispatch('tagsView/delView', router.currentRoute).then(({ visitedViews }) => { + const latestView = visitedViews.slice(-1)[0] + if (latestView) { + return router.push(latestView.fullPath) + } + return router.push('/'); + }); + } + return store.dispatch('tagsView/delView', obj); + }, + // 关闭所有tab页签 + closeAllPage() { + return store.dispatch('tagsView/delAllViews'); + }, + // 关闭左侧tab页签 + closeLeftPage(obj) { + return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute); + }, + // 关闭右侧tab页签 + closeRightPage(obj) { + return store.dispatch('tagsView/delRightTags', obj || router.currentRoute); + }, + // 关闭其他tab页签 + closeOtherPage(obj) { + return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute); + }, + // 添加tab页签 + openPage(title, url, params) { + const obj = { path: url, meta: { title: title } } + store.dispatch('tagsView/addView', obj); + return router.push({ path: url, query: params }); + }, + // 修改tab页签 + updatePage(obj) { + return store.dispatch('tagsView/updateVisitedView', obj); + } +} diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js new file mode 100644 index 000000000..71907b696 --- /dev/null +++ b/ruoyi-ui/src/router/index.js @@ -0,0 +1,183 @@ +import Vue from 'vue' +import Router from 'vue-router' + +Vue.use(Router) + +/* Layout */ +import Layout from '@/layout' + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login'), + hidden: true + }, + { + path: '/register', + component: () => import('@/views/register'), + hidden: true + }, + { + path: '/404', + component: () => import('@/views/error/404'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/error/401'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: 'index', + children: [ + { + path: 'index', + component: () => import('@/views/index'), + name: 'Index', + meta: { title: '首页', icon: 'dashboard', affix: true } + } + ] + }, + { + path: '/user', + component: Layout, + hidden: true, + redirect: 'noredirect', + children: [ + { + path: 'profile', + component: () => import('@/views/system/user/profile/index'), + name: 'Profile', + meta: { title: '个人中心', icon: 'user' } + } + ] + } +] + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole'), + name: 'AuthRole', + meta: { title: '分配角色', activeMenu: '/system/user' } + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser'), + name: 'AuthUser', + meta: { title: '分配用户', activeMenu: '/system/role' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data'), + name: 'Data', + meta: { title: '字典数据', activeMenu: '/system/dict' } + } + ] + }, + { + path: '/monitor/job-log', + component: Layout, + hidden: true, + permissions: ['monitor:job:list'], + children: [ + { + path: 'index/:jobId(\\d+)', + component: () => import('@/views/monitor/job/log'), + name: 'JobLog', + meta: { title: '调度日志', activeMenu: '/monitor/job' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + path: 'index/:tableId(\\d+)', + component: () => import('@/views/tool/gen/editTable'), + name: 'GenEdit', + meta: { title: '修改生成配置', activeMenu: '/tool/gen' } + } + ] + } +] + +// 防止连续点击多次路由报错 +let routerPush = Router.prototype.push; +let routerReplace = Router.prototype.replace; +// push +Router.prototype.push = function push(location) { + return routerPush.call(this, location).catch(err => err) +} +// replace +Router.prototype.replace = function push(location) { + return routerReplace.call(this, location).catch(err => err) +} + +export default new Router({ + mode: 'history', // 去掉url中的# + scrollBehavior: () => ({ y: 0 }), + routes: constantRoutes +}) diff --git a/ruoyi-ui/src/settings.js b/ruoyi-ui/src/settings.js new file mode 100644 index 000000000..6a0b09f45 --- /dev/null +++ b/ruoyi-ui/src/settings.js @@ -0,0 +1,44 @@ +module.exports = { + /** + * 侧边栏主题 深色主题theme-dark,浅色主题theme-light + */ + sideTheme: 'theme-dark', + + /** + * 是否系统布局配置 + */ + showSettings: false, + + /** + * 是否显示顶部导航 + */ + topNav: false, + + /** + * 是否显示 tagsView + */ + tagsView: true, + + /** + * 是否固定头部 + */ + fixedHeader: false, + + /** + * 是否显示logo + */ + sidebarLogo: true, + + /** + * 是否显示动态标题 + */ + dynamicTitle: false, + + /** + * @type {string | array} 'production' | ['production', 'development'] + * @description Need show err logs component. + * The default is only used in the production env + * If you want to also use it in dev, you can pass ['production', 'development'] + */ + errorLog: 'production' +} diff --git a/ruoyi-ui/src/store/getters.js b/ruoyi-ui/src/store/getters.js new file mode 100644 index 000000000..8adb1b653 --- /dev/null +++ b/ruoyi-ui/src/store/getters.js @@ -0,0 +1,19 @@ +const getters = { + sidebar: state => state.app.sidebar, + size: state => state.app.size, + device: state => state.app.device, + dict: state => state.dict.dict, + visitedViews: state => state.tagsView.visitedViews, + cachedViews: state => state.tagsView.cachedViews, + token: state => state.user.token, + avatar: state => state.user.avatar, + name: state => state.user.name, + introduction: state => state.user.introduction, + roles: state => state.user.roles, + permissions: state => state.user.permissions, + permission_routes: state => state.permission.routes, + topbarRouters:state => state.permission.topbarRouters, + defaultRoutes:state => state.permission.defaultRoutes, + sidebarRouters:state => state.permission.sidebarRouters, +} +export default getters diff --git a/ruoyi-ui/src/store/index.js b/ruoyi-ui/src/store/index.js new file mode 100644 index 000000000..97aaef80e --- /dev/null +++ b/ruoyi-ui/src/store/index.js @@ -0,0 +1,25 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import app from './modules/app' +import dict from './modules/dict' +import user from './modules/user' +import tagsView from './modules/tagsView' +import permission from './modules/permission' +import settings from './modules/settings' +import getters from './getters' + +Vue.use(Vuex) + +const store = new Vuex.Store({ + modules: { + app, + dict, + user, + tagsView, + permission, + settings + }, + getters +}) + +export default store diff --git a/ruoyi-ui/src/store/modules/app.js b/ruoyi-ui/src/store/modules/app.js new file mode 100644 index 000000000..3e22d1c16 --- /dev/null +++ b/ruoyi-ui/src/store/modules/app.js @@ -0,0 +1,66 @@ +import Cookies from 'js-cookie' + +const state = { + sidebar: { + opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, + withoutAnimation: false, + hide: false + }, + device: 'desktop', + size: Cookies.get('size') || 'medium' +} + +const mutations = { + TOGGLE_SIDEBAR: state => { + if (state.sidebar.hide) { + return false; + } + state.sidebar.opened = !state.sidebar.opened + state.sidebar.withoutAnimation = false + if (state.sidebar.opened) { + Cookies.set('sidebarStatus', 1) + } else { + Cookies.set('sidebarStatus', 0) + } + }, + CLOSE_SIDEBAR: (state, withoutAnimation) => { + Cookies.set('sidebarStatus', 0) + state.sidebar.opened = false + state.sidebar.withoutAnimation = withoutAnimation + }, + TOGGLE_DEVICE: (state, device) => { + state.device = device + }, + SET_SIZE: (state, size) => { + state.size = size + Cookies.set('size', size) + }, + SET_SIDEBAR_HIDE: (state, status) => { + state.sidebar.hide = status + } +} + +const actions = { + toggleSideBar({ commit }) { + commit('TOGGLE_SIDEBAR') + }, + closeSideBar({ commit }, { withoutAnimation }) { + commit('CLOSE_SIDEBAR', withoutAnimation) + }, + toggleDevice({ commit }, device) { + commit('TOGGLE_DEVICE', device) + }, + setSize({ commit }, size) { + commit('SET_SIZE', size) + }, + toggleSideBarHide({ commit }, status) { + commit('SET_SIDEBAR_HIDE', status) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/ruoyi-ui/src/store/modules/dict.js b/ruoyi-ui/src/store/modules/dict.js new file mode 100644 index 000000000..7a1b2f02b --- /dev/null +++ b/ruoyi-ui/src/store/modules/dict.js @@ -0,0 +1,50 @@ +const state = { + dict: new Array() +} +const mutations = { + SET_DICT: (state, { key, value }) => { + if (key !== null && key !== "") { + state.dict.push({ + key: key, + value: value + }) + } + }, + REMOVE_DICT: (state, key) => { + try { + for (let i = 0; i < state.dict.length; i++) { + if (state.dict[i].key == key) { + state.dict.splice(i, 1) + return true + } + } + } catch (e) { + } + }, + CLEAN_DICT: (state) => { + state.dict = new Array() + } +} + +const actions = { + // 设置字典 + setDict({ commit }, data) { + commit('SET_DICT', data) + }, + // 删除字典 + removeDict({ commit }, key) { + commit('REMOVE_DICT', key) + }, + // 清空字典 + cleanDict({ commit }) { + commit('CLEAN_DICT') + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js new file mode 100644 index 000000000..b3c216a83 --- /dev/null +++ b/ruoyi-ui/src/store/modules/permission.js @@ -0,0 +1,137 @@ +import auth from '@/plugins/auth' +import router, { constantRoutes, dynamicRoutes } from '@/router' +import { getRouters } from '@/api/menu' +import Layout from '@/layout/index' +import ParentView from '@/components/ParentView' +import InnerLink from '@/layout/components/InnerLink' + +const permission = { + state: { + routes: [], + addRoutes: [], + defaultRoutes: [], + topbarRouters: [], + sidebarRouters: [] + }, + mutations: { + SET_ROUTES: (state, routes) => { + state.addRoutes = routes + state.routes = constantRoutes.concat(routes) + }, + SET_DEFAULT_ROUTES: (state, routes) => { + state.defaultRoutes = constantRoutes.concat(routes) + }, + SET_TOPBAR_ROUTES: (state, routes) => { + state.topbarRouters = routes + }, + SET_SIDEBAR_ROUTERS: (state, routes) => { + state.sidebarRouters = routes + }, + }, + actions: { + // 生成路由 + GenerateRoutes({ commit }) { + return new Promise(resolve => { + // 向后端请求路由数据 + getRouters().then(res => { + const sdata = JSON.parse(JSON.stringify(res.data)) + const rdata = JSON.parse(JSON.stringify(res.data)) + const sidebarRoutes = filterAsyncRouter(sdata) + const rewriteRoutes = filterAsyncRouter(rdata, false, true) + const asyncRoutes = filterDynamicRoutes(dynamicRoutes); + rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) + router.addRoutes(asyncRoutes); + commit('SET_ROUTES', rewriteRoutes) + commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) + commit('SET_DEFAULT_ROUTES', sidebarRoutes) + commit('SET_TOPBAR_ROUTES', sidebarRoutes) + resolve(rewriteRoutes) + }) + }) + } + } +} + +// 遍历后台传来的路由字符串,转换为组件对象 +function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { + return asyncRouterMap.filter(route => { + if (type && route.children) { + route.children = filterChildren(route.children) + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === 'Layout') { + route.component = Layout + } else if (route.component === 'ParentView') { + route.component = ParentView + } else if (route.component === 'InnerLink') { + route.component = InnerLink + } else { + route.component = loadView(route.component) + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type) + } else { + delete route['children'] + delete route['redirect'] + } + return true + }) +} + +function filterChildren(childrenMap, lastRouter = false) { + var children = [] + childrenMap.forEach((el, index) => { + if (el.children && el.children.length) { + if (el.component === 'ParentView' && !lastRouter) { + el.children.forEach(c => { + c.path = el.path + '/' + c.path + if (c.children && c.children.length) { + children = children.concat(filterChildren(c.children, c)) + return + } + children.push(c) + }) + return + } + } + if (lastRouter) { + el.path = lastRouter.path + '/' + el.path + if (el.children && el.children.length) { + children = children.concat(filterChildren(el.children, el)) + return + } + } + children = children.concat(el) + }) + return children +} + +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes) { + const res = [] + routes.forEach(route => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route) + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route) + } + } + }) + return res +} + +export const loadView = (view) => { + if (process.env.NODE_ENV === 'development') { + return (resolve) => require([`@/views/${view}`], resolve) + } else { + // 使用 import 实现生产环境的路由懒加载 + return () => import(`@/views/${view}`) + } +} + +export default permission diff --git a/ruoyi-ui/src/store/modules/settings.js b/ruoyi-ui/src/store/modules/settings.js new file mode 100644 index 000000000..2455a1e27 --- /dev/null +++ b/ruoyi-ui/src/store/modules/settings.js @@ -0,0 +1,42 @@ +import defaultSettings from '@/settings' + +const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings + +const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' +const state = { + title: '', + theme: storageSetting.theme || '#409EFF', + sideTheme: storageSetting.sideTheme || sideTheme, + showSettings: showSettings, + topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, + tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, + fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, + sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, + dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle +} +const mutations = { + CHANGE_SETTING: (state, { key, value }) => { + if (state.hasOwnProperty(key)) { + state[key] = value + } + } +} + +const actions = { + // 修改布局设置 + changeSetting({ commit }, data) { + commit('CHANGE_SETTING', data) + }, + // 设置网页标题 + setTitle({ commit }, title) { + state.title = title + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/ruoyi-ui/src/store/modules/tagsView.js b/ruoyi-ui/src/store/modules/tagsView.js new file mode 100644 index 000000000..5fc011ca8 --- /dev/null +++ b/ruoyi-ui/src/store/modules/tagsView.js @@ -0,0 +1,228 @@ +const state = { + visitedViews: [], + cachedViews: [], + iframeViews: [] +} + +const mutations = { + ADD_IFRAME_VIEW: (state, view) => { + if (state.iframeViews.some(v => v.path === view.path)) return + state.iframeViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + ADD_VISITED_VIEW: (state, view) => { + if (state.visitedViews.some(v => v.path === view.path)) return + state.visitedViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + ADD_CACHED_VIEW: (state, view) => { + if (state.cachedViews.includes(view.name)) return + if (view.meta && !view.meta.noCache) { + state.cachedViews.push(view.name) + } + }, + DEL_VISITED_VIEW: (state, view) => { + for (const [i, v] of state.visitedViews.entries()) { + if (v.path === view.path) { + state.visitedViews.splice(i, 1) + break + } + } + state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) + }, + DEL_IFRAME_VIEW: (state, view) => { + state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) + }, + DEL_CACHED_VIEW: (state, view) => { + const index = state.cachedViews.indexOf(view.name) + index > -1 && state.cachedViews.splice(index, 1) + }, + + DEL_OTHERS_VISITED_VIEWS: (state, view) => { + state.visitedViews = state.visitedViews.filter(v => { + return v.meta.affix || v.path === view.path + }) + state.iframeViews = state.iframeViews.filter(item => item.path === view.path) + }, + DEL_OTHERS_CACHED_VIEWS: (state, view) => { + const index = state.cachedViews.indexOf(view.name) + if (index > -1) { + state.cachedViews = state.cachedViews.slice(index, index + 1) + } else { + state.cachedViews = [] + } + }, + DEL_ALL_VISITED_VIEWS: state => { + // keep affix tags + const affixTags = state.visitedViews.filter(tag => tag.meta.affix) + state.visitedViews = affixTags + state.iframeViews = [] + }, + DEL_ALL_CACHED_VIEWS: state => { + state.cachedViews = [] + }, + UPDATE_VISITED_VIEW: (state, view) => { + for (let v of state.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + }, + DEL_RIGHT_VIEWS: (state, view) => { + const index = state.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + state.visitedViews = state.visitedViews.filter((item, idx) => { + if (idx <= index || (item.meta && item.meta.affix)) { + return true + } + const i = state.cachedViews.indexOf(item.name) + if (i > -1) { + state.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = state.iframeViews.findIndex(v => v.path === item.path) + state.iframeViews.splice(fi, 1) + } + return false + }) + }, + DEL_LEFT_VIEWS: (state, view) => { + const index = state.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + state.visitedViews = state.visitedViews.filter((item, idx) => { + if (idx >= index || (item.meta && item.meta.affix)) { + return true + } + const i = state.cachedViews.indexOf(item.name) + if (i > -1) { + state.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = state.iframeViews.findIndex(v => v.path === item.path) + state.iframeViews.splice(fi, 1) + } + return false + }) + } +} + +const actions = { + addView({ dispatch }, view) { + dispatch('addVisitedView', view) + dispatch('addCachedView', view) + }, + addIframeView({ commit }, view) { + commit('ADD_IFRAME_VIEW', view) + }, + addVisitedView({ commit }, view) { + commit('ADD_VISITED_VIEW', view) + }, + addCachedView({ commit }, view) { + commit('ADD_CACHED_VIEW', view) + }, + delView({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delVisitedView', view) + dispatch('delCachedView', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delVisitedView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_VISITED_VIEW', view) + resolve([...state.visitedViews]) + }) + }, + delIframeView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_IFRAME_VIEW', view) + resolve([...state.iframeViews]) + }) + }, + delCachedView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_CACHED_VIEW', view) + resolve([...state.cachedViews]) + }) + }, + delOthersViews({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delOthersVisitedViews', view) + dispatch('delOthersCachedViews', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delOthersVisitedViews({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_OTHERS_VISITED_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, + delOthersCachedViews({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_OTHERS_CACHED_VIEWS', view) + resolve([...state.cachedViews]) + }) + }, + delAllViews({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delAllVisitedViews', view) + dispatch('delAllCachedViews', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delAllVisitedViews({ commit, state }) { + return new Promise(resolve => { + commit('DEL_ALL_VISITED_VIEWS') + resolve([...state.visitedViews]) + }) + }, + delAllCachedViews({ commit, state }) { + return new Promise(resolve => { + commit('DEL_ALL_CACHED_VIEWS') + resolve([...state.cachedViews]) + }) + }, + updateVisitedView({ commit }, view) { + commit('UPDATE_VISITED_VIEW', view) + }, + delRightTags({ commit }, view) { + return new Promise(resolve => { + commit('DEL_RIGHT_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, + delLeftTags({ commit }, view) { + return new Promise(resolve => { + commit('DEL_LEFT_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js new file mode 100644 index 000000000..cdbab1e9d --- /dev/null +++ b/ruoyi-ui/src/store/modules/user.js @@ -0,0 +1,101 @@ +import { login, logout, getInfo } from '@/api/login' +import { getToken, setToken, removeToken } from '@/utils/auth' + +const user = { + state: { + token: getToken(), + id: '', + name: '', + avatar: '', + roles: [], + permissions: [] + }, + + mutations: { + SET_TOKEN: (state, token) => { + state.token = token + }, + SET_ID: (state, id) => { + state.id = id + }, + SET_NAME: (state, name) => { + state.name = name + }, + SET_AVATAR: (state, avatar) => { + state.avatar = avatar + }, + SET_ROLES: (state, roles) => { + state.roles = roles + }, + SET_PERMISSIONS: (state, permissions) => { + state.permissions = permissions + } + }, + + actions: { + // 登录 + Login({ commit }, userInfo) { + const username = userInfo.username.trim() + const password = userInfo.password + const code = userInfo.code + const uuid = userInfo.uuid + return new Promise((resolve, reject) => { + login(username, password, code, uuid).then(res => { + setToken(res.token) + commit('SET_TOKEN', res.token) + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // 获取用户信息 + GetInfo({ commit, state }) { + return new Promise((resolve, reject) => { + getInfo().then(res => { + const user = res.user + const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar; + if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 + commit('SET_ROLES', res.roles) + commit('SET_PERMISSIONS', res.permissions) + } else { + commit('SET_ROLES', ['ROLE_DEFAULT']) + } + commit('SET_ID', user.userId) + commit('SET_NAME', user.userName) + commit('SET_AVATAR', avatar) + resolve(res) + }).catch(error => { + reject(error) + }) + }) + }, + + // 退出系统 + LogOut({ commit, state }) { + return new Promise((resolve, reject) => { + logout(state.token).then(() => { + commit('SET_TOKEN', '') + commit('SET_ROLES', []) + commit('SET_PERMISSIONS', []) + removeToken() + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // 前端 登出 + FedLogOut({ commit }) { + return new Promise(resolve => { + commit('SET_TOKEN', '') + removeToken() + resolve() + }) + } + } +} + +export default user diff --git a/ruoyi-ui/src/utils/auth.js b/ruoyi-ui/src/utils/auth.js new file mode 100644 index 000000000..08a43d6e2 --- /dev/null +++ b/ruoyi-ui/src/utils/auth.js @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'Admin-Token' + +export function getToken() { + return Cookies.get(TokenKey) +} + +export function setToken(token) { + return Cookies.set(TokenKey, token) +} + +export function removeToken() { + return Cookies.remove(TokenKey) +} diff --git a/ruoyi-ui/src/utils/dict/Dict.js b/ruoyi-ui/src/utils/dict/Dict.js new file mode 100644 index 000000000..104bd6e72 --- /dev/null +++ b/ruoyi-ui/src/utils/dict/Dict.js @@ -0,0 +1,82 @@ +import Vue from 'vue' +import { mergeRecursive } from "@/utils/ruoyi"; +import DictMeta from './DictMeta' +import DictData from './DictData' + +const DEFAULT_DICT_OPTIONS = { + types: [], +} + +/** + * @classdesc 字典 + * @property {Object} label 标签对象,内部属性名为字典类型名称 + * @property {Object} dict 字段数组,内部属性名为字典类型名称 + * @property {Array.} _dictMetas 字典元数据数组 + */ +export default class Dict { + constructor() { + this.owner = null + this.label = {} + this.type = {} + } + + init(options) { + if (options instanceof Array) { + options = { types: options } + } + const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options) + if (opts.types === undefined) { + throw new Error('need dict types') + } + const ps = [] + this._dictMetas = opts.types.map(t => DictMeta.parse(t)) + this._dictMetas.forEach(dictMeta => { + const type = dictMeta.type + Vue.set(this.label, type, {}) + Vue.set(this.type, type, []) + if (dictMeta.lazy) { + return + } + ps.push(loadDict(this, dictMeta)) + }) + return Promise.all(ps) + } + + /** + * 重新加载字典 + * @param {String} type 字典类型 + */ + reloadDict(type) { + const dictMeta = this._dictMetas.find(e => e.type === type) + if (dictMeta === undefined) { + return Promise.reject(`the dict meta of ${type} was not found`) + } + return loadDict(this, dictMeta) + } +} + +/** + * 加载字典 + * @param {Dict} dict 字典 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {Promise} + */ +function loadDict(dict, dictMeta) { + return dictMeta.request(dictMeta) + .then(response => { + const type = dictMeta.type + let dicts = dictMeta.responseConverter(response, dictMeta) + if (!(dicts instanceof Array)) { + console.error('the return of responseConverter must be Array.') + dicts = [] + } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) { + console.error('the type of elements in dicts must be DictData') + dicts = [] + } + dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts) + dicts.forEach(d => { + Vue.set(dict.label[type], d.value, d.label) + }) + return dicts + }) +} diff --git a/ruoyi-ui/src/utils/dict/DictConverter.js b/ruoyi-ui/src/utils/dict/DictConverter.js new file mode 100644 index 000000000..0cf5df863 --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictConverter.js @@ -0,0 +1,17 @@ +import DictOptions from './DictOptions' +import DictData from './DictData' + +export default function(dict, dictMeta) { + const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS) + const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS) + return new DictData(dict[label], dict[value], dict) +} + +/** + * 确定字典字段 + * @param {DictData} dict + * @param {...String} fields + */ +function determineDictField(dict, ...fields) { + return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f)) +} diff --git a/ruoyi-ui/src/utils/dict/DictData.js b/ruoyi-ui/src/utils/dict/DictData.js new file mode 100644 index 000000000..afc763e80 --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictData.js @@ -0,0 +1,13 @@ +/** + * @classdesc 字典数据 + * @property {String} label 标签 + * @property {*} value 标签 + * @property {Object} raw 原始数据 + */ +export default class DictData { + constructor(label, value, raw) { + this.label = label + this.value = value + this.raw = raw + } +} diff --git a/ruoyi-ui/src/utils/dict/DictMeta.js b/ruoyi-ui/src/utils/dict/DictMeta.js new file mode 100644 index 000000000..9779daa43 --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictMeta.js @@ -0,0 +1,38 @@ +import { mergeRecursive } from "@/utils/ruoyi"; +import DictOptions from './DictOptions' + +/** + * @classdesc 字典元数据 + * @property {String} type 类型 + * @property {Function} request 请求 + * @property {String} label 标签字段 + * @property {String} value 值字段 + */ +export default class DictMeta { + constructor(options) { + this.type = options.type + this.request = options.request + this.responseConverter = options.responseConverter + this.labelField = options.labelField + this.valueField = options.valueField + this.lazy = options.lazy === true + } +} + + +/** + * 解析字典元数据 + * @param {Object} options + * @returns {DictMeta} + */ +DictMeta.parse= function(options) { + let opts = null + if (typeof options === 'string') { + opts = DictOptions.metas[options] || {} + opts.type = options + } else if (typeof options === 'object') { + opts = options + } + opts = mergeRecursive(DictOptions.metas['*'], opts) + return new DictMeta(opts) +} diff --git a/ruoyi-ui/src/utils/dict/DictOptions.js b/ruoyi-ui/src/utils/dict/DictOptions.js new file mode 100644 index 000000000..338a94e1a --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictOptions.js @@ -0,0 +1,51 @@ +import { mergeRecursive } from "@/utils/ruoyi"; +import dictConverter from './DictConverter' + +export const options = { + metas: { + '*': { + /** + * 字典请求,方法签名为function(dictMeta: DictMeta): Promise + */ + request: (dictMeta) => { + console.log(`load dict ${dictMeta.type}`) + return Promise.resolve([]) + }, + /** + * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData + */ + responseConverter, + labelField: 'label', + valueField: 'value', + }, + }, + /** + * 默认标签字段 + */ + DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'], + /** + * 默认值字段 + */ + DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'], +} + +/** + * 映射字典 + * @param {Object} response 字典数据 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {DictData} + */ +function responseConverter(response, dictMeta) { + const dicts = response.content instanceof Array ? response.content : response + if (dicts === undefined) { + console.warn(`no dict data of "${dictMeta.type}" found in the response`) + return [] + } + return dicts.map(d => dictConverter(d, dictMeta)) +} + +export function mergeOptions(src) { + mergeRecursive(options, src) +} + +export default options diff --git a/ruoyi-ui/src/utils/dict/index.js b/ruoyi-ui/src/utils/dict/index.js new file mode 100644 index 000000000..215eb9e05 --- /dev/null +++ b/ruoyi-ui/src/utils/dict/index.js @@ -0,0 +1,33 @@ +import Dict from './Dict' +import { mergeOptions } from './DictOptions' + +export default function(Vue, options) { + mergeOptions(options) + Vue.mixin({ + data() { + if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) { + return {} + } + const dict = new Dict() + dict.owner = this + return { + dict + } + }, + created() { + if (!(this.dict instanceof Dict)) { + return + } + options.onCreated && options.onCreated(this.dict) + this.dict.init(this.$options.dicts).then(() => { + options.onReady && options.onReady(this.dict) + this.$nextTick(() => { + this.$emit('dictReady', this.dict) + if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) { + this.$options.methods.onDictReady.call(this, this.dict) + } + }) + }) + }, + }) +} diff --git a/ruoyi-ui/src/utils/errorCode.js b/ruoyi-ui/src/utils/errorCode.js new file mode 100644 index 000000000..d2111ee10 --- /dev/null +++ b/ruoyi-ui/src/utils/errorCode.js @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} diff --git a/ruoyi-ui/src/utils/generator/config.js b/ruoyi-ui/src/utils/generator/config.js new file mode 100644 index 000000000..7abf227d5 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/config.js @@ -0,0 +1,438 @@ +export const formConf = { + formRef: 'elForm', + formModel: 'formData', + size: 'medium', + labelPosition: 'right', + labelWidth: 100, + formRules: 'rules', + gutter: 15, + disabled: false, + span: 24, + formBtns: true +} + +export const inputComponents = [ + { + label: '单行文本', + tag: 'el-input', + tagIcon: 'input', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '多行文本', + tag: 'el-input', + tagIcon: 'textarea', + type: 'textarea', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + autosize: { + minRows: 4, + maxRows: 4 + }, + style: { width: '100%' }, + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '密码', + tag: 'el-input', + tagIcon: 'password', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + 'show-password': true, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '计数器', + tag: 'el-input-number', + tagIcon: 'number', + placeholder: '', + defaultValue: undefined, + span: 24, + labelWidth: null, + min: undefined, + max: undefined, + step: undefined, + 'step-strictly': false, + precision: undefined, + 'controls-position': '', + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input-number' + } +] + +export const selectComponents = [ + { + label: '下拉选择', + tag: 'el-select', + tagIcon: 'select', + placeholder: '请选择', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + disabled: false, + required: true, + filterable: false, + multiple: false, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/select' + }, + { + label: '级联选择', + tag: 'el-cascader', + tagIcon: 'cascader', + placeholder: '请选择', + defaultValue: [], + span: 24, + labelWidth: null, + style: { width: '100%' }, + props: { + props: { + multiple: false + } + }, + 'show-all-levels': true, + disabled: false, + clearable: true, + filterable: false, + required: true, + options: [{ + id: 1, + value: 1, + label: '选项1', + children: [{ + id: 2, + value: 2, + label: '选项1-1' + }] + }], + dataType: 'dynamic', + labelKey: 'label', + valueKey: 'value', + childrenKey: 'children', + separator: '/', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/cascader' + }, + { + label: '单选框组', + tag: 'el-radio-group', + tagIcon: 'radio', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'medium', + disabled: false, + required: true, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/radio' + }, + { + label: '多选框组', + tag: 'el-checkbox-group', + tagIcon: 'checkbox', + defaultValue: [], + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'medium', + disabled: false, + required: true, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/checkbox' + }, + { + label: '开关', + tag: 'el-switch', + tagIcon: 'switch', + defaultValue: false, + span: 24, + labelWidth: null, + style: {}, + disabled: false, + required: true, + 'active-text': '', + 'inactive-text': '', + 'active-color': null, + 'inactive-color': null, + 'active-value': true, + 'inactive-value': false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/switch' + }, + { + label: '滑块', + tag: 'el-slider', + tagIcon: 'slider', + defaultValue: null, + span: 24, + labelWidth: null, + disabled: false, + required: true, + min: 0, + max: 100, + step: 1, + 'show-stops': false, + range: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/slider' + }, + { + label: '时间选择', + tag: 'el-time-picker', + tagIcon: 'time', + placeholder: '请选择', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + 'picker-options': { + selectableRange: '00:00:00-23:59:59' + }, + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' + }, + { + label: '时间范围', + tag: 'el-time-picker', + tagIcon: 'time-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + 'is-range': true, + 'range-separator': '至', + 'start-placeholder': '开始时间', + 'end-placeholder': '结束时间', + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' + }, + { + label: '日期选择', + tag: 'el-date-picker', + tagIcon: 'date', + placeholder: '请选择', + defaultValue: null, + type: 'date', + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + format: 'yyyy-MM-dd', + 'value-format': 'yyyy-MM-dd', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' + }, + { + label: '日期范围', + tag: 'el-date-picker', + tagIcon: 'date-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + type: 'daterange', + 'range-separator': '至', + 'start-placeholder': '开始日期', + 'end-placeholder': '结束日期', + disabled: false, + clearable: true, + required: true, + format: 'yyyy-MM-dd', + 'value-format': 'yyyy-MM-dd', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' + }, + { + label: '评分', + tag: 'el-rate', + tagIcon: 'rate', + defaultValue: 0, + span: 24, + labelWidth: null, + style: {}, + max: 5, + 'allow-half': false, + 'show-text': false, + 'show-score': false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/rate' + }, + { + label: '颜色选择', + tag: 'el-color-picker', + tagIcon: 'color', + defaultValue: null, + labelWidth: null, + 'show-alpha': false, + 'color-format': '', + disabled: false, + required: true, + size: 'medium', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/color-picker' + }, + { + label: '上传', + tag: 'el-upload', + tagIcon: 'upload', + action: 'https://jsonplaceholder.typicode.com/posts/', + defaultValue: null, + labelWidth: null, + disabled: false, + required: true, + accept: '', + name: 'file', + 'auto-upload': true, + showTip: false, + buttonText: '点击上传', + fileSize: 2, + sizeUnit: 'MB', + 'list-type': 'text', + multiple: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/upload' + } +] + +export const layoutComponents = [ + { + layout: 'rowFormItem', + tagIcon: 'row', + type: 'default', + justify: 'start', + align: 'top', + label: '行容器', + layoutTree: true, + children: [], + document: 'https://element.eleme.cn/#/zh-CN/component/layout' + }, + { + layout: 'colFormItem', + label: '按钮', + changeTag: true, + labelWidth: null, + tag: 'el-button', + tagIcon: 'button', + span: 24, + default: '主要按钮', + type: 'primary', + icon: 'el-icon-search', + size: 'medium', + disabled: false, + document: 'https://element.eleme.cn/#/zh-CN/component/button' + } +] + +// 组件rule的触发方式,无触发方式的组件不生成rule +export const trigger = { + 'el-input': 'blur', + 'el-input-number': 'blur', + 'el-select': 'change', + 'el-radio-group': 'change', + 'el-checkbox-group': 'change', + 'el-cascader': 'change', + 'el-time-picker': 'change', + 'el-date-picker': 'change', + 'el-rate': 'change' +} diff --git a/ruoyi-ui/src/utils/generator/css.js b/ruoyi-ui/src/utils/generator/css.js new file mode 100644 index 000000000..c1c62e607 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/css.js @@ -0,0 +1,18 @@ +const styles = { + 'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}', + 'el-upload': '.el-upload__tip{line-height: 1.2;}' +} + +function addCss(cssList, el) { + const css = styles[el.tag] + css && cssList.indexOf(css) === -1 && cssList.push(css) + if (el.children) { + el.children.forEach(el2 => addCss(cssList, el2)) + } +} + +export function makeUpCss(conf) { + const cssList = [] + conf.fields.forEach(el => addCss(cssList, el)) + return cssList.join('\n') +} diff --git a/ruoyi-ui/src/utils/generator/drawingDefault.js b/ruoyi-ui/src/utils/generator/drawingDefault.js new file mode 100644 index 000000000..09f133ca1 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/drawingDefault.js @@ -0,0 +1,29 @@ +export default [ + { + layout: 'colFormItem', + tagIcon: 'input', + label: '手机号', + vModel: 'mobile', + formId: 6, + tag: 'el-input', + placeholder: '请输入手机号', + defaultValue: '', + span: 24, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': 'el-icon-mobile', + 'suffix-icon': '', + maxlength: 11, + 'show-word-limit': true, + readonly: false, + disabled: false, + required: true, + changeTag: true, + regList: [{ + pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', + message: '手机号格式错误' + }] + } +] diff --git a/ruoyi-ui/src/utils/generator/html.js b/ruoyi-ui/src/utils/generator/html.js new file mode 100644 index 000000000..9bcc5361d --- /dev/null +++ b/ruoyi-ui/src/utils/generator/html.js @@ -0,0 +1,359 @@ +/* eslint-disable max-len */ +import { trigger } from './config' + +let confGlobal +let someSpanIsNot24 + +export function dialogWrapper(str) { + return ` + ${str} +

    + ` +} + +export function vueTemplate(str) { + return `` +} + +export function vueScript(str) { + return `` +} + +export function cssStyle(cssStr) { + return `` +} + +function buildFormTemplate(conf, child, type) { + let labelPosition = '' + if (conf.labelPosition !== 'right') { + labelPosition = `label-position="${conf.labelPosition}"` + } + const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : '' + let str = ` + ${child} + ${buildFromBtns(conf, type)} + ` + if (someSpanIsNot24) { + str = ` + ${str} + ` + } + return str +} + +function buildFromBtns(conf, type) { + let str = '' + if (conf.formBtns && type === 'file') { + str = ` + 提交 + 重置 + ` + if (someSpanIsNot24) { + str = ` + ${str} + ` + } + } + return str +} + +// span不为24的用el-col包裹 +function colWrapper(element, str) { + if (someSpanIsNot24 || element.span !== 24) { + return ` + ${str} + ` + } + return str +} + +const layouts = { + colFormItem(element) { + let labelWidth = '' + if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) { + labelWidth = `label-width="${element.labelWidth}px"` + } + const required = !trigger[element.tag] && element.required ? 'required' : '' + const tagDom = tags[element.tag] ? tags[element.tag](element) : null + let str = ` + ${tagDom} + ` + str = colWrapper(element, str) + return str + }, + rowFormItem(element) { + const type = element.type === 'default' ? '' : `type="${element.type}"` + const justify = element.type === 'default' ? '' : `justify="${element.justify}"` + const align = element.type === 'default' ? '' : `align="${element.align}"` + const gutter = element.gutter ? `gutter="${element.gutter}"` : '' + const children = element.children.map(el => layouts[el.layout](el)) + let str = ` + ${children.join('\n')} + ` + str = colWrapper(element, str) + return str + } +} + +const tags = { + 'el-button': el => { + const { + tag, disabled + } = attrBuilder(el) + const type = el.type ? `type="${el.type}"` : '' + const icon = el.icon ? `icon="${el.icon}"` : '' + const size = el.size ? `size="${el.size}"` : '' + let child = buildElButtonChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}` + }, + 'el-input': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : '' + const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : '' + const readonly = el.readonly ? 'readonly' : '' + const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : '' + const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : '' + const showPassword = el['show-password'] ? 'show-password' : '' + const type = el.type ? `type="${el.type}"` : '' + const autosize = el.autosize && el.autosize.minRows + ? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"` + : '' + let child = buildElInputChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}` + }, + 'el-input-number': el => { + const { disabled, vModel, placeholder } = attrBuilder(el) + const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : '' + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const stepStrictly = el['step-strictly'] ? 'step-strictly' : '' + const precision = el.precision ? `:precision='${el.precision}'` : '' + + return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}>` + }, + 'el-select': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const filterable = el.filterable ? 'filterable' : '' + const multiple = el.multiple ? 'multiple' : '' + let child = buildElSelectChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}` + }, + 'el-radio-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + let child = buildElRadioGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${size} ${disabled}>${child}` + }, + 'el-checkbox-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const min = el.min ? `:min="${el.min}"` : '' + const max = el.max ? `:max="${el.max}"` : '' + let child = buildElCheckboxGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}` + }, + 'el-switch': el => { + const { disabled, vModel } = attrBuilder(el) + const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : '' + const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : '' + const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : '' + const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : '' + const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : '' + const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : '' + + return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}>` + }, + 'el-cascader': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const options = el.options ? `:options="${el.vModel}Options"` : '' + const props = el.props ? `:props="${el.vModel}Props"` : '' + const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"' + const filterable = el.filterable ? 'filterable' : '' + const separator = el.separator === '/' ? '' : `separator="${el.separator}"` + + return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}>` + }, + 'el-slider': el => { + const { disabled, vModel } = attrBuilder(el) + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const range = el.range ? 'range' : '' + const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : '' + + return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}>` + }, + 'el-time-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const isRange = el['is-range'] ? 'is-range' : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : '' + + return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}>` + }, + 'el-date-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const type = el.type === 'date' ? '' : `type="${el.type}"` + const readonly = el.readonly ? 'readonly' : '' + + return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}>` + }, + 'el-rate': el => { + const { disabled, vModel } = attrBuilder(el) + const max = el.max ? `:max='${el.max}'` : '' + const allowHalf = el['allow-half'] ? 'allow-half' : '' + const showText = el['show-text'] ? 'show-text' : '' + const showScore = el['show-score'] ? 'show-score' : '' + + return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}>` + }, + 'el-color-picker': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const showAlpha = el['show-alpha'] ? 'show-alpha' : '' + const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : '' + + return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}>` + }, + 'el-upload': el => { + const disabled = el.disabled ? ':disabled=\'true\'' : '' + const action = el.action ? `:action="${el.vModel}Action"` : '' + const multiple = el.multiple ? 'multiple' : '' + const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : '' + const accept = el.accept ? `accept="${el.accept}"` : '' + const name = el.name !== 'file' ? `name="${el.name}"` : '' + const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : '' + const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"` + const fileList = `:file-list="${el.vModel}fileList"` + const ref = `ref="${el.vModel}"` + let child = buildElUploadChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}` + } +} + +function attrBuilder(el) { + return { + vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`, + clearable: el.clearable ? 'clearable' : '', + placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '', + width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '', + disabled: el.disabled ? ':disabled=\'true\'' : '' + } +} + +// el-buttin 子级 +function buildElButtonChild(conf) { + const children = [] + if (conf.default) { + children.push(conf.default) + } + return children.join('\n') +} + +// el-input innerHTML +function buildElInputChild(conf) { + const children = [] + if (conf.prepend) { + children.push(``) + } + if (conf.append) { + children.push(``) + } + return children.join('\n') +} + +function buildElSelectChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + children.push(``) + } + return children.join('\n') +} + +function buildElRadioGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) + } + return children.join('\n') +} + +function buildElCheckboxGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) + } + return children.join('\n') +} + +function buildElUploadChild(conf) { + const list = [] + if (conf['list-type'] === 'picture-card') list.push('') + else list.push(`${conf.buttonText}`) + if (conf.showTip) list.push(`
    只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件
    `) + return list.join('\n') +} + +export function makeUpHtml(conf, type) { + const htmlList = [] + confGlobal = conf + someSpanIsNot24 = conf.fields.some(item => item.span !== 24) + conf.fields.forEach(el => { + htmlList.push(layouts[el.layout](el)) + }) + const htmlStr = htmlList.join('\n') + + let temp = buildFormTemplate(conf, htmlStr, type) + if (type === 'dialog') { + temp = dialogWrapper(temp) + } + confGlobal = null + return temp +} diff --git a/ruoyi-ui/src/utils/generator/icon.json b/ruoyi-ui/src/utils/generator/icon.json new file mode 100644 index 000000000..2d9999a31 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/icon.json @@ -0,0 +1 @@ +["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"] \ No newline at end of file diff --git a/ruoyi-ui/src/utils/generator/js.js b/ruoyi-ui/src/utils/generator/js.js new file mode 100644 index 000000000..ee8668dc6 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/js.js @@ -0,0 +1,235 @@ +import { exportDefault, titleCase } from '@/utils/index' +import { trigger } from './config' + +const units = { + KB: '1024', + MB: '1024 / 1024', + GB: '1024 / 1024 / 1024' +} +let confGlobal +const inheritAttrs = { + file: '', + dialog: 'inheritAttrs: false,' +} + + +export function makeUpJs(conf, type) { + confGlobal = conf = JSON.parse(JSON.stringify(conf)) + const dataList = [] + const ruleList = [] + const optionsList = [] + const propsList = [] + const methodList = mixinMethod(type) + const uploadVarList = [] + + conf.fields.forEach(el => { + buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) + }) + + const script = buildexport( + conf, + type, + dataList.join('\n'), + ruleList.join('\n'), + optionsList.join('\n'), + uploadVarList.join('\n'), + propsList.join('\n'), + methodList.join('\n') + ) + confGlobal = null + return script +} + +function buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) { + buildData(el, dataList) + buildRules(el, ruleList) + + if (el.options && el.options.length) { + buildOptions(el, optionsList) + if (el.dataType === 'dynamic') { + const model = `${el.vModel}Options` + const options = titleCase(model) + buildOptionMethod(`get${options}`, model, methodList) + } + } + + if (el.props && el.props.props) { + buildProps(el, propsList) + } + + if (el.action && el.tag === 'el-upload') { + uploadVarList.push( + `${el.vModel}Action: '${el.action}', + ${el.vModel}fileList: [],` + ) + methodList.push(buildBeforeUpload(el)) + if (!el['auto-upload']) { + methodList.push(buildSubmitUpload(el)) + } + } + + if (el.children) { + el.children.forEach(el2 => { + buildAttributes(el2, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) + }) + } +} + +function mixinMethod(type) { + const list = []; const + minxins = { + file: confGlobal.formBtns ? { + submitForm: `submitForm() { + this.$refs['${confGlobal.formRef}'].validate(valid => { + if(!valid) return + // TODO 提交表单 + }) + },`, + resetForm: `resetForm() { + this.$refs['${confGlobal.formRef}'].resetFields() + },` + } : null, + dialog: { + onOpen: 'onOpen() {},', + onClose: `onClose() { + this.$refs['${confGlobal.formRef}'].resetFields() + },`, + close: `close() { + this.$emit('update:visible', false) + },`, + handleConfirm: `handleConfirm() { + this.$refs['${confGlobal.formRef}'].validate(valid => { + if(!valid) return + this.close() + }) + },` + } + } + + const methods = minxins[type] + if (methods) { + Object.keys(methods).forEach(key => { + list.push(methods[key]) + }) + } + + return list +} + +function buildData(conf, dataList) { + if (conf.vModel === undefined) return + let defaultValue + if (typeof (conf.defaultValue) === 'string' && !conf.multiple) { + defaultValue = `'${conf.defaultValue}'` + } else { + defaultValue = `${JSON.stringify(conf.defaultValue)}` + } + dataList.push(`${conf.vModel}: ${defaultValue},`) +} + +function buildRules(conf, ruleList) { + if (conf.vModel === undefined) return + const rules = [] + if (trigger[conf.tag]) { + if (conf.required) { + const type = Array.isArray(conf.defaultValue) ? 'type: \'array\',' : '' + let message = Array.isArray(conf.defaultValue) ? `请至少选择一个${conf.vModel}` : conf.placeholder + if (message === undefined) message = `${conf.label}不能为空` + rules.push(`{ required: true, ${type} message: '${message}', trigger: '${trigger[conf.tag]}' }`) + } + if (conf.regList && Array.isArray(conf.regList)) { + conf.regList.forEach(item => { + if (item.pattern) { + rules.push(`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${trigger[conf.tag]}' }`) + } + }) + } + ruleList.push(`${conf.vModel}: [${rules.join(',')}],`) + } +} + +function buildOptions(conf, optionsList) { + if (conf.vModel === undefined) return + if (conf.dataType === 'dynamic') { conf.options = [] } + const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},` + optionsList.push(str) +} + +function buildProps(conf, propsList) { + if (conf.dataType === 'dynamic') { + conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey) + conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey) + conf.childrenKey !== 'children' && (conf.props.props.children = conf.childrenKey) + } + const str = `${conf.vModel}Props: ${JSON.stringify(conf.props.props)},` + propsList.push(str) +} + +function buildBeforeUpload(conf) { + const unitNum = units[conf.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const + returnList = [] + if (conf.fileSize) { + rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize} + if(!isRightSize){ + this.$message.error('文件大小超过 ${conf.fileSize}${conf.sizeUnit}') + }` + returnList.push('isRightSize') + } + if (conf.accept) { + acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type) + if(!isAccept){ + this.$message.error('应该选择${conf.accept}类型的文件') + }` + returnList.push('isAccept') + } + const str = `${conf.vModel}BeforeUpload(file) { + ${rightSizeCode} + ${acceptCode} + return ${returnList.join('&&')} + },` + return returnList.length ? str : '' +} + +function buildSubmitUpload(conf) { + const str = `submitUpload() { + this.$refs['${conf.vModel}'].submit() + },` + return str +} + +function buildOptionMethod(methodName, model, methodList) { + const str = `${methodName}() { + // TODO 发起请求获取数据 + this.${model} + },` + methodList.push(str) +} + +function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods) { + const str = `${exportDefault}{ + ${inheritAttrs[type]} + components: {}, + props: [], + data () { + return { + ${conf.formModel}: { + ${data} + }, + ${conf.formRules}: { + ${rules} + }, + ${uploadVar} + ${selectOptions} + ${props} + } + }, + computed: {}, + watch: {}, + created () {}, + mounted () {}, + methods: { + ${methods} + } +}` + return str +} diff --git a/ruoyi-ui/src/utils/generator/render.js b/ruoyi-ui/src/utils/generator/render.js new file mode 100644 index 000000000..e8640f0a2 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/render.js @@ -0,0 +1,126 @@ +import { makeMap } from '@/utils/index' + +// 参考https://github.com/vuejs/vue/blob/v2.6.10/src/platforms/web/server/util.js +const isAttr = makeMap( + 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + + 'target,title,type,usemap,value,width,wrap' +) + +function vModel(self, dataObject, defaultValue) { + dataObject.props.value = defaultValue + + dataObject.on.input = val => { + self.$emit('input', val) + } +} + +const componentChild = { + 'el-button': { + default(h, conf, key) { + return conf[key] + }, + }, + 'el-input': { + prepend(h, conf, key) { + return + }, + append(h, conf, key) { + return + } + }, + 'el-select': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + list.push() + }) + return list + } + }, + 'el-radio-group': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + if (conf.optionType === 'button') list.push({item.label}) + else list.push({item.label}) + }) + return list + } + }, + 'el-checkbox-group': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + if (conf.optionType === 'button') { + list.push({item.label}) + } else { + list.push({item.label}) + } + }) + return list + } + }, + 'el-upload': { + 'list-type': (h, conf, key) => { + const list = [] + if (conf['list-type'] === 'picture-card') { + list.push() + } else { + list.push({conf.buttonText}) + } + if (conf.showTip) { + list.push(
    只能上传不超过 {conf.fileSize}{conf.sizeUnit} 的{conf.accept}文件
    ) + } + return list + } + } +} + +export default { + render(h) { + const dataObject = { + attrs: {}, + props: {}, + on: {}, + style: {} + } + const confClone = JSON.parse(JSON.stringify(this.conf)) + const children = [] + + const childObjs = componentChild[confClone.tag] + if (childObjs) { + Object.keys(childObjs).forEach(key => { + const childFunc = childObjs[key] + if (confClone[key]) { + children.push(childFunc(h, confClone, key)) + } + }) + } + + Object.keys(confClone).forEach(key => { + const val = confClone[key] + if (key === 'vModel') { + vModel(this, dataObject, confClone.defaultValue) + } else if (dataObject[key]) { + dataObject[key] = val + } else if (!isAttr(key)) { + dataObject.props[key] = val + } else { + dataObject.attrs[key] = val + } + }) + return h(this.conf.tag, dataObject, children) + }, + props: ['conf'] +} diff --git a/ruoyi-ui/src/utils/index.js b/ruoyi-ui/src/utils/index.js new file mode 100644 index 000000000..df5db12b0 --- /dev/null +++ b/ruoyi-ui/src/utils/index.js @@ -0,0 +1,390 @@ +import { parseTime } from './ruoyi' + +/** + * 表格时间格式化 + */ +export function formatDate(cellValue) { + if (cellValue == null || cellValue == "") return ""; + var date = new Date(cellValue) + var year = date.getFullYear() + var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 + var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() + var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds +} + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export function formatTime(time, option) { + if (('' + time).length === 10) { + time = parseInt(time) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param {string} url + * @returns {Object} + */ +export function getQueryObject(url) { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param {string} input value + * @returns {number} output value + */ +export function byteLength(str) { + // returns the byte length of an utf8 string + let s = str.length + for (var i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param {Array} actual + * @returns {Array} + */ +export function cleanArray(actual) { + const newArray = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param {Object} json + * @returns {Array} + */ +export function param(json) { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj(url) { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') + if (!search) { + return {} + } + const obj = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj +} + +/** + * @param {string} val + * @returns {string} + */ +export function html2Text(val) { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText +} + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export function objectMerge(target, source) { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export function toggleClass(element, className) { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += '' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param {string} type + * @returns {Date} + */ +export function getTime(type) { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()) + } +} + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +/** + * @param {Array} arr + * @returns {Array} + */ +export function uniqueArr(arr) { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString() { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536) + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function makeMap(str, expectsLowerCase) { + const map = Object.create(null) + const list = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? val => map[val.toLowerCase()] + : val => map[val] +} + +export const exportDefault = 'export default ' + +export const beautifierConf = { + html: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'separate', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: false, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + }, + js: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'normal', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: true, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + } +} + +// 首字母大小 +export function titleCase(str) { + return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) +} + +// 下划转驼峰 +export function camelCase(str) { + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) +} + +export function isNumberStr(str) { + return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) +} + diff --git a/ruoyi-ui/src/utils/jsencrypt.js b/ruoyi-ui/src/utils/jsencrypt.js new file mode 100644 index 000000000..78d95234a --- /dev/null +++ b/ruoyi-ui/src/utils/jsencrypt.js @@ -0,0 +1,30 @@ +import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' + +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' + +const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + + 'UP8iWi1Qw0Y=' + +// 加密 +export function encrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对数据进行加密 +} + +// 解密 +export function decrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) // 设置私钥 + return encryptor.decrypt(txt) // 对数据进行解密 +} + diff --git a/ruoyi-ui/src/utils/permission.js b/ruoyi-ui/src/utils/permission.js new file mode 100644 index 000000000..189a71629 --- /dev/null +++ b/ruoyi-ui/src/utils/permission.js @@ -0,0 +1,47 @@ +import store from '@/store' + +/** + * 字符权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkPermi(value) { + if (value && value instanceof Array && value.length > 0) { + const permissions = store.getters && store.getters.permissions + const permissionDatas = value + const all_permission = "*:*:*"; + + const hasPermission = permissions.some(permission => { + return all_permission === permission || permissionDatas.includes(permission) + }) + + return hasPermission; + + } else { + console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) + return false + } +} + +/** + * 角色权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkRole(value) { + if (value && value instanceof Array && value.length > 0) { + const roles = store.getters && store.getters.roles + const permissionRoles = value + const super_admin = "admin"; + + const hasRole = roles.some(role => { + return super_admin === role || permissionRoles.includes(role) + }) + + return hasRole; + + } else { + console.error(`need roles! Like checkRole="['admin','editor']"`) + return false + } +} \ No newline at end of file diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js new file mode 100644 index 000000000..ffb0d219e --- /dev/null +++ b/ruoyi-ui/src/utils/request.js @@ -0,0 +1,152 @@ +import axios from 'axios' +import { Notification, MessageBox, Message, Loading } from 'element-ui' +import store from '@/store' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { tansParams, blobValidate } from "@/utils/ruoyi"; +import cache from '@/plugins/cache' +import { saveAs } from 'file-saver' + +let downloadLoadingInstance; +// 是否显示重新登录 +export let isRelogin = { show: false }; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: process.env.VUE_APP_BASE_API, + // 超时 + timeout: 10000 +}) + +// request拦截器 +service.interceptors.request.use(config => { + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 + const limitSize = 5 * 1024 * 1024; // 限制存放数据5M + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') + return config; + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } + } + } + return config +}, error => { + console.log(error) + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use(res => { + // 未设置状态码则默认成功状态 + const code = res.data.code || 200; + // 获取错误信息 + const msg = errorCode[code] || res.data.msg || errorCode['default'] + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + if (code === 401) { + if (!isRelogin.show) { + isRelogin.show = true; + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + isRelogin.show = false; + store.dispatch('LogOut').then(() => { + location.href = '/index'; + }) + }).catch(() => { + isRelogin.show = false; + }); + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + } else if (code === 500) { + Message({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code === 601) { + Message({ message: msg, type: 'warning' }) + return Promise.reject('error') + } else if (code !== 200) { + Notification.error({ title: msg }) + return Promise.reject('error') + } else { + return res.data + } + }, + error => { + console.log('err' + error) + let { message } = error; + if (message == "Network Error") { + message = "后端接口连接异常"; + } else if (message.includes("timeout")) { + message = "系统接口请求超时"; + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.substr(message.length - 3) + "异常"; + } + Message({ message: message, type: 'error', duration: 5 * 1000 }) + return Promise.reject(error) + } +) + +// 通用下载方法 +export function download(url, params, filename, config) { + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + return service.post(url, params, { + transformRequest: [(params) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob', + ...config + }).then(async (data) => { + const isBlob = blobValidate(data); + if (isBlob) { + const blob = new Blob([data]) + saveAs(blob, filename) + } else { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) +} + +export default service diff --git a/ruoyi-ui/src/utils/ruoyi.js b/ruoyi-ui/src/utils/ruoyi.js new file mode 100644 index 000000000..44bf9c403 --- /dev/null +++ b/ruoyi-ui/src/utils/ruoyi.js @@ -0,0 +1,233 @@ + + +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +// 表单重置 +export function resetForm(refName) { + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } +} + +// 添加日期范围 +export function addDateRange(params, dateRange, propName) { + let search = params; + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; +} + +// 回显数据字典 +export function selectDictLabel(datas, value) { + if (value === undefined) { + return ""; + } + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label); + return true; + } + }) + if (actions.length === 0) { + actions.push(value); + } + return actions.join(''); +} + +// 回显数据字典(字符串、数组) +export function selectDictLabels(datas, value, separator) { + if (value === undefined || value.length ===0) { + return ""; + } + if (Array.isArray(value)) { + value = value.join(","); + } + var actions = []; + var currentSeparator = undefined === separator ? "," : separator; + var temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + var match = false; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator); + match = true; + } + }) + if (!match) { + actions.push(temp[val] + currentSeparator); + } + }) + return actions.join('').substring(0, actions.join('').length - 1); +} + +// 字符串格式化(%s ) +export function sprintf(str) { + var args = arguments, flag = true, i = 1; + str = str.replace(/%s/g, function () { + var arg = args[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; +} + +// 转换字符串,undefined,null等转化为"" +export function parseStrEmpty(str) { + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; +} + +// 数据合并 +export function mergeRecursive(source, target) { + for (var p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; + } + } + return source; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export function handleTree(data, id, parentId, children) { + let config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; + + var childrenListMap = {}; + var nodeIds = {}; + var tree = []; + + for (let d of data) { + let parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (let d of data) { + let parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + + for (let t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (let c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + return tree; +} + +/** +* 参数处理 +* @param {*} params 参数 +*/ +export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result +} + +// 验证是否为blob格式 +export function blobValidate(data) { + return data.type !== 'application/json' +} diff --git a/ruoyi-ui/src/utils/scroll-to.js b/ruoyi-ui/src/utils/scroll-to.js new file mode 100644 index 000000000..c5d8e04e0 --- /dev/null +++ b/ruoyi-ui/src/utils/scroll-to.js @@ -0,0 +1,58 @@ +Math.easeInOutQuad = function(t, b, c, d) { + t /= d / 2 + if (t < 1) { + return c / 2 * t * t + b + } + t-- + return -c / 2 * (t * (t - 2) - 1) + b +} + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +var requestAnimFrame = (function() { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } +})() + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +function move(amount) { + document.documentElement.scrollTop = amount + document.body.parentNode.scrollTop = amount + document.body.scrollTop = amount +} + +function position() { + return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop +} + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export function scrollTo(to, duration, callback) { + const start = position() + const change = to - start + const increment = 20 + let currentTime = 0 + duration = (typeof (duration) === 'undefined') ? 500 : duration + var animateScroll = function() { + // increment the time + currentTime += increment + // find the value with the quadratic in-out easing function + var val = Math.easeInOutQuad(currentTime, start, change, duration) + // move the document.body + move(val) + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll) + } else { + if (callback && typeof (callback) === 'function') { + // the animation is done so lets callback + callback() + } + } + } + animateScroll() +} diff --git a/ruoyi-ui/src/utils/validate.js b/ruoyi-ui/src/utils/validate.js new file mode 100644 index 000000000..57a568e9f --- /dev/null +++ b/ruoyi-ui/src/utils/validate.js @@ -0,0 +1,80 @@ +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUsername(str) { + const valid_map = ['admin', 'editor'] + return valid_map.indexOf(str.trim()) >= 0 +} + +/** + * @param {string} url + * @returns {Boolean} + */ +export function validURL(url) { + const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ + return reg.test(url) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validLowerCase(str) { + const reg = /^[a-z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUpperCase(str) { + const reg = /^[A-Z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validAlphabets(str) { + const reg = /^[A-Za-z]+$/ + return reg.test(str) +} + +/** + * @param {string} email + * @returns {Boolean} + */ +export function validEmail(email) { + const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return reg.test(email) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function isString(str) { + return typeof str === 'string' || str instanceof String; +} + +/** + * @param {Array} arg + * @returns {Boolean} + */ +export function isArray(arg) { + if (typeof Array.isArray === 'undefined') { + return Object.prototype.toString.call(arg) === '[object Array]' + } + return Array.isArray(arg) +} diff --git a/ruoyi-ui/src/views/dashboard/BarChart.vue b/ruoyi-ui/src/views/dashboard/BarChart.vue new file mode 100644 index 000000000..88e7ef64c --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/BarChart.vue @@ -0,0 +1,102 @@ + + + diff --git a/ruoyi-ui/src/views/dashboard/LineChart.vue b/ruoyi-ui/src/views/dashboard/LineChart.vue new file mode 100644 index 000000000..702ff73fe --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/LineChart.vue @@ -0,0 +1,135 @@ + + + diff --git a/ruoyi-ui/src/views/dashboard/PanelGroup.vue b/ruoyi-ui/src/views/dashboard/PanelGroup.vue new file mode 100644 index 000000000..1a1081fcb --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/PanelGroup.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/ruoyi-ui/src/views/dashboard/PieChart.vue b/ruoyi-ui/src/views/dashboard/PieChart.vue new file mode 100644 index 000000000..63f0d849b --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/PieChart.vue @@ -0,0 +1,79 @@ + + + diff --git a/ruoyi-ui/src/views/dashboard/RaddarChart.vue b/ruoyi-ui/src/views/dashboard/RaddarChart.vue new file mode 100644 index 000000000..312e018a5 --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/RaddarChart.vue @@ -0,0 +1,116 @@ + + + diff --git a/ruoyi-ui/src/views/dashboard/mixins/resize.js b/ruoyi-ui/src/views/dashboard/mixins/resize.js new file mode 100644 index 000000000..b1e76e947 --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/mixins/resize.js @@ -0,0 +1,56 @@ +import { debounce } from '@/utils' + +export default { + data() { + return { + $_sidebarElm: null, + $_resizeHandler: null + } + }, + mounted() { + this.initListener() + }, + activated() { + if (!this.$_resizeHandler) { + // avoid duplication init + this.initListener() + } + + // when keep-alive chart activated, auto resize + this.resize() + }, + beforeDestroy() { + this.destroyListener() + }, + deactivated() { + this.destroyListener() + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_sidebarResizeHandler(e) { + if (e.propertyName === 'width') { + this.$_resizeHandler() + } + }, + initListener() { + this.$_resizeHandler = debounce(() => { + this.resize() + }, 100) + window.addEventListener('resize', this.$_resizeHandler) + + this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0] + this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler) + }, + destroyListener() { + window.removeEventListener('resize', this.$_resizeHandler) + this.$_resizeHandler = null + + this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler) + }, + resize() { + const { chart } = this + chart && chart.resize() + } + } +} diff --git a/ruoyi-ui/src/views/error/401.vue b/ruoyi-ui/src/views/error/401.vue new file mode 100644 index 000000000..448b6ecd6 --- /dev/null +++ b/ruoyi-ui/src/views/error/401.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/ruoyi-ui/src/views/error/404.vue b/ruoyi-ui/src/views/error/404.vue new file mode 100644 index 000000000..96f075c17 --- /dev/null +++ b/ruoyi-ui/src/views/error/404.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/ruoyi-ui/src/views/index.vue b/ruoyi-ui/src/views/index.vue new file mode 100644 index 000000000..1a7310a51 --- /dev/null +++ b/ruoyi-ui/src/views/index.vue @@ -0,0 +1,1067 @@ + + + + + + diff --git a/ruoyi-ui/src/views/index_v1.vue b/ruoyi-ui/src/views/index_v1.vue new file mode 100644 index 000000000..d2d2ec633 --- /dev/null +++ b/ruoyi-ui/src/views/index_v1.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/ruoyi-ui/src/views/login.vue b/ruoyi-ui/src/views/login.vue new file mode 100644 index 000000000..06c09d287 --- /dev/null +++ b/ruoyi-ui/src/views/login.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/ruoyi-ui/src/views/monitor/cache/index.vue b/ruoyi-ui/src/views/monitor/cache/index.vue new file mode 100644 index 000000000..8d2f37840 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/cache/index.vue @@ -0,0 +1,148 @@ + + + diff --git a/ruoyi-ui/src/views/monitor/cache/list.vue b/ruoyi-ui/src/views/monitor/cache/list.vue new file mode 100644 index 000000000..29a7c741d --- /dev/null +++ b/ruoyi-ui/src/views/monitor/cache/list.vue @@ -0,0 +1,241 @@ + + + diff --git a/ruoyi-ui/src/views/monitor/druid/index.vue b/ruoyi-ui/src/views/monitor/druid/index.vue new file mode 100644 index 000000000..c6ad585c1 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/druid/index.vue @@ -0,0 +1,15 @@ + + diff --git a/ruoyi-ui/src/views/monitor/job/index.vue b/ruoyi-ui/src/views/monitor/job/index.vue new file mode 100644 index 000000000..892c72750 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/job/index.vue @@ -0,0 +1,513 @@ + + + diff --git a/ruoyi-ui/src/views/monitor/job/log.vue b/ruoyi-ui/src/views/monitor/job/log.vue new file mode 100644 index 000000000..60bee1def --- /dev/null +++ b/ruoyi-ui/src/views/monitor/job/log.vue @@ -0,0 +1,295 @@ + + + diff --git a/ruoyi-ui/src/views/monitor/logininfor/index.vue b/ruoyi-ui/src/views/monitor/logininfor/index.vue new file mode 100644 index 000000000..d6af834c3 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/logininfor/index.vue @@ -0,0 +1,246 @@ + + + + diff --git a/ruoyi-ui/src/views/monitor/online/index.vue b/ruoyi-ui/src/views/monitor/online/index.vue new file mode 100644 index 000000000..ad613c969 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/online/index.vue @@ -0,0 +1,122 @@ + + + + diff --git a/ruoyi-ui/src/views/monitor/operlog/index.vue b/ruoyi-ui/src/views/monitor/operlog/index.vue new file mode 100644 index 000000000..4a1828f39 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/operlog/index.vue @@ -0,0 +1,323 @@ + + + + diff --git a/ruoyi-ui/src/views/monitor/server/index.vue b/ruoyi-ui/src/views/monitor/server/index.vue new file mode 100644 index 000000000..15ffc9a69 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/server/index.vue @@ -0,0 +1,207 @@ + + + diff --git a/ruoyi-ui/src/views/redirect.vue b/ruoyi-ui/src/views/redirect.vue new file mode 100644 index 000000000..db4c1d66d --- /dev/null +++ b/ruoyi-ui/src/views/redirect.vue @@ -0,0 +1,12 @@ + diff --git a/ruoyi-ui/src/views/register.vue b/ruoyi-ui/src/views/register.vue new file mode 100644 index 000000000..7bf6f43f7 --- /dev/null +++ b/ruoyi-ui/src/views/register.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/ruoyi-ui/src/views/system/config/index.vue b/ruoyi-ui/src/views/system/config/index.vue new file mode 100644 index 000000000..3ab81fc20 --- /dev/null +++ b/ruoyi-ui/src/views/system/config/index.vue @@ -0,0 +1,343 @@ + + + diff --git a/ruoyi-ui/src/views/system/dept/index.vue b/ruoyi-ui/src/views/system/dept/index.vue new file mode 100644 index 000000000..e502b4ea7 --- /dev/null +++ b/ruoyi-ui/src/views/system/dept/index.vue @@ -0,0 +1,340 @@ + + + diff --git a/ruoyi-ui/src/views/system/dict/data.vue b/ruoyi-ui/src/views/system/dict/data.vue new file mode 100644 index 000000000..3befe4a62 --- /dev/null +++ b/ruoyi-ui/src/views/system/dict/data.vue @@ -0,0 +1,402 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/dict/index.vue b/ruoyi-ui/src/views/system/dict/index.vue new file mode 100644 index 000000000..6ca54571e --- /dev/null +++ b/ruoyi-ui/src/views/system/dict/index.vue @@ -0,0 +1,347 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/menu/index.vue b/ruoyi-ui/src/views/system/menu/index.vue new file mode 100644 index 000000000..c703fa0aa --- /dev/null +++ b/ruoyi-ui/src/views/system/menu/index.vue @@ -0,0 +1,452 @@ + + + diff --git a/ruoyi-ui/src/views/system/notice/index.vue b/ruoyi-ui/src/views/system/notice/index.vue new file mode 100644 index 000000000..7982b545e --- /dev/null +++ b/ruoyi-ui/src/views/system/notice/index.vue @@ -0,0 +1,312 @@ + + + diff --git a/ruoyi-ui/src/views/system/post/index.vue b/ruoyi-ui/src/views/system/post/index.vue new file mode 100644 index 000000000..444bf6344 --- /dev/null +++ b/ruoyi-ui/src/views/system/post/index.vue @@ -0,0 +1,309 @@ + + + diff --git a/ruoyi-ui/src/views/system/role/authUser.vue b/ruoyi-ui/src/views/system/role/authUser.vue new file mode 100644 index 000000000..147aa33e4 --- /dev/null +++ b/ruoyi-ui/src/views/system/role/authUser.vue @@ -0,0 +1,199 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/role/index.vue b/ruoyi-ui/src/views/system/role/index.vue new file mode 100644 index 000000000..fb3b5ef04 --- /dev/null +++ b/ruoyi-ui/src/views/system/role/index.vue @@ -0,0 +1,605 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/role/selectUser.vue b/ruoyi-ui/src/views/system/role/selectUser.vue new file mode 100644 index 000000000..b2b072f9c --- /dev/null +++ b/ruoyi-ui/src/views/system/role/selectUser.vue @@ -0,0 +1,138 @@ + + + diff --git a/ruoyi-ui/src/views/system/user/authRole.vue b/ruoyi-ui/src/views/system/user/authRole.vue new file mode 100644 index 000000000..943710e52 --- /dev/null +++ b/ruoyi-ui/src/views/system/user/authRole.vue @@ -0,0 +1,117 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/user/index.vue b/ruoyi-ui/src/views/system/user/index.vue new file mode 100644 index 000000000..6b2a0aaf3 --- /dev/null +++ b/ruoyi-ui/src/views/system/user/index.vue @@ -0,0 +1,676 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/user/profile/index.vue b/ruoyi-ui/src/views/system/user/profile/index.vue new file mode 100644 index 000000000..529c56445 --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/index.vue @@ -0,0 +1,91 @@ + + + diff --git a/ruoyi-ui/src/views/system/user/profile/resetPwd.vue b/ruoyi-ui/src/views/system/user/profile/resetPwd.vue new file mode 100644 index 000000000..f329e6e87 --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/resetPwd.vue @@ -0,0 +1,69 @@ + + + diff --git a/ruoyi-ui/src/views/system/user/profile/userAvatar.vue b/ruoyi-ui/src/views/system/user/profile/userAvatar.vue new file mode 100644 index 000000000..cbf3ca197 --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/userAvatar.vue @@ -0,0 +1,184 @@ + + + + diff --git a/ruoyi-ui/src/views/system/user/profile/userInfo.vue b/ruoyi-ui/src/views/system/user/profile/userInfo.vue new file mode 100644 index 000000000..c970dc98e --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/userInfo.vue @@ -0,0 +1,88 @@ + + + diff --git a/ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue b/ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue new file mode 100644 index 000000000..b5c2e2e49 --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue @@ -0,0 +1,106 @@ + + diff --git a/ruoyi-ui/src/views/tool/build/DraggableItem.vue b/ruoyi-ui/src/views/tool/build/DraggableItem.vue new file mode 100644 index 000000000..e881778f0 --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/DraggableItem.vue @@ -0,0 +1,100 @@ + diff --git a/ruoyi-ui/src/views/tool/build/IconsDialog.vue b/ruoyi-ui/src/views/tool/build/IconsDialog.vue new file mode 100644 index 000000000..958be50c5 --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/IconsDialog.vue @@ -0,0 +1,123 @@ + + + diff --git a/ruoyi-ui/src/views/tool/build/RightPanel.vue b/ruoyi-ui/src/views/tool/build/RightPanel.vue new file mode 100644 index 000000000..c2760eb5e --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/RightPanel.vue @@ -0,0 +1,946 @@ + + + + + diff --git a/ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue b/ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue new file mode 100644 index 000000000..fa7f0b285 --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue @@ -0,0 +1,149 @@ + + diff --git a/ruoyi-ui/src/views/tool/build/index.vue b/ruoyi-ui/src/views/tool/build/index.vue new file mode 100644 index 000000000..2bd298b8f --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/index.vue @@ -0,0 +1,768 @@ + + + + + diff --git a/ruoyi-ui/src/views/tool/gen/basicInfoForm.vue b/ruoyi-ui/src/views/tool/gen/basicInfoForm.vue new file mode 100644 index 000000000..70295298e --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/basicInfoForm.vue @@ -0,0 +1,60 @@ + + + diff --git a/ruoyi-ui/src/views/tool/gen/createTable.vue b/ruoyi-ui/src/views/tool/gen/createTable.vue new file mode 100644 index 000000000..f914b5d39 --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/createTable.vue @@ -0,0 +1,45 @@ + + + diff --git a/ruoyi-ui/src/views/tool/gen/editTable.vue b/ruoyi-ui/src/views/tool/gen/editTable.vue new file mode 100644 index 000000000..951497a71 --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/editTable.vue @@ -0,0 +1,234 @@ + + + diff --git a/ruoyi-ui/src/views/tool/gen/genInfoForm.vue b/ruoyi-ui/src/views/tool/gen/genInfoForm.vue new file mode 100644 index 000000000..98daf6d52 --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/genInfoForm.vue @@ -0,0 +1,312 @@ + + + diff --git a/ruoyi-ui/src/views/tool/gen/importTable.vue b/ruoyi-ui/src/views/tool/gen/importTable.vue new file mode 100644 index 000000000..3ea9532bf --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/importTable.vue @@ -0,0 +1,120 @@ + + + diff --git a/ruoyi-ui/src/views/tool/gen/index.vue b/ruoyi-ui/src/views/tool/gen/index.vue new file mode 100644 index 000000000..9237c3028 --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/index.vue @@ -0,0 +1,354 @@ + + + diff --git a/ruoyi-ui/src/views/tool/swagger/index.vue b/ruoyi-ui/src/views/tool/swagger/index.vue new file mode 100644 index 000000000..b8becc67c --- /dev/null +++ b/ruoyi-ui/src/views/tool/swagger/index.vue @@ -0,0 +1,15 @@ + + diff --git a/ruoyi-ui/vue.config.js b/ruoyi-ui/vue.config.js new file mode 100644 index 000000000..85f3133bc --- /dev/null +++ b/ruoyi-ui/vue.config.js @@ -0,0 +1,130 @@ +'use strict' +const path = require('path') + +function resolve(dir) { + return path.join(__dirname, dir) +} + +const CompressionPlugin = require('compression-webpack-plugin') + +const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题 + +const port = process.env.port || process.env.npm_config_port || 80 // 端口 + +// vue.config.js 配置说明 +//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions +// 这里只列一部分,具体配置参考文档 +module.exports = { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + publicPath: process.env.NODE_ENV === "production" ? "/" : "/", + // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) + outputDir: 'dist', + // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) + assetsDir: 'static', + // 是否开启eslint保存检测,有效值:ture | false | 'error' + lintOnSave: process.env.NODE_ENV === 'development', + // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 + productionSourceMap: false, + // webpack-dev-server 相关配置 + devServer: { + host: '0.0.0.0', + port: port, + open: true, + proxy: { + // detail: https://cli.vuejs.org/config/#devserver-proxy + [process.env.VUE_APP_BASE_API]: { + target: `http://localhost:8080`, + changeOrigin: true, + pathRewrite: { + ['^' + process.env.VUE_APP_BASE_API]: '' + } + } + }, + disableHostCheck: true + }, + css: { + loaderOptions: { + sass: { + sassOptions: { outputStyle: "expanded" } + } + } + }, + configureWebpack: { + name: name, + resolve: { + alias: { + '@': resolve('src') + } + }, + plugins: [ + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + new CompressionPlugin({ + cache: false, // 不启用文件缓存 + test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式 + filename: '[path][base].gz[query]', // 压缩后的文件名 + algorithm: 'gzip', // 使用gzip压缩 + minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩 + deleteOriginalAssets: false // 压缩后删除原文件 + }) + ], + }, + chainWebpack(config) { + config.plugins.delete('preload') // TODO: need test + config.plugins.delete('prefetch') // TODO: need test + + // set svg-sprite-loader + config.module + .rule('svg') + .exclude.add(resolve('src/assets/icons')) + .end() + config.module + .rule('icons') + .test(/\.svg$/) + .include.add(resolve('src/assets/icons')) + .end() + .use('svg-sprite-loader') + .loader('svg-sprite-loader') + .options({ + symbolId: 'icon-[name]' + }) + .end() + + config.when(process.env.NODE_ENV !== 'development', config => { + config + .plugin('ScriptExtHtmlWebpackPlugin') + .after('html') + .use('script-ext-html-webpack-plugin', [{ + // `runtime` must same as runtimeChunk name. default is `runtime` + inline: /runtime\..*\.js$/ + }]) + .end() + + config.optimization.splitChunks({ + chunks: 'all', + cacheGroups: { + libs: { + name: 'chunk-libs', + test: /[\\/]node_modules[\\/]/, + priority: 10, + chunks: 'initial' // only package third parties that are initially dependent + }, + elementUI: { + name: 'chunk-elementUI', // split elementUI into a single package + test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm + priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app + }, + commons: { + name: 'chunk-commons', + test: resolve('src/components'), // can customize your rules + minChunks: 3, // minimum common number + priority: 5, + reuseExistingChunk: true + } + } + }) + config.optimization.runtimeChunk('single') + }) + } +} diff --git a/sql/ruoyi.html b/sql/ruoyi.html deleted file mode 100644 index 915fa1fbc..000000000 --- a/sql/ruoyi.html +++ /dev/null @@ -1,2890 +0,0 @@ - - - - -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 deleted file mode 100644 index 9dbddc69d..000000000 --- a/sql/ruoyi.pdm +++ /dev/null @@ -1,4851 +0,0 @@ - - - - - - - - - -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_20240112.sql b/sql/ry_20231130.sql similarity index 62% rename from sql/ry_20240112.sql rename to sql/ry_20231130.sql index eab64b0b9..452c2fcf1 100644 --- a/sql/ry_20240112.sql +++ b/sql/ry_20231130.sql @@ -42,20 +42,18 @@ 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注册用户)', + user_name varchar(30) not null comment '用户账号', + nick_name varchar(30) not null comment '用户昵称', + user_type varchar(2) default '00' comment '用户类型(00系统用户)', 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 '盐加密', + avatar varchar(100) default '' comment '头像地址', + password varchar(100) 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 '更新者', @@ -67,8 +65,8 @@ create table sys_user ( -- ---------------------------- -- 初始化-用户信息表数据 -- ---------------------------- -insert into sys_user values(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '29c67a30398638269fe600f73a054934', '111111', '0', '0', '127.0.0.1', null, null, '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', null, null, 'admin', sysdate(), '', null, '测试员'); +insert into sys_user values(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员'); +insert into sys_user values(2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员'); -- ---------------------------- @@ -104,26 +102,28 @@ insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate -- ---------------------------- 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 '备注', + 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:本部门及以下数据权限)', + menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示', + dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示', + 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, '普通角色'); +insert into sys_role values('1', '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); +insert into sys_role values('2', '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色'); -- ---------------------------- @@ -135,11 +135,14 @@ create table sys_menu ( 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新窗口)', + path varchar(200) default '' comment '路由地址', + component varchar(255) default null comment '组件路径', + query varchar(255) default null comment '路由参数', + is_frame int(1) default 1 comment '是否为外链(0是 1否)', + is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)', 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不刷新)', + status char(1) default 0 comment '菜单状态(0正常 1停用)', perms varchar(100) default null comment '权限标识', icon varchar(100) default '#' comment '菜单图标', create_by varchar(64) default '' comment '创建者', @@ -154,106 +157,106 @@ create table sys_menu ( -- 初始化-菜单信息表数据 -- ---------------------------- -- 一级菜单 -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('1', '系统管理', '0', '1', 'system', null, '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录'); +insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录'); +insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录'); +insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', 0, 0, 'M', '0', '0', '', 'guide', '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('100', '用户管理', '1', '1', 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单'); +insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单'); +insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单'); +insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单'); +insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单'); +insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单'); +insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单'); +insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单'); +insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单'); +insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单'); +insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单'); +insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单'); +insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单'); +insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单'); +insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单'); +insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单'); +insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单'); +insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', '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('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单'); +insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', '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('1000', '用户查询', '100', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', 1, 0, 'F', '0', '0', '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('1007', '角色查询', '101', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', 1, 0, 'F', '0', '0', '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('1012', '菜单查询', '102', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', 1, 0, 'F', '0', '0', '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('1016', '部门查询', '103', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', 1, 0, 'F', '0', '0', '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('1020', '岗位查询', '104', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', 1, 0, 'F', '0', '0', '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('1025', '字典查询', '105', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', 1, 0, 'F', '0', '0', '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('1030', '参数查询', '106', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', 1, 0, 'F', '0', '0', '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('1035', '公告查询', '107', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', 1, 0, 'F', '0', '0', '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('1039', '操作查询', '500', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', 1, 0, 'F', '0', '0', '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('1042', '登录查询', '501', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', 1, 0, 'F', '0', '0', '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('1046', '在线查询', '109', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', 1, 0, 'F', '0', '0', '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('1049', '任务查询', '110', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', 1, 0, 'F', '0', '0', '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, ''); +insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); -- ---------------------------- @@ -307,6 +310,7 @@ 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', '117'); insert into sys_role_menu values ('2', '500'); insert into sys_role_menu values ('2', '501'); insert into sys_role_menu values ('2', '1000'); @@ -370,7 +374,6 @@ 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部门 @@ -389,6 +392,7 @@ 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岗位 -- ---------------------------- @@ -539,17 +543,12 @@ create table sys_config ( 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', '1', '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黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); +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' ); +insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)'); +insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); -- ---------------------------- @@ -558,7 +557,7 @@ insert into sys_config values(11, '用户登录-黑名单列表', 'sys 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 '登录账号', + user_name varchar(50) default '' comment '用户账号', ipaddr varchar(128) default '' comment '登录IP地址', login_location varchar(255) default '' comment '登录地点', browser varchar(50) default '' comment '浏览器类型', @@ -573,27 +572,7 @@ create table sys_logininfor ( -- ---------------------------- --- 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、定时任务调度表 +-- 15、定时任务调度表 -- ---------------------------- drop table if exists sys_job; create table sys_job ( @@ -619,7 +598,7 @@ insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryM -- ---------------------------- --- 17、定时任务调度日志表 +-- 16、定时任务调度日志表 -- ---------------------------- drop table if exists sys_job_log; create table sys_job_log ( @@ -636,7 +615,7 @@ create table sys_job_log ( -- ---------------------------- --- 18、通知公告表 +-- 17、通知公告表 -- ---------------------------- drop table if exists sys_notice; create table sys_notice ( @@ -658,40 +637,40 @@ create table sys_notice ( -- ---------------------------- 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, '管理员'); -insert into sys_notice values('3', '若依开源框架介绍', '1', '

    项目介绍

    RuoYi开源项目是为企业用户定制的后台脚手架框架,为企业打造的一站式解决方案,降低企业开发成本,提升开发效率。主要包括用户管理、角色管理、部门管理、菜单管理、参数管理、字典管理、岗位管理、定时任务服务监控、登录日志、操作日志、代码生成等功能。其中,还支持多数据源、数据权限、国际化、Redis缓存、Docker部署、滑动验证码、第三方认证登录、分布式事务、分布式文件存储、分库分表处理等技术特点。


    官网及演示

    若依官网地址: http://ruoyi.vip

    若依文档地址: http://doc.ruoyi.vip

    演示地址【不分离版】: http://demo.ruoyi.vip

    演示地址【分离版本】: http://vue.ruoyi.vip

    演示地址【微服务版】: http://cloud.ruoyi.vip

    演示地址【移动端版】: http://h5.ruoyi.vip


    ', '0', 'admin', sysdate(), '', null, '管理员'); -- ---------------------------- --- 19、代码生成业务表 +-- 18、代码生成业务表 -- ---------------------------- 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 '备注', + 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树表操作)', + tpl_web_type varchar(30) default '' comment '前端模板类型(element-ui模版 element-plus模版)', + 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、代码生成业务表字段 +-- 19、代码生成业务表字段 -- ---------------------------- drop table if exists gen_table_column; create table gen_table_column ( From ff6b73c501d6496e68f7dfc181fd240f4dd613fd Mon Sep 17 00:00:00 2001 From: zgtfx <2844350049@qq.com> Date: Sun, 21 Apr 2024 12:06:25 +0800 Subject: [PATCH 2/2] 1 --- ruoyi-admin/src/main/resources/application-druid.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index bcfad3eae..b3cb88d51 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -8,7 +8,7 @@ spring: master: url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root - password: password + password: root # 从库数据源 slave: # 从数据源开关/默认关闭