From 07954b47e8fe8258699b465fe0050c5f73a75945 Mon Sep 17 00:00:00 2001 From: Anyon Date: Tue, 13 Mar 2018 15:37:38 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=9B=B4=E6=96=B0]=E5=A4=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=9B=B4=E6=96=B0V3,Thinkphp5.1.x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .htaccess | 1 + README.md | 6 +- admin_v3.sql | 878 +++ application/.htaccess | 1 + application/admin/controller/Auth.php | 51 +- application/admin/controller/Config.php | 41 +- application/admin/controller/Index.php | 35 +- application/admin/controller/Log.php | 37 +- application/admin/controller/Login.php | 17 +- application/admin/controller/Menu.php | 33 +- application/admin/controller/Node.php | 38 +- application/admin/controller/Plugs.php | 43 +- application/admin/controller/User.php | 41 +- application/admin/view/auth.index.html | 92 - .../view/{auth.apply.html => auth/apply.html} | 104 +- .../view/{auth.form.html => auth/form.html} | 4 +- application/admin/view/auth/index.html | 82 + application/admin/view/config.file.html | 244 - application/admin/view/config.index.html | 68 - application/admin/view/config/file.html | 228 + application/admin/view/config/index.html | 82 + application/admin/view/config/sms.html | 41 + application/admin/view/index.index.html | 9 - application/admin/view/index.main.html | 115 - application/admin/view/index/index.html | 83 + application/admin/view/index/main.html | 145 + .../view/{log.index.html => log/index.html} | 193 +- application/admin/view/log/sms.html | 76 + .../{login.index.html => login/index.html} | 174 +- application/admin/view/menu.index.html | 80 - .../view/{menu.form.html => menu/form.html} | 15 +- application/admin/view/menu/index.html | 87 + application/admin/view/node.index.html | 106 - application/admin/view/node/index.html | 208 + application/admin/view/plugs.upfile.html | 555 -- .../view/{plugs.icon.html => plugs/icon.html} | 1054 +-- application/admin/view/plugs/upfile.html | 555 ++ application/admin/view/public/content.html | 13 + application/admin/view/public/main.html | 26 + application/admin/view/user.index.html | 139 - .../view/{user.auth.html => user/auth.html} | 83 +- .../view/{user.form.html => user/form.html} | 131 +- application/admin/view/user/index.html | 137 + .../view/{user.pass.html => user/pass.html} | 95 +- application/common.php | 145 +- application/config.php | 224 - application/demo/controller/Plugs.php | 56 - application/demo/view/plugs.editor.html | 47 - application/demo/view/plugs.file.html | 263 - application/demo/view/plugs.region.html | 65 - application/extra/mines.php | 161 - application/extra/view/admin.content.html | 22 - application/extra/view/admin.main.html | 24 - application/extra/view/admin.main.left.html | 52 - application/extra/view/admin.main.top.html | 64 - application/extra/view/handler.exception.html | 53 - application/extra/view/handler.jump.html | 49 - application/index/controller/Index.php | 24 +- application/index/controller/Wap.php | 120 - application/index/view/wap.payjs.html | 57 - application/index/view/wap.payqrc.html | 53 - application/route.php | 64 - application/tags.php | 37 +- application/wechat/controller/Api.php | 263 - application/wechat/controller/Block.php | 114 + application/wechat/controller/Config.php | 112 +- application/wechat/controller/Fans.php | 124 +- application/wechat/controller/Keys.php | 116 +- application/wechat/controller/Menu.php | 86 +- application/wechat/controller/News.php | 157 +- application/wechat/controller/Notify.php | 68 - application/wechat/controller/Review.php | 35 +- application/wechat/controller/Tags.php | 84 +- application/wechat/controller/api/Push.php | 256 + application/wechat/service/FansService.php | 123 + application/wechat/service/MediaService.php | 101 + application/wechat/service/TagsService.php | 69 + application/wechat/view/block/index.html | 160 + application/wechat/view/config.index.html | 72 - application/wechat/view/config.pay.html | 132 - application/wechat/view/config/index.html | 67 + application/wechat/view/fans.back.html | 201 - application/wechat/view/fans.index.html | 207 - application/wechat/view/fans/index.html | 160 + application/wechat/view/fans/tags_inc.html | 80 + application/wechat/view/keys.index.html | 203 - .../view/{keys.form.html => keys/form.html} | 586 +- application/wechat/view/keys/index.html | 187 + .../view/{menu.index.html => menu/index.html} | 678 +- application/wechat/view/news.form.html | 278 - application/wechat/view/news.image.html | 43 - application/wechat/view/news.push.html | 112 - application/wechat/view/news.select.html | 62 - application/wechat/view/news/form.html | 362 ++ application/wechat/view/news/image.html | 42 + .../view/{news.index.html => news/index.html} | 336 +- application/wechat/view/news/push.html | 192 + application/wechat/view/news/select.html | 80 + .../{review.index.html => review/index.html} | 353 +- application/wechat/view/tags.index.html | 89 - .../view/{tags.form.html => tags/form.html} | 40 +- application/wechat/view/tags/index.html | 89 + composer.json | 75 +- config/app.php | 111 + .../include.php => config/cache.php | 37 +- config/cookie.php | 30 + {application => config}/database.php | 10 +- config/log.php | 20 + config/session.php | 29 + config/template.php | 43 + .../extra/queue.php => config/trace.php | 2 +- extend/controller/BasicAdmin.php | 98 +- extend/controller/BasicApi.php | 67 - extend/controller/BasicApp.php | 145 + extend/controller/BasicPda.php | 122 + extend/controller/BasicWechat.php | 100 +- extend/hook/AccessAuth.php | 84 - extend/hook/FilterView.php | 75 - extend/service/DataService.php | 57 +- extend/service/ExtendService.php | 82 +- extend/service/FileService.php | 152 +- extend/service/HttpService.php | 130 +- extend/service/LogService.php | 14 +- extend/service/NodeService.php | 58 +- extend/service/PayService.php | 146 - extend/service/SoapService.php | 36 +- extend/service/ToolsService.php | 38 +- extend/service/WechatService.php | 278 +- index.php | 14 +- route/route.php | 57 + static/admin.js | 664 ++ static/admin/app.js | 75 - static/admin/listen.js | 149 - static/admin/plugs.js | 484 -- static/app.js | 72 + static/plugs/angular/angular.min.js | 644 +- .../bootstrap-multiselect.css | 1 + .../bootstrap-multiselect.js | 1683 +++++ static/plugs/ckeditor/config.js | 92 +- static/plugs/distpicker/distpicker.data.js | 4528 +++++++++++++ static/plugs/distpicker/distpicker.js | 248 + static/plugs/jquery/masonry.min.js | 6 +- static/plugs/layui/css/layui.css | 4 +- static/plugs/layui/css/layui.mobile.css | 2 +- static/plugs/layui/css/modules/code.css | 2 +- .../css/modules/laydate/default/laydate.css | 4 +- .../layui/css/modules/layer/default/layer.css | 2 +- .../layui/css/modules/layim/html/chatlog.html | 96 - .../layui/css/modules/layim/html/find.html | 38 - .../layui/css/modules/layim/html/getmsg.json | 87 - .../layui/css/modules/layim/html/msgbox.html | 208 - .../plugs/layui/css/modules/layim/layim.css | 2 - .../layui/css/modules/layim/mobile/layim.css | 2 - .../plugs/layui/css/modules/layim/skin/1.jpg | Bin 18386 -> 0 bytes .../plugs/layui/css/modules/layim/skin/2.jpg | Bin 11883 -> 0 bytes .../plugs/layui/css/modules/layim/skin/3.jpg | Bin 18610 -> 0 bytes .../plugs/layui/css/modules/layim/skin/4.jpg | Bin 39279 -> 0 bytes .../plugs/layui/css/modules/layim/skin/5.jpg | Bin 33379 -> 0 bytes .../layui/css/modules/layim/skin/logo.jpg | Bin 3284 -> 0 bytes .../layui/css/modules/layim/voice/default.mp3 | Bin 9341 -> 0 bytes static/plugs/layui/font/iconfont.eot | Bin 33180 -> 38536 bytes static/plugs/layui/font/iconfont.svg | 121 +- static/plugs/layui/font/iconfont.ttf | Bin 33004 -> 38360 bytes static/plugs/layui/font/iconfont.woff | Bin 21688 -> 25244 bytes static/plugs/layui/lay/modules/carousel.js | 4 +- static/plugs/layui/lay/modules/code.js | 2 +- static/plugs/layui/lay/modules/element.js | 4 +- static/plugs/layui/lay/modules/flow.js | 2 +- static/plugs/layui/lay/modules/form.js | 4 +- static/plugs/layui/lay/modules/jquery.js | 2 +- static/plugs/layui/lay/modules/laydate.js | 4 +- static/plugs/layui/lay/modules/layedit.js | 2 +- static/plugs/layui/lay/modules/layer.js | 4 +- static/plugs/layui/lay/modules/layim.js | 3 - static/plugs/layui/lay/modules/laypage.js | 4 +- static/plugs/layui/lay/modules/laytpl.js | 4 +- static/plugs/layui/lay/modules/mobile.js | 4 +- static/plugs/layui/lay/modules/table.js | 4 +- static/plugs/layui/lay/modules/tree.js | 2 +- static/plugs/layui/lay/modules/upload.js | 4 +- static/plugs/layui/lay/modules/util.js | 4 +- static/plugs/layui/layui.all.js | 6 +- static/plugs/layui/layui.js | 4 +- static/plugs/require/require.js | 2 +- static/theme/{default => }/css/animate.css | 5718 ++++++++--------- static/theme/css/console.css | 163 + static/theme/css/login.css | 63 + static/theme/default/css/console.css | 217 - static/theme/default/css/login.css | 61 - static/theme/default/img/cuci.png | Bin 2998 -> 0 bytes static/theme/default/img/cuci_favicon.ico | Bin 67646 -> 0 bytes static/theme/default/img/head.gif | Bin 129103 -> 0 bytes static/theme/default/img/icon.png | Bin 4871 -> 0 bytes static/theme/default/img/login/buttonbg.png | Bin 3000 -> 0 bytes static/theme/default/img/login/cloud_two.png | Bin 212928 -> 0 bytes static/theme/default/img/login/light.png | Bin 132156 -> 0 bytes static/theme/default/img/login/loginbg1.png | Bin 2834 -> 0 bytes static/theme/default/img/login/loginbg2.png | Bin 2829 -> 0 bytes .../theme/default/img/login/loginpassword.png | Bin 3453 -> 0 bytes static/theme/default/img/login/loginuser.png | Bin 3425 -> 0 bytes static/theme/default/img/paper.jpg | Bin 309 -> 0 bytes static/theme/{default => }/img/404_icon.png | Bin static/theme/{default => }/img/505_icon.png | Bin static/theme/img/headimg.png | Bin 0 -> 4869 bytes static/theme/img/icon.png | Bin 0 -> 2857 bytes static/theme/{default => }/img/image.png | Bin .../login/loginbg3.png => img/login/bg.png} | Bin .../logininfo.png => img/login/bg_form.png} | Bin .../theme/{default => }/img/login/cloud.png | Bin .../{default => }/img/login/cloud_one.png | Bin .../login/cloud_two.png} | Bin static/theme/{default => }/img/login/hand.png | Bin .../left_hand.png => img/login/hand_left.png} | Bin .../login/hand_right.png} | Bin .../login/tou.png => img/login/hand_tou.png} | Bin .../theme/{default => }/img/wechat/index.png | Bin .../{default => }/img/wechat/mobile_foot.png | Bin .../{default => }/img/wechat/mobile_head.png | Bin .../{default => }/img/wechat/qrc_payed.jpg | Bin think | 22 + think.admin.sql | 570 -- thinkphp/.travis.yml | 47 - thinkphp/CONTRIBUTING.md | 4 +- thinkphp/LICENSE.txt | 2 +- thinkphp/README.md | 101 +- thinkphp/base.php | 132 +- thinkphp/codecov.yml | 12 - thinkphp/composer.json | 14 +- thinkphp/convention.php | 277 +- thinkphp/helper.php | 994 +-- thinkphp/lang/zh-cn.php | 1 + thinkphp/library/think/App.php | 1290 ++-- thinkphp/library/think/Build.php | 335 +- thinkphp/library/think/Cache.php | 214 +- thinkphp/library/think/Collection.php | 339 +- thinkphp/library/think/Config.php | 306 +- thinkphp/library/think/Console.php | 308 +- thinkphp/library/think/Container.php | 295 + thinkphp/library/think/Controller.php | 111 +- thinkphp/library/think/Cookie.php | 178 +- thinkphp/library/think/Db.php | 149 +- thinkphp/library/think/Debug.php | 167 +- thinkphp/library/think/Env.php | 84 +- thinkphp/library/think/Error.php | 62 +- thinkphp/library/think/Exception.php | 11 +- thinkphp/library/think/Facade.php | 122 + thinkphp/library/think/File.php | 180 +- thinkphp/library/think/Hook.php | 173 +- thinkphp/library/think/Lang.php | 194 +- thinkphp/library/think/Loader.php | 626 +- thinkphp/library/think/Log.php | 355 +- thinkphp/library/think/Model.php | 2539 ++------ thinkphp/library/think/Paginator.php | 122 +- thinkphp/library/think/Process.php | 151 +- thinkphp/library/think/Request.php | 701 +- thinkphp/library/think/Response.php | 185 +- thinkphp/library/think/Route.php | 2052 +++--- thinkphp/library/think/Session.php | 359 +- thinkphp/library/think/Template.php | 359 +- thinkphp/library/think/Url.php | 221 +- thinkphp/library/think/Validate.php | 897 +-- thinkphp/library/think/View.php | 179 +- thinkphp/library/think/cache/Driver.php | 176 +- thinkphp/library/think/cache/driver/File.php | 91 +- thinkphp/library/think/cache/driver/Lite.php | 62 +- .../library/think/cache/driver/Memcache.php | 76 +- .../library/think/cache/driver/Memcached.php | 95 +- thinkphp/library/think/cache/driver/Redis.php | 62 +- .../library/think/cache/driver/Sqlite.php | 84 +- .../library/think/cache/driver/Wincache.php | 66 +- .../library/think/cache/driver/Xcache.php | 64 +- thinkphp/library/think/config/driver/Json.php | 2 + thinkphp/library/think/config/driver/Xml.php | 2 + .../library/think/console/command/Build.php | 10 +- .../library/think/console/command/Clear.php | 17 +- .../library/think/console/command/Make.php | 15 +- .../think/console/command/RunServer.php | 54 + .../think/console/command/make/Controller.php | 2 +- .../console/command/optimize/Autoload.php | 68 +- .../think/console/command/optimize/Config.php | 74 +- .../think/console/command/optimize/Route.php | 55 +- .../think/console/command/optimize/Schema.php | 55 +- .../think/console/output/Descriptor.php | 10 +- .../think/console/output/driver/Console.php | 7 +- thinkphp/library/think/controller/Rest.php | 99 - thinkphp/library/think/controller/Yar.php | 51 - thinkphp/library/think/db/Builder.php | 1012 +-- thinkphp/library/think/db/Connection.php | 1307 +++- thinkphp/library/think/db/Query.php | 2311 +++---- thinkphp/library/think/db/builder/Mysql.php | 179 +- thinkphp/library/think/db/builder/Pgsql.php | 38 +- thinkphp/library/think/db/builder/Sqlite.php | 32 +- thinkphp/library/think/db/builder/Sqlsrv.php | 56 +- thinkphp/library/think/db/connector/Mysql.php | 43 +- thinkphp/library/think/db/connector/Pgsql.php | 23 +- .../library/think/db/connector/Sqlite.php | 14 +- .../library/think/db/connector/Sqlsrv.php | 26 +- .../think/db/exception/BindParamException.php | 11 +- .../db/exception/DataNotFoundException.php | 7 +- .../db/exception/ModelNotFoundException.php | 6 +- thinkphp/library/think/debug/Console.php | 30 +- thinkphp/library/think/debug/Html.php | 31 +- .../library/think/exception/DbException.php | 9 +- .../think/exception/ErrorException.php | 15 +- thinkphp/library/think/exception/Handle.php | 74 +- .../library/think/exception/PDOException.php | 9 +- thinkphp/library/think/facade/App.php | 54 + thinkphp/library/think/facade/Build.php | 24 + thinkphp/library/think/facade/Cache.php | 36 + thinkphp/library/think/facade/Config.php | 27 + thinkphp/library/think/facade/Cookie.php | 30 + thinkphp/library/think/facade/Debug.php | 31 + thinkphp/library/think/facade/Env.php | 25 + thinkphp/library/think/facade/Hook.php | 28 + thinkphp/library/think/facade/Lang.php | 32 + thinkphp/library/think/facade/Log.php | 40 + thinkphp/library/think/facade/Request.php | 88 + thinkphp/library/think/facade/Response.php | 38 + thinkphp/library/think/facade/Route.php | 48 + thinkphp/library/think/facade/Session.php | 37 + thinkphp/library/think/facade/Url.php | 24 + thinkphp/library/think/facade/Validate.php | 65 + thinkphp/library/think/facade/View.php | 31 + .../think/http/middleware/Dispatcher.php | 86 + .../http/middleware/DispatcherInterface.php | 45 + .../middleware/MissingResponseException.php | 14 +- .../http/tests/middleware/DispatcherTest.php | 69 + thinkphp/library/think/log/driver/File.php | 70 +- thinkphp/library/think/log/driver/Socket.php | 52 +- thinkphp/library/think/log/driver/Test.php | 2 +- thinkphp/library/think/model/Collection.php | 29 +- thinkphp/library/think/model/Merge.php | 322 - thinkphp/library/think/model/Pivot.php | 6 +- thinkphp/library/think/model/Relation.php | 34 +- .../library/think/model/concern/Attribute.php | 556 ++ .../think/model/concern/Conversion.php | 276 + .../think/model/concern/ModelEvent.php | 157 + .../think/model/concern/RelationShip.php | 643 ++ .../model/concern}/SoftDelete.php | 102 +- .../library/think/model/concern/TimeStamp.php | 79 + .../think/model/relation/BelongsTo.php | 93 +- .../think/model/relation/BelongsToMany.php | 253 +- .../library/think/model/relation/HasMany.php | 185 +- .../think/model/relation/HasManyThrough.php | 74 +- .../library/think/model/relation/HasOne.php | 87 +- .../think/model/relation/MorphMany.php | 180 +- .../library/think/model/relation/MorphOne.php | 97 +- .../library/think/model/relation/MorphTo.php | 94 +- .../library/think/model/relation/OneToOne.php | 122 +- .../library/think/paginator/Collection.php | 74 + .../think/paginator/driver/Bootstrap.php | 7 +- .../library/think/process/exception/Faild.php | 42 + thinkphp/library/think/response/Json.php | 4 +- thinkphp/library/think/response/Jsonp.php | 9 +- thinkphp/library/think/response/Jump.php | 36 + thinkphp/library/think/response/Redirect.php | 37 +- thinkphp/library/think/response/View.php | 51 +- thinkphp/library/think/response/Xml.php | 25 +- thinkphp/library/think/route/Dispatch.php | 56 + thinkphp/library/think/route/Domain.php | 257 + thinkphp/library/think/route/Resource.php | 170 + thinkphp/library/think/route/Rule.php | 909 +++ thinkphp/library/think/route/RuleGroup.php | 291 + thinkphp/library/think/route/RuleItem.php | 298 + .../library/think/route/dispatch/Callback.php | 27 + .../think/route/dispatch/Controller.php | 30 + .../library/think/route/dispatch/Module.php | 126 + .../think/route/dispatch/Redirect.php} | 23 +- .../think/route/dispatch/Response.php} | 16 +- thinkphp/library/think/route/dispatch/Url.php | 190 + .../library/think/route/dispatch/View.php | 28 + .../library/think/session/driver/Memcache.php | 24 +- .../think/session/driver/Memcached.php | 27 +- .../library/think/session/driver/Redis.php | 59 +- thinkphp/library/think/template/TagLib.php | 35 +- .../library/think/template/driver/File.php | 22 +- thinkphp/library/think/template/taglib/Cx.php | 145 +- .../library/think/validate/ValidateRule.php | 171 + thinkphp/library/think/view/driver/Php.php | 48 +- thinkphp/library/think/view/driver/Think.php | 51 +- thinkphp/library/traits/controller/Jump.php | 105 +- thinkphp/library/traits/think/Instance.php | 54 - thinkphp/phpunit.xml | 35 - thinkphp/phpunit.xml.dist | 41 + thinkphp/tpl/default_index.tpl | 2 +- thinkphp/tpl/page_trace.tpl | 2 +- thinkphp/tpl/think_exception.tpl | 64 +- vendor/autoload.php | 2 +- vendor/composer/autoload_classmap.php | 233 +- vendor/composer/autoload_files.php | 4 +- vendor/composer/autoload_psr4.php | 7 +- vendor/composer/autoload_real.php | 14 +- vendor/composer/autoload_static.php | 276 +- vendor/composer/installed.json | 411 +- vendor/topthink/think-captcha/README.md | 4 +- vendor/topthink/think-captcha/composer.json | 4 +- vendor/topthink/think-captcha/src/Captcha.php | 50 +- .../think-captcha/src/CaptchaController.php | 6 +- vendor/topthink/think-captcha/src/helper.php | 25 +- vendor/topthink/think-helper/.gitignore | 2 - vendor/topthink/think-helper/LICENSE | 201 - vendor/topthink/think-helper/README.md | 92 - vendor/topthink/think-helper/composer.json | 19 - vendor/topthink/think-helper/src/Arr.php | 41 - vendor/topthink/think-helper/src/Hash.php | 48 - vendor/topthink/think-helper/src/Str.php | 202 - vendor/topthink/think-helper/src/Time.php | 198 - .../topthink/think-helper/src/hash/Bcrypt.php | 51 - vendor/topthink/think-helper/src/hash/Md5.php | 42 - vendor/topthink/think-helper/src/helper.php | 93 - vendor/topthink/think-mongo/.gitignore | 1 - vendor/topthink/think-mongo/LICENSE | 201 - vendor/topthink/think-mongo/README.md | 26 - vendor/topthink/think-mongo/composer.json | 19 - vendor/topthink/think-mongo/src/Builder.php | 573 -- .../topthink/think-mongo/src/Connection.php | 654 -- vendor/topthink/think-mongo/src/Query.php | 2157 ------- vendor/topthink/think-queue/.gitignore | 4 - vendor/topthink/think-queue/LICENSE | 201 - vendor/topthink/think-queue/README.md | 132 - vendor/topthink/think-queue/composer.json | 29 - vendor/topthink/think-queue/src/Queue.php | 49 - vendor/topthink/think-queue/src/common.php | 36 - vendor/topthink/think-queue/src/config.php | 14 - .../src/queue/CallQueuedHandler.php | 36 - .../think-queue/src/queue/Connector.php | 69 - vendor/topthink/think-queue/src/queue/Job.php | 213 - .../think-queue/src/queue/Listener.php | 164 - .../think-queue/src/queue/Queueable.php | 46 - .../topthink/think-queue/src/queue/Worker.php | 119 - .../think-queue/src/queue/command/Listen.php | 60 - .../think-queue/src/queue/command/Restart.php | 31 - .../src/queue/command/Subscribe.php | 46 - .../think-queue/src/queue/command/Work.php | 210 - .../src/queue/connector/Database.php | 171 - .../think-queue/src/queue/connector/Redis.php | 236 - .../think-queue/src/queue/connector/Sync.php | 57 - .../src/queue/connector/Topthink.php | 225 - .../think-queue/src/queue/job/Database.php | 88 - .../think-queue/src/queue/job/Redis.php | 92 - .../think-queue/src/queue/job/Sync.php | 56 - .../think-queue/src/queue/job/Topthink.php | 85 - vendor/zoujingli/wechat-developer/.gitignore | 5 + .../MIT-LICENSE.txt | 2 +- vendor/zoujingli/wechat-developer/README.md | 136 + .../wechat-developer/Test/config.php | 28 + .../Test/pay-download-bill.php | 40 + .../wechat-developer/Test/pay-order-close.php | 37 + .../Test/pay-order-create.php | 45 + .../wechat-developer/Test/pay-order-query.php | 40 + .../Test/pay-refund-create.php | 42 + .../Test/pay-refund-query.php | 42 + .../Test/wechat-jssdk-sign.php | 36 + .../wechat-developer/Test/wechat-menu-get.php | 36 + .../Test/wechat-qrcode-create.php | 40 + .../wechat-developer/Test/wechat-user-get.php | 42 + .../wechat-developer/WeChat/Card.php | 616 ++ .../WeChat/Contracts/BasicPushEvent.php | 228 + .../WeChat/Contracts/BasicWeChat.php | 205 + .../WeChat/Contracts/DataArray.php | 124 + .../WeChat/Contracts/DataError.php | 194 + .../WeChat/Contracts}/Prpcrypt.php | 97 +- .../WeChat/Contracts/Tools.php | 315 + .../wechat-developer/WeChat/Custom.php | 251 + .../Exceptions/InvalidArgumentException.php | 40 + .../Exceptions/InvalidDecryptException.php | 40 + .../Exceptions/InvalidResponseException.php | 41 + .../WeChat/Exceptions/LocalCacheException.php | 42 + .../wechat-developer/WeChat/Limit.php | 42 + .../wechat-developer/WeChat/Media.php | 183 + .../wechat-developer/WeChat/Menu.php | 112 + .../wechat-developer/WeChat/Oauth.php | 91 + .../zoujingli/wechat-developer/WeChat/Pay.php | 275 + .../wechat-developer/WeChat/Product.php | 178 + .../wechat-developer/WeChat/Qrcode.php | 77 + .../wechat-developer/WeChat/Receive.php | 164 + .../wechat-developer/WeChat/Scan.php | 199 + .../wechat-developer/WeChat/Script.php | 121 + .../wechat-developer/WeChat/Shake.php | 364 ++ .../wechat-developer/WeChat/Tags.php | 124 + .../wechat-developer/WeChat/Template.php | 110 + .../wechat-developer/WeChat/User.php | 132 + .../wechat-developer/WeChat/Wifi.php | 285 + .../zoujingli/wechat-developer/composer.json | 29 + vendor/zoujingli/wechat-developer/include.php | 21 + vendor/zoujingli/wechat-php-sdk/.gitignore | 1 - vendor/zoujingli/wechat-php-sdk/README.md | 89 - .../wechat-php-sdk/Wechat/Lib/Cache.php | 146 - .../wechat-php-sdk/Wechat/Lib/Common.php | 232 - .../wechat-php-sdk/Wechat/Lib/Tools.php | 292 - .../wechat-php-sdk/Wechat/Loader.php | 129 - .../wechat-php-sdk/Wechat/WechatCard.php | 819 --- .../wechat-php-sdk/Wechat/WechatCustom.php | 361 -- .../wechat-php-sdk/Wechat/WechatDevice.php | 467 -- .../wechat-php-sdk/Wechat/WechatExtends.php | 209 - .../wechat-php-sdk/Wechat/WechatHardware.php | 160 - .../wechat-php-sdk/Wechat/WechatMedia.php | 429 -- .../wechat-php-sdk/Wechat/WechatMenu.php | 183 - .../wechat-php-sdk/Wechat/WechatMessage.php | 352 - .../wechat-php-sdk/Wechat/WechatOauth.php | 138 - .../wechat-php-sdk/Wechat/WechatPay.php | 616 -- .../wechat-php-sdk/Wechat/WechatPoi.php | 193 - .../wechat-php-sdk/Wechat/WechatReceive.php | 767 --- .../wechat-php-sdk/Wechat/WechatScript.php | 138 - .../wechat-php-sdk/Wechat/WechatService.php | 408 -- .../wechat-php-sdk/Wechat/WechatUser.php | 612 -- vendor/zoujingli/wechat-php-sdk/composer.json | 18 - vendor/zoujingli/wechat-php-sdk/test.php | 68 - vendor/zoujingli/weopen-developer/.gitignore | 4 + vendor/zoujingli/weopen-developer/README.md | 122 + .../zoujingli/weopen-developer/Test/open.php | 57 + .../weopen-developer/Test/wechat.sql | 27 + .../weopen-developer/WeOpen/Service.php | 325 + .../zoujingli/weopen-developer/composer.json | 30 + 514 files changed, 42932 insertions(+), 39762 deletions(-) create mode 100644 admin_v3.sql create mode 100644 application/.htaccess delete mode 100644 application/admin/view/auth.index.html rename application/admin/view/{auth.apply.html => auth/apply.html} (67%) rename application/admin/view/{auth.form.html => auth/form.html} (93%) create mode 100644 application/admin/view/auth/index.html delete mode 100644 application/admin/view/config.file.html delete mode 100644 application/admin/view/config.index.html create mode 100644 application/admin/view/config/file.html create mode 100644 application/admin/view/config/index.html create mode 100644 application/admin/view/config/sms.html delete mode 100644 application/admin/view/index.index.html delete mode 100644 application/admin/view/index.main.html create mode 100644 application/admin/view/index/index.html create mode 100644 application/admin/view/index/main.html rename application/admin/view/{log.index.html => log/index.html} (52%) create mode 100644 application/admin/view/log/sms.html rename application/admin/view/{login.index.html => login/index.html} (52%) delete mode 100644 application/admin/view/menu.index.html rename application/admin/view/{menu.form.html => menu/form.html} (85%) create mode 100644 application/admin/view/menu/index.html delete mode 100644 application/admin/view/node.index.html create mode 100644 application/admin/view/node/index.html delete mode 100644 application/admin/view/plugs.upfile.html rename application/admin/view/{plugs.icon.html => plugs/icon.html} (64%) create mode 100644 application/admin/view/plugs/upfile.html create mode 100644 application/admin/view/public/content.html create mode 100644 application/admin/view/public/main.html delete mode 100644 application/admin/view/user.index.html rename application/admin/view/{user.auth.html => user/auth.html} (65%) rename application/admin/view/{user.form.html => user/form.html} (62%) create mode 100644 application/admin/view/user/index.html rename application/admin/view/{user.pass.html => user/pass.html} (54%) delete mode 100644 application/config.php delete mode 100644 application/demo/controller/Plugs.php delete mode 100644 application/demo/view/plugs.editor.html delete mode 100644 application/demo/view/plugs.file.html delete mode 100644 application/demo/view/plugs.region.html delete mode 100644 application/extra/mines.php delete mode 100644 application/extra/view/admin.content.html delete mode 100644 application/extra/view/admin.main.html delete mode 100644 application/extra/view/admin.main.left.html delete mode 100644 application/extra/view/admin.main.top.html delete mode 100644 application/extra/view/handler.exception.html delete mode 100644 application/extra/view/handler.jump.html delete mode 100644 application/index/controller/Wap.php delete mode 100644 application/index/view/wap.payjs.html delete mode 100644 application/index/view/wap.payqrc.html delete mode 100644 application/route.php delete mode 100644 application/wechat/controller/Api.php create mode 100644 application/wechat/controller/Block.php delete mode 100644 application/wechat/controller/Notify.php create mode 100644 application/wechat/controller/api/Push.php create mode 100644 application/wechat/service/FansService.php create mode 100644 application/wechat/service/MediaService.php create mode 100644 application/wechat/service/TagsService.php create mode 100644 application/wechat/view/block/index.html delete mode 100644 application/wechat/view/config.index.html delete mode 100644 application/wechat/view/config.pay.html create mode 100644 application/wechat/view/config/index.html delete mode 100644 application/wechat/view/fans.back.html delete mode 100644 application/wechat/view/fans.index.html create mode 100644 application/wechat/view/fans/index.html create mode 100644 application/wechat/view/fans/tags_inc.html delete mode 100644 application/wechat/view/keys.index.html rename application/wechat/view/{keys.form.html => keys/form.html} (60%) create mode 100644 application/wechat/view/keys/index.html rename application/wechat/view/{menu.index.html => menu/index.html} (57%) delete mode 100644 application/wechat/view/news.form.html delete mode 100644 application/wechat/view/news.image.html delete mode 100644 application/wechat/view/news.push.html delete mode 100644 application/wechat/view/news.select.html create mode 100644 application/wechat/view/news/form.html create mode 100644 application/wechat/view/news/image.html rename application/wechat/view/{news.index.html => news/index.html} (87%) create mode 100644 application/wechat/view/news/push.html create mode 100644 application/wechat/view/news/select.html rename application/wechat/view/{review.index.html => review/index.html} (93%) delete mode 100644 application/wechat/view/tags.index.html rename application/wechat/view/{tags.form.html => tags/form.html} (75%) create mode 100644 application/wechat/view/tags/index.html create mode 100644 config/app.php rename vendor/zoujingli/wechat-php-sdk/include.php => config/cache.php (65%) create mode 100644 config/cookie.php rename {application => config}/database.php (90%) create mode 100644 config/log.php create mode 100644 config/session.php create mode 100644 config/template.php rename application/extra/queue.php => config/trace.php (96%) delete mode 100644 extend/controller/BasicApi.php create mode 100644 extend/controller/BasicApp.php create mode 100644 extend/controller/BasicPda.php delete mode 100644 extend/hook/AccessAuth.php delete mode 100644 extend/hook/FilterView.php delete mode 100644 extend/service/PayService.php create mode 100644 route/route.php create mode 100644 static/admin.js delete mode 100644 static/admin/app.js delete mode 100644 static/admin/listen.js delete mode 100644 static/admin/plugs.js create mode 100644 static/app.js create mode 100644 static/plugs/bootstrap-multiselect/bootstrap-multiselect.css create mode 100644 static/plugs/bootstrap-multiselect/bootstrap-multiselect.js create mode 100644 static/plugs/distpicker/distpicker.data.js create mode 100644 static/plugs/distpicker/distpicker.js delete mode 100644 static/plugs/layui/css/modules/layim/html/chatlog.html delete mode 100644 static/plugs/layui/css/modules/layim/html/find.html delete mode 100644 static/plugs/layui/css/modules/layim/html/getmsg.json delete mode 100644 static/plugs/layui/css/modules/layim/html/msgbox.html delete mode 100644 static/plugs/layui/css/modules/layim/layim.css delete mode 100644 static/plugs/layui/css/modules/layim/mobile/layim.css delete mode 100644 static/plugs/layui/css/modules/layim/skin/1.jpg delete mode 100644 static/plugs/layui/css/modules/layim/skin/2.jpg delete mode 100644 static/plugs/layui/css/modules/layim/skin/3.jpg delete mode 100644 static/plugs/layui/css/modules/layim/skin/4.jpg delete mode 100644 static/plugs/layui/css/modules/layim/skin/5.jpg delete mode 100644 static/plugs/layui/css/modules/layim/skin/logo.jpg delete mode 100644 static/plugs/layui/css/modules/layim/voice/default.mp3 delete mode 100644 static/plugs/layui/lay/modules/layim.js rename static/theme/{default => }/css/animate.css (95%) create mode 100644 static/theme/css/console.css create mode 100644 static/theme/css/login.css delete mode 100644 static/theme/default/css/console.css delete mode 100644 static/theme/default/css/login.css delete mode 100644 static/theme/default/img/cuci.png delete mode 100644 static/theme/default/img/cuci_favicon.ico delete mode 100644 static/theme/default/img/head.gif delete mode 100644 static/theme/default/img/icon.png delete mode 100644 static/theme/default/img/login/buttonbg.png delete mode 100644 static/theme/default/img/login/cloud_two.png delete mode 100644 static/theme/default/img/login/light.png delete mode 100644 static/theme/default/img/login/loginbg1.png delete mode 100644 static/theme/default/img/login/loginbg2.png delete mode 100644 static/theme/default/img/login/loginpassword.png delete mode 100644 static/theme/default/img/login/loginuser.png delete mode 100644 static/theme/default/img/paper.jpg rename static/theme/{default => }/img/404_icon.png (100%) rename static/theme/{default => }/img/505_icon.png (100%) create mode 100644 static/theme/img/headimg.png create mode 100644 static/theme/img/icon.png rename static/theme/{default => }/img/image.png (100%) rename static/theme/{default/img/login/loginbg3.png => img/login/bg.png} (100%) rename static/theme/{default/img/login/logininfo.png => img/login/bg_form.png} (100%) rename static/theme/{default => }/img/login/cloud.png (100%) rename static/theme/{default => }/img/login/cloud_one.png (100%) rename static/theme/{default/img/login/cloud_three.png => img/login/cloud_two.png} (100%) rename static/theme/{default => }/img/login/hand.png (100%) rename static/theme/{default/img/login/left_hand.png => img/login/hand_left.png} (100%) rename static/theme/{default/img/login/right_hand.png => img/login/hand_right.png} (100%) rename static/theme/{default/img/login/tou.png => img/login/hand_tou.png} (100%) rename static/theme/{default => }/img/wechat/index.png (100%) rename static/theme/{default => }/img/wechat/mobile_foot.png (100%) rename static/theme/{default => }/img/wechat/mobile_head.png (100%) rename static/theme/{default => }/img/wechat/qrc_payed.jpg (100%) create mode 100644 think delete mode 100644 think.admin.sql delete mode 100644 thinkphp/.travis.yml delete mode 100644 thinkphp/codecov.yml create mode 100644 thinkphp/library/think/Container.php create mode 100644 thinkphp/library/think/Facade.php create mode 100644 thinkphp/library/think/console/command/RunServer.php delete mode 100644 thinkphp/library/think/controller/Rest.php delete mode 100644 thinkphp/library/think/controller/Yar.php create mode 100644 thinkphp/library/think/facade/App.php create mode 100644 thinkphp/library/think/facade/Build.php create mode 100644 thinkphp/library/think/facade/Cache.php create mode 100644 thinkphp/library/think/facade/Config.php create mode 100644 thinkphp/library/think/facade/Cookie.php create mode 100644 thinkphp/library/think/facade/Debug.php create mode 100644 thinkphp/library/think/facade/Env.php create mode 100644 thinkphp/library/think/facade/Hook.php create mode 100644 thinkphp/library/think/facade/Lang.php create mode 100644 thinkphp/library/think/facade/Log.php create mode 100644 thinkphp/library/think/facade/Request.php create mode 100644 thinkphp/library/think/facade/Response.php create mode 100644 thinkphp/library/think/facade/Route.php create mode 100644 thinkphp/library/think/facade/Session.php create mode 100644 thinkphp/library/think/facade/Url.php create mode 100644 thinkphp/library/think/facade/Validate.php create mode 100644 thinkphp/library/think/facade/View.php create mode 100644 thinkphp/library/think/http/middleware/Dispatcher.php create mode 100644 thinkphp/library/think/http/middleware/DispatcherInterface.php rename vendor/topthink/think-queue/src/queue/ShouldQueue.php => thinkphp/library/think/http/middleware/MissingResponseException.php (64%) create mode 100644 thinkphp/library/think/http/tests/middleware/DispatcherTest.php delete mode 100644 thinkphp/library/think/model/Merge.php create mode 100644 thinkphp/library/think/model/concern/Attribute.php create mode 100644 thinkphp/library/think/model/concern/Conversion.php create mode 100644 thinkphp/library/think/model/concern/ModelEvent.php create mode 100644 thinkphp/library/think/model/concern/RelationShip.php rename thinkphp/library/{traits/model => think/model/concern}/SoftDelete.php (60%) create mode 100644 thinkphp/library/think/model/concern/TimeStamp.php create mode 100644 thinkphp/library/think/paginator/Collection.php create mode 100644 thinkphp/library/think/process/exception/Faild.php create mode 100644 thinkphp/library/think/response/Jump.php create mode 100644 thinkphp/library/think/route/Dispatch.php create mode 100644 thinkphp/library/think/route/Domain.php create mode 100644 thinkphp/library/think/route/Resource.php create mode 100644 thinkphp/library/think/route/Rule.php create mode 100644 thinkphp/library/think/route/RuleGroup.php create mode 100644 thinkphp/library/think/route/RuleItem.php create mode 100644 thinkphp/library/think/route/dispatch/Callback.php create mode 100644 thinkphp/library/think/route/dispatch/Controller.php create mode 100644 thinkphp/library/think/route/dispatch/Module.php rename thinkphp/{console.php => library/think/route/dispatch/Redirect.php} (54%) rename thinkphp/{start.php => library/think/route/dispatch/Response.php} (78%) create mode 100644 thinkphp/library/think/route/dispatch/Url.php create mode 100644 thinkphp/library/think/route/dispatch/View.php create mode 100644 thinkphp/library/think/validate/ValidateRule.php delete mode 100644 thinkphp/library/traits/think/Instance.php delete mode 100644 thinkphp/phpunit.xml create mode 100644 thinkphp/phpunit.xml.dist delete mode 100644 vendor/topthink/think-helper/.gitignore delete mode 100644 vendor/topthink/think-helper/LICENSE delete mode 100644 vendor/topthink/think-helper/README.md delete mode 100644 vendor/topthink/think-helper/composer.json delete mode 100644 vendor/topthink/think-helper/src/Arr.php delete mode 100644 vendor/topthink/think-helper/src/Hash.php delete mode 100644 vendor/topthink/think-helper/src/Str.php delete mode 100644 vendor/topthink/think-helper/src/Time.php delete mode 100644 vendor/topthink/think-helper/src/hash/Bcrypt.php delete mode 100644 vendor/topthink/think-helper/src/hash/Md5.php delete mode 100644 vendor/topthink/think-helper/src/helper.php delete mode 100644 vendor/topthink/think-mongo/.gitignore delete mode 100644 vendor/topthink/think-mongo/LICENSE delete mode 100644 vendor/topthink/think-mongo/README.md delete mode 100644 vendor/topthink/think-mongo/composer.json delete mode 100644 vendor/topthink/think-mongo/src/Builder.php delete mode 100644 vendor/topthink/think-mongo/src/Connection.php delete mode 100644 vendor/topthink/think-mongo/src/Query.php delete mode 100644 vendor/topthink/think-queue/.gitignore delete mode 100644 vendor/topthink/think-queue/LICENSE delete mode 100644 vendor/topthink/think-queue/README.md delete mode 100644 vendor/topthink/think-queue/composer.json delete mode 100644 vendor/topthink/think-queue/src/Queue.php delete mode 100644 vendor/topthink/think-queue/src/common.php delete mode 100644 vendor/topthink/think-queue/src/config.php delete mode 100644 vendor/topthink/think-queue/src/queue/CallQueuedHandler.php delete mode 100644 vendor/topthink/think-queue/src/queue/Connector.php delete mode 100644 vendor/topthink/think-queue/src/queue/Job.php delete mode 100644 vendor/topthink/think-queue/src/queue/Listener.php delete mode 100644 vendor/topthink/think-queue/src/queue/Queueable.php delete mode 100644 vendor/topthink/think-queue/src/queue/Worker.php delete mode 100644 vendor/topthink/think-queue/src/queue/command/Listen.php delete mode 100644 vendor/topthink/think-queue/src/queue/command/Restart.php delete mode 100644 vendor/topthink/think-queue/src/queue/command/Subscribe.php delete mode 100644 vendor/topthink/think-queue/src/queue/command/Work.php delete mode 100644 vendor/topthink/think-queue/src/queue/connector/Database.php delete mode 100644 vendor/topthink/think-queue/src/queue/connector/Redis.php delete mode 100644 vendor/topthink/think-queue/src/queue/connector/Sync.php delete mode 100644 vendor/topthink/think-queue/src/queue/connector/Topthink.php delete mode 100644 vendor/topthink/think-queue/src/queue/job/Database.php delete mode 100644 vendor/topthink/think-queue/src/queue/job/Redis.php delete mode 100644 vendor/topthink/think-queue/src/queue/job/Sync.php delete mode 100644 vendor/topthink/think-queue/src/queue/job/Topthink.php create mode 100644 vendor/zoujingli/wechat-developer/.gitignore rename vendor/zoujingli/{wechat-php-sdk => wechat-developer}/MIT-LICENSE.txt (95%) create mode 100644 vendor/zoujingli/wechat-developer/README.md create mode 100644 vendor/zoujingli/wechat-developer/Test/config.php create mode 100644 vendor/zoujingli/wechat-developer/Test/pay-download-bill.php create mode 100644 vendor/zoujingli/wechat-developer/Test/pay-order-close.php create mode 100644 vendor/zoujingli/wechat-developer/Test/pay-order-create.php create mode 100644 vendor/zoujingli/wechat-developer/Test/pay-order-query.php create mode 100644 vendor/zoujingli/wechat-developer/Test/pay-refund-create.php create mode 100644 vendor/zoujingli/wechat-developer/Test/pay-refund-query.php create mode 100644 vendor/zoujingli/wechat-developer/Test/wechat-jssdk-sign.php create mode 100644 vendor/zoujingli/wechat-developer/Test/wechat-menu-get.php create mode 100644 vendor/zoujingli/wechat-developer/Test/wechat-qrcode-create.php create mode 100644 vendor/zoujingli/wechat-developer/Test/wechat-user-get.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Card.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Contracts/BasicPushEvent.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Contracts/BasicWeChat.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Contracts/DataArray.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Contracts/DataError.php rename vendor/zoujingli/{wechat-php-sdk/Wechat/Lib => wechat-developer/WeChat/Contracts}/Prpcrypt.php (64%) create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Contracts/Tools.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Custom.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Exceptions/InvalidArgumentException.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Exceptions/InvalidDecryptException.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Exceptions/InvalidResponseException.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Exceptions/LocalCacheException.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Limit.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Media.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Menu.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Oauth.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Pay.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Product.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Qrcode.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Receive.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Scan.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Script.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Shake.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Tags.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Template.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/User.php create mode 100644 vendor/zoujingli/wechat-developer/WeChat/Wifi.php create mode 100644 vendor/zoujingli/wechat-developer/composer.json create mode 100644 vendor/zoujingli/wechat-developer/include.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/.gitignore delete mode 100644 vendor/zoujingli/wechat-php-sdk/README.md delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/Lib/Cache.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/Lib/Common.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/Lib/Tools.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/Loader.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatCard.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatCustom.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatDevice.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatExtends.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatHardware.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatMedia.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatMenu.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatMessage.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatOauth.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatPay.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatPoi.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatReceive.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatScript.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatService.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/Wechat/WechatUser.php delete mode 100644 vendor/zoujingli/wechat-php-sdk/composer.json delete mode 100644 vendor/zoujingli/wechat-php-sdk/test.php create mode 100644 vendor/zoujingli/weopen-developer/.gitignore create mode 100644 vendor/zoujingli/weopen-developer/README.md create mode 100644 vendor/zoujingli/weopen-developer/Test/open.php create mode 100644 vendor/zoujingli/weopen-developer/Test/wechat.sql create mode 100644 vendor/zoujingli/weopen-developer/WeOpen/Service.php create mode 100644 vendor/zoujingli/weopen-developer/composer.json diff --git a/.htaccess b/.htaccess index 2cfa33ed1..cbc786893 100644 --- a/.htaccess +++ b/.htaccess @@ -1,6 +1,7 @@ Options +FollowSymlinks -Multiviews RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] diff --git a/README.md b/README.md index 5c97b1556..e7c11160b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ThinkAdmin for PHP -- -* ThinkAdmin 是一个基于 Thinkphp 5.0.x 开发的后台管理系统,集成后台系统常用功能。 +* ThinkAdmin 是一个基于 Thinkphp 5.1.x 开发的后台管理系统,集成后台系统常用功能。 * 项目安装及二次开发请参考 ThinkPHP 官方文档及下面的服务环境说明,数据库 sql 文件存放于项目根目录下。 > 注意:项目测试请另行搭建环境并创建数据库(数据库配置 application/database.php), 切勿直接使用测试环境数据! @@ -25,8 +25,8 @@ Module * 自建秒传文件上载组件(本地存储、七牛云存储,阿里云OSS存储) * 基站数据服务组件(唯一随机序号、表单更新) * `Http`服务组件(原生`CURL`封装,兼容PHP多版本) -* 微信公众号服务组件(基于[wechat-php-sdk](https://github.com/zoujingli/wechat-php-sdk),微信网页授权获取用户信息、已关注粉丝管理、自定义菜单管理等等) -* 微信商户支付服务组件(基于[wechat-php-sdk](https://github.com/zoujingli/wechat-php-sdk),支持JSAPI支付、扫码模式一支付、扫码模式二支付) +* 微信公众号服务组件(基于[WeChatDevloper](https://github.com/zoujingli/wechat-devloper),微信网页授权获取用户信息、已关注粉丝管理、自定义菜单管理等等) +* 微信商户支付服务组件(基于[WeChatDevloper](https://github.com/zoujingli/wechat-devloper),支持JSAPI支付、扫码模式一支付、扫码模式二支付) * 测试公众号名称:思过崖思过 (大家可以关注它来进行简单的测试) * 更多组件开发中... diff --git a/admin_v3.sql b/admin_v3.sql new file mode 100644 index 000000000..272662e99 --- /dev/null +++ b/admin_v3.sql @@ -0,0 +1,878 @@ +/* +Navicat MySQL Data Transfer + +Source Server : ctolog.com +Source Server Version : 50559 +Source Host : ctolog.com:3306 +Source Database : admin_v3 + +Target Server Type : MYSQL +Target Server Version : 50559 +File Encoding : 65001 + +Date: 2018-03-13 15:35:47 +*/ + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for system_auth +-- ---------------------------- +DROP TABLE IF EXISTS `system_auth`; +CREATE TABLE `system_auth` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(20) NOT NULL COMMENT '权限名称', + `status` tinyint(1) unsigned DEFAULT '1' COMMENT '状态(1:禁用,2:启用)', + `sort` smallint(6) unsigned DEFAULT '0' COMMENT '排序权重', + `desc` varchar(255) DEFAULT NULL COMMENT '备注说明', + `create_by` bigint(11) unsigned DEFAULT '0' COMMENT '创建人', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `index_system_auth_title` (`title`) USING BTREE, + KEY `index_system_auth_status` (`status`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='系统权限表'; + +-- ---------------------------- +-- Records of system_auth +-- ---------------------------- +INSERT INTO `system_auth` VALUES ('1', '测试', '1', '1', '测试权限', '0', '2018-01-23 13:28:14'); + +-- ---------------------------- +-- Table structure for system_auth_node +-- ---------------------------- +DROP TABLE IF EXISTS `system_auth_node`; +CREATE TABLE `system_auth_node` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `auth` bigint(20) unsigned DEFAULT NULL COMMENT '角色ID', + `node` varchar(200) DEFAULT NULL COMMENT '节点路径', + PRIMARY KEY (`id`), + KEY `index_system_auth_auth` (`auth`) USING BTREE, + KEY `index_system_auth_node` (`node`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=154 DEFAULT CHARSET=utf8 COMMENT='系统角色与节点绑定'; + +-- ---------------------------- +-- Records of system_auth_node +-- ---------------------------- +INSERT INTO `system_auth_node` VALUES ('77', '1', 'admin'); +INSERT INTO `system_auth_node` VALUES ('78', '1', 'admin/auth'); +INSERT INTO `system_auth_node` VALUES ('79', '1', 'admin/auth/index'); +INSERT INTO `system_auth_node` VALUES ('80', '1', 'admin/auth/apply'); +INSERT INTO `system_auth_node` VALUES ('81', '1', 'admin/auth/add'); +INSERT INTO `system_auth_node` VALUES ('82', '1', 'admin/auth/edit'); +INSERT INTO `system_auth_node` VALUES ('83', '1', 'admin/auth/forbid'); +INSERT INTO `system_auth_node` VALUES ('84', '1', 'admin/auth/resume'); +INSERT INTO `system_auth_node` VALUES ('85', '1', 'admin/auth/del'); +INSERT INTO `system_auth_node` VALUES ('86', '1', 'admin/config'); +INSERT INTO `system_auth_node` VALUES ('87', '1', 'admin/config/index'); +INSERT INTO `system_auth_node` VALUES ('88', '1', 'admin/config/file'); +INSERT INTO `system_auth_node` VALUES ('89', '1', 'admin/config/sms'); +INSERT INTO `system_auth_node` VALUES ('90', '1', 'admin/log'); +INSERT INTO `system_auth_node` VALUES ('91', '1', 'admin/log/index'); +INSERT INTO `system_auth_node` VALUES ('92', '1', 'admin/log/sms'); +INSERT INTO `system_auth_node` VALUES ('93', '1', 'admin/log/del'); +INSERT INTO `system_auth_node` VALUES ('94', '1', 'admin/menu'); +INSERT INTO `system_auth_node` VALUES ('95', '1', 'admin/menu/index'); +INSERT INTO `system_auth_node` VALUES ('96', '1', 'admin/menu/add'); +INSERT INTO `system_auth_node` VALUES ('97', '1', 'admin/menu/edit'); +INSERT INTO `system_auth_node` VALUES ('98', '1', 'admin/menu/del'); +INSERT INTO `system_auth_node` VALUES ('99', '1', 'admin/menu/forbid'); +INSERT INTO `system_auth_node` VALUES ('100', '1', 'admin/menu/resume'); +INSERT INTO `system_auth_node` VALUES ('101', '1', 'admin/node'); +INSERT INTO `system_auth_node` VALUES ('102', '1', 'admin/node/index'); +INSERT INTO `system_auth_node` VALUES ('103', '1', 'admin/node/clear'); +INSERT INTO `system_auth_node` VALUES ('104', '1', 'admin/node/save'); +INSERT INTO `system_auth_node` VALUES ('105', '1', 'admin/user'); +INSERT INTO `system_auth_node` VALUES ('106', '1', 'admin/user/index'); +INSERT INTO `system_auth_node` VALUES ('107', '1', 'admin/user/auth'); +INSERT INTO `system_auth_node` VALUES ('108', '1', 'admin/user/add'); +INSERT INTO `system_auth_node` VALUES ('109', '1', 'admin/user/edit'); +INSERT INTO `system_auth_node` VALUES ('110', '1', 'admin/user/pass'); +INSERT INTO `system_auth_node` VALUES ('111', '1', 'admin/user/del'); +INSERT INTO `system_auth_node` VALUES ('112', '1', 'admin/user/forbid'); +INSERT INTO `system_auth_node` VALUES ('113', '1', 'admin/user/resume'); +INSERT INTO `system_auth_node` VALUES ('114', '1', 'wechat'); +INSERT INTO `system_auth_node` VALUES ('115', '1', 'wechat/block'); +INSERT INTO `system_auth_node` VALUES ('116', '1', 'wechat/block/index'); +INSERT INTO `system_auth_node` VALUES ('117', '1', 'wechat/block/backdel'); +INSERT INTO `system_auth_node` VALUES ('118', '1', 'wechat/config'); +INSERT INTO `system_auth_node` VALUES ('119', '1', 'wechat/config/index'); +INSERT INTO `system_auth_node` VALUES ('120', '1', 'wechat/fans'); +INSERT INTO `system_auth_node` VALUES ('121', '1', 'wechat/fans/index'); +INSERT INTO `system_auth_node` VALUES ('122', '1', 'wechat/fans/backadd'); +INSERT INTO `system_auth_node` VALUES ('123', '1', 'wechat/fans/tagset'); +INSERT INTO `system_auth_node` VALUES ('124', '1', 'wechat/fans/tagadd'); +INSERT INTO `system_auth_node` VALUES ('125', '1', 'wechat/fans/tagdel'); +INSERT INTO `system_auth_node` VALUES ('126', '1', 'wechat/fans/sync'); +INSERT INTO `system_auth_node` VALUES ('127', '1', 'wechat/keys'); +INSERT INTO `system_auth_node` VALUES ('128', '1', 'wechat/keys/index'); +INSERT INTO `system_auth_node` VALUES ('129', '1', 'wechat/keys/add'); +INSERT INTO `system_auth_node` VALUES ('130', '1', 'wechat/keys/edit'); +INSERT INTO `system_auth_node` VALUES ('131', '1', 'wechat/keys/del'); +INSERT INTO `system_auth_node` VALUES ('132', '1', 'wechat/keys/forbid'); +INSERT INTO `system_auth_node` VALUES ('133', '1', 'wechat/keys/resume'); +INSERT INTO `system_auth_node` VALUES ('134', '1', 'wechat/keys/subscribe'); +INSERT INTO `system_auth_node` VALUES ('135', '1', 'wechat/keys/defaults'); +INSERT INTO `system_auth_node` VALUES ('136', '1', 'wechat/menu'); +INSERT INTO `system_auth_node` VALUES ('137', '1', 'wechat/menu/index'); +INSERT INTO `system_auth_node` VALUES ('138', '1', 'wechat/menu/edit'); +INSERT INTO `system_auth_node` VALUES ('139', '1', 'wechat/menu/cancel'); +INSERT INTO `system_auth_node` VALUES ('140', '1', 'wechat/news'); +INSERT INTO `system_auth_node` VALUES ('141', '1', 'wechat/news/index'); +INSERT INTO `system_auth_node` VALUES ('142', '1', 'wechat/news/select'); +INSERT INTO `system_auth_node` VALUES ('143', '1', 'wechat/news/image'); +INSERT INTO `system_auth_node` VALUES ('144', '1', 'wechat/news/add'); +INSERT INTO `system_auth_node` VALUES ('145', '1', 'wechat/news/edit'); +INSERT INTO `system_auth_node` VALUES ('146', '1', 'wechat/news/del'); +INSERT INTO `system_auth_node` VALUES ('147', '1', 'wechat/news/push'); +INSERT INTO `system_auth_node` VALUES ('148', '1', 'wechat/tags'); +INSERT INTO `system_auth_node` VALUES ('149', '1', 'wechat/tags/index'); +INSERT INTO `system_auth_node` VALUES ('150', '1', 'wechat/tags/add'); +INSERT INTO `system_auth_node` VALUES ('151', '1', 'wechat/tags/edit'); +INSERT INTO `system_auth_node` VALUES ('152', '1', 'wechat/tags/del'); +INSERT INTO `system_auth_node` VALUES ('153', '1', 'wechat/tags/sync'); + +-- ---------------------------- +-- Table structure for system_config +-- ---------------------------- +DROP TABLE IF EXISTS `system_config`; +CREATE TABLE `system_config` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL COMMENT '配置编码', + `value` varchar(500) DEFAULT NULL COMMENT '配置值', + PRIMARY KEY (`id`), + KEY `index_system_config_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统参数配置'; + +-- ---------------------------- +-- Records of system_config +-- ---------------------------- +INSERT INTO `system_config` VALUES ('1', 'app_name', 'ThinkAdmin'); +INSERT INTO `system_config` VALUES ('2', 'site_name', 'ThinkAdmin'); +INSERT INTO `system_config` VALUES ('3', 'app_version', '3.0 dev'); +INSERT INTO `system_config` VALUES ('4', 'site_copy', '©版权所有 2014-2018 楚才科技'); +INSERT INTO `system_config` VALUES ('5', 'browser_icon', 'http://service.thinkadmin.top/static/upload/f47b8fe06e38ae99/08e8398da45583b9.png'); +INSERT INTO `system_config` VALUES ('6', 'tongji_baidu_key', ''); +INSERT INTO `system_config` VALUES ('7', 'miitbeian', '粤ICP备16006642号-2'); +INSERT INTO `system_config` VALUES ('8', 'storage_type', 'local'); +INSERT INTO `system_config` VALUES ('9', 'storage_local_exts', 'png,jpg,rar,doc,icon,mp4'); +INSERT INTO `system_config` VALUES ('10', 'storage_qiniu_bucket', ''); +INSERT INTO `system_config` VALUES ('11', 'storage_qiniu_domain', ''); +INSERT INTO `system_config` VALUES ('12', 'storage_qiniu_access_key', ''); +INSERT INTO `system_config` VALUES ('13', 'storage_qiniu_secret_key', ''); +INSERT INTO `system_config` VALUES ('14', 'storage_oss_bucket', 'cuci'); +INSERT INTO `system_config` VALUES ('15', 'storage_oss_endpoint', 'oss-cn-beijing.aliyuncs.com'); +INSERT INTO `system_config` VALUES ('16', 'storage_oss_domain', 'cuci.oss-cn-beijing.aliyuncs.com'); +INSERT INTO `system_config` VALUES ('17', 'storage_oss_keyid', '用你自己的吧'); +INSERT INTO `system_config` VALUES ('18', 'storage_oss_secret', '用你自己的吧'); +INSERT INTO `system_config` VALUES ('19', 'component_appid', 'wx1b8278fa121d8dc6'); +INSERT INTO `system_config` VALUES ('20', 'component_appsecret', 'f404e33a75d278d6a0f944229bb84afb'); +INSERT INTO `system_config` VALUES ('21', 'component_token', 'P8QHTIxpBEq88IrxatqhgpBm2OAQROkI'); +INSERT INTO `system_config` VALUES ('22', 'component_encodingaeskey', 'L5uFIa0U6KLalPyXckyqoVIJYLhsfrg8k9YzybZIHsx'); +INSERT INTO `system_config` VALUES ('23', 'wechat_appid', 'wx60a43dd8161666d4'); +INSERT INTO `system_config` VALUES ('24', 'wechat_appkey', '67b0056909f8ac5f42add03323d1faa0'); +INSERT INTO `system_config` VALUES ('25', 'wuma_appid', '2844152665343808'); +INSERT INTO `system_config` VALUES ('26', 'wuma_appkey', '25280e2930a30d81bc93b2bf40a62b48'); +INSERT INTO `system_config` VALUES ('27', 'depot_pda_secret_key', 'ACE7041C3204B10736F11309B66E2214'); +INSERT INTO `system_config` VALUES ('28', 'storage_oss_is_https', 'http'); +INSERT INTO `system_config` VALUES ('29', 'store_title', '商城'); +INSERT INTO `system_config` VALUES ('30', 'store_runtime_status', '0'); +INSERT INTO `system_config` VALUES ('31', 'store_order_dalay', '1'); +INSERT INTO `system_config` VALUES ('32', 'store_order_auto_cancel', '12.00'); +INSERT INTO `system_config` VALUES ('33', 'store_order_auto_comfirm', '12.00'); + +-- ---------------------------- +-- Table structure for system_log +-- ---------------------------- +DROP TABLE IF EXISTS `system_log`; +CREATE TABLE `system_log` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `ip` char(15) NOT NULL DEFAULT '' COMMENT '操作者IP地址', + `node` char(200) NOT NULL DEFAULT '' COMMENT '当前操作节点', + `username` varchar(32) NOT NULL DEFAULT '' COMMENT '操作人用户名', + `action` varchar(200) NOT NULL DEFAULT '' COMMENT '操作行为', + `content` text NOT NULL COMMENT '操作内容描述', + `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=366 DEFAULT CHARSET=utf8 COMMENT='系统操作日志表'; + +-- ---------------------------- +-- Records of system_log +-- ---------------------------- +INSERT INTO `system_log` VALUES ('169', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-25 18:49:05'); +INSERT INTO `system_log` VALUES ('170', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-25 18:53:02'); +INSERT INTO `system_log` VALUES ('171', '116.21.13.54', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-26 10:28:18'); +INSERT INTO `system_log` VALUES ('172', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-26 10:30:21'); +INSERT INTO `system_log` VALUES ('173', '113.67.75.233', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-27 10:08:51'); +INSERT INTO `system_log` VALUES ('174', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 13:17:51'); +INSERT INTO `system_log` VALUES ('175', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 17:41:58'); +INSERT INTO `system_log` VALUES ('176', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 17:57:28'); +INSERT INTO `system_log` VALUES ('177', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 17:59:01'); +INSERT INTO `system_log` VALUES ('178', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 17:59:04'); +INSERT INTO `system_log` VALUES ('179', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:01:31'); +INSERT INTO `system_log` VALUES ('180', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 18:01:35'); +INSERT INTO `system_log` VALUES ('181', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:01:37'); +INSERT INTO `system_log` VALUES ('182', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 18:01:43'); +INSERT INTO `system_log` VALUES ('183', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:01:56'); +INSERT INTO `system_log` VALUES ('184', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 18:02:14'); +INSERT INTO `system_log` VALUES ('185', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:02:26'); +INSERT INTO `system_log` VALUES ('186', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 18:02:30'); +INSERT INTO `system_log` VALUES ('187', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:07:07'); +INSERT INTO `system_log` VALUES ('188', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 18:07:50'); +INSERT INTO `system_log` VALUES ('189', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:10:34'); +INSERT INTO `system_log` VALUES ('190', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-02-28 18:10:38'); +INSERT INTO `system_log` VALUES ('191', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:12:41'); +INSERT INTO `system_log` VALUES ('192', '113.67.75.233', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 18:22:15'); +INSERT INTO `system_log` VALUES ('193', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 19:06:46'); +INSERT INTO `system_log` VALUES ('194', '61.140.235.78', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-02-28 20:51:18'); +INSERT INTO `system_log` VALUES ('195', '61.140.235.78', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-01 09:15:54'); +INSERT INTO `system_log` VALUES ('196', '59.42.236.22', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-01 11:17:00'); +INSERT INTO `system_log` VALUES ('197', '113.67.74.221', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-02 10:00:39'); +INSERT INTO `system_log` VALUES ('198', '113.67.74.221', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-02 11:09:52'); +INSERT INTO `system_log` VALUES ('199', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-02 11:10:47'); +INSERT INTO `system_log` VALUES ('200', '113.67.74.221', 'admin/config/index', 'admin', '系统管理', '系统参数配置成功', '2018-03-02 11:39:40'); +INSERT INTO `system_log` VALUES ('201', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-02 12:28:13'); +INSERT INTO `system_log` VALUES ('202', '113.67.74.221', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-02 12:31:01'); +INSERT INTO `system_log` VALUES ('203', '113.67.74.221', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-02 17:33:13'); +INSERT INTO `system_log` VALUES ('204', '61.136.79.242', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-05 09:02:47'); +INSERT INTO `system_log` VALUES ('205', '113.67.73.37', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-05 13:53:34'); +INSERT INTO `system_log` VALUES ('206', '113.67.73.37', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-05 18:40:09'); +INSERT INTO `system_log` VALUES ('207', '116.21.13.188', 'admin/config/index', 'admin', '系统管理', '系统参数配置成功', '2018-03-05 20:01:19'); +INSERT INTO `system_log` VALUES ('208', '61.140.235.78', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 00:08:40'); +INSERT INTO `system_log` VALUES ('209', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 09:48:57'); +INSERT INTO `system_log` VALUES ('210', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-03-06 09:51:12'); +INSERT INTO `system_log` VALUES ('211', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 09:52:44'); +INSERT INTO `system_log` VALUES ('212', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 10:01:02'); +INSERT INTO `system_log` VALUES ('213', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-03-06 10:01:09'); +INSERT INTO `system_log` VALUES ('214', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 10:01:11'); +INSERT INTO `system_log` VALUES ('215', '113.67.73.37', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-03-06 10:48:41'); +INSERT INTO `system_log` VALUES ('216', '113.67.73.37', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 10:49:00'); +INSERT INTO `system_log` VALUES ('217', '113.67.73.37', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-03-06 10:49:18'); +INSERT INTO `system_log` VALUES ('218', '113.67.73.37', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 10:49:45'); +INSERT INTO `system_log` VALUES ('219', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-03-06 10:58:05'); +INSERT INTO `system_log` VALUES ('220', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 10:58:20'); +INSERT INTO `system_log` VALUES ('221', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 11:25:13'); +INSERT INTO `system_log` VALUES ('222', '127.0.0.1', 'admin/config/index', 'admin', '系统管理', '系统参数配置成功', '2018-03-06 14:05:44'); +INSERT INTO `system_log` VALUES ('223', '127.0.0.1', 'admin/config/index', 'admin', '系统管理', '系统参数配置成功', '2018-03-06 14:05:48'); +INSERT INTO `system_log` VALUES ('224', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 14:09:04'); +INSERT INTO `system_log` VALUES ('225', '127.0.0.1', 'admin/config/file', 'admin', '系统管理', '系统参数配置成功', '2018-03-06 14:10:11'); +INSERT INTO `system_log` VALUES ('226', '127.0.0.1', 'admin/config/index', 'admin', '系统管理', '系统参数配置成功', '2018-03-06 14:10:23'); +INSERT INTO `system_log` VALUES ('227', '127.0.0.1', 'admin/config/index', 'admin', '系统管理', '系统参数配置成功', '2018-03-06 14:10:32'); +INSERT INTO `system_log` VALUES ('228', '127.0.0.1', 'wechat/tags/sync', 'admin', '微信管理', '同步全部微信粉丝标签成功', '2018-03-06 15:10:31'); +INSERT INTO `system_log` VALUES ('229', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:31:09'); +INSERT INTO `system_log` VALUES ('230', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:31:30'); +INSERT INTO `system_log` VALUES ('231', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:31:54'); +INSERT INTO `system_log` VALUES ('232', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:32:15'); +INSERT INTO `system_log` VALUES ('233', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:32:35'); +INSERT INTO `system_log` VALUES ('234', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:32:57'); +INSERT INTO `system_log` VALUES ('235', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:33:18'); +INSERT INTO `system_log` VALUES ('236', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:33:37'); +INSERT INTO `system_log` VALUES ('237', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:33:57'); +INSERT INTO `system_log` VALUES ('238', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:34:17'); +INSERT INTO `system_log` VALUES ('239', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:34:35'); +INSERT INTO `system_log` VALUES ('240', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:34:55'); +INSERT INTO `system_log` VALUES ('241', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:35:14'); +INSERT INTO `system_log` VALUES ('242', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:35:34'); +INSERT INTO `system_log` VALUES ('243', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:35:55'); +INSERT INTO `system_log` VALUES ('244', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:36:15'); +INSERT INTO `system_log` VALUES ('245', '127.0.0.1', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-06 15:36:33'); +INSERT INTO `system_log` VALUES ('246', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-06 15:37:05'); +INSERT INTO `system_log` VALUES ('247', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 10:11:52'); +INSERT INTO `system_log` VALUES ('248', '59.42.238.6', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 10:20:48'); +INSERT INTO `system_log` VALUES ('249', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:28:36'); +INSERT INTO `system_log` VALUES ('250', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:29:09'); +INSERT INTO `system_log` VALUES ('251', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:29:13'); +INSERT INTO `system_log` VALUES ('252', '127.0.0.1', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:32:28'); +INSERT INTO `system_log` VALUES ('253', '127.0.0.1', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:35:52'); +INSERT INTO `system_log` VALUES ('254', '127.0.0.1', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:37:08'); +INSERT INTO `system_log` VALUES ('255', '127.0.0.1', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:37:35'); +INSERT INTO `system_log` VALUES ('256', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:38:48'); +INSERT INTO `system_log` VALUES ('257', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 10:38:50'); +INSERT INTO `system_log` VALUES ('258', '113.67.75.198', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 11:49:52'); +INSERT INTO `system_log` VALUES ('259', '59.42.238.6', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 11:50:08'); +INSERT INTO `system_log` VALUES ('260', '59.42.238.6', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 11:50:41'); +INSERT INTO `system_log` VALUES ('261', '127.0.0.1', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 12:14:40'); +INSERT INTO `system_log` VALUES ('262', '127.0.0.1', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 12:15:27'); +INSERT INTO `system_log` VALUES ('263', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 14:26:50'); +INSERT INTO `system_log` VALUES ('264', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 14:28:27'); +INSERT INTO `system_log` VALUES ('265', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 14:57:32'); +INSERT INTO `system_log` VALUES ('266', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 15:02:02'); +INSERT INTO `system_log` VALUES ('267', '59.42.238.6', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-07 15:02:04'); +INSERT INTO `system_log` VALUES ('268', '127.0.0.1', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-07 15:36:58'); +INSERT INTO `system_log` VALUES ('269', '59.42.238.6', 'wechat/news/push', 'admin', '微信管理', '图文[1]推送成功', '2018-03-07 15:38:19'); +INSERT INTO `system_log` VALUES ('270', '59.42.238.6', 'wechat/tags/sync', 'admin', '微信管理', '同步全部微信粉丝标签成功', '2018-03-07 15:38:46'); +INSERT INTO `system_log` VALUES ('271', '127.0.0.1', 'wechat/tags/sync', 'admin', '微信管理', '同步全部微信粉丝标签成功', '2018-03-07 15:41:43'); +INSERT INTO `system_log` VALUES ('273', '175.172.29.18', 'wechat/news/push', 'admin', '微信管理', '图文[1]推送成功', '2018-03-07 15:48:41'); +INSERT INTO `system_log` VALUES ('274', '113.67.75.198', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-07 17:59:56'); +INSERT INTO `system_log` VALUES ('275', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 18:13:08'); +INSERT INTO `system_log` VALUES ('276', '113.67.75.198', 'wechat/tags/sync', 'admin', '微信管理', '同步全部微信粉丝标签成功', '2018-03-07 18:46:16'); +INSERT INTO `system_log` VALUES ('280', '59.42.238.6', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-07 23:01:37'); +INSERT INTO `system_log` VALUES ('285', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-08 15:30:02'); +INSERT INTO `system_log` VALUES ('286', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-08 17:06:28'); +INSERT INTO `system_log` VALUES ('287', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-08 18:11:10'); +INSERT INTO `system_log` VALUES ('288', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-08 18:40:51'); +INSERT INTO `system_log` VALUES ('289', '116.22.196.196', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-08 20:32:32'); +INSERT INTO `system_log` VALUES ('291', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 11:25:50'); +INSERT INTO `system_log` VALUES ('292', '127.0.0.1', 'admin/login/out', 'admin', '系统管理', '用户退出系统成功', '2018-03-09 14:58:23'); +INSERT INTO `system_log` VALUES ('293', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 14:58:46'); +INSERT INTO `system_log` VALUES ('294', '59.42.237.151', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:09:15'); +INSERT INTO `system_log` VALUES ('295', '59.42.237.151', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-09 15:10:10'); +INSERT INTO `system_log` VALUES ('296', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 15:20:55'); +INSERT INTO `system_log` VALUES ('297', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 15:21:08'); +INSERT INTO `system_log` VALUES ('298', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 15:24:56'); +INSERT INTO `system_log` VALUES ('299', '59.42.237.151', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:26:19'); +INSERT INTO `system_log` VALUES ('300', '59.42.237.151', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:30:41'); +INSERT INTO `system_log` VALUES ('301', '59.42.237.151', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:31:20'); +INSERT INTO `system_log` VALUES ('302', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 15:31:24'); +INSERT INTO `system_log` VALUES ('303', '127.0.0.1', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:45:34'); +INSERT INTO `system_log` VALUES ('304', '127.0.0.1', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:46:11'); +INSERT INTO `system_log` VALUES ('305', '127.0.0.1', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:46:31'); +INSERT INTO `system_log` VALUES ('306', '127.0.0.1', 'wechat/menu/edit', 'admin', '微信管理', '发布微信菜单成功', '2018-03-09 15:46:43'); +INSERT INTO `system_log` VALUES ('307', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 16:21:21'); +INSERT INTO `system_log` VALUES ('308', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 16:23:21'); +INSERT INTO `system_log` VALUES ('309', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 16:32:09'); +INSERT INTO `system_log` VALUES ('310', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 16:32:45'); +INSERT INTO `system_log` VALUES ('311', '59.42.237.151', 'wechat/fans/sync', 'admin', '微信管理', '同步全部微信粉丝成功', '2018-03-09 19:35:45'); +INSERT INTO `system_log` VALUES ('312', '59.42.237.151', 'wechat/tags/sync', 'admin', '微信管理', '同步全部微信粉丝标签成功', '2018-03-09 19:36:38'); +INSERT INTO `system_log` VALUES ('313', '116.22.196.196', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 21:12:54'); +INSERT INTO `system_log` VALUES ('314', '116.22.196.196', 'wechat/config/index', 'admin', '微信管理', '修改微信接口参数成功', '2018-03-09 21:23:54'); +INSERT INTO `system_log` VALUES ('315', '119.108.225.208', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-09 23:44:25'); +INSERT INTO `system_log` VALUES ('316', '119.108.225.208', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-10 02:51:25'); +INSERT INTO `system_log` VALUES ('317', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-10 11:06:45'); +INSERT INTO `system_log` VALUES ('318', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-10 11:10:15'); +INSERT INTO `system_log` VALUES ('319', '59.42.237.151', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-10 11:14:46'); +INSERT INTO `system_log` VALUES ('360', '127.0.0.1', 'admin/config/file', 'admin', '系统管理', '系统参数配置成功', '2018-03-13 14:54:30'); +INSERT INTO `system_log` VALUES ('361', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-13 14:57:52'); +INSERT INTO `system_log` VALUES ('362', '127.0.0.1', 'admin/login/index', 'admin', '系统管理', '用户登录系统成功', '2018-03-13 15:04:35'); +INSERT INTO `system_log` VALUES ('363', '127.0.0.1', 'admin/config/file', 'admin', '系统管理', '系统参数配置成功', '2018-03-13 15:07:22'); +INSERT INTO `system_log` VALUES ('364', '127.0.0.1', 'admin/config/file', 'admin', '系统管理', '系统参数配置成功', '2018-03-13 15:07:25'); +INSERT INTO `system_log` VALUES ('365', '127.0.0.1', 'admin/config/index', 'admin', '系统管理', '系统参数配置成功', '2018-03-13 15:07:59'); + +-- ---------------------------- +-- Table structure for system_menu +-- ---------------------------- +DROP TABLE IF EXISTS `system_menu`; +CREATE TABLE `system_menu` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `pid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '父id', + `title` varchar(100) NOT NULL DEFAULT '' COMMENT '名称', + `node` varchar(200) NOT NULL DEFAULT '' COMMENT '节点代码', + `icon` varchar(100) NOT NULL DEFAULT '' COMMENT '菜单图标', + `url` varchar(400) NOT NULL DEFAULT '' COMMENT '链接', + `params` varchar(500) DEFAULT '' COMMENT '链接参数', + `target` varchar(20) NOT NULL DEFAULT '_self' COMMENT '链接打开方式', + `sort` int(11) unsigned DEFAULT '0' COMMENT '菜单排序', + `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)', + `create_by` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '创建人', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `index_system_menu_node` (`node`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=utf8 COMMENT='系统菜单表'; + +-- ---------------------------- +-- Records of system_menu +-- ---------------------------- +INSERT INTO `system_menu` VALUES ('1', '0', '系统设置', '', '', '#', '', '_self', '9000', '1', '10000', '2018-01-19 15:27:00'); +INSERT INTO `system_menu` VALUES ('2', '10', '后台菜单', '', 'fa fa-leaf', 'admin/menu/index', '', '_self', '10', '1', '10000', '2018-01-19 15:27:17'); +INSERT INTO `system_menu` VALUES ('3', '10', '系统参数', '', 'fa fa-modx', 'admin/config/index', '', '_self', '20', '1', '10000', '2018-01-19 15:27:57'); +INSERT INTO `system_menu` VALUES ('4', '11', '访问授权', '', 'fa fa-group', 'admin/auth/index', '', '_self', '20', '1', '10000', '2018-01-22 11:13:02'); +INSERT INTO `system_menu` VALUES ('5', '11', '用户管理', '', 'fa fa-user', 'admin/user/index', '', '_self', '10', '1', '0', '2018-01-23 12:15:12'); +INSERT INTO `system_menu` VALUES ('6', '11', '访问节点', '', 'fa fa-fort-awesome', 'admin/node/index', '', '_self', '30', '1', '0', '2018-01-23 12:36:54'); +INSERT INTO `system_menu` VALUES ('7', '0', '后台首页', '', '', 'admin/index/main', '', '_self', '1000', '1', '0', '2018-01-23 13:42:30'); +INSERT INTO `system_menu` VALUES ('8', '16', '系统日志', '', 'fa fa-code', 'admin/log/index', '', '_self', '10', '1', '0', '2018-01-24 13:52:58'); +INSERT INTO `system_menu` VALUES ('9', '10', '文件存储', '', 'fa fa-stop-circle', 'admin/config/file', '', '_self', '30', '1', '0', '2018-01-25 10:54:28'); +INSERT INTO `system_menu` VALUES ('10', '1', '系统管理', '', 'fa fa-scribd', '#', '', '_self', '200', '1', '0', '2018-01-25 18:14:28'); +INSERT INTO `system_menu` VALUES ('11', '1', '访问权限', '', 'fa fa-anchor', '#', '', '_self', '300', '1', '0', '2018-01-25 18:15:08'); +INSERT INTO `system_menu` VALUES ('16', '1', '日志管理', '', 'fa fa-hashtag', '#', '', '_self', '400', '1', '0', '2018-02-10 16:31:15'); +INSERT INTO `system_menu` VALUES ('17', '0', '微信管理', '', '', '#', '', '_self', '8000', '1', '0', '2018-03-06 14:42:49'); +INSERT INTO `system_menu` VALUES ('18', '17', '公众号配置', '', 'fa fa-cogs', '#', '', '_self', '0', '1', '0', '2018-03-06 14:43:05'); +INSERT INTO `system_menu` VALUES ('19', '18', '微信授权绑定', '', 'fa fa-cog', 'wechat/config/index', '', '_self', '0', '1', '0', '2018-03-06 14:43:26'); +INSERT INTO `system_menu` VALUES ('20', '18', '关注默认回复', '', 'fa fa-comment-o', 'wechat/keys/subscribe', '', '_self', '0', '1', '0', '2018-03-06 14:44:45'); +INSERT INTO `system_menu` VALUES ('21', '18', '无反馈默认回复', '', 'fa fa-commenting', 'wechat/keys/defaults', '', '_self', '0', '1', '0', '2018-03-06 14:45:55'); +INSERT INTO `system_menu` VALUES ('22', '18', '微信关键字管理', '', 'fa fa-hashtag', 'wechat/keys/index', '', '_self', '0', '1', '0', '2018-03-06 14:46:23'); +INSERT INTO `system_menu` VALUES ('23', '17', '微信服务定制', '', 'fa fa-cubes', '#', '', '_self', '0', '1', '0', '2018-03-06 14:47:11'); +INSERT INTO `system_menu` VALUES ('24', '23', '微信菜单管理', '', 'fa fa-gg-circle', 'wechat/menu/index', '', '_self', '0', '1', '0', '2018-03-06 14:47:39'); +INSERT INTO `system_menu` VALUES ('25', '23', '微信图文管理', '', 'fa fa-map-o', 'wechat/news/index', '', '_self', '0', '1', '0', '2018-03-06 14:48:14'); +INSERT INTO `system_menu` VALUES ('26', '17', '微信粉丝管理', '', 'fa fa-user', '#', '', '_self', '0', '1', '0', '2018-03-06 14:48:33'); +INSERT INTO `system_menu` VALUES ('27', '26', '微信粉丝列表', '', 'fa fa-users', 'wechat/fans/index', '', '_self', '20', '1', '0', '2018-03-06 14:49:04'); +INSERT INTO `system_menu` VALUES ('28', '26', '微信黑名单管理', '', 'fa fa-user-times', 'wechat/block/index', '', '_self', '30', '1', '0', '2018-03-06 14:49:22'); +INSERT INTO `system_menu` VALUES ('29', '26', '微信标签管理', '', 'fa fa-tags', 'wechat/tags/index', '', '_self', '10', '1', '0', '2018-03-06 14:49:39'); + +-- ---------------------------- +-- Table structure for system_node +-- ---------------------------- +DROP TABLE IF EXISTS `system_node`; +CREATE TABLE `system_node` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `node` varchar(100) DEFAULT NULL COMMENT '节点代码', + `title` varchar(500) DEFAULT NULL COMMENT '节点标题', + `is_menu` tinyint(1) unsigned DEFAULT '0' COMMENT '是否可设置为菜单', + `is_auth` tinyint(1) unsigned DEFAULT '1' COMMENT '是否启动RBAC权限控制', + `is_login` tinyint(1) unsigned DEFAULT '1' COMMENT '是否启动登录控制', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `index_system_node_node` (`node`) +) ENGINE=InnoDB AUTO_INCREMENT=322 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统节点表'; + +-- ---------------------------- +-- Records of system_node +-- ---------------------------- +INSERT INTO `system_node` VALUES ('1', 'admin', '系统管理', '0', '1', '1', '2018-01-23 12:39:13'); +INSERT INTO `system_node` VALUES ('2', 'admin/auth', '权限管理', '0', '1', '1', '2018-01-23 12:39:14'); +INSERT INTO `system_node` VALUES ('3', 'admin/auth/index', '权限列表', '1', '1', '1', '2018-01-23 12:39:14'); +INSERT INTO `system_node` VALUES ('4', 'admin/auth/apply', '访问授权', '0', '1', '1', '2018-01-23 12:39:19'); +INSERT INTO `system_node` VALUES ('5', 'admin/auth/add', '添加权限', '0', '1', '1', '2018-01-23 12:39:22'); +INSERT INTO `system_node` VALUES ('6', 'admin/auth/edit', '编辑权限', '0', '1', '1', '2018-01-23 12:39:23'); +INSERT INTO `system_node` VALUES ('7', 'admin/auth/forbid', '禁用权限', '0', '1', '1', '2018-01-23 12:39:23'); +INSERT INTO `system_node` VALUES ('8', 'admin/auth/resume', '启用权限', '0', '1', '1', '2018-01-23 12:39:24'); +INSERT INTO `system_node` VALUES ('9', 'admin/auth/del', '删除权限', '0', '1', '1', '2018-01-23 12:39:25'); +INSERT INTO `system_node` VALUES ('10', 'admin/config/index', '系统参数', '1', '1', '1', '2018-01-23 12:39:25'); +INSERT INTO `system_node` VALUES ('11', 'admin/config/file', '文件存储', '0', '1', '1', '2018-01-23 12:39:26'); +INSERT INTO `system_node` VALUES ('12', 'admin/config/sms', '短信接口', '0', '1', '1', '2018-01-23 12:39:28'); +INSERT INTO `system_node` VALUES ('13', 'admin/log/index', '日志记录', '1', '1', '1', '2018-01-23 12:39:28'); +INSERT INTO `system_node` VALUES ('14', 'admin/log/sms', '短信记录', '0', '1', '1', '2018-01-23 12:39:29'); +INSERT INTO `system_node` VALUES ('15', 'admin/log/del', '日志删除', '0', '1', '1', '2018-01-23 12:39:29'); +INSERT INTO `system_node` VALUES ('16', 'admin/menu/index', '系统菜单列表', '1', '1', '1', '2018-01-23 12:39:31'); +INSERT INTO `system_node` VALUES ('17', 'admin/menu/add', '添加系统菜单', '0', '1', '1', '2018-01-23 12:39:31'); +INSERT INTO `system_node` VALUES ('18', 'admin/menu/edit', '编辑系统菜单', '0', '1', '1', '2018-01-23 12:39:32'); +INSERT INTO `system_node` VALUES ('19', 'admin/menu/del', '删除系统菜单', '0', '1', '1', '2018-01-23 12:39:33'); +INSERT INTO `system_node` VALUES ('20', 'admin/menu/forbid', '禁用系统菜单', '0', '1', '1', '2018-01-23 12:39:33'); +INSERT INTO `system_node` VALUES ('21', 'admin/menu/resume', '启用系统菜单', '0', '1', '1', '2018-01-23 12:39:34'); +INSERT INTO `system_node` VALUES ('22', 'admin/node/index', '系统节点列表', '1', '1', '1', '2018-01-23 12:39:36'); +INSERT INTO `system_node` VALUES ('23', 'admin/node/save', '保存节点信息', '0', '1', '1', '2018-01-23 12:39:36'); +INSERT INTO `system_node` VALUES ('24', 'admin/user/index', '系统用户列表', '1', '1', '1', '2018-01-23 12:39:37'); +INSERT INTO `system_node` VALUES ('25', 'admin/user/auth', '用户授权操作', '0', '1', '1', '2018-01-23 12:39:38'); +INSERT INTO `system_node` VALUES ('26', 'admin/user/add', '添加系统用户', '0', '1', '1', '2018-01-23 12:39:39'); +INSERT INTO `system_node` VALUES ('27', 'admin/user/edit', '编辑系统用户', '0', '1', '1', '2018-01-23 12:39:39'); +INSERT INTO `system_node` VALUES ('28', 'admin/user/pass', '修改用户密码', '0', '1', '1', '2018-01-23 12:39:40'); +INSERT INTO `system_node` VALUES ('29', 'admin/user/del', '删除系统用户', '0', '1', '1', '2018-01-23 12:39:41'); +INSERT INTO `system_node` VALUES ('30', 'admin/user/forbid', '禁用系统用户', '0', '1', '1', '2018-01-23 12:39:41'); +INSERT INTO `system_node` VALUES ('31', 'admin/user/resume', '启用系统用户', '0', '1', '1', '2018-01-23 12:39:42'); +INSERT INTO `system_node` VALUES ('32', 'admin/config', '系统配置', '0', '1', '1', '2018-01-23 13:34:37'); +INSERT INTO `system_node` VALUES ('33', 'admin/log', '日志管理', '0', '1', '1', '2018-01-23 13:34:48'); +INSERT INTO `system_node` VALUES ('34', 'admin/menu', '系统菜单管理', '0', '1', '1', '2018-01-23 13:34:58'); +INSERT INTO `system_node` VALUES ('35', 'admin/node', '系统节点管理', '0', '1', '1', '2018-01-23 13:35:17'); +INSERT INTO `system_node` VALUES ('36', 'admin/user', '系统用户管理', '0', '1', '1', '2018-01-23 13:35:26'); +INSERT INTO `system_node` VALUES ('37', 'wechat', '微信管理', '0', '1', '1', '2018-02-06 11:53:21'); +INSERT INTO `system_node` VALUES ('38', 'wechat/config', '公众号对接', '0', '1', '1', '2018-02-06 11:53:32'); +INSERT INTO `system_node` VALUES ('39', 'wechat/config/index', '公众号对接', '1', '1', '1', '2018-02-06 11:53:32'); +INSERT INTO `system_node` VALUES ('45', 'wechat/block', '黑名单', '0', '1', '1', '2018-03-06 14:37:37'); +INSERT INTO `system_node` VALUES ('46', 'wechat/block/index', '黑名单列表', '1', '1', '1', '2018-03-06 14:37:47'); +INSERT INTO `system_node` VALUES ('47', 'wechat/block/backdel', '移出黑名单', '0', '1', '1', '2018-03-06 14:37:49'); +INSERT INTO `system_node` VALUES ('48', 'wechat/fans', '粉丝管理', '0', '1', '1', '2018-03-06 14:38:06'); +INSERT INTO `system_node` VALUES ('49', 'wechat/fans/index', '粉丝列表', '1', '1', '1', '2018-03-06 14:38:25'); +INSERT INTO `system_node` VALUES ('50', 'wechat/fans/backadd', '移入黑名单', '0', '1', '1', '2018-03-06 14:38:35'); +INSERT INTO `system_node` VALUES ('51', 'wechat/fans/tagset', '标签设置', '0', '1', '1', '2018-03-06 14:38:36'); +INSERT INTO `system_node` VALUES ('52', 'wechat/fans/tagadd', '添加标签', '0', '1', '1', '2018-03-06 14:38:37'); +INSERT INTO `system_node` VALUES ('53', 'wechat/fans/tagdel', '删除标签', '0', '1', '1', '2018-03-06 14:38:38'); +INSERT INTO `system_node` VALUES ('54', 'wechat/fans/sync', '同步粉丝', '0', '1', '1', '2018-03-06 14:38:38'); +INSERT INTO `system_node` VALUES ('55', 'wechat/keys', '关键字管理', '0', '1', '1', '2018-03-06 14:39:21'); +INSERT INTO `system_node` VALUES ('56', 'wechat/keys/index', '关键字列表', '1', '1', '1', '2018-03-06 14:39:25'); +INSERT INTO `system_node` VALUES ('57', 'wechat/keys/add', '添加关键字', '0', '1', '1', '2018-03-06 14:39:27'); +INSERT INTO `system_node` VALUES ('58', 'wechat/keys/edit', '编辑关键字', '0', '1', '1', '2018-03-06 14:39:28'); +INSERT INTO `system_node` VALUES ('59', 'wechat/keys/del', '删除关键字', '0', '1', '1', '2018-03-06 14:39:42'); +INSERT INTO `system_node` VALUES ('60', 'wechat/keys/forbid', '禁用关键字', '0', '1', '1', '2018-03-06 14:39:55'); +INSERT INTO `system_node` VALUES ('61', 'wechat/keys/resume', '启用关键字', '0', '1', '1', '2018-03-06 14:40:01'); +INSERT INTO `system_node` VALUES ('62', 'wechat/keys/subscribe', '关注默认回复', '1', '1', '1', '2018-03-06 14:40:12'); +INSERT INTO `system_node` VALUES ('63', 'wechat/keys/defaults', '无反馈默认回复', '1', '1', '1', '2018-03-06 14:40:27'); +INSERT INTO `system_node` VALUES ('64', 'wechat/menu', '微信菜单管理', '0', '1', '1', '2018-03-06 14:40:37'); +INSERT INTO `system_node` VALUES ('65', 'wechat/menu/index', '微信菜单管理', '1', '1', '1', '2018-03-06 14:40:41'); +INSERT INTO `system_node` VALUES ('66', 'wechat/menu/edit', '编辑发布菜单', '0', '1', '1', '2018-03-06 14:40:53'); +INSERT INTO `system_node` VALUES ('67', 'wechat/menu/cancel', '取消发布菜单', '0', '1', '1', '2018-03-06 14:41:01'); +INSERT INTO `system_node` VALUES ('68', 'wechat/news', '图文内容管理', '0', '1', '1', '2018-03-06 14:41:13'); +INSERT INTO `system_node` VALUES ('69', 'wechat/news/index', '图文内容管理', '1', '1', '1', '2018-03-06 14:41:20'); +INSERT INTO `system_node` VALUES ('70', 'wechat/news/select', '图文选择器', '0', '1', '1', '2018-03-06 14:41:22'); +INSERT INTO `system_node` VALUES ('71', 'wechat/news/image', '图文选择器', '0', '1', '1', '2018-03-06 14:41:22'); +INSERT INTO `system_node` VALUES ('72', 'wechat/news/add', '添加图文', '0', '1', '1', '2018-03-06 14:41:23'); +INSERT INTO `system_node` VALUES ('73', 'wechat/news/del', '删除图文', '0', '1', '1', '2018-03-06 14:41:23'); +INSERT INTO `system_node` VALUES ('74', 'wechat/news/push', '推荐图文', '0', '1', '1', '2018-03-06 14:41:24'); +INSERT INTO `system_node` VALUES ('75', 'wechat/news/edit', '编辑图文', '0', '1', '1', '2018-03-06 14:41:25'); +INSERT INTO `system_node` VALUES ('76', 'wechat/tags', '标签管理', '0', '1', '1', '2018-03-06 14:42:06'); +INSERT INTO `system_node` VALUES ('77', 'wechat/tags/index', '标签列表', '1', '1', '1', '2018-03-06 14:42:09'); +INSERT INTO `system_node` VALUES ('78', 'wechat/tags/add', '添加标签', '0', '1', '1', '2018-03-06 14:42:14'); +INSERT INTO `system_node` VALUES ('79', 'wechat/tags/edit', '编辑标签', '0', '1', '1', '2018-03-06 14:42:17'); +INSERT INTO `system_node` VALUES ('80', 'wechat/tags/del', '删除标签', '0', '1', '1', '2018-03-06 14:42:20'); +INSERT INTO `system_node` VALUES ('81', 'wechat/tags/sync', '同步标签', '0', '1', '1', '2018-03-06 14:42:23'); +INSERT INTO `system_node` VALUES ('229', 'admin/node/clear', '清理无效记录', '0', '1', '1', '2018-03-09 12:24:31'); + +-- ---------------------------- +-- Table structure for system_sequence +-- ---------------------------- +DROP TABLE IF EXISTS `system_sequence`; +CREATE TABLE `system_sequence` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `type` varchar(20) DEFAULT NULL COMMENT '序号类型', + `sequence` char(50) NOT NULL COMMENT '序号值', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `index_system_sequence_unique` (`type`,`sequence`) USING BTREE, + KEY `index_system_sequence_type` (`type`), + KEY `index_system_sequence_number` (`sequence`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统序号表'; + +-- ---------------------------- +-- Records of system_sequence +-- ---------------------------- + +-- ---------------------------- +-- Table structure for system_user +-- ---------------------------- +DROP TABLE IF EXISTS `system_user`; +CREATE TABLE `system_user` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户登录名', + `password` char(32) NOT NULL DEFAULT '' COMMENT '用户登录密码', + `qq` varchar(16) DEFAULT NULL COMMENT '联系QQ', + `mail` varchar(32) DEFAULT NULL COMMENT '联系邮箱', + `phone` varchar(16) DEFAULT NULL COMMENT '联系手机号', + `desc` varchar(255) DEFAULT '' COMMENT '备注说明', + `login_num` bigint(20) unsigned DEFAULT '0' COMMENT '登录次数', + `login_at` datetime DEFAULT NULL, + `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)', + `authorize` varchar(255) DEFAULT NULL, + `is_deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '删除状态(1:删除,0:未删)', + `create_by` bigint(20) unsigned DEFAULT NULL COMMENT '创建人', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `index_system_user_username` (`username`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='系统用户表'; + +-- ---------------------------- +-- Records of system_user +-- ---------------------------- +INSERT INTO `system_user` VALUES ('10000', 'admin', '21232f297a57a5a743894a0e4a801fc3', '22222222', 'zoujingli@qq.com', '13617343800', '', '22405', '2018-03-13 15:04:35', '1', '1', '0', null, '2015-11-13 15:14:22'); + +-- ---------------------------- +-- Table structure for wechat_fans +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_fans`; +CREATE TABLE `wechat_fans` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `appid` char(50) DEFAULT '' COMMENT '公众号Appid', + `unionid` char(100) DEFAULT '' COMMENT 'unionid', + `openid` char(100) DEFAULT '' COMMENT '用户的标识,对当前公众号唯一', + `spread_openid` char(100) DEFAULT '' COMMENT '推荐人OPENID', + `spread_at` datetime DEFAULT NULL COMMENT '推荐时间', + `tagid_list` varchar(100) DEFAULT '' COMMENT '标签id', + `is_black` tinyint(1) unsigned DEFAULT '0' COMMENT '是否为黑名单用户', + `subscribe` tinyint(1) unsigned DEFAULT '0' COMMENT '用户是否关注该公众号(0:未关注, 1:已关注)', + `nickname` varchar(200) DEFAULT '' COMMENT '用户的昵称', + `sex` tinyint(1) unsigned DEFAULT NULL COMMENT '用户的性别,值为1时是男性,值为2时是女性,值为0时是未知', + `country` varchar(50) DEFAULT '' COMMENT '用户所在国家', + `province` varchar(50) DEFAULT '' COMMENT '用户所在省份', + `city` varchar(50) DEFAULT '' COMMENT '用户所在城市', + `language` varchar(50) DEFAULT '' COMMENT '用户的语言,简体中文为zh_CN', + `headimgurl` varchar(500) DEFAULT '' COMMENT '用户头像', + `subscribe_time` bigint(20) unsigned DEFAULT '0' COMMENT '用户关注时间', + `subscribe_at` datetime DEFAULT NULL COMMENT '关注时间', + `remark` varchar(50) DEFAULT '' COMMENT '备注', + `expires_in` bigint(20) unsigned DEFAULT '0' COMMENT '有效时间', + `refresh_token` varchar(200) DEFAULT '' COMMENT '刷新token', + `access_token` varchar(200) DEFAULT '' COMMENT '访问token', + `subscribe_scene` varchar(200) DEFAULT '' COMMENT '扫码关注场景', + `qr_scene` varchar(100) DEFAULT '' COMMENT '二维码场景值', + `qr_scene_str` varchar(200) DEFAULT '' COMMENT '二维码场景内容', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `index_wechat_fans_spread_openid` (`spread_openid`) USING BTREE, + KEY `index_wechat_fans_openid` (`openid`) USING BTREE, + KEY `index_wechat_fans_unionid` (`unionid`), + KEY `index_wechat_fans_is_back` (`is_black`), + KEY `index_wechat_fans_subscribe` (`subscribe`) +) ENGINE=InnoDB AUTO_INCREMENT=2324 DEFAULT CHARSET=utf8 COMMENT='微信粉丝'; + +-- ---------------------------- +-- Records of wechat_fans +-- ---------------------------- +INSERT INTO `wechat_fans` VALUES ('2227', 'wx60a43dd8161666d4', 'oGsrkszQSaou6tKM-4c2Tl625Ta8', 'o38gps8yp0qvO2Oa0OelGA66uX8k', '', null, '2', '0', '1', '\\ud83d\\ude36 夏俊杰', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/ajNVdqHZLLAWLLrYeJLDoL7FaicsJ2zbI92l04ibMIk8ym62Unzg9ZhatQDXqicXkXjjVDbicN9dK5GRVz2Pcfv7JA/132', '1484634161', '2017-01-17 14:22:41', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2228', 'wx60a43dd8161666d4', 'oGsrks1qovmJUGycmiAKSry3ISp4', 'o38gps4pJjHGkXVGe1aXxgzKJ7u4', '', null, '', '0', '1', 'Y.c', '1', '中国', '贵州', '贵阳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhXkkCBI9xTlsSFWhic9MhhoVcFsjR7AS1u3sOQCQdZYpt4MIaIvulmZhibBT9ibmuPiby7Qdic1NPjnnQ/132', '1517301228', '2018-01-30 16:33:48', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2229', 'wx60a43dd8161666d4', 'oGsrks9XUFq6O6g2len8l2TaS6dA', 'o38gps5khVMMG2trfekmkNX13VOI', '', null, '2', '0', '1', '成哲', '1', '中国', '福建', '泉州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/Q3auHgzwzM5BicBM70CuKklke6GvFFGzPibK60aR42zibKfpOe6xzwJFlUreOmTtagKXYyK6p2tlqaKAx3ISMyaCw/132', '1497508654', '2017-06-15 14:37:34', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2230', 'wx60a43dd8161666d4', 'oGsrks9i1WGC341Sw8YSzLh_L-ws', 'o38gps-IeXhG6ghhF1oUHmPrC4mw', '', null, '', '0', '1', '胜!!!', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKqWzwNeqjpichF75WDj9sib9oexCiaU8sY3fZtmNUA3Qnc7wDKG3csRuIrN5wbfospITr4icqa89IGUruHOHQstdEy4/132', '1499953283', '2017-07-13 21:41:23', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2231', 'wx60a43dd8161666d4', 'oGsrkswZ9aRbMxNeADBiIoFs7Ixs', 'o38gps5cxg7TIJjhW40hcfx3ceM0', '', null, '', '0', '1', '大豪', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKqWzwNeqjpichAgPGia3yWJjWELUkZB1ZeNLiaA5FUtiaicc4t1nicDW8j7bllJVnAlKNYOD3hrXiaricadxuzTUveqIghR/132', '1501054131', '2017-07-26 15:28:51', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2232', 'wx60a43dd8161666d4', 'oGsrkszCVFA8v-3_FGYhmTRiURlc', 'o38gpsza67FfLSapYrfBTo1Zu5zw', '', null, '', '0', '1', '杨永安', '1', '中国', '陕西', '汉中', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnjFHIKUiabCChkMVagHWKWmHrtxcgfrSRM9SrYjhcXvXTIuTwcfoT2g2HjdckiaxUyicU2T6hcKazca6IzF1vKz2Jic/132', '1514250857', '2017-12-26 09:14:17', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2233', 'wx60a43dd8161666d4', 'oGsrks8FUHqgI4nOt8JJP8qd8ioE', 'o38gps7jMkHnBSV6JN-Rpgog6lu0', '', null, '', '0', '1', '生命精神', '1', '中国', '浙江', '杭州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1nJzR8unwfcHwsPJh1P5SWGcxK4tCMh0to6KrsRCzWn8MZLibmeD6bMgItT1bs8ROmrNahJib2lDhAQ/132', '1476402502', '2016-10-14 07:48:22', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2234', 'wx60a43dd8161666d4', 'oGsrks2Mv-0flhVG8Oi-bZZSdRFE', 'o38gps1COGG9--oRbPIazGFPmtcE', '', null, '2', '0', '1', 'Howard', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnjFHIKUiabCChlvlBvNO4ItvQANPqmdKRIoDJTXm56vaS0ic2luQH0MwyMglnuYJlrVHNJp78RhYkknibSZI68RvZp/132', '1466066875', '2016-06-16 16:47:55', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2235', 'wx60a43dd8161666d4', 'oGsrks5PzhNfMRmV1E5dL6Ydsm4A', 'o38gps9lnht0uxYR6cTlnntT6bA0', '', null, '', '0', '1', '_Xiαпg™', '1', '中国', '广东', '深圳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1mkKSyWyia8EEvwt23OaKQmcibicbzKmyDHP8HNeHEHqc552NUtZeoTZ4PhBNYWvYyae5WeSa6KRoq4gtwciamjI9DI/132', '1496038821', '2017-05-29 14:20:21', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2236', 'wx60a43dd8161666d4', 'oGsrks9Sl2pcIhFCLoXBwxFi5P8w', 'o38gps2DCEuBJkh-5i9kGg7lxNsE', '', null, '', '0', '1', '在路上', '1', '中国', '上海', '浦东新区', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/ajNVdqHZLLCicuAKzkTwgEBjApfbJO4leQhpxR3kLsszfu3qcNaIzAsod4jW06HasuBCgFURe13b1NiabBHblpcQ/132', '1496907572', '2017-06-08 15:39:32', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2237', 'wx60a43dd8161666d4', 'oGsrks7bq2oijI9rA38zZ1KxTHyA', 'o38gps1-O1OEFn6Fle0jQGoDuDdg', '', null, '', '0', '1', '无形…', '1', '中国', '四川', '成都', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFlqFUCaneic7zYBL9qFNibfCSyyTxQfRwlmYGE7EL54lHJ6DbuWUqfbvTLnWqiaMYzGSiatDNqwKwuE2z1OLKygepqe/132', '1510799509', '2017-11-16 10:31:49', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2238', 'wx60a43dd8161666d4', 'oGsrks68g1elLyggOA9wUXezjkVY', 'o38gps-clgtfbM1BjVHTV7OdQTiY', '', null, '', '0', '1', '瓦力', '1', '中国', '河南', '洛阳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/ajNVdqHZLLCM2O5qico9GsVRoicn1HHh6BXuyJ1xUkjuc0icq1e1DN6hh87P5KchiblFkpYic9cHmlcQwBXdwKebQBg/132', '1498804317', '2017-06-30 14:31:57', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2239', 'wx60a43dd8161666d4', 'oGsrksydSQWBQZBegH8J5u6CM8Jc', 'o38gpszDg5iN-1GlAObk6Lj4RhfM', '', null, '2', '0', '1', '一卡易|谢永发', '1', '中国', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1mnLu0ourZB5uibPZM6m9cCXO2AmPTHHciaMjgLbibRTjpjKwrbFmhqfPKkXbttWZj8uGibcaphnicibSng/132', '1492329188', '2017-04-16 15:53:08', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2240', 'wx60a43dd8161666d4', 'oGsrks-mJu7hqiRMhNDPVGR0Fu9o', 'o38gpszLGFY86inXygfs3klN5QMI', '', null, '', '0', '1', '叫我小周', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/PiajxSqBRaEIxKQLuhIBibnLUkbHjfyiaKXBWaZcWbS6vibLAiaicFDkMWyxia4Mt75pUMp0WiczciaEUM8keeQg71D7X6A/132', '1461314119', '2016-04-22 16:35:19', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2241', 'wx60a43dd8161666d4', 'oGsrks0Q0Q5WAT-aSKHuJsmXjojU', 'o38gps_d4aGPEpf6eyRH73SAuCuY', '', null, '', '0', '1', '李彦18127877079微商系统一站通', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1mkKSyWyia8EEquArYSyUgT9dHEUBrFMvuUVWTAQMVcTpicEUicafhGqzYHyslrvLUvZ5TziaEKJvcYQkq6hOBw5qNk/132', '1501207299', '2017-07-28 10:01:39', '', '0', '', '', 'ADD_SCENE_PROFILE_CARD', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2242', 'wx60a43dd8161666d4', 'oGsrks1_4lRQum_CKGWAVsDZxYps', 'o38gps33cPD8u2cxNx5evYBYIVs8', '', null, '', '0', '1', '逸尘', '1', '中国', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/ajNVdqHZLLBia7xaYiafnETkiczAxjqJsAuyEiapPbjcEFqO3HWtPC2Ekny5jY1Dq8dPCLDRZGFoxBy1ELOia1hLkDg/132', '1520303270', '2018-03-06 10:27:50', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2243', 'wx60a43dd8161666d4', 'oGsrksx8x7WPo5hELWahpPnbDcKw', 'o38gps1OSpEMht-JsrSZ2-WwqZpY', '', null, '2', '0', '1', '蚊子', '1', '中国', '浙江', '杭州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/Q3auHgzwzM6W7naf6WCsmEcEKXZ7EDlHR4RtbKzvfAOxDf4ZicAZ9yRU0MA3icVW0PSmgHR7hd7AR8JZQcumBKxw/132', '1499867678', '2017-07-12 21:54:38', '', '0', '', '', 'ADD_SCENE_PROFILE_CARD', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2244', 'wx60a43dd8161666d4', 'oGsrks6zpXTf3A9n6hJeElMM1CF0', 'o38gps31jfVXc2XAHI-kkkb68btw', '', null, '', '0', '1', '辫子编程', '1', '中国', '河北', '石家庄', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1lKOpDLzcicpHdUPtFHovDeujrfCwD1ibRplDic3W5aaziaZAFoX22085f82zl9FGmjGIADnPsKIgGaSg/132', '1505962042', '2017-09-21 10:47:22', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2245', 'wx60a43dd8161666d4', 'oGsrks7b6Vr4xR5zEfUxfkocvpFs', 'o38gps1zUbMdHqVrneOcRX2s9w-Y', '', null, '', '0', '1', '叫我小贤\\ue131', '2', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaa2ZABsHxhbDstmoknTNNDJ5UaLHQsbkvpjJUAT2yBtrgS9VA8v5CbmDsHKKiam8FNDuDqHudfVtBPIYcw3eicw4/132', '1500262365', '2017-07-17 11:32:45', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2246', 'wx60a43dd8161666d4', 'oGsrks1-omPbZPIyJjqDHGrJ2uNU', 'o38gpszg_-jIQYq6Iy2zBj3N5ndM', '', null, '', '0', '1', '周峥宇\\ud83e\\udd20我又胖了', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/PiajxSqBRaEJDT62uNn5Apd4hOGVbkYLIr0JeCuZiakMRYnpqcEibwbJhgd6tKLiba66NFNsFbWibshGup3ITAKsUhA/132', '1500284215', '2017-07-17 17:36:55', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2247', 'wx60a43dd8161666d4', 'oGsrks3xwnOorGl_tvt9oupQLxwY', 'o38gps8HQRZr3mk32MdM-KCm1rAI', '', null, '2', '0', '1', 'Aries', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaa2ZABsHxhbMBdNOwMpBIJNI4IguZ4d6BzQ6yKVxHWJmvNNZzjrMbloHj0Tm65p996cWmgib51WkLzxQiaNZ3gtl/132', '1499998442', '2017-07-14 10:14:02', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2248', 'wx60a43dd8161666d4', 'oGsrksxL7JHpBw-JQkzZlkoSKL_E', 'o38gps-YmcToFI9KPCIU50EzyApM', '', null, '', '0', '1', '动感排骨', '1', '阿拉伯联合酋长国', '迪拜', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1licrjBnJJkGRAIetAibGZMqsdFpIBUqjsL0CpTbTLWJwz48FHKZNj5VTOeQBBlJ8ufib3Qb3pLYvYfA/132', '1493167619', '2017-04-26 08:46:59', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2249', 'wx60a43dd8161666d4', 'oGsrks_ReHkrfr-fHmd9CROHrX7k', 'o38gps3KOfAj3hqGaLuYdi-F8Pcw', '', null, '', '0', '1', '(´-ι_-`)', '1', '中国', '湖北', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/Q3auHgzwzM5Ba4Ha2y7wJQ5pH33bxJOCJXW4kHY48gwN2iaNB5L1Vib8eHxAzTnticHb9g2aLpEjAcRzqOknMNlMw/132', '1495683774', '2017-05-25 11:42:54', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2250', 'wx60a43dd8161666d4', 'oGsrks4vN3pEBSCIwOB8_Juat5l0', 'o38gpswFGKNyVQe8WEiEPj5V_o-E', '', null, '2', '0', '1', '喧气...炎魔', '0', '', '', '', 'zh_CN', '', '1467451373', '2016-07-02 17:22:53', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2251', 'wx60a43dd8161666d4', 'oGsrks8wZprONrqzxV50sUxsbyEM', 'o38gps6H7P1RE2pkvyLgHhK5X5yQ', '', null, '', '0', '1', '小朋', '1', '中国', '陕西', '西安', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKpZfaTicicM8gaa6Zyz3ZB4pLv8YZbq0yN35tS2H27fdAkkQIA1FlmHKr4c0Uz6G8nR3zLxNsPia2qzRNjygD57ia6s/132', '1461829132', '2016-04-28 15:38:52', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2252', 'wx60a43dd8161666d4', 'oGsrks5h-iq2PsHWnomgbHQW7cbI', 'o38gps2gTDZrgo7xDQcBnOv6kSCw', '', null, '', '0', '1', '肖', '1', '中国', '北京', '昌平', 'zh_TW', 'http://thirdwx.qlogo.cn/mmopen/Q3auHgzwzM6P5wB3cJN5SkRHzwKTJ8Ixtyk8LDcA6zvTPTdkaBMicypiaxOTVYH2P1nib8wwmCIkrUkvskut1sVXQ/132', '1511144044', '2017-11-20 10:14:04', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2253', 'wx60a43dd8161666d4', 'oGsrks-4KSMJUTVBrkiZwmKFnKkw', 'o38gpsyHiwv9XeLT7FnB-X-wXnrY', '', null, '2', '0', '1', '杨志斌', '1', '中国', '江西', '吉安', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFnfDEMRYcBCAVHLhWduAGWWh1SmEfGnth6Qpibvzhiax5tUePAVg6w3CjbibUrACRrG9HxegTt0zCJ6A/132', '1468834443', '2016-07-18 17:34:03', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2254', 'wx60a43dd8161666d4', 'oGsrksx6pGST6OZpQd3kxVDVe0-4', 'o38gpsxqDndY2CNb-wKqzfQOCxOs', '', null, '', '0', '0', '.', '1', '阿鲁巴', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1ktoYc3JFOdy0UBggCicqR6QMCp09kTIhG96h6dRwLAI4CrTJ7AKR95n8CVKUjt1pftol3Sias1WFvQ/132', '1506388223', '2017-09-26 09:10:23', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2255', 'wx60a43dd8161666d4', 'oGsrks_ByPI_LoQcutU_Efgh9VFo', 'o38gps-hP-kkUfiDRrPNNgHt-2sk', '', null, '2', '0', '1', '李兴宇(大宇)', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaa2ZABsHxhbH3GFewwtabnLnd1dFvjVwvH232efzzR96aia0iaur7EhEC1NEuTQRKcbfiaVYNxt0Wplkj4nkEU1sw/132', '1466065790', '2016-06-16 16:29:50', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2256', 'wx60a43dd8161666d4', 'oGsrks33jf6hGYbr-fN4hl2O3t2E', 'o38gps0VZNX4tMBGf9wc62ze50iY', '', null, '', '0', '1', 'clement', '1', '中国', '河南', '郑州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFnStnyIWbGOSib3R3feZytljzGuMh82Q9oXLJejdDoXGhJjGtRXicXVJcAmc8KRBQOIL7dMKp41YXVw/132', '1501581665', '2017-08-01 18:01:05', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2257', 'wx60a43dd8161666d4', 'oGsrks0JJffPwk-8i9k1omC812ho', 'o38gpswRdt7MOJ0qTYkvcTzLVOL0', '', null, '', '0', '1', '孙海锋', '1', '中国', '四川', '达州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/ajNVdqHZLLDpWmRlKAcow16dZuXYKokRyX8kiazDtWEjXXOiaYaAXRjdrLribt8eB7pEodKzWqJ0gmdfoqlDbIG5g/132', '1499827720', '2017-07-12 10:48:40', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2258', 'wx60a43dd8161666d4', 'oGsrksy8u0sYaQ3n1oYliCkMzC68', 'o38gps2Tx8cdNfGzaEis2Mc4pvTI', '', null, '2', '0', '1', '周星星', '1', '中国', '陕西', '西安', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKpZfaTicicM8gaXUd33E2xm49mibmfE14cLwH2ROdxK7kI8vsVeVPOUvKZgE6JcwUmX0ZXWAVP44dKeFLUIz4zDWtia/132', '1498098232', '2017-06-22 10:23:52', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2259', 'wx60a43dd8161666d4', 'oGsrks23Z-JsvGGn9rFSNqBnW9ik', 'o38gps2HY7h23CJoVvk5PM303kNY', '', null, '', '0', '1', 'hxxx', '1', '中国', '广东', '深圳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaa2ZABsHxhbOOaHOUFD9B5wSic3q3icK8EE58G8PY0f4nALGOWdia25Z7wqSSanlfK6tBGuysyTenTLSJCBLSVnXF/132', '1506316538', '2017-09-25 13:15:38', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2260', 'wx60a43dd8161666d4', 'oGsrks_hP6tLtqnPLDO2c2mdCiKU', 'o38gpsyh30CKHUULhEf4ILUQqZ5s', '', null, '', '0', '1', 'Promise', '1', '中国', '广东', '深圳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKpZfaTicicM8gaUeOuxB80yqBYX6YiaYUkIS54eEmUpelLPvFd0uXfeoreo5wdb4uxSeYEaia5IYw2tiaS98Pg0Q64XK/132', '1509438514', '2017-10-31 16:28:34', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2261', 'wx60a43dd8161666d4', 'oGsrks8MgmWGcHiTXw8-MVOud_jk', 'o38gps3vNdCqaggFfrBRCRikwlWY', '', null, '2,195', '0', '1', 'Anyon', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKpZfaTicicM8gabdQdx2gUg5S8g0bpwruPiccFZRPhicvIiazQISqcia9kKQBETsYnJiakE1DSFhUN7t529jXh7iaibBjB5z/132', '1520407544', '2018-03-07 15:25:44', '', '0', '', '', 'ADD_SCENE_QR_CODE', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2262', 'wx60a43dd8161666d4', 'oGsrksyuQFA4llVF36HPs5Gv2UMQ', 'o38gps-uNjXogTeIcCq_Fs5d_J6E', '', null, '', '0', '1', '晨曦记忆,过往云烟', '1', '中国', '广西', '南宁', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1kJibfasazNpwQbdwJo28iaYfQRg5b7mPTQib52JHUSjrIaZ4uTO3HfriaAM6v9T8fpr3tpenv5mI9y8Q/132', '1497952981', '2017-06-20 18:03:01', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2263', 'wx60a43dd8161666d4', 'oGsrks9yOCbqOASjxEwfMwVNildI', 'o38gps79rC4d7rwnfhmWnxXi_1i8', '', null, '', '0', '1', '狒狒', '2', '中国', '湖南', '长沙', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaa2ZABsHxhbGVF36I9BzRtZKsHVnr8ucPeqwR03EAYXMLC36aN71dNJLc8axpaMkpDCepQLmltyZibjtZYeqfwe/132', '1461593762', '2016-04-25 22:16:02', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2264', 'wx60a43dd8161666d4', 'oGsrkszZVl6PTAtbzrmfkUl4KyLM', 'o38gpsxFtOXxBpZ9AjXs5RkEfPj0', '', null, '', '0', '1', 'Chen', '1', '中国', '广西', '南宁', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/QxhD6QevWDypRKO3jAbt91libGBOUmV1GibwYNJ7mQmP5xkdUDVoVXvN36bYeicD71pTFL4SgTbeqhe8DVvibt3uL6adwOt8J8bH/132', '1500003321', '2017-07-14 11:35:21', '', '0', '', '', 'ADD_SCENE_QR_CODE', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2265', 'wx60a43dd8161666d4', 'oGsrks7qKxIVsxToJYHuRANPEFB8', 'o38gps83KBbjv_gvAA41Hf2JTBDI', '', null, '', '0', '1', '谈德茂', '1', '中国', '湖北', '武汉', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFn6rUOLzb2HTQSoVHmHiaicqz45Td0ocFib53iaAzFicpuFwR9QAt8X87eotIhib1s69T8syxMxibiatJibGQw/132', '1504061982', '2017-08-30 10:59:42', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2266', 'wx60a43dd8161666d4', 'oGsrks_Yf9S9KemX-5shUhvPkWSk', 'o38gps_3EjXgILhDBDRhhrD7DMkk', '', null, '', '0', '1', 'NH', '2', '中国', '陕西', '西安', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/PiajxSqBRaELPibepDuGJ8xMgJCJRvBfwjVyq390nibich61YlXNFFia4aibvA4jibvcQtwLiczucKrdOpC25RHrReDSUA/132', '1461314624', '2016-04-22 16:43:44', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2267', 'wx60a43dd8161666d4', 'oGsrksxRpWEHDH9IdIxdus8_h57g', 'o38gpswZkDihqLaHEIgCYg2rDFek', '', null, '', '0', '1', '梁金城', '1', '中国', '广东', '湛江', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaa2ZABsHxhbKOjhibtPQbN6WE2l1hqFEjZYPlHxMeEjoXiaP8lnbyTmzRUxgWGcgwibTcthlLdyAAs4pwhsgFYpIc/132', '1509860364', '2017-11-05 13:39:24', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2268', 'wx60a43dd8161666d4', 'oGsrksxKC6HssWvXSv2hHXNFHf6M', 'o38gps4ssW_HpC_wiML44kJDPObo', '', null, '', '0', '1', '山里人', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/QxhD6QevWDypRKO3jAbt90Hc72JgV7Go0ygPRztzQXn5OBn4sTD7icSJTzrUsiaE9gGE7oPaNRmXicZsd2OVQAbZN83VswO2rmd/132', '1497665032', '2017-06-17 10:03:52', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2269', 'wx60a43dd8161666d4', 'oGsrks3QCAmS0w84Rz-vEId_vEjE', 'o38gpsyhNsz9oufVc9cJWX8YTkTE', '', null, '', '0', '1', 'Biao', '1', '中国', '广西', '南宁', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFnLq4hSElRXEqHibwT5P6gee7Q9dk9ca8icSCBhHnTNbh9KteCYKgepTUar9USzJlZLX3TvvpFVCsU3fNXyby3Bky/132', '1505788891', '2017-09-19 10:41:31', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2270', 'wx60a43dd8161666d4', 'oGsrks6H0hBnuX9dRGeqNQmoFm_Y', 'o38gps0ZJKvxjEc0N2BOgR-T1doM', '', null, '', '0', '1', 'hhaoqin', '1', '安道尔', '', '', 'en', 'http://thirdwx.qlogo.cn/mmopen/KULsUKmynFu6SiahhNaO6M5RmJZYJTEM8TcsuiaUiaDuUh6NuQzx17CZ0JrU3XD8vzUvCdhPfT8Hcxhian6kY96ZqIgOfta4Vv5Z/132', '1513841189', '2017-12-21 15:26:29', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2271', 'wx60a43dd8161666d4', 'oGsrksynwJRL7pBTOshy-iMzFwOc', 'o38gps-4TexA575MN0Qy_x7hPeGU', '', null, '2,195', '0', '1', '黑马\\ue419青田嗨购', '1', '中国', '浙江', '丽水', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/PiajxSqBRaEKDmibBPic7ia9uCAznbmiacpXjlPLbK8RO6DunGb4BKLlaK9szMae6p64xtmkN1hIAmiaHWwEyeKBEjZ3GJo7bUc0AqovWRn16icSL4/132', '1520417834', '2018-03-07 18:17:14', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2272', 'wx60a43dd8161666d4', 'oGsrks68I0iO8K1a-ovuXlQbe6ek', 'o38gps35ZFbur02-CpnFoCJ-a_BM', '', null, '', '0', '1', '代号为纯洁\\ud83d\\ude43', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKrBw3cFObrgH5sPDvLic7on8pdfrgRSVXicBEiaJ8XXsrojeAma7SHIL4vWAibnYhpRYYzklVO5OhhQ3Yjngr2mp3Ag/132', '1487058795', '2017-02-14 15:53:15', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2273', 'wx60a43dd8161666d4', 'oGsrks5oAnVDeARhvzJsXkv9cwj0', 'o38gps2FSyO8uilTJiC5RqaqPptQ', '', null, '', '0', '1', 'Miranda', '2', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlK9MUdQ3sMKzPChibZRlLKNDfZQRU2OUshtAyFsUZL0BhxbrR09zrH1p1QFRb67gbKm0oaG6GSl6y/132', '1461418982', '2016-04-23 21:43:02', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2274', 'wx60a43dd8161666d4', 'oGsrksw6We2OFFxFcjtKgvHK9f10', 'o38gpszO1lJ05I_jRV0vUNmFzZR4', '', null, '', '0', '0', '衣先生', '1', '中国', '山东', '青岛', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFmr7dAfxG0unedyDjK6rjffLsvEPGZHqWptiac60Y4Ts0U8IibDvGzic4iaNaO4Hx5RzPGlM0gkLPnolyzSUIMsDj2k/132', '1520302268', '2018-03-06 10:11:08', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2275', 'wx60a43dd8161666d4', 'oGsrks_cMX730air6ZKOSPhUv5bI', 'o38gps5zUa08f5ivxsv10AqPVTM8', '', null, '', '0', '1', '阿科', '1', '中国', '湖北', '荆州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlCrtvWZCGVUsTQpZzGR53Ykyg1rjg5jTWiaEDQKdtsewAwEXFavouZcwfOAF390ibDC9MXWOdoDwicy/132', '1517735573', '2018-02-04 17:12:53', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2276', 'wx60a43dd8161666d4', 'oGsrks43DcAGrJnOczRRYbEywK_A', 'o38gps-K1CBkMth56oq0YpiP7z6s', '', null, '', '0', '0', '叶之伟', '1', '中国', '山东', '济南', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/KULsUKmynFu6SiahhNaO6M0NicAD2zcuYiaCLolMdp0YicmTqjzoHTst50GQJtjxBRKpG4SKP7yFzhpVYx4FQA0MguicpXwnwUJca/132', '1515672598', '2018-01-11 20:09:58', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2277', 'wx60a43dd8161666d4', 'oGsrks6KXJGK44csA7Ye6kIfbLn4', 'o38gps2fH8aFwmFhFhU4Aurv18os', '', null, '', '0', '1', 'UM - 15524468881', '1', '中国', '辽宁', '沈阳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFmr7dAfxG0unQpG2L0JFNuvwW19VD6ZuhR3ycpbk1SjIp4daCm9sdrzWI7E4ZjlXbibRcvy1JKHYc9U7XPAQ3W8V/132', '1516189815', '2018-01-17 19:50:15', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2278', 'wx60a43dd8161666d4', 'oGsrksyJ9EZJAjlzQdZnGByZiwyE', 'o38gps9GErwzTtZPTiNPC9f8ay1s', '', null, '', '0', '1', '昂', '2', '中国', '吉林', '长春', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/KULsUKmynFu6SiahhNaO6M3MunrZR1ACAIalhmLGWSs4RHhY6d4ibglDGJXypxhmpDrcgb50muM37bR3wZTcpjzlGP3LQ2KFJR/132', '1511158120', '2017-11-20 14:08:40', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2279', 'wx60a43dd8161666d4', 'oGsrkswvewcgA2obzWXeCj_yIHzs', 'o38gpsycVcwBKD3CfXL4hhzvOgt4', '', null, '', '0', '1', 'linf', '1', '中国', '上海', '青浦', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlNiaEKOh52TwmbCSiaUavoqRAGZvgjlcEbgibZlHUODepwaicMcME7EYD5q7wicOPKBKtEIYzFicibES4gK/132', '1495794329', '2017-05-26 18:25:29', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2280', 'wx60a43dd8161666d4', 'oGsrks2xiOxVbOWvaJYHEWkGxzWk', 'o38gps-cq3BpRloot4Zn_SgkERIU', '', null, '', '0', '1', '瑋瑋', '2', '中国', '湖南', '衡阳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1mWdjxsYDFxLgNia0zvtXj9NTLNic1btoLLpIGVu6CviafENqODPtZ4icH7YL07ImRciaEc6BwlJRmDxVfxic2pIiaeOTL/132', '1472542553', '2016-08-30 15:35:53', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2281', 'wx60a43dd8161666d4', 'oGsrksyLTQeGsy4ePCY_yKfBYMF8', 'o38gps2VZzGMKIpQyebRKfv--5Gk', '', null, '', '0', '1', 'lemon', '1', '中国', '湖北', '武汉', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlKfwQcmu0EqdAzpibIiao3Sr8oHbBt2sObibUHOJj2errQyfbRW29TTTooI4eCJWiafPXAh9rUntlFQT/132', '1508837682', '2017-10-24 17:34:42', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2282', 'wx60a43dd8161666d4', 'oGsrks0jW9y25wKFxLurrUPrxvFw', 'o38gps-VE_f7mOEfN5NRf9fzYLNg', '', null, '2', '0', '1', '暴躁的jk苏', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1mWdjxsYDFxLjE1gRKdbiaibvesz4iaXN4wCEJX49wKuVUhjic57UicRjT6mbaEFVeFUvwQUkffRjP7za5ezHicNCr0WZ/132', '1476260898', '2016-10-12 16:28:18', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2283', 'wx60a43dd8161666d4', 'oGsrksxKqIJpVprxJUsbAdZZ5tjU', 'o38gps9nSy6jFfqk8dS5QhtPh8nc', '', null, '', '0', '1', 'JHChan', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/bnoQ9ztOjnTj93lo94aVb4ZIwA4m2jKBZnqVpcjm7fKqua8fkgp8mIRwicl68uGpxZ65GibLCzJoNcbOVWbpQRYWib1D6VCGSrC/132', '1508309343', '2017-10-18 14:49:03', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2284', 'wx60a43dd8161666d4', 'oGsrks8ZUdqV1DJ7nAqWXCtli5YI', 'o38gps14GCla4tRvK5vLpYWyYeQU', '', null, '', '0', '1', 'fantasticcat', '1', '中国', '四川', '成都', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKpT2WiaPkStSibc7ROfOldhenn4G8hJMsL3Ga3q63ia3vEibytQF1IRqo7flfI62iaUuOIP3OECU217Cfib6icnxBSDGsC/132', '1504289794', '2017-09-02 02:16:34', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2285', 'wx60a43dd8161666d4', 'oGsrks3bi69ROBM7wdcnwLiCJN7g', 'o38gps0kRV2gBR97_kn69yXfiQ4o', '', null, '', '0', '1', '༄ེིོུ張偉ཉ྄ིོུ', '1', '中国', '江苏', '南京', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlPKWmdmurlyTJvu98zJvDCfmfvHQUjPhackiatOzWQbHHGntrocpaicEwNtmNAtVuCEkAZ9okPyaWf/132', '1512742506', '2017-12-08 22:15:06', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2286', 'wx60a43dd8161666d4', 'oGsrksy0OwP1IYm09qo_kaXyCxgc', 'o38gps76Z2Pn1Iv6y85sDS0jIgG0', '', null, '', '0', '1', '.', '1', '中国', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/Q3auHgzwzM7rYo6NN0asQjBKCjlPibyyxichoibBXLY1VeKHDw7dmdJLGHJzQtbqOgTdpsj9GGBLu77Ef2ezFp6g8qgDuhfKCQte5LcECqlPtw/132', '1503387391', '2017-08-22 15:36:31', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2287', 'wx60a43dd8161666d4', 'oGsrks8wNGTqTCcLsqXSLYAVeio8', 'o38gpsyU0XajgtHzkCbd5tGhTdEc', '', null, '', '0', '1', '劲松', '1', '中国', '湖北', '武汉', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlMDlhvyJq18DBIrKUJ5Fu2ibCeltbQAPEtvloLd1qornF5zHrzR4hODCuHxoYUk0daLOhsQd4WaSS/132', '1519129201', '2018-02-20 20:20:01', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2288', 'wx60a43dd8161666d4', 'oGsrks--NFosz0qEXlty29HMvDhU', 'o38gps_VXV82i7k-aDEBxf73_wqc', '', null, '', '0', '1', 'xuhf', '1', '中国', '安徽', '合肥', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlPV94mAf8GIAficLU4icVjqbaRicBnUjry0qLk9tNvfibKcWsiba4fWObiaYs06bKK3mrd2KwpQN4ibKticR/132', '1498359038', '2017-06-25 10:50:38', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2289', 'wx60a43dd8161666d4', 'oGsrks9HRARkEHMSqsZKh5L0kfhw', 'o38gps-a5LvX7yhK_q0RMmavHLZs', '', null, '', '0', '1', '柯靖', '1', '中国', '重庆', '江北', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1mcAn4p8XickiciclKy1eG2Jxl41kaBGbib75xg8ocqKSAVuytCTKKbNoibARg3GPQDQyzjp5coqdZNBQiaEAWGtIgo9p/132', '1499769194', '2017-07-11 18:33:14', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2290', 'wx60a43dd8161666d4', 'oGsrksxBQW56NSMZvXuBDIDJh1iM', 'o38gps2sIrPg2g74VLwGgY-FRQz4', '', null, '', '0', '1', '二流子', '1', '中国', '四川', '成都', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/Q3auHgzwzM7sk1IrZ7LEPG52cktWKpB0f2xYzsxWJOBibfS6Ct9R2ZGyL61h43Y2gibLmNsf3pACIRomicqicdV14jzKZia4sibQhUBhUIAsmqtNc/132', '1502625756', '2017-08-13 20:02:36', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2291', 'wx60a43dd8161666d4', 'oGsrks4gMjJSBSWQbtyxwJiZQj18', 'o38gps0Trh8Ggyc7mwBH2XIRIAEc', '', null, '', '0', '1', 'Mr. La', '1', '中国', '青海', '西宁', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlIw66OXtcgoHictuwB6ZyWRQGOkSeEuibMytbxyZvPmM6r1HJHdia4Hgr07toMBj4PczIlfjnIVAtaB/132', '1516598324', '2018-01-22 13:18:44', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2292', 'wx60a43dd8161666d4', 'oGsrks5EfMlOFmfmwTwYG6WFlGCw', 'o38gps3PQyzns75wVKupe_8zQN14', '', null, '2', '0', '1', '杨翊(yì)', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFlhdxossu0UW6MKUKGvJObib9TXaNibDInOkPticBCeCTRl8HOibh8e0SLmFvusIspPuJU5EdRFnyKTibZOUCzB6ibk2G/132', '1464141541', '2016-05-25 09:59:01', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2293', 'wx60a43dd8161666d4', 'oGsrks5arhP8js3-C_Mc2tLOaB34', 'o38gps5soAzcDO6PvbTz3XE88VIY', '', null, '2', '0', '1', 'WGZXU', '2', '中国', '广东', '梅州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlHPCX0qeQZpGxaJRlwA7GjjKY30Rr4HpW9GwibzqdV2mSiaYYypDbqLDZyRWQiatcP8YoGD552mYgiaH/132', '1472019816', '2016-08-24 14:23:36', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2294', 'wx60a43dd8161666d4', 'oGsrks9Bl7SPEjPgN-ui3eFMhDog', 'o38gpszG6ZnGcvNV7feJnT70MCkI', '', null, '', '0', '1', 'piu piu', '1', '澳大利亚', '堪培拉', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFmr7dAfxG0unZbrGAx1eB9WAn3A6qlRrC6v49zINA1Om1zibUsQuR48enw1iajve86lEiciaWiboKzGaqTSSPF0HSRpO/132', '1496744829', '2017-06-06 18:27:09', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2295', 'wx60a43dd8161666d4', 'oGsrkszhE3kIdWyYlN4E_zgLWdUk', 'o38gpsyVfsO9gDNPoRDvsme8n3FE', '', null, '', '0', '1', 'A鼎盛电脑科技', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniataKbRtW1icXGOBCSkhgGs9MUyKylzOuWvqOoyX1OM8aXtTRKLek8yssWialQhneHdxBnl9vbU6OrJ6PaRQHuGm2/132', '1514213921', '2017-12-25 22:58:41', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2296', 'wx60a43dd8161666d4', 'oGsrks1Wd6i5m0YalfiuXg6X6a40', 'o38gpswc8aL1dUFMb2TcABmsksDw', '', null, '2', '0', '1', 'Adrian、', '1', '中国', '广东', '江门', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/6x8XdQdSNbyAXStw8vdF2G8hVk7RzNjpvZjExn3l7s7xLqlsL5unJvShahZ67kZXeytfxvySU1VXZ5JfGBvGOriahadYe9qtib/132', '1495683769', '2017-05-25 11:42:49', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2297', 'wx60a43dd8161666d4', 'oGsrks_nMhj7vI10yWxY8GzscIx0', 'o38gps4zEPecqw8rdL1b9d1D1YPo', '', null, '', '0', '1', '麦客', '1', '中国', '广东', '东莞', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/ajNVdqHZLLAiapCVthUgNysHHjeyicsQ8FtMhhjkcSOMX3yZaAK2IdRCsMO9J8H0ApicOvlZUsiaMiaibcouAftEwstiaTMQkMg5YB4YVicGIt8lGEc/132', '1493125763', '2017-04-25 21:09:23', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2298', 'wx60a43dd8161666d4', 'oGsrkswo8fDPab1lg_UXQkK6kOFA', 'o38gps74mjt-oVMudAu7zHmD70Jk', '', null, '', '0', '1', 'Simon', '1', '中国', '云南', '昆明', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhmHmkWasbv8O5rEkY908OEKxibG9wgn22GbG7W8zmEiaia5rvuQggGlnWOGs5aFc34E8UG6dx01Iicg9ZDlvsyyE2V/132', '1506754302', '2017-09-30 14:51:42', '', '0', '', '', 'ADD_SCENE_QR_CODE', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2299', 'wx60a43dd8161666d4', 'oGsrks1Vd2boIxw3KjWdfrgD2XjY', 'o38gps1Unf64JOTdxNdd424lsEmM', '', null, '2,195', '0', '1', 'SuperX', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/tqRiaNianNl1m81icHORPw7chvQ4c1V0mJibS5Iia6elg2nJFpfmDvtibbEFvC9wIQaI4kibEybHnl2gwChAqQUBHlnh4eDKkOv7QPm/132', '1520409810', '2018-03-07 16:03:30', '', '0', '', '', 'ADD_SCENE_QR_CODE', '0', '测试', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2300', 'wx60a43dd8161666d4', 'oGsrkszrkFk55kOq6LCuEA-IttMQ', 'o38gpsxVkzZd6UY_3c6qAN-LiUw8', '', null, '195,2', '0', '1', '姜敏', '1', '中国', '上海', '浦东新区', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFmr7dAfxG0unXubkwiblxic68V1gx9oz6nJww5MtFOGnmlEu6fxDRUicLOlZBf16XOPiaaSWJSaXjlibshibQib0pic2ic07/132', '1520561646', '2018-03-09 10:14:06', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2301', 'wx60a43dd8161666d4', 'oGsrks5mX7Kr6b1guI9ecDgrXJVE', 'o38gps1tvPvDBPeMXFTZkFvBMxjU', '', null, '', '0', '1', '刘彪', '1', '中国', '广东', '深圳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFme3s8TLGP6cciaxANiaJdtJVZiaibE3x4QDqpFTsM0Hm4ibHR3bX0klc8QWNdbq2nLoRgdXo2sMGVxCnbfnb28TOMHB/132', '1515404964', '2018-01-08 17:49:24', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2302', 'wx60a43dd8161666d4', 'oGsrks6bAMKwkxgHnQtwZzrw7HH0', 'o38gps-kWCJNweHtom-Ur6-_7ipU', '', null, '', '0', '1', '郭树峰', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhmWzkEyg0xZlcRFJ9uzsHwpkn6mKuBCtRs8pO2wACy20joQ9fSwolf3Ny0achnCyUbKGn5ibpgJNDMkADy12ianZ/132', '1515727836', '2018-01-12 11:30:36', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2303', 'wx60a43dd8161666d4', 'oGsrks-n2GxjfG75V5EjcwK3k7Ks', 'o38gpsx-lvKmbJE0YkySrWWgYCuA', '', null, '', '0', '1', 'Mr.L', '1', '中国', '重庆', '两江新区', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlEOfjomrweTyEuy1DEoMscI3m4srjIZBxYNia59E6rLAaTbibxc1E2RrWraqoDz1eImc3fXEnwMD8a/132', '1510220091', '2017-11-09 17:34:51', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2304', 'wx60a43dd8161666d4', 'oGsrks93peyNDIECPpLAppB-kFvU', 'o38gps-4z7nkOJ9VljnrjZ1hJs9Q', '', null, '', '0', '1', '刘鹏飞', '1', '中国', '河南', '郑州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKoicF6Kjzex0vJFrqTpJK7MOEVJgLwLz3f08sMEQLxMcwCKsJibmR4KCd9NPvTycT7w6TZ0ib1aehnt8PO2kkIWRDx/132', '1498788522', '2017-06-30 10:08:42', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2305', 'wx60a43dd8161666d4', 'oGsrks89DGZXf8VfajzOVlbAQ3z8', 'o38gps8K3eiJ3cNAU0TKkAs2a9CU', '', null, '2', '0', '1', 'Johnny Y.', '1', '中国', '湖南', '长沙', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnia6pYhZ3PxuJ0XM46qfibjVS4Y4R4qMm5N4fosz9JWP8NibNO4tjI0eWDpojgkw73WKFovI5O0wW14LnEgcT1oQ2ib/132', '1460548852', '2016-04-13 20:00:52', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2306', 'wx60a43dd8161666d4', 'oGsrks3ubNH4NlW9vTethfs9Yfk8', 'o38gps4yFLVFKQ5TRwRJF-gxhoFE', '', null, '', '0', '1', 'Colin', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKqWzwNeqjpichLW6UrpKOcr0ct9y2nEAxfnhTWQMj6luiaMO8KYL4de5pAMoJyiaGrLaYiaH9liaIzvRhlXADgPqicyzu/132', '1513856956', '2017-12-21 19:49:16', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2307', 'wx60a43dd8161666d4', 'oGsrks7YjGV_35_A98qIyanIXx4s', 'o38gpswjzR0S7wMTdDI-lF6kvYaU', '', null, '', '0', '1', '卡卡', '1', '中国', '江苏', '苏州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlFh2E95z0gvulwLNVERZelr67hKMYGxCwbCMsaBapwt2Ef3a0ibLAicVtAzhVgOIvL3UGQTQOWwnq8/132', '1512692256', '2017-12-08 08:17:36', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2308', 'wx60a43dd8161666d4', 'oGsrksxkol9l-RayJxFnhi8ARb6E', 'o38gps0aBICw5jkQ9fYU77T2e-ig', '', null, '', '0', '1', '小时候', '1', '中国', '湖南', '长沙', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFnLhucNNeBTibnMkOfiaSPNXDl1c0ia74NCqHuk3GJO23PFy1wzFibIJCBLl1ibAxyWguBSPPAocQicelEBy8icoN163ZZ/132', '1513565731', '2017-12-18 10:55:31', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2309', 'wx60a43dd8161666d4', 'oGsrkswYnd9qL8EdvS-ABl55wb-I', 'o38gps6_chW-YV83sCx9fm_LOids', '', null, '2', '0', '1', '盼盼\\ud83d\\udd30雅滋美特®总部', '2', '中国', '陕西', '西安', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKpbjOibFLiaRzBwY2ia7XH9Ive6oCQiazl8hRBQr8WORSph7a5swBFoCy9ZewOMqExx7wGQzt4z5Tehoa7qulnEXdO4/132', '1462429726', '2016-05-05 14:28:46', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2310', 'wx60a43dd8161666d4', 'oGsrksylW6rvVpCOTMgBwVeA7mXM', 'o38gps_lFGor-5-sQAxuYCOq4H98', '', null, '', '0', '0', '阿莫', '1', '中国', '广东', '东莞', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKoPVFiceZY7rlfDWxiawIiboh9NjbRlWCugJzgkUtgEqZiaemqhmicWPWu6aBxqBDq4WhSMS67eWxLOFyOQIxT3ME6tO/132', '1508999198', '2017-10-26 14:26:38', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2311', 'wx60a43dd8161666d4', 'oGsrks-DFJgLi94eTz9bR9b6hukU', 'o38gpsw8os7p6RnIW9zDF5INjd58', '', null, '2', '0', '1', '瑋瑋', '2', '中国', '湖南', '衡阳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSngWiakNf5LZdzibaJ2Bzhc39n1crMbeqG55dylBrCwxX5ubpqIxaepRGiayic3xBC8tcTiaVP0I7pdxb3AZQKRtXwf7E/132', '1472105946', '2016-08-25 14:19:06', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2312', 'wx60a43dd8161666d4', 'oGsrksxHmXVcZMJeySQ37uDytOmg', 'o38gpsxjv6TDDkmlPpOCybQ9tjJ0', '', null, '2', '0', '1', '        ', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKqHxl82Xol99xTovqtPdth2ulKkXk8Ckibwop7NqC4SASx0iaFNB0FxtgF9ubk9xPWSDW1OLd7RNZibfp0XMJpIecia/132', '1491880522', '2017-04-11 11:15:22', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2313', 'wx60a43dd8161666d4', 'oGsrks8AQ_mp1XePvYuUDAJ7oBJg', 'o38gpswwa0WyHEKVXaHtF0kcjoJk', '', null, '2', '0', '1', '堂堂', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKp5nibynO469KphibAhuVZ1ZWA5hR79YlLUT9l8ic4ckibpgkPlC0zm1Rwem9kLsN3kPBAhibfIWo7gBmPSvSsLjQKuE/132', '1467771020', '2016-07-06 10:10:20', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2314', 'wx60a43dd8161666d4', 'oGsrkszEXoNDqIw5Og2WBNAiP2D0', 'o38gps4gSFILIoalRs0PP8FfSir8', '', null, '', '0', '1', '老何', '1', '中国', '福建', '福州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/wLcWG0U6YFmr7dAfxG0unZegKCys0B6beoZrQBibU8vppYRwDfx10zxiaA7W7BSuFl0GN0lOjfpleP9h2MPpl2zOib7ZycbMCGh/132', '1502371948', '2017-08-10 21:32:28', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2315', 'wx60a43dd8161666d4', 'oGsrks71BIPANqZDyv4B64e4Eozg', 'o38gps7sSKAiIosZcPtvDCIp5iqg', '', null, '', '0', '1', '风⃢染⃢季⃢末', '1', '北马里亚纳群岛', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnhlmkw5WDLWlMqlYggVlxQQjEz0gI06NiaiciaLd1jFpjMmj6lc4SlQicuPtSAdmfXAoEhQeCIqY9cU7Agp8gYUYLAT/132', '1508379559', '2017-10-19 10:19:19', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2316', 'wx60a43dd8161666d4', 'oGsrks22hZFbB-J-Y98nM-h_9uMM', 'o38gps2Fh1GdM5rQkC3_Bckn3iEY', '', null, '', '0', '1', '永圣', '1', '中国', '陕西', '西安', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/PiajxSqBRaEI86EI2Ha7IFPfp74kFRMzpgYKMwSialEYlhQrD2xKePzDU27MCmZhYayYAJ1gmsrsuBzVkfgeXl3F0k2dMElMEcNaKtRHZduxc/132', '1462429914', '2016-05-05 14:31:54', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2317', 'wx60a43dd8161666d4', 'oGsrks6Y0TUcHa-IhfxS0Me6gl4c', 'o38gps08RgM4pxI5aKCMvAC6iQNw', '', null, '', '0', '1', '麻水劲松_楚鸟', '1', '中国', '湖北', '荆州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/KULsUKmynFu6SiahhNaO6MwjVAA8Bb0TUf0aaElVVYCibMLqI5BNclYGhGM9Lgict7uI2UOxnfuricfcdUIa1SWb0tfBW1uqYewF/132', '1495687411', '2017-05-25 12:43:31', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2318', 'wx60a43dd8161666d4', 'oGsrks4by1arcjSIeWdtWVKx_Vjc', 'o38gps8JDZdTpmnyBzrXqfabhdTw', '', null, '', '0', '0', 'i', '1', '中国', '广东', '深圳', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/Q3auHgzwzM7rYo6NN0asQjBKCjlPibyyxEMD3benk5akljSsj7SljY8OsKVbdGQrejeOFGOMdhgNotpnX9yPCKIjnDO51RmeIWpjgFLUrlvY/132', '1510584392', '2017-11-13 22:46:32', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2319', 'wx60a43dd8161666d4', 'oGsrks9nEbjHQbOOgKFjvgpw4qj4', 'o38gps4Awds1FrY2N9HAidN0FF8Q', '', null, '', '0', '1', '网络共享吧', '0', '', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSnjjBLgK5sTicJaDCncHEGHdj3fPwCIicWXGcljtxrdvh5ugHped5smMamcCIAM133yic1ZsymQgrSWzx2ptyahBy5R/132', '1509361509', '2017-10-30 19:05:09', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2320', 'wx60a43dd8161666d4', 'oGsrksxKTwgEWj0ApQlblA_V9NEo', 'o38gps9mF9Em-CTs7aV9B88DuTpw', '', null, '', '0', '1', 'yyhhyj', '0', '', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaZgJibibxOwB5fygtmZ1JTrNYwqxKRPBPJYhSmibVzibKeuhQxbT8r9QLk4y9ro0ictValBGaibxIOiaQHW1RbgjFViahM/132', '1503323592', '2017-08-21 21:53:12', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2321', 'wx60a43dd8161666d4', 'oGsrks0mBGjUxYLw1GGbQgNdaN4s', 'o38gpszoJoC9oJYz3UHHf6bEp0Lo', '', null, '', '0', '1', '伟红', '0', '', '', '', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/lQEcz8ricSniaaiciciagbYiahct41to1c8qKJJq00odyYm1kskyE6og7ic0VSnuWankib2DOUZuHvkicGc9XZUN8c55uRb5PMMQmhlrT/132', '1514285567', '2017-12-26 18:52:47', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2322', 'wx60a43dd8161666d4', 'oGsrksxawVziyr-ePlU6nOxALYgA', 'o38gps47H7kIFrv0pQ15ODVQR1mA', '', null, '2', '0', '1', '才子佳人', '1', '中国', '广东', '广州', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKqWzwNeqjpichC3NRcX3D5LVKUapG9XE3xzbt2jdh0qlRYiabia541bkdsY81ICFWrcKhicbAOic4iatlKts4nSVosACa/132', '1461809072', '2016-04-28 10:04:32', '', '0', '', '', 'ADD_SCENE_OTHERS', '0', '', '2018-03-10 17:42:18'); +INSERT INTO `wechat_fans` VALUES ('2323', 'wx60a43dd8161666d4', 'oGsrks3twpOdvQ0N61bJ53UYU0nI', 'o38gps_jAAYPzd6xmE_eNFiecVQA', '', null, '', '0', '0', '上帝抬了我一手。', '1', '中国', '湖北', '宜昌', 'zh_CN', 'http://thirdwx.qlogo.cn/mmopen/cXaM0LzKLKruLDwc9TvAZ4gZmrzr9FLUhOYKtpkbj3xvyt6IXeT38WG2X7dCvABefB3JOkl6svZjjvxoaH2Z6eb0wW1ZLGur/132', '1520905980', '2018-03-13 09:53:00', '', '0', '', '', 'ADD_SCENE_SEARCH', '0', '', '2018-03-13 09:53:02'); + +-- ---------------------------- +-- Table structure for wechat_fans_tags +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_fans_tags`; +CREATE TABLE `wechat_fans_tags` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '标签ID', + `appid` char(50) DEFAULT NULL COMMENT '公众号APPID', + `name` varchar(35) DEFAULT NULL COMMENT '标签名称', + `count` int(11) unsigned DEFAULT NULL COMMENT '总数', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期', + KEY `index_wechat_fans_tags_id` (`id`) USING BTREE, + KEY `index_wechat_fans_tags_appid` (`appid`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=196 DEFAULT CHARSET=utf8 COMMENT='微信会员标签'; + +-- ---------------------------- +-- Records of wechat_fans_tags +-- ---------------------------- +INSERT INTO `wechat_fans_tags` VALUES ('2', 'wx60a43dd8161666d4', '星标组', '20', '2018-03-12 09:55:32'); +INSERT INTO `wechat_fans_tags` VALUES ('195', 'wx60a43dd8161666d4', '新增标签', '0', '2018-03-12 09:55:32'); + +-- ---------------------------- +-- Table structure for wechat_keys +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_keys`; +CREATE TABLE `wechat_keys` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `appid` char(100) DEFAULT '' COMMENT '公众号APPID', + `type` varchar(20) DEFAULT '' COMMENT '类型,text 文件消息,image 图片消息,news 图文消息', + `keys` varchar(100) DEFAULT NULL COMMENT '关键字', + `content` text COMMENT '文本内容', + `image_url` varchar(255) DEFAULT '' COMMENT '图片链接', + `voice_url` varchar(255) DEFAULT '' COMMENT '语音链接', + `music_title` varchar(100) DEFAULT '' COMMENT '音乐标题', + `music_url` varchar(255) DEFAULT '' COMMENT '音乐链接', + `music_image` varchar(255) DEFAULT '' COMMENT '音乐缩略图链接', + `music_desc` varchar(255) DEFAULT '' COMMENT '音乐描述', + `video_title` varchar(100) DEFAULT '' COMMENT '视频标题', + `video_url` varchar(255) DEFAULT '' COMMENT '视频URL', + `video_desc` varchar(255) DEFAULT '' COMMENT '视频描述', + `news_id` bigint(20) unsigned DEFAULT NULL COMMENT '图文ID', + `sort` bigint(20) unsigned DEFAULT '0' COMMENT '排序字段', + `status` tinyint(1) unsigned DEFAULT '1' COMMENT '0 禁用,1 启用', + `create_by` bigint(20) unsigned DEFAULT NULL COMMENT '创建人', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `index_wechat_keys_appid` (`appid`) +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='微信关键字'; + +-- ---------------------------- +-- Records of wechat_keys +-- ---------------------------- +INSERT INTO `wechat_keys` VALUES ('1', '', 'news', '测试', '说点什么吧', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', '', '音乐标题', '', 'http://plugs.ctolog.com/theme/default/img/image.png', '音乐描述', '视频标题', '', '视频描述', '3', '0', '1', null, '2018-03-12 10:52:03'); +INSERT INTO `wechat_keys` VALUES ('2', '', 'news', 'default', '说点什么吧', 'http://plugs.ctolog.com/theme/default/img/image.png', '', '音乐标题', '', 'http://plugs.ctolog.com/theme/default/img/image.png', '音乐描述', '视频标题', '', '视频描述', '1', '0', '1', null, '2018-03-07 15:16:20'); +INSERT INTO `wechat_keys` VALUES ('3', '', 'news', 'default', '说点什么吧', 'http://plugs.ctolog.com/theme/default/img/image.png', '', '音乐标题', '', 'http://plugs.ctolog.com/theme/default/img/image.png', '音乐描述', '视频标题', '', '视频描述', '1', '0', '1', null, '2018-03-06 22:16:23'); +INSERT INTO `wechat_keys` VALUES ('4', '', 'text', 'default', '说点什么吧', 'http://plugs.ctolog.com/theme/default/img/image.png', '', '音乐标题', '', 'http://plugs.ctolog.com/theme/default/img/image.png', '音乐描述', '视频标题', '', '视频描述', '0', '0', '1', null, '2018-03-06 22:22:31'); +INSERT INTO `wechat_keys` VALUES ('5', '', 'text', 'default', '说点什么吧', 'http://plugs.ctolog.com/theme/default/img/image.png', '', '音乐标题', '', 'http://plugs.ctolog.com/theme/default/img/image.png', '音乐描述', '视频标题', '', '视频描述', '0', '0', '1', null, '2018-03-06 22:22:52'); +INSERT INTO `wechat_keys` VALUES ('6', '', 'text', 'default', '说点什么吧', 'http://plugs.ctolog.com/theme/default/img/image.png', '', '音乐标题', '', 'http://plugs.ctolog.com/theme/default/img/image.png', '音乐描述', '视频标题', '', '视频描述', '0', '0', '1', null, '2018-03-06 22:23:39'); +INSERT INTO `wechat_keys` VALUES ('7', '', 'news', 'subscribe', '说点什么吧', 'http://thinkagent.data.cuci.cc/static/upload/1325ee90939a1545/05e10b79fd70fa3a.jpg', '', '音乐标题', '', 'http://plugs.ctolog.com/theme/default/img/image.png', '音乐描述', '视频标题', '', '视频描述', '4', '0', '1', null, '2018-03-12 14:17:05'); + +-- ---------------------------- +-- Table structure for wechat_menu +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_menu`; +CREATE TABLE `wechat_menu` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `index` bigint(20) DEFAULT NULL, + `pindex` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '父id', + `type` varchar(24) NOT NULL DEFAULT '' COMMENT '菜单类型 null主菜单 link链接 keys关键字', + `name` varchar(256) NOT NULL DEFAULT '' COMMENT '菜单名称', + `content` text NOT NULL COMMENT '文字内容', + `sort` bigint(20) unsigned DEFAULT '0' COMMENT '排序', + `status` tinyint(1) unsigned DEFAULT '1' COMMENT '状态(0禁用1启用)', + `create_by` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '创建人', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `index_wechat_menu_pindex` (`pindex`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=150 DEFAULT CHARSET=utf8 COMMENT='微信菜单配置'; + +-- ---------------------------- +-- Records of wechat_menu +-- ---------------------------- +INSERT INTO `wechat_menu` VALUES ('134', '1', '0', 'miniprogram', '一级菜单', ',,', '0', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('135', '11', '1', 'text', '二级菜单', '13412,41234,413241', '0', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('136', '12', '1', 'text', '二级菜单', '请输入内容1', '1', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('137', '13', '1', 'text', '二级菜单', '请输入内容2', '2', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('138', '14', '1', 'text', '二级菜单', '请输入内容3', '3', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('139', '15', '1', 'text', '二级菜单', '请输入内容4', '4', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('140', '2', '0', 'text', '一级菜单', '', '1', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('141', '21', '2', 'text', '二级菜单', '请输入内容5', '0', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('142', '22', '2', 'text', '二级菜单', '请输入内容6', '1', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('143', '23', '2', 'text', '二级菜单', '请输入内容7', '2', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('144', '24', '2', 'text', '二级菜单', '请输入内容8', '3', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('145', '25', '2', 'text', '二级菜单', '请输入内容9', '4', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('146', '3', '0', 'text', '一级菜单', '', '2', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('147', '31', '3', 'text', '二级菜单', '请输入内容11', '0', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('148', '32', '3', 'text', '二级菜单', '请输入内容22', '1', '1', '0', '2018-03-09 15:46:41'); +INSERT INTO `wechat_menu` VALUES ('149', '33', '3', 'text', '二级菜单', '请输入内容33', '2', '1', '0', '2018-03-09 15:46:41'); + +-- ---------------------------- +-- Table structure for wechat_news +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_news`; +CREATE TABLE `wechat_news` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `media_id` varchar(100) DEFAULT '' COMMENT '永久素材MediaID', + `local_url` varchar(300) DEFAULT '' COMMENT '永久素材显示URL', + `article_id` varchar(60) DEFAULT '' COMMENT '关联图文ID,用,号做分割', + `is_deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建人', + PRIMARY KEY (`id`), + KEY `index_wechat_news_artcle_id` (`article_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='微信图文表'; + +-- ---------------------------- +-- Records of wechat_news +-- ---------------------------- +INSERT INTO `wechat_news` VALUES ('1', 'Bw3hChJY74VW97EwV7b79bU_PgbaoLQzuoxw0LRfTk0', '', '2,11', '0', '2018-03-06 22:07:37', '10000'); +INSERT INTO `wechat_news` VALUES ('2', '', '', '3', '0', '2018-03-12 10:24:35', '10000'); +INSERT INTO `wechat_news` VALUES ('3', '', '', '4,5,10', '0', '2018-03-12 10:24:55', '10000'); +INSERT INTO `wechat_news` VALUES ('4', 'Bw3hChJY74VW97EwV7b79ZM2QqCSN9e5d0-uucJ9Dno', '', '6,7,8,9', '0', '2018-03-12 10:25:42', '10000'); + +-- ---------------------------- +-- Table structure for wechat_news_article +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_news_article`; +CREATE TABLE `wechat_news_article` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(50) DEFAULT '' COMMENT '素材标题', + `local_url` varchar(300) DEFAULT '' COMMENT '永久素材显示URL', + `show_cover_pic` tinyint(4) unsigned DEFAULT '0' COMMENT '是否显示封面 0不显示,1 显示', + `author` varchar(20) DEFAULT '' COMMENT '作者', + `digest` varchar(300) DEFAULT '' COMMENT '摘要内容', + `content` longtext COMMENT '图文内容', + `content_source_url` varchar(200) DEFAULT '' COMMENT '图文消息原文地址', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建人', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COMMENT='微信素材表'; + +-- ---------------------------- +-- Records of wechat_news_article +-- ---------------------------- +INSERT INTO `wechat_news_article` VALUES ('2', '测试商户', 'http://api.cuci.cc/static/upload/f47b8fe06e38ae99/08e8398da45583b9.png', '0', '3412', '文章内容不能留空,请输入内容!', '<p>421341234</p>', '', '2018-03-12 10:26:17', '10000'); +INSERT INTO `wechat_news_article` VALUES ('3', '测试图文', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', '0', 'test', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:24:36', '10000'); +INSERT INTO `wechat_news_article` VALUES ('4', '测试商户', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', '0', '系统', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:25:58', '10000'); +INSERT INTO `wechat_news_article` VALUES ('5', '数码管理', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', '0', '系统', '文章内容不能留空,请输入内容!', '&lt;p&gt;文章内容不能留空,请输入内容!&lt;/p&gt;', '', '2018-03-12 10:25:58', '10000'); +INSERT INTO `wechat_news_article` VALUES ('6', '测试商户', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', '0', '系统', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:54:39', '10000'); +INSERT INTO `wechat_news_article` VALUES ('7', '测试商户', 'http://thinkagent.data.cuci.cc/static/upload/1325ee90939a1545/05e10b79fd70fa3a.jpg', '0', '系统', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:54:39', '10000'); +INSERT INTO `wechat_news_article` VALUES ('8', '测试商户2', 'http://api.cuci.cc/static/upload/f47b8fe06e38ae99/08e8398da45583b9.png', '0', '系统', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:54:39', '10000'); +INSERT INTO `wechat_news_article` VALUES ('9', '数码管理', 'http://thinkagent.data.cuci.cc/static/upload/1325ee90939a1545/05e10b79fd70fa3a.jpg', '0', '系统', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:54:39', '10000'); +INSERT INTO `wechat_news_article` VALUES ('10', '测试商户2', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', '0', '系统', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:25:58', '10000'); +INSERT INTO `wechat_news_article` VALUES ('11', '商户管理', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', '0', '系统', '文章内容不能留空,请输入内容!', '<p>文章内容不能留空,请输入内容!</p>', '', '2018-03-12 10:26:17', '10000'); + +-- ---------------------------- +-- Table structure for wechat_news_image +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_news_image`; +CREATE TABLE `wechat_news_image` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `md5` varchar(32) DEFAULT '' COMMENT '文件md5', + `local_url` varchar(300) DEFAULT '' COMMENT '本地文件链接', + `media_url` varchar(300) DEFAULT '' COMMENT '远程图片链接', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `index_wechat_news_image_md5` (`md5`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='微信服务器图片'; + +-- ---------------------------- +-- Records of wechat_news_image +-- ---------------------------- +INSERT INTO `wechat_news_image` VALUES ('2', '27a5a7e3d00175ebda58a90276cba129', 'http://api.cuci.cc/static/upload/f47b8fe06e38ae99/08e8398da45583b9.png', 'http://mmbiz.qpic.cn/mmbiz_png/nMCGwywCQYKJ9J6w993p5zaZUOwkXQaZBgicxicRu0zRfyDJicGQg2eMmiabM59NH8bYvjl8QdAoSV0YialSfdatfVQ/0', '2018-03-06 13:35:55'); + +-- ---------------------------- +-- Table structure for wechat_news_media +-- ---------------------------- +DROP TABLE IF EXISTS `wechat_news_media`; +CREATE TABLE `wechat_news_media` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `appid` varchar(100) DEFAULT '' COMMENT '公众号ID', + `md5` varchar(32) DEFAULT '' COMMENT '文件md5', + `type` varchar(20) DEFAULT '' COMMENT '媒体类型', + `media_id` varchar(100) DEFAULT '' COMMENT '永久素材MediaID', + `local_url` varchar(300) DEFAULT '' COMMENT '本地文件链接', + `media_url` varchar(300) DEFAULT '' COMMENT '远程图片链接', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT='微信素材表'; + +-- ---------------------------- +-- Records of wechat_news_media +-- ---------------------------- +INSERT INTO `wechat_news_media` VALUES ('1', 'wx60a43dd8161666d4', '27a5a7e3d00175ebda58a90276cba129', 'image', 'Bw3hChJY74VW97EwV7b79dmVQNeSJ6cdRZwS7yiTM28', 'http://api.cuci.cc/static/upload/f47b8fe06e38ae99/08e8398da45583b9.png', 'http://mmbiz.qpic.cn/mmbiz_png/nMCGwywCQYKJ9J6w993p5zaZUOwkXQaZBgicxicRu0zRfyDJicGQg2eMmiabM59NH8bYvjl8QdAoSV0YialSfdatfVQ/0?wx_fmt=png', '2018-03-06 13:52:52'); +INSERT INTO `wechat_news_media` VALUES ('28', 'wx60a43dd8161666d4', 'c9f9f81deb548d98d74096ed8a7aa758', 'image', 'Bw3hChJY74VW97EwV7b79ZwhaEkwVSoga0nSVUMFwYs', 'http://thinkagent.data.cuci.cc/static/upload/1325ee90939a1545/05e10b79fd70fa3a.jpg', 'http://mmbiz.qpic.cn/mmbiz_jpg/nMCGwywCQYKeUibetjDUAsfnuwoamicmavX8Y52qvIK2l6YUykc8QzUufYkDAK60Y47zgtTnVttgMs42TBM6UWjA/0?wx_fmt=jpeg', '2018-03-10 11:13:48'); +INSERT INTO `wechat_news_media` VALUES ('29', 'wx60a43dd8161666d4', 'cc6967059b91b21a8c4164652cfa739d', 'image', 'Bw3hChJY74VW97EwV7b79YJXRuN8mmUIhRwfLmr0SN0', 'http://thinkagent.data.cuci.cc/static/upload/aca8c68f0d1c0d24/0e2af8e0ac440157.jpg', 'http://mmbiz.qpic.cn/mmbiz_jpg/nMCGwywCQYKeUibetjDUAsfnuwoamicmav2cCibv6JyY315bSeXDAxLSMWfAJGCmFk1032lhAo8Y2KIr75odJTWibA/0?wx_fmt=jpeg', '2018-03-10 20:34:56'); diff --git a/application/.htaccess b/application/.htaccess new file mode 100644 index 000000000..3418e55a6 --- /dev/null +++ b/application/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/application/admin/controller/Auth.php b/application/admin/controller/Auth.php index 872948ea4..b9beb8adb 100644 --- a/application/admin/controller/Auth.php +++ b/application/admin/controller/Auth.php @@ -38,6 +38,11 @@ class Auth extends BasicAdmin /** * 权限列表 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function index() { @@ -48,26 +53,30 @@ class Auth extends BasicAdmin /** * 权限授权 * @return string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function apply() { + $this->title = '节点授权'; $auth_id = $this->request->get('id', '0'); $method = '_apply_' . strtolower($this->request->get('action', '0')); if (method_exists($this, $method)) { return $this->$method($auth_id); } - $this->assign('title', '节点授权'); return $this->_form($this->table, 'apply'); } /** * 读取授权节点 - * @param $auth_id + * @param string $auth */ - protected function _apply_getnode($auth_id) + protected function _apply_getnode($auth) { $nodes = NodeService::get(); - $checked = Db::name('SystemAuthNode')->where(['auth' => $auth_id])->column('node'); + $checked = Db::name('SystemAuthNode')->where(['auth' => $auth])->column('node'); foreach ($nodes as &$node) { $node['checked'] = in_array($node['node'], $checked); } @@ -77,15 +86,17 @@ class Auth extends BasicAdmin /** * 保存授权节点 - * @param $auth_id + * @param string $auth + * @throws \think\Exception + * @throws \think\exception\PDOException */ - protected function _apply_save($auth_id) + protected function _apply_save($auth) { list($data, $post) = [[], $this->request->post()]; foreach (isset($post['nodes']) ? $post['nodes'] : [] as $node) { - $data[] = ['auth' => $auth_id, 'node' => $node]; + $data[] = ['auth' => $auth, 'node' => $node]; } - Db::name('SystemAuthNode')->where(['auth' => $auth_id])->delete(); + Db::name('SystemAuthNode')->where(['auth' => $auth])->delete(); Db::name('SystemAuthNode')->insertAll($data); $this->success('节点授权更新成功!', ''); } @@ -98,9 +109,9 @@ class Auth extends BasicAdmin */ protected function _apply_filter($nodes, $level = 1) { - foreach ($nodes as $key => &$node) { + foreach ($nodes as $key => $node) { if (!empty($node['_sub_']) && is_array($node['_sub_'])) { - $node['_sub_'] = $this->_apply_filter($node['_sub_'], $level + 1); + $node[$key]['_sub_'] = $this->_apply_filter($node['_sub_'], $level + 1); } } return $nodes; @@ -108,6 +119,11 @@ class Auth extends BasicAdmin /** * 权限添加 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function add() { @@ -116,6 +132,11 @@ class Auth extends BasicAdmin /** * 权限编辑 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function edit() { @@ -124,6 +145,8 @@ class Auth extends BasicAdmin /** * 权限禁用 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function forbid() { @@ -135,6 +158,8 @@ class Auth extends BasicAdmin /** * 权限恢复 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function resume() { @@ -146,12 +171,14 @@ class Auth extends BasicAdmin /** * 权限删除 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function del() { if (DataService::update($this->table)) { - $id = $this->request->post('id'); - Db::name('SystemAuthNode')->where(['auth' => $id])->delete(); + $where = ['auth' => $this->request->post('id')]; + Db::name('SystemAuthNode')->where($where)->delete(); $this->success("权限删除成功!", ''); } $this->error("权限删除失败,请稍候再试!"); diff --git a/application/admin/controller/Config.php b/application/admin/controller/Config.php index b1feb78b1..6678b3944 100644 --- a/application/admin/controller/Config.php +++ b/application/admin/controller/Config.php @@ -38,35 +38,50 @@ class Config extends BasicAdmin * 当前页面标题 * @var string */ - public $title = '网站参数配置'; + public $title = '系统参数配置'; /** * 显示系统常规配置 + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function index() { - if (!$this->request->isPost()) { - return view('', ['title' => $this->title]); + if ($this->request->isGet()) { + return $this->fetch('', ['title' => $this->title]); } - foreach ($this->request->post() as $key => $vo) { - sysconf($key, $vo); + if ($this->request->isPost()) { + foreach ($this->request->post() as $key => $vo) { + sysconf($key, $vo); + } + LogService::write('系统管理', '系统参数配置成功'); + $this->success('系统参数配置成功!', ''); } - LogService::write('系统管理', '系统参数配置成功'); - $this->success('系统参数配置成功!', ''); } /** * 文件存储配置 + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function file() { - $alert = [ - 'type' => 'danger', - 'title' => '操作安全警告(默认使用本地服务存储)', - 'content' => '请根据实际情况配置存储引擎,合理做好站点下载分流。建议尽量使用云存储服务,同时保证文件访问协议与网站访问协议一致!' - ]; $this->title = '文件存储配置'; - $this->assign('alert', $alert); + return $this->index(); + } + + /** + * 短信参数配置 + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function sms() + { + $this->title = '短信服务配置'; + $this->assign('result', ExtendService::querySmsBalance()); return $this->index(); } diff --git a/application/admin/controller/Index.php b/application/admin/controller/Index.php index 6f8cd8f08..9bf6b29a1 100644 --- a/application/admin/controller/Index.php +++ b/application/admin/controller/Index.php @@ -18,8 +18,8 @@ use controller\BasicAdmin; use service\DataService; use service\NodeService; use service\ToolsService; +use think\App; use think\Db; -use think\View; /** * 后台入口 @@ -33,14 +33,16 @@ class Index extends BasicAdmin /** * 后台框架布局 - * @return View + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function index() { NodeService::applyAuthNode(); - $list = (array) Db::name('SystemMenu')->where(['status' => '1'])->order('sort asc,id asc')->select(); - $menus = $this->_filterMenuData(ToolsService::arr2tree($list), NodeService::get(), !!session('user')); - return view('', ['title' => '系统管理', 'menus' => $menus]); + $list = (array)Db::name('SystemMenu')->where(['status' => '1'])->order('sort asc,id asc')->select(); + $menus = $this->buildMenuData(ToolsService::arr2tree($list), NodeService::get(), !!session('user')); + return $this->fetch('', ['title' => '系统管理', 'menus' => $menus]); } /** @@ -50,10 +52,10 @@ class Index extends BasicAdmin * @param bool $isLogin 是否已经登录 * @return array */ - private function _filterMenuData($menus, $nodes, $isLogin) + private function buildMenuData($menus, $nodes, $isLogin) { foreach ($menus as $key => &$menu) { - !empty($menu['sub']) && $menu['sub'] = $this->_filterMenuData($menu['sub'], $nodes, $isLogin); + !empty($menu['sub']) && $menu['sub'] = $this->buildMenuData($menu['sub'], $nodes, $isLogin); if (!empty($menu['sub'])) { $menu['url'] = '#'; } elseif (preg_match('/^https?\:/i', $menu['url'])) { @@ -75,16 +77,26 @@ class Index extends BasicAdmin /** * 主机信息显示 - * @return View + * @return string */ public function main() { $_version = Db::query('select version() as ver'); - return view('', ['mysql_ver' => array_pop($_version)['ver'], 'title' => '后台首页']); + return $this->fetch('', [ + 'title' => '后台首页', + 'think_ver' => App::VERSION, + 'mysql_ver' => array_pop($_version)['ver'], + ]); } /** * 修改密码 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException */ public function pass() { @@ -111,6 +123,11 @@ class Index extends BasicAdmin /** * 修改资料 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function info() { diff --git a/application/admin/controller/Log.php b/application/admin/controller/Log.php index 2d9efd237..d909b9e76 100644 --- a/application/admin/controller/Log.php +++ b/application/admin/controller/Log.php @@ -37,6 +37,10 @@ class Log extends BasicAdmin /** * 日志列表 * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function index() { @@ -49,8 +53,8 @@ class Log extends BasicAdmin foreach (['action', 'content', 'username'] as $key) { (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%"); } - if (isset($get['date']) && $get['date'] !== '') { - list($start, $end) = explode('-', str_replace(' ', '', $get['date'])); + if (isset($get['create_at']) && $get['create_at'] !== '') { + list($start, $end) = explode(' - ', $get['create_at']); $db->whereBetween('create_at', ["{$start} 00:00:00", "{$end} 23:59:59"]); } return parent::_list($db); @@ -59,6 +63,7 @@ class Log extends BasicAdmin /** * 列表数据处理 * @param array $data + * @throws \Exception */ protected function _index_data_filter(&$data) { @@ -66,12 +71,38 @@ class Log extends BasicAdmin foreach ($data as &$vo) { $result = $ip->btreeSearch($vo['ip']); $vo['isp'] = isset($result['region']) ? $result['region'] : ''; - $vo['isp'] = str_replace(['|0|0|0|0', '0', '|'], ['', '', ' '], $vo['isp']); + $vo['isp'] = str_replace(['内网IP', '0', '|'], '', $vo['isp']); } } + /** + * 短信发送记录 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function sms() + { + // 日志数据库对象 + $this->title = '短信发送日志'; + $get = $this->request->get(); + $db = Db::name('MemberSmsHistory'); + foreach (['phone', 'content', 'result'] as $key) { + (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%"); + } + if (isset($get['date']) && $get['date'] !== '') { + list($start, $end) = explode(' - ', $get['date']); + $db->whereBetween('create_at', ["{$start} 00:00:00", "{$end} 23:59:59"]); + } + return parent::_list($db->order('id desc')); + } + /** * 日志删除操作 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function del() { diff --git a/application/admin/controller/Login.php b/application/admin/controller/Login.php index b4b8716ba..25b28d4a8 100644 --- a/application/admin/controller/Login.php +++ b/application/admin/controller/Login.php @@ -32,9 +32,9 @@ class Login extends BasicAdmin /** * 控制器基础方法 */ - public function _initialize() + public function initialize() { - if (session('user') && $this->request->action() !== 'out') { + if (session('user.id') && $this->request->action() !== 'out') { $this->redirect('@admin'); } } @@ -42,6 +42,11 @@ class Login extends BasicAdmin /** * 用户登录 * @return string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException */ public function index() { @@ -72,11 +77,9 @@ class Login extends BasicAdmin */ public function out() { - if (session('user')) { - LogService::write('系统管理', '用户退出系统成功'); - } - session('user', null); - session_destroy(); + session('user') && LogService::write('系统管理', '用户退出系统成功'); + !empty($_SESSION) && $_SESSION = []; + [session_unset(), session_destroy()]; $this->success('退出登录成功!', '@admin/login'); } diff --git a/application/admin/controller/Menu.php b/application/admin/controller/Menu.php index 6cce96e15..7335d27ab 100644 --- a/application/admin/controller/Menu.php +++ b/application/admin/controller/Menu.php @@ -38,10 +38,15 @@ class Menu extends BasicAdmin /** * 菜单列表 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function index() { - $this->title = '系统菜单管理'; + $this->title = '后台菜单管理'; $db = Db::name($this->table)->order('sort asc,id asc'); return parent::_list($db, false); } @@ -61,6 +66,11 @@ class Menu extends BasicAdmin /** * 添加菜单 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function add() { @@ -69,6 +79,11 @@ class Menu extends BasicAdmin /** * 编辑菜单 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function edit() { @@ -78,6 +93,9 @@ class Menu extends BasicAdmin /** * 表单数据前缀方法 * @param array $vo + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ protected function _form_filter(&$vo) { @@ -106,13 +124,18 @@ class Menu extends BasicAdmin unset($nodes[$key]); } } - $this->assign('nodes', array_column($nodes, 'node')); - $this->assign('menus', $menus); + // 设置上级菜单 + if (!isset($vo['pid']) && $this->request->get('pid', '0')) { + $vo['pid'] = $this->request->get('pid', '0'); + } + $this->assign(['nodes' => array_column($nodes, 'node'), 'menus' => $menus]); } } /** * 删除菜单 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function del() { @@ -124,6 +147,8 @@ class Menu extends BasicAdmin /** * 菜单禁用 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function forbid() { @@ -135,6 +160,8 @@ class Menu extends BasicAdmin /** * 菜单禁用 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function resume() { diff --git a/application/admin/controller/Node.php b/application/admin/controller/Node.php index 9ca6e4ec1..8995274ce 100644 --- a/application/admin/controller/Node.php +++ b/application/admin/controller/Node.php @@ -18,6 +18,7 @@ use controller\BasicAdmin; use service\DataService; use service\NodeService; use service\ToolsService; +use think\Db; /** * 系统功能节点管理 @@ -37,36 +38,47 @@ class Node extends BasicAdmin /** * 显示节点列表 + * @return string */ public function index() { - $alert = [ - 'type' => 'danger', - 'title' => '操作安全警告(默认新节点所有人可以访问,请勾选登录控制)', - 'content' => '结构为系统自动生成,其权限各选项直接影响到不同权限用户的访问及操作,请勿随意修改数据!' - ]; $nodes = ToolsService::arr2table(NodeService::get(), 'node', 'pnode'); - return view('', ['title' => '系统节点管理', 'nodes' => $nodes, 'alert' => $alert]); + return $this->fetch('', ['title' => '系统节点管理', 'nodes' => $nodes]); + } + + /** + * 清理无效的节点记录 + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function clear() + { + $nodes = array_keys(NodeService::get()); + if (false !== Db::name($this->table)->whereNotIn('node', $nodes)->delete()) { + $this->success('清理无效节点记录成功!', ''); + } + $this->error('清理无效记录失败,请稍候再试!'); } /** * 保存节点变更 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function save() { if ($this->request->isPost()) { - list($data, $post) = [[], $this->request->post()]; - if (isset($post['list'])) { - foreach ($post['list'] as $vo) { + list($post, $data) = [$this->request->post(), []]; + foreach ($post['list'] as $vo) { + if (!empty($vo['node'])) { $data['node'] = $vo['node']; $data[$vo['name']] = $vo['value']; } - !empty($data) && DataService::save($this->table, $data, 'node'); - $this->success('参数保存成功!', ''); } - } else { - $this->error('访问异常,请重新进入...'); + !empty($data) && DataService::save($this->table, $data, 'node'); + $this->success('参数保存成功!', ''); } + $this->error('访问异常,请重新进入...'); } } diff --git a/application/admin/controller/Plugs.php b/application/admin/controller/Plugs.php index 38aaf51c7..8e400be48 100644 --- a/application/admin/controller/Plugs.php +++ b/application/admin/controller/Plugs.php @@ -29,7 +29,9 @@ class Plugs extends BasicAdmin /** * 文件上传 - * @return \think\response\View + * @return mixed + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function upfile() { @@ -41,28 +43,31 @@ class Plugs extends BasicAdmin $types = $this->request->get('type', 'jpg,png'); $this->assign('mimes', FileService::getFileMine($types)); $this->assign('field', $this->request->get('field', 'file')); - return view('', ['mode' => $mode, 'types' => $types, 'uptype' => $uptype]); + return $this->fetch('', ['mode' => $mode, 'types' => $types, 'uptype' => $uptype]); } /** * 通用文件上传 * @return \think\response\Json + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws \OSS\Core\OssException */ public function upload() { $file = $this->request->file('file'); - $ext = strtolower(pathinfo($file->getInfo('name'), 4)); - $md5 = str_split($this->request->post('md5'), 16); - $filename = join('/', $md5) . ".{$ext}"; - if (!in_array($ext, explode(',', strtolower(sysconf('storage_local_exts'))))) { + if (!$file->checkExt(strtolower(sysconf('storage_local_exts')))) { return json(['code' => 'ERROR', 'msg' => '文件上传类型受限']); } + $ext = strtolower(pathinfo($file->getInfo('name'), 4)); + $names = str_split($this->request->post('md5'), 16); + $filename = "{$names[0]}/{$names[1]}.{$ext}"; // 文件上传Token验证 if ($this->request->post('token') !== md5($filename . session_id())) { return json(['code' => 'ERROR', 'msg' => '文件上传验证失败']); } // 文件上传处理 - if (($info = $file->move('static' . DS . 'upload' . DS . $md5[0], $md5[1], true))) { + if (($info = $file->move("static/upload/{$names[0]}", "{$names[1]}.{$ext}", true))) { if (($site_url = FileService::getFileUrl($filename, 'local'))) { return json(['data' => ['site_url' => $site_url], 'code' => 'SUCCESS', 'msg' => '文件上传成功']); } @@ -72,11 +77,14 @@ class Plugs extends BasicAdmin /** * 文件状态检查 + * @throws \OSS\Core\OssException + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function upstate() { $post = $this->request->post(); - $filename = join('/', str_split($post['md5'], 16)) . '.' . pathinfo($post['filename'], 4); + $filename = join('/', str_split($post['md5'], 16)) . '.' . strtolower(pathinfo($post['filename'], 4)); // 检查文件是否已上传 if (($site_url = FileService::getFileUrl($filename))) { $this->result(['site_url' => $site_url], 'IS_FOUND'); @@ -84,22 +92,22 @@ class Plugs extends BasicAdmin // 需要上传文件,生成上传配置参数 $config = ['uptype' => $post['uptype'], 'file_url' => $filename]; switch (strtolower($post['uptype'])) { - case 'qiniu': - $config['server'] = FileService::getUploadQiniuUrl(true); - $config['token'] = $this->_getQiniuToken($filename); - break; case 'local': $config['server'] = FileService::getUploadLocalUrl(); $config['token'] = md5($filename . session_id()); break; + case 'qiniu': + $config['server'] = FileService::getUploadQiniuUrl(true); + $config['token'] = $this->_getQiniuToken($filename); + break; case 'oss': $time = time() + 3600; $policyText = [ 'expiration' => date('Y-m-d', $time) . 'T' . date('H:i:s', $time) . '.000Z', 'conditions' => [['content-length-range', 0, 1048576000]], ]; - $config['policy'] = base64_encode(json_encode($policyText)); $config['server'] = FileService::getUploadOssUrl(); + $config['policy'] = base64_encode(json_encode($policyText)); $config['site_url'] = FileService::getBaseUriOss() . $filename; $config['signature'] = base64_encode(hash_hmac('sha1', $config['policy'], sysconf('storage_oss_secret'), true)); $config['OSSAccessKeyId'] = sysconf('storage_oss_keyid'); @@ -111,17 +119,18 @@ class Plugs extends BasicAdmin * 生成七牛文件上传Token * @param string $key * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ protected function _getQiniuToken($key) { - $host = sysconf('storage_qiniu_domain'); + $baseUrl = FileService::getBaseUriQiniu(); $bucket = sysconf('storage_qiniu_bucket'); $accessKey = sysconf('storage_qiniu_access_key'); $secretKey = sysconf('storage_qiniu_secret_key'); - $protocol = sysconf('storage_qiniu_is_https') ? 'https' : 'http'; $params = [ "scope" => "{$bucket}:{$key}", "deadline" => 3600 + time(), - "returnBody" => "{\"data\":{\"site_url\":\"{$protocol}://{$host}/$(key)\",\"file_url\":\"$(key)\"}, \"code\": \"SUCCESS\"}", + "returnBody" => "{\"data\":{\"site_url\":\"{$baseUrl}/$(key)\",\"file_url\":\"$(key)\"}, \"code\": \"SUCCESS\"}", ]; $data = str_replace(['+', '/'], ['-', '_'], base64_encode(json_encode($params))); return $accessKey . ':' . str_replace(['+', '/'], ['-', '_'], base64_encode(hash_hmac('sha1', $data, $secretKey, true))) . ':' . $data; @@ -134,7 +143,7 @@ class Plugs extends BasicAdmin public function icon() { $field = $this->request->get('field', 'icon'); - return view('', ['field' => $field]); + return $this->fetch('', ['field' => $field]); } } diff --git a/application/admin/controller/User.php b/application/admin/controller/User.php index 320edcae6..407648783 100644 --- a/application/admin/controller/User.php +++ b/application/admin/controller/User.php @@ -36,25 +36,33 @@ class User extends BasicAdmin /** * 用户列表 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function index() { $this->title = '系统用户管理'; - $get = $this->request->get(); - $db = Db::name($this->table)->where(['is_deleted' => '0']); + list($get, $db) = [$this->request->get(), Db::name($this->table)]; foreach (['username', 'phone', 'mail'] as $key) { (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%"); } if (isset($get['date']) && $get['date'] !== '') { - list($start, $end) = explode('-', str_replace(' ', '', $get['date'])); + list($start, $end) = explode(' - ', $get['date']); $db->whereBetween('login_at', ["{$start} 00:00:00", "{$end} 23:59:59"]); } - return parent::_list($db); + return parent::_list($db->where(['is_deleted' => '0'])); } /** * 授权管理 * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function auth() { @@ -63,6 +71,11 @@ class User extends BasicAdmin /** * 用户添加 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function add() { @@ -71,6 +84,11 @@ class User extends BasicAdmin /** * 用户编辑 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function edit() { @@ -79,6 +97,12 @@ class User extends BasicAdmin /** * 用户密码修改 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException */ public function pass() { @@ -100,6 +124,9 @@ class User extends BasicAdmin /** * 表单数据默认处理 * @param array $data + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function _form_filter(&$data) { @@ -120,6 +147,8 @@ class User extends BasicAdmin /** * 删除用户 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function del() { @@ -134,6 +163,8 @@ class User extends BasicAdmin /** * 用户禁用 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function forbid() { @@ -148,6 +179,8 @@ class User extends BasicAdmin /** * 用户禁用 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function resume() { diff --git a/application/admin/view/auth.index.html b/application/admin/view/auth.index.html deleted file mode 100644 index 36c62a8ba..000000000 --- a/application/admin/view/auth.index.html +++ /dev/null @@ -1,92 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="button"} -
- - -
-{/block} - -{block name="content"} -
- {if empty($list)} -

没 有 记 录 哦!

- {else} - - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - - {/foreach} - -
- - - - 权限名称权限描述状态添加时间操作
- - - - {$vo.title}{$vo.desc|default="没有写描述哦!"} - {if $vo.status eq 0} - 已禁用 - {elseif $vo.status eq 1} - 使用中 - {/if} - {$vo.create_at|format_datetime} - - {if auth("$classuri/edit")} - | - 编辑 - {/if} - - {if auth("$classuri/apply")} - | - 授权 - {/if} - - {if $vo.status eq 1 and auth("$classuri/forbid")} - | - 禁用 - {elseif auth("$classuri/resume")} - | - 启用 - {/if} - - {if auth("$classuri/del")} - | - 删除 - {/if} - -
- {if isset($page)}

{$page}

{/if} - {/if} -
-{/block} \ No newline at end of file diff --git a/application/admin/view/auth.apply.html b/application/admin/view/auth/apply.html similarity index 67% rename from application/admin/view/auth.apply.html rename to application/admin/view/auth/apply.html index 30ae678ab..122881af6 100644 --- a/application/admin/view/auth.apply.html +++ b/application/admin/view/auth/apply.html @@ -1,36 +1,22 @@ -{extend name='extra@admin/content'} +{extend name='admin@public/content'} {block name="content"} - - - -
-
+{/block} +{block name="script"} + + +{/block} +{block name="style"} + {/block} \ No newline at end of file diff --git a/application/admin/view/auth.form.html b/application/admin/view/auth/form.html similarity index 93% rename from application/admin/view/auth.form.html rename to application/admin/view/auth/form.html index ec94c8856..5ca12242d 100644 --- a/application/admin/view/auth.form.html +++ b/application/admin/view/auth/form.html @@ -1,4 +1,4 @@ -
+
@@ -17,7 +17,7 @@
- {if isset($vo['id'])}{/if} + {if isset($vo['id'])}{/if}
diff --git a/application/admin/view/auth/index.html b/application/admin/view/auth/index.html new file mode 100644 index 000000000..d6891369b --- /dev/null +++ b/application/admin/view/auth/index.html @@ -0,0 +1,82 @@ +{extend name='admin@public/content'} + +{block name="button"} + + + + + + +{/block} + +{block name="content"} + + +

没 有 记 录 哦!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 名称描述状态添加时间
+ + + + {$vo.title}{$vo.desc|default="没有写描述哦!"} + {if $vo.status eq 0}已禁用{elseif $vo.status eq 1}使用中{/if} + {$vo.create_at|format_datetime} + + {if auth("$classuri/edit")} + | + 编辑 + {/if} + + {if auth("$classuri/apply")} + | + 授权 + {/if} + + {if $vo.status eq 1 and auth("$classuri/forbid")} + | + 禁用 + {elseif auth("$classuri/resume")} + | + 启用 + {/if} + + {if auth("$classuri/del")} + | + 删除 + {/if} + +
+ {if isset($page)}

{$page|raw}

{/if} + + +{/block} \ No newline at end of file diff --git a/application/admin/view/config.file.html b/application/admin/view/config.file.html deleted file mode 100644 index 9cba66d01..000000000 --- a/application/admin/view/config.file.html +++ /dev/null @@ -1,244 +0,0 @@ -{extend name="extra@admin/content"} - -{block name="content"} -
- -
- -
- - - -
- 文件将存储在本地服务器,请确保服务器的 ./static/upload 目录有写入权限 -
-
- 若还没有七牛云帐号,请点击 - 免费申请10G存储空间, - 申请成功后添加公开bucket空间 -
-
- 若还没有AliOSS存储账号, 请点击 创建AliOSS存储空间, - 目前仅支持公开空间URL访问, 另外还需要配置AliOSS跨域策略 -
-
-
- -
-
- -
- -
- - - - -

七牛云存储空间所在区域,需要严格对应储存所在区域才能上传文件

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

七牛云资源访问协议(http 或 https),https 需要配置证书才能使用

-
-
- -
- -
- -

设置系统允许上传文件的后缀,多个以英文逗号隔开。如:png,jpg,rar,doc

-
-
- -
- -
- -

填写七牛云存储空间名称,如:static

-
-
- -
- -
- -

填写七牛云存储访问域名,如:static.ctolog.cc

-
-
- -
- -
- -

可以在 [ 七牛云 > 个人中心 ] 设置并获取到访问密钥

-
-
- - -
- -
- -

可以在 [ 七牛云 > 个人中心 ] 设置并获取到安全密钥

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

AliOSS资源访问协议(http 或 https),https 需要配置证书才能使用

-
-
- -
- -
- -

填写OSS存储空间名称,如:static

-
-
- -
- -
- -

填写OSS存储外部访问域名,如:static.ctolog.cc

-
-
- -
- -
- -

可以在 [ 阿里云 > 个人中心 ] 设置并获取到访问密钥

-
-
- - -
- -
- -

可以在 [ 阿里云 > 个人中心 ] 设置并获取到安全密钥

-
-
- -
- -
-
- -
-
- -
-{/block} - -{block name="script"} - -{/block} \ No newline at end of file diff --git a/application/admin/view/config.index.html b/application/admin/view/config.index.html deleted file mode 100644 index 0141514b0..000000000 --- a/application/admin/view/config.index.html +++ /dev/null @@ -1,68 +0,0 @@ -{extend name="extra@admin/content"} - -{block name="content"} -
- -
- -
- -

网站名称,显示在浏览器标签上

-
-
- -
- -
- -

程序的版权信息设置,在后台登录页面显示

-
-
- -
- -
- -

当前程序名称,在后台主标题上显示

-
-
- - -
- -
- -

当前程序版本号,在后台主标题上标显示

-
-
- -
- -
- -

百度统计应用ID,可以在百度网站统计申请并获取

-
-
- -
- -
- - - 上传图片 -

建议上传ICO图标的尺寸为128x128px,此图标用于网站标题前,ICON在线制作。

-
-
- -
- -
-
- -
-
- -
- -{/block} \ No newline at end of file diff --git a/application/admin/view/config/file.html b/application/admin/view/config/file.html new file mode 100644 index 000000000..584a33f19 --- /dev/null +++ b/application/admin/view/config/file.html @@ -0,0 +1,228 @@ +{extend name="admin@public/content"} + +{block name="content"} +
+ +
+ +
+ {foreach ['local'=>'本地服务器存储','qiniu'=>'七牛云存储','oss'=>'阿里云OSS存储'] as $k=>$v} + + {/foreach} +
+ 文件存储在本地服务器,请确保服务器的 ./static/upload/ 目录有写入权限 +
+
+ 若还没有七牛云帐号,可免费申请10G存储,申请成功后添加公开bucket。 +
+
+ 若还没有OSS存储账号, 可创建阿里云OSS存储,需要配置OSS公开访问及跨域策略。 +
+
+
+ +
+ +
+ +
+ +

设置系统允许上传文件的后缀,多个以英文逗号隔开。如:png,jpg,rar,doc

+
+
+ +
+ +
+ {foreach ['华东','华北','华南','北美'] as $area} + + {/foreach} +

七牛云存储空间所在区域,需要严格对应储存所在区域才能上传文件。

+
+
+ +
+ +
+ + + +

七牛云存储访问协议(http、https、auto),其中 https 需要配置证书才能使用,auto 为相对协议自动根据域名切换http与https。

+
+
+ +
+ +
+ +

填写七牛云存储空间名称,如:static

+
+
+ +
+ +
+ +

填写七牛云存储访问域名,如:static.ctolog.cc

+
+
+ +
+ +
+ +

可以在 [ 七牛云 > 个人中心 ] 设置并获取到访问密钥。

+
+
+ +
+ +
+ +

可以在 [ 七牛云 > 个人中心 ] 设置并获取到安全密钥。

+
+
+ +
+ +
+ + + +

阿里云对象存储访问协议(http、https、auto),其中 https 需要配置证书才能使用,auto 为相对协议自动根据域名切换http与https。

+
+
+ +
+ +
+ +

填写OSS存储空间名称,如:think-admin-oss

+
+
+ +
+ +
+ +

填写OSS数据中心访问域名,如:oss-cn-shenzhen.aliyuncs.com

+
+
+ +
+ +
+ +

填写OSS存储外部访问域名,如:think-admin-oss.oss-cn-shenzhen.aliyuncs.com

+
+
+ +
+ +
+ +

可以在 [ 阿里云 > 个人中心 ] 设置并获取到访问密钥。

+
+
+ +
+ +
+ +

可以在 [ 阿里云 > 个人中心 ] 设置并获取到安全密钥。

+
+
+ + +
+ +
+
+ +
+
+ +
+{/block} + +{block name="script"} + +{/block} \ No newline at end of file diff --git a/application/admin/view/config/index.html b/application/admin/view/config/index.html new file mode 100644 index 000000000..9aec3838a --- /dev/null +++ b/application/admin/view/config/index.html @@ -0,0 +1,82 @@ +{extend name="admin@public/content"} + +{block name="content"} +
+ +
+ +
+ +

当前程序名称,在后台主标题上显示

+
+
+ +
+ +
+ +

当前程序版本号,在后台主标题上标显示

+
+
+ +
+ +
+ +
+ +

网站名称,显示在浏览器标签上

+
+
+ +
+ +
+ +

程序的版权信息设置,在后台登录页面显示

+
+
+ +
+ +
+ +
+ + + 上传图片 +

建议上传ICO图标的尺寸为128x128px,此图标用于网站标题前,ICON在线制作

+
+
+ +
+ +
+ +

网站备案号,可以在备案管理中心查询获取

+
+
+ +
+ +
+
+ +
+
+ +
+ +{/block} \ No newline at end of file diff --git a/application/admin/view/config/sms.html b/application/admin/view/config/sms.html new file mode 100644 index 000000000..366b51382 --- /dev/null +++ b/application/admin/view/config/sms.html @@ -0,0 +1,41 @@ +{extend name="admin@public/content"} + +{block name="content"} +
+ +
+ +
+ +

肋通SMS接口状态,若短信剩余条数不足时,请提前充值。

+
+
+ +
+ +
+ +

助通SMS接口平台账号,平台管理 登录平台

+
+
+ +
+ +
+ +

助通SMS接口平台密码,需要在助通平台修改。

+
+
+ +
+ +
+
+ +
+
+ +
+{/block} diff --git a/application/admin/view/index.index.html b/application/admin/view/index.index.html deleted file mode 100644 index 871102d24..000000000 --- a/application/admin/view/index.index.html +++ /dev/null @@ -1,9 +0,0 @@ -{extend name="extra@admin/main"} - -{block name='body'} -{include file="extra@admin/main/top"} -
- {include file="extra@admin/main/left"} -
-
-{/block} \ No newline at end of file diff --git a/application/admin/view/index.main.html b/application/admin/view/index.main.html deleted file mode 100644 index 7a723dcaa..000000000 --- a/application/admin/view/index.main.html +++ /dev/null @@ -1,115 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="content"} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
系统信息
ThinkAdmin 版本{:sysconf('app_version')}
ThinkPHP 版本{$Think.const.THINK_VERSION}
服务器操作系统{:php_uname('s')}
WEB运行环境{:php_sapi_name()}
MySQL数据库版本{$mysql_ver}
运行PHP版本{:phpversion()}
上传大小限制{:ini_get('upload_max_filesize')}
POST大小限制{:ini_get('post_max_size')}
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
产品团队
产品名称ThinkAdmin 管理框架
产品研发团队广州楚才信息科技有限公司
产品DEMO体验 - https://think.ctolog.com -
官方QQ群 - - - -
BUG反馈 - - https://github.com/zoujingli/ThinkAdmin/issues - -
项目地址 - - https://github.com/zoujingli/ThinkAdmin - -
公司官网http://www.cuci.cc
公司地址广东省 广州市 海珠区 世港国际公寓E1栋
-
-
-{/block} \ No newline at end of file diff --git a/application/admin/view/index/index.html b/application/admin/view/index/index.html new file mode 100644 index 000000000..f0922dac4 --- /dev/null +++ b/application/admin/view/index/index.html @@ -0,0 +1,83 @@ +{extend name="admin@public/main"} + +{block name='body'} + + + + + + +
+ {foreach $menus as $oneMenu} + + + + {/foreach} +
+ + + +
+ +{/block} \ No newline at end of file diff --git a/application/admin/view/index/main.html b/application/admin/view/index/main.html new file mode 100644 index 000000000..abd045bdc --- /dev/null +++ b/application/admin/view/index/main.html @@ -0,0 +1,145 @@ +{extend name='admin@public/content'} + +{block name="content"} + +
    +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1','is_black'=>'0'])->count()}

    +
  • +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1'])->count()}

    +
  • +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1'])->count()}

    +
  • +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1'])->count()}

    +
  • +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1'])->count()}

    +
  • +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1'])->count()}

    +
  • +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1'])->count()}

    +
  • +
  • +

    微信粉丝统计

    +

    {:db('WechatFans')->cache(true,60)->where(['subscribe'=>'1'])->count()}

    +
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
系统信息产品团队
ThinkAdmin 版本{:sysconf('app_version')}产品名称ThinkAdmin 管理框架
ThinkPHP 版本{$think_ver}产品名称ThinkAdmin 管理框架
服务器操作系统{:php_uname('s')}产品DEMO体验 + http://service.thinkadmin.top +
WEB运行环境{:php_sapi_name()}官方QQ群 + + + +
MySQL数据库版本{$mysql_ver}BUG反馈 + + https://github.com/zoujingli/ThinkAdmin/issues + +
运行PHP版本{$Think.PHP_VERSION}项目地址 + + https://github.com/zoujingli/ThinkAdmin + +
上传大小限制{:ini_get('upload_max_filesize')}公司官网http://www.cuci.cc
POST大小限制{:ini_get('post_max_size')}公司地址广州市天河区东圃一横路东泷商贸中心C座316
+{/block} + +{block name='style'} + +{/block} \ No newline at end of file diff --git a/application/admin/view/log.index.html b/application/admin/view/log/index.html similarity index 52% rename from application/admin/view/log.index.html rename to application/admin/view/log/index.html index 6124668b0..55813eee8 100644 --- a/application/admin/view/log.index.html +++ b/application/admin/view/log/index.html @@ -1,99 +1,96 @@ -{extend name='extra@admin/content'} - -{block name="button"} -
- {if auth("$classuri/del")} - - {/if} -
-{/block} - -{block name="content"} - - - - - -
- - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - - {/foreach} - -
- - 操作者节点行为操作内容操作位置操作时间
- - {$vo.username}{$vo.node}{$vo.action}{$vo.content}{$vo.isp|default=$vo.ip}{$vo.create_at|format_datetime}
- {if isset($page)}

{$page}

{/if} - -
+{extend name='admin@public/content'} + +{block name="button"} + + + +{/block} + +{block name="content"} + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + 操作账号权限节点操作行为操作内容操作位置操作时间
+ + {$vo.username}{$vo.node}{$vo.action}{$vo.content}{$vo.isp|default=$vo.ip}{$vo.create_at|format_datetime}
+ {if isset($page)}

{$page|raw}

{/if} +
{/block} \ No newline at end of file diff --git a/application/admin/view/log/sms.html b/application/admin/view/log/sms.html new file mode 100644 index 000000000..6d99c46d3 --- /dev/null +++ b/application/admin/view/log/sms.html @@ -0,0 +1,76 @@ +{extend name='admin@public/content'} + +{block name="content"} + + + + + +
+ + + + + + + + + + + + + {foreach $list as $key=>$vo} + + + + + + + + {/foreach} + +
+ + 手机号短信内容返回结果发送时间
+ + {$vo.phone}{$vo.content}{$vo.result}{$vo.create_at|format_datetime}
+ {if isset($page)}

{$page}

{/if} + +
+{/block} \ No newline at end of file diff --git a/application/admin/view/login.index.html b/application/admin/view/login/index.html similarity index 52% rename from application/admin/view/login.index.html rename to application/admin/view/login/index.html index bac77df8c..42c4069a8 100644 --- a/application/admin/view/login.index.html +++ b/application/admin/view/login/index.html @@ -1,89 +1,87 @@ -{extend name="extra@admin/main"} - -{block name="style"} - -{/block} - -{block name="body"} - -{/block} - -{block name="script"} - +{extend name="admin@public/main"} + +{block name="style"} + +{/block} + +{block name="body"} + +{/block} + +{block name="script"} + {/block} \ No newline at end of file diff --git a/application/admin/view/menu.index.html b/application/admin/view/menu.index.html deleted file mode 100644 index d8e0be33b..000000000 --- a/application/admin/view/menu.index.html +++ /dev/null @@ -1,80 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="button"} -
- - -
-{/block} - -{block name="content"} - -
- - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - - {/foreach} - -
- - - - 菜单名称菜单链接状态操作
- - - - - - {$vo.spl}{$vo.title}{$vo.url} - {if $vo.status eq 0} - 已禁用 - {elseif $vo.status eq 1} - 使用中 - {/if} - - {if auth("$classuri/edit")} - | - 编辑 - {/if} - {if $vo.status eq 1 and auth("$classuri/forbid")} - | - 禁用 - {elseif auth("$classuri/resume")} - | - 启用 - {/if} - {if auth("$classuri/del")} - | - 删除 - {/if} -
-
-{/block} \ No newline at end of file diff --git a/application/admin/view/menu.form.html b/application/admin/view/menu/form.html similarity index 85% rename from application/admin/view/menu.form.html rename to application/admin/view/menu/form.html index dd5ec3121..86196980f 100644 --- a/application/admin/view/menu.form.html +++ b/application/admin/view/menu/form.html @@ -1,14 +1,14 @@ -
+
- - + - + @@ -34,7 +34,7 @@
- + @@ -43,14 +43,13 @@
- {if isset($vo['id'])}{/if} + {if isset($vo['id'])}{/if}
- diff --git a/application/admin/view/menu/index.html b/application/admin/view/menu/index.html new file mode 100644 index 000000000..863fe5336 --- /dev/null +++ b/application/admin/view/menu/index.html @@ -0,0 +1,87 @@ +{extend name='admin@public/content'} + +{block name="button"} + + + + + + +{/block} + +{block name="content"} + + +

没 有 记 录 哦!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + {$vo.spl|raw}{$vo.title}{$vo.url} + {if $vo.status eq 0}已禁用{elseif $vo.status eq 1}使用中{/if} + + + {if auth("$classuri/add")} + | + + 添加下级 + + 添加下级 + + {/if} + + {if auth("$classuri/edit")} + | + 编辑 + {/if} + + {if $vo.status eq 1 and auth("$classuri/forbid")} + | + 禁用 + {elseif auth("$classuri/resume")} + | + 启用 + {/if} + + {if auth("$classuri/del")} + | + 删除 + {/if} + +
+ + +{/block} \ No newline at end of file diff --git a/application/admin/view/node.index.html b/application/admin/view/node.index.html deleted file mode 100644 index 9cb61f0bb..000000000 --- a/application/admin/view/node.index.html +++ /dev/null @@ -1,106 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="content"} - - - - - - - - - - - {foreach $nodes as $key=>$vo} - - - - - - - {/foreach} - -
系统节点结构 
- {$vo.spl}{$vo.node} - {if auth("$classuri/save")} -   - {/if} - - {if auth("$classuri/save")} - -      - -      - - {/if} -
-{if auth("$classuri/save")} - -{/if} -{/block} \ No newline at end of file diff --git a/application/admin/view/node/index.html b/application/admin/view/node/index.html new file mode 100644 index 000000000..671d68bc7 --- /dev/null +++ b/application/admin/view/node/index.html @@ -0,0 +1,208 @@ +{extend name='admin@public/content'} + +{block name='button'} + + + +{/block} + +{block name="content"} + + + + + + +

没 有 记 录 哦!

+ + + + + + + + + +
+ {$vo.spl|raw} {$vo.node} + {if auth("$classuri/save")} {/if} + + {if auth("$classuri/save") and $vo.spt eq 1} + +      + +      + + {/if} + {if auth("$classuri/save") and $vo.spt eq 2} +  ├─ + +       ├─ + +       ├─ + + {/if} +
+ + + +{/block} \ No newline at end of file diff --git a/application/admin/view/plugs.upfile.html b/application/admin/view/plugs.upfile.html deleted file mode 100644 index c864145bc..000000000 --- a/application/admin/view/plugs.upfile.html +++ /dev/null @@ -1,555 +0,0 @@ - - - - {:sysconf('app_name')} {:sysconf('app_version')} - - - - - - - - - - - -
-
-
-
-
-
- -
- - - diff --git a/application/admin/view/plugs.icon.html b/application/admin/view/plugs/icon.html similarity index 64% rename from application/admin/view/plugs.icon.html rename to application/admin/view/plugs/icon.html index 8d95944bc..01fdff8a2 100644 --- a/application/admin/view/plugs.icon.html +++ b/application/admin/view/plugs/icon.html @@ -1,7 +1,7 @@ - {'app_name'|sysconf} {'app_version'|sysconf} + {:sysconf('app_name')} {:sysconf('app_version')} @@ -847,1058 +847,6 @@
  • fa fa-stethoscope
  • fa fa-user-md
  • fa fa-wheelchair
  • -
  • - - 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/application/admin/view/plugs/upfile.html b/application/admin/view/plugs/upfile.html new file mode 100644 index 000000000..3f4f80530 --- /dev/null +++ b/application/admin/view/plugs/upfile.html @@ -0,0 +1,555 @@ + + + + {:sysconf('app_name')} {:sysconf('app_version')} + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/application/admin/view/public/content.html b/application/admin/view/public/content.html new file mode 100644 index 000000000..26b5227f6 --- /dev/null +++ b/application/admin/view/public/content.html @@ -0,0 +1,13 @@ + +{block name="style"}{/block} +
    + +
    +
    {$title}
    +
    {block name="button"}{/block}
    +
    + +
    {block name='content'}{/block}
    +
    +{block name='script'}{/block} + \ No newline at end of file diff --git a/application/admin/view/public/main.html b/application/admin/view/public/main.html new file mode 100644 index 000000000..e24ce4287 --- /dev/null +++ b/application/admin/view/public/main.html @@ -0,0 +1,26 @@ + + + + + + {block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block} + + + + + + + + {block name="style"}{/block} + + + + + + + {block name="body"}{/block} + + + {block name="script"}{/block} + + \ No newline at end of file diff --git a/application/admin/view/user.index.html b/application/admin/view/user.index.html deleted file mode 100644 index f9a70eea0..000000000 --- a/application/admin/view/user.index.html +++ /dev/null @@ -1,139 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="button"} -
    - - -
    -{/block} - -{block name="content"} - - - - - -
    - {if empty($list)} -

    没 有 记 录 哦!

    - {else} - - - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - - - {/foreach} - -
    - - 用户名手机号电子邮箱登录次数最后登录状态操作
    - - - {$vo.username} - - {$vo.phone|default="还没有设置手机号"} - - {$vo.mail|default="还没有设置邮箱"} - - {$vo.login_num|default="从未登录"} - - {$vo.login_at|format_datetime|default="从未登录"} - - {if $vo.status eq 0} - 已禁用 - {elseif $vo.status eq 1} - 使用中 - {/if} - - {if auth("$classuri/edit")} - | - 编辑 - {/if} - {if auth("$classuri/auth")} - | - 授权 - {/if} - {if auth("$classuri/pass")} - | - 密码 - {/if} - {if $vo.status eq 1 and auth("$classuri/forbid")} - | - 禁用 - {elseif auth("$classuri/resume")} - | - 启用 - {/if} - {if auth("$classuri/del")} - | - 删除 - {/if} -
    - {if isset($page)}

    {$page}

    {/if} - {/if} - -
    -{/block} \ No newline at end of file diff --git a/application/admin/view/user.auth.html b/application/admin/view/user/auth.html similarity index 65% rename from application/admin/view/user.auth.html rename to application/admin/view/user/auth.html index 1b4574210..d448eccee 100644 --- a/application/admin/view/user.auth.html +++ b/application/admin/view/user/auth.html @@ -1,44 +1,39 @@ -
    - -
    - -
    - {if $vo and $vo.username} - - {else} - - {/if} -
    -
    - -
    - -
    - {foreach $authorizes as $authorize} - {if in_array($authorize['id'],$vo['authorize'])} - - {else} - - {/if} - {/foreach} - {if empty($authorizes)} - 未配置权限 - {/if} -
    -
    - -
    - -
    - {if isset($vo['id'])}{/if} - - -
    - -
    +
    + +
    + +
    + {if $vo and $vo.username} + + {else} + + {/if} +
    +
    + +
    + +
    + {foreach $authorizes as $authorize} + + {/foreach} + {if empty($authorizes)}未配置权限{/if} +
    +
    + +
    + +
    + {if isset($vo['id'])}{/if} + + +
    + + +
    diff --git a/application/admin/view/user.form.html b/application/admin/view/user/form.html similarity index 62% rename from application/admin/view/user.form.html rename to application/admin/view/user/form.html index 66e3f5e91..00d5a90e8 100644 --- a/application/admin/view/user.form.html +++ b/application/admin/view/user/form.html @@ -1,70 +1,61 @@ -
    - -
    - -
    - {if $vo and isset($vo.username)} - - {else} - - {/if} -
    -
    - -
    - -
    - -
    -
    - -
    - -
    - -
    -
    - - {if isset($authorizes)} -
    - -
    - {foreach $authorizes as $authorize} - {if in_array($authorize['id'],$vo['authorize'])} - - {else} - - {/if} - {/foreach} - {if empty($authorizes)} - 未配置权限 - {/if} -
    -
    - {/if} - -
    - -
    - -
    -
    - -
    - -
    - {if isset($vo['id'])}{/if} - - -
    - -
    +
    + +
    + +
    + {if $vo and isset($vo.username)} + + {else} + + {/if} +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + + {if isset($authorizes)} +
    + +
    + {foreach $authorizes as $authorize} + + {/foreach} + {if empty($authorizes)}未配置权限{/if} +
    +
    + {/if} + +
    + +
    + +
    +
    + +
    + +
    + {if isset($vo['id'])}{/if} + + +
    + +
    diff --git a/application/admin/view/user/index.html b/application/admin/view/user/index.html new file mode 100644 index 000000000..63962e01e --- /dev/null +++ b/application/admin/view/user/index.html @@ -0,0 +1,137 @@ +{extend name='admin@public/content'} + +{block name="button"} + + + + + + +{/block} + +{block name="content"} + + + + + + +
    + +

    没 有 记 录 哦!

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + 用户名手机号电子邮箱登录次数最后登录状态
    + + + {$vo.username} + + {$vo.phone|default="还没有设置手机号"|raw} + + {$vo.mail|default="还没有设置邮箱"|raw} + + {$vo.login_num|default="从未登录"|raw} + + {$vo.login_at|format_datetime|default="从未登录"|raw} + + {if $vo.status eq 0}已禁用{elseif $vo.status eq 1}使用中{/if} + + + {if auth("$classuri/edit")} + | + 编辑 + {/if} + + {if auth("$classuri/auth")} + | + 授权 + {/if} + + {if auth("$classuri/pass")} + | + 密码 + {/if} + + {if $vo.status eq 1 and auth("$classuri/forbid")} + | + 禁用 + {elseif auth("$classuri/resume")} + | + 启用 + {/if} + + {if auth("$classuri/del")} + | + 删除 + {/if} + +
    + {if isset($page)}

    {$page|raw}

    {/if} + +
    +{/block} \ No newline at end of file diff --git a/application/admin/view/user.pass.html b/application/admin/view/user/pass.html similarity index 54% rename from application/admin/view/user.pass.html rename to application/admin/view/user/pass.html index 0184e87bb..fb294646d 100644 --- a/application/admin/view/user.pass.html +++ b/application/admin/view/user/pass.html @@ -1,50 +1,45 @@ -
    - -
    - -
    - {if $vo and $vo.username} - - {else} - - {/if} -
    -
    - - {if $verify} -
    - -
    - -
    -
    - {/if} - -
    - -
    - -
    -
    - -
    - -
    - -
    -
    - -
    - -
    - {if isset($vo['id'])}{/if} - - -
    - -
    +
    + +
    + +
    + {if $vo and $vo.username} + + {else} + + {/if} +
    +
    + + +
    + +
    + +
    +
    + + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + {if isset($vo['id'])}{/if} + + +
    + +
    diff --git a/application/common.php b/application/common.php index b3ce16659..94ce94fae 100644 --- a/application/common.php +++ b/application/common.php @@ -12,63 +12,63 @@ // | github开源项目:https://github.com/zoujingli/ThinkAdmin // +---------------------------------------------------------------------- - use service\DataService; -use service\FileService; use service\NodeService; -use service\SoapService; use think\Db; -use Wechat\Loader; /** * 打印输出数据到文件 - * @param mixed $data - * @param bool $replace - * @param string|null $pathname + * @param mixed $data 输出的数据 + * @param bool $force 强制替换 + * @param string|null $file */ -function p($data, $replace = false, $pathname = null) +function p($data, $force = false, $file = null) { - is_null($pathname) && $pathname = RUNTIME_PATH . date('Ymd') . '.txt'; - $str = (is_string($data) ? $data : (is_array($data) || is_object($data)) ? print_r($data, true) : var_export($data, true)) . "\n"; - $replace ? file_put_contents($pathname, $str) : file_put_contents($pathname, $str, FILE_APPEND); + is_null($file) && $file = env('runtime_path') . date('Ymd') . '.txt'; + $str = (is_string($data) ? $data : (is_array($data) || is_object($data)) ? print_r($data, true) : var_export($data, true)) . PHP_EOL; + $force ? file_put_contents($file, $str) : file_put_contents($file, $str, FILE_APPEND); } /** - * 获取mongoDB连接 - * @param string $col 数据库集合 - * @param bool $force 是否强制连接 - * @return \think\db\Query|\think\mongo\Query + * RBAC节点权限验证 + * @param string $node + * @return bool */ -function mongo($col, $force = false) +function auth($node) { - return Db::connect(config('mongo'), $force)->name($col); + return NodeService::checkAuthNode($node); } /** - * 获取微信操作对象 - * @param string $type - * @return \Wechat\WechatMedia|\Wechat\WechatMenu|\Wechat\WechatOauth|\Wechat\WechatPay|\Wechat\WechatReceive|\Wechat\WechatScript|\Wechat\WechatUser|\Wechat\WechatExtends|\Wechat\WechatMessage - * @throws Exception + * 设备或配置系统参数 + * @param string $name 参数名称 + * @param bool $value 默认是null为获取值,否则为更新 + * @return string|bool + * @throws \think\Exception + * @throws \think\exception\PDOException */ -function & load_wechat($type = '') +function sysconf($name, $value = null) { - static $wechat = []; - $index = md5(strtolower($type)); - if (!isset($wechat[$index])) { - $config = [ - 'token' => sysconf('wechat_token'), - 'appid' => sysconf('wechat_appid'), - 'appsecret' => sysconf('wechat_appsecret'), - 'encodingaeskey' => sysconf('wechat_encodingaeskey'), - 'mch_id' => sysconf('wechat_mch_id'), - 'partnerkey' => sysconf('wechat_partnerkey'), - 'ssl_cer' => sysconf('wechat_cert_cert'), - 'ssl_key' => sysconf('wechat_cert_key'), - 'cachepath' => CACHE_PATH . 'wxpay' . DS, - ]; - $wechat[$index] = Loader::get($type, $config); + static $config = []; + if ($value !== null) { + list($config, $data) = [[], ['name' => $name, 'value' => $value]]; + return DataService::save('SystemConfig', $data, 'name'); } - return $wechat[$index]; + if (empty($config)) { + $config = Db::name('SystemConfig')->column('name,value'); + } + return isset($config[$name]) ? $config[$name] : ''; +} + +/** + * 日期格式标准输出 + * @param string $datetime 输入日期 + * @param string $format 输出格式 + * @return false|string + */ +function format_datetime($datetime, $format = 'Y年m月d日 H:i:s') +{ + return date($format, strtotime($datetime)); } /** @@ -100,74 +100,11 @@ function decode($string) } /** - * 网络图片本地化 - * @param string $url + * 下载远程文件到本地 + * @param string $url 远程图片地址 * @return string */ function local_image($url) { - if (is_array(($result = FileService::download($url)))) { - return $result['url']; - } - return $url; + return \service\FileService::download($url)['url']; } - -/** - * 日期格式化 - * @param string $date 标准日期格式 - * @param string $format 输出格式化date - * @return false|string - */ -function format_datetime($date, $format = 'Y年m月d日 H:i:s') -{ - return empty($date) ? '' : date($format, strtotime($date)); -} - -/** - * 设备或配置系统参数 - * @param string $name 参数名称 - * @param bool $value 默认是null为获取值,否则为更新 - * @return string|bool - */ -function sysconf($name, $value = null) -{ - static $config = []; - if ($value !== null) { - list($config, $data) = [[], ['name' => $name, 'value' => $value]]; - return DataService::save('SystemConfig', $data, 'name'); - } - if (empty($config)) { - $config = Db::name('SystemConfig')->column('name,value'); - } - return isset($config[$name]) ? $config[$name] : ''; -} - -/** - * RBAC节点权限验证 - * @param string $node - * @return bool - */ -function auth($node) -{ - return NodeService::checkAuthNode($node); -} - -/** - * array_column 函数兼容 - */ -if (!function_exists("array_column")) { - - function array_column(array &$rows, $column_key, $index_key = null) - { - $data = []; - foreach ($rows as $row) { - if (empty($index_key)) { - $data[] = $row[$column_key]; - } else { - $data[$row[$index_key]] = $row[$column_key]; - } - } - return $data; - } - -} \ No newline at end of file diff --git a/application/config.php b/application/config.php deleted file mode 100644 index 5b2143bac..000000000 --- a/application/config.php +++ /dev/null @@ -1,224 +0,0 @@ - 'app', - // 应用调试模式 - 'app_debug' => true, - // 应用Trace - 'app_trace' => false, - // 应用模式状态 - 'app_status' => '', - // 是否支持多模块 - 'app_multi_module' => true, - // 入口自动绑定模块 - 'auto_bind_module' => false, - // 注册的根命名空间 - 'root_namespace' => [], - // 扩展函数文件 - 'extra_file_list' => [THINK_PATH . 'helper' . EXT], - // 默认输出类型 - 'default_return_type' => 'html', - // 默认AJAX 数据返回格式,可选json xml ... - 'default_ajax_return' => 'json', - // 默认JSONP格式返回的处理方法 - 'default_jsonp_handler' => 'jsonpReturn', - // 默认JSONP处理方法 - 'var_jsonp_handler' => 'callback', - // 默认时区 - 'default_timezone' => 'PRC', - // 是否开启多语言 - 'lang_switch_on' => false, - // 默认全局过滤方法 用逗号分隔多个 - 'default_filter' => 'htmlspecialchars', - // 默认语言 - 'default_lang' => 'zh-cn', - // 应用类库后缀 - 'class_suffix' => false, - // 控制器类后缀 - 'controller_suffix' => false, - // +---------------------------------------------------------------------- - // | 模块设置 - // +---------------------------------------------------------------------- - // 默认模块名 - 'default_module' => 'index', - // 禁止访问模块 - 'deny_module_list' => ['common'], - // 默认控制器名 - 'default_controller' => 'Index', - // 默认操作名 - 'default_action' => 'index', - // 默认验证器 - 'default_validate' => '', - // 默认的空控制器名 - 'empty_controller' => 'Error', - // 操作方法后缀 - 'action_suffix' => '', - // 自动搜索控制器 - 'controller_auto_search' => false, - // +---------------------------------------------------------------------- - // | URL设置 - // +---------------------------------------------------------------------- - // PATHINFO变量名 用于兼容模式 - 'var_pathinfo' => 's', - // 兼容PATH_INFO获取 - 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], - // pathinfo分隔符 - 'pathinfo_depr' => '/', - // URL伪静态后缀 - 'url_html_suffix' => 'html', - // URL普通方式参数 用于自动生成 - 'url_common_param' => false, - // URL参数方式 0 按名称成对解析 1 按顺序解析 - 'url_param_type' => 0, - // 是否开启路由 - 'url_route_on' => true, - // 路由使用完整匹配 - 'route_complete_match' => false, - // 路由配置文件(支持配置多个) - 'route_config_file' => ['route'], - // 是否强制使用路由 - 'url_route_must' => false, - // 域名部署 - 'url_domain_deploy' => false, - // 域名根,如thinkphp.cn - 'url_domain_root' => '', - // 是否自动转换URL中的控制器和操作名 - 'url_convert' => true, - // 默认的访问控制器层 - 'url_controller_layer' => 'controller', - // 表单请求类型伪装变量 - 'var_method' => '_method', - // 表单ajax伪装变量 - 'var_ajax' => '_ajax', - // 表单pjax伪装变量 - 'var_pjax' => '_pjax', - // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 - 'request_cache' => false, - // 请求缓存有效期 - 'request_cache_expire' => null, - // +---------------------------------------------------------------------- - // | 模板设置 - // +---------------------------------------------------------------------- - 'template' => [ - // 模板引擎类型 支持 php think 支持扩展 - 'type' => 'Think', - // 模板路径 - 'view_path' => '', - // 模板后缀 - 'view_suffix' => 'html', - // 模板文件名分隔符 - 'view_depr' => '.', - // 模板引擎普通标签开始标记 - 'tpl_begin' => '{', - // 模板引擎普通标签结束标记 - 'tpl_end' => '}', - // 标签库标签开始标记 - 'taglib_begin' => '{', - // 标签库标签结束标记 - 'taglib_end' => '}', - ], - // 视图输出字符串内容替换 - 'view_replace_str' => [], - // 默认跳转页面对应的模板文件 - 'dispatch_success_tmpl' => APP_PATH . 'extra' . DS . 'view' . DS . 'handler.jump.html', - 'dispatch_error_tmpl' => APP_PATH . 'extra' . DS . 'view' . DS . 'handler.jump.html', - // +---------------------------------------------------------------------- - // | 异常及错误设置 - // +---------------------------------------------------------------------- - // 异常页面的模板文件 -// 'exception_tmpl' => APP_PATH . 'extra' . DS . 'view' . DS . 'handler.exception.html', - // 错误显示信息,非调试模式有效 - 'error_message' => '页面错误!请稍后再试~', - // 显示错误信息 - 'show_error_msg' => false, - // 异常处理handle类 留空使用 \think\exception\Handle - 'exception_handle' => '', - // +---------------------------------------------------------------------- - // | 日志设置 - // +---------------------------------------------------------------------- - 'log' => [ - // 日志记录方式,内置 file socket 支持扩展 - 'type' => 'File', - // 日志保存目录 - 'path' => LOG_PATH, - // 日志记录级别 log,error,info,sql,notice,alert,debug - 'level' => ['error', 'log', 'info', 'sql', 'notice', 'alert', 'debug'], - // error和sql日志单独记录 - 'apart_level' => ['error', 'sql'], - ], - // +---------------------------------------------------------------------- - // | Trace设置 开启 app_trace 后 有效 - // +---------------------------------------------------------------------- - 'trace' => [ - // 内置Html Console 支持扩展 - 'type' => 'Html', - ], - // +---------------------------------------------------------------------- - // | 缓存设置 - // +---------------------------------------------------------------------- - 'cache' => [ - // 驱动方式 - 'type' => 'File', - // 缓存保存目录 - 'path' => CACHE_PATH, - // 缓存前缀 - 'prefix' => '', - // 缓存有效期 0表示永久缓存 - 'expire' => 0, - ], - // +---------------------------------------------------------------------- - // | 会话设置 - // +---------------------------------------------------------------------- - 'session' => [ - 'id' => '', - // SESSION_ID的提交变量,解决flash上传跨域 - 'var_session_id' => '', - // SESSION 前缀 - 'prefix' => 'think', - // 驱动方式 支持redis memcache memcached - 'type' => '', - // 是否自动开启 SESSION - 'auto_start' => true, - ], - // +---------------------------------------------------------------------- - // | Cookie设置 - // +---------------------------------------------------------------------- - 'cookie' => [ - // cookie 名称前缀 - 'prefix' => '', - // cookie 保存时间 - 'expire' => 0, - // cookie 保存路径 - 'path' => '/', - // cookie 有效域名 - 'domain' => '', - // cookie 启用安全传输 - 'secure' => false, - // httponly设置 - 'httponly' => '', - // 是否使用 setcookie - 'setcookie' => true, - ], - //分页配置 - 'paginate' => [ - 'type' => 'bootstrap', - 'var_page' => 'page', - 'list_rows' => 15, - ], -]; diff --git a/application/demo/controller/Plugs.php b/application/demo/controller/Plugs.php deleted file mode 100644 index 1113134d2..000000000 --- a/application/demo/controller/Plugs.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @date 2017/07/10 18:13 - */ -class Plugs extends Controller -{ - - - /** - * 文件上传 - * @return \think\response\View - */ - public function file() - { - return view('', ['title' => '文件上传']); - } - - /** - * 省市区插件 - * @return \think\response\View - */ - public function region() - { - return view('', ['title' => '省市区插件']); - } - - /** - * 富文本编辑器 - * @return \think\response\View - */ - public function editor() - { - return view('', ['title' => '富文本编辑器']); - } -} diff --git a/application/demo/view/plugs.editor.html b/application/demo/view/plugs.editor.html deleted file mode 100644 index a1ba80d0f..000000000 --- a/application/demo/view/plugs.editor.html +++ /dev/null @@ -1,47 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="content"} - - - - - -
    - - -
    - -
    -
    -
    -    
    - -
    -
    -require(['ckeditor'], function () {
    -    var editor = window.createEditor('[name="content"]');
    -    // 设置内容
    -    editor.setData('修改内容');
    -    // 获取内容
    -    var content = editor.getData();
    -    console.log(content);
    -});
    -
    -    
    -
    - - -{/block} \ No newline at end of file diff --git a/application/demo/view/plugs.file.html b/application/demo/view/plugs.file.html deleted file mode 100644 index 0375a3121..000000000 --- a/application/demo/view/plugs.file.html +++ /dev/null @@ -1,263 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="content"} - -
    -
    - 单图片上传 -
    -

    - 1. data-file 指定上传文件模式,可选择 ( one | mut ),one : 表示单文件上传, mut : 表示多文件上传。 -

    -

    - 2. data-type 指定上传文件后缀,多种后缀时使用英文逗号分割,如:jpg,png。 -

    -

    - 3. data-uptype 指定上传文件存储方式,可选择( local | qiniu | oss )。local : 文件存储在本地服务器, qiniu : 文件存储在七牛云, oss : - 文件存储在阿里云OSS。 -

    -

    - 4. data-field 绑定上传成功后返回URL地址的 input,同时会触发 input 的 change 事件,可以监听并进行处理。 -

    -
    -
    -
    - 上传图片 -
    - - -
    -
    -
    -
    - 上传图片 -
    -
    - -
    -
    -// 监听 input 的 onchange 事件
    -// 当有文件上传时,会触发 change 事件
    -require(['jquery'], function () {
    -    $('[name="file_1"]').on('change', function () {
    -        $(this).parent().attr('data-tips-image', this.value).css('backgroundImage', 'url(' + this.value + ')');
    -    });
    -});
    -    
    -
    - -
    -
    - 多图片上传 -
    -

    - 1. data-file 指定上传文件模式,可选择 ( one | mut ),one : 表示单文件上传, mut : 表示多文件上传。 -

    -

    - 2. data-type 指定上传文件后缀,多种后缀时使用英文逗号分割,如:jpg,png。 -

    -

    - 3. data-uptype 指定上传文件存储方式,可选择( local | qiniu | oss )。local : 文件存储在本地服务器, qiniu : 文件存储在七牛云, oss : - 文件存储在阿里云OSS。 -

    -

    - 4. data-field 绑定上传成功后返回URL地址的 input,同时会触发 input 的 change 事件,可以监听并进行处理。 -

    -
    -
    - - -

    - 上传图片 -

    -
    - - -
    -
    -
    - - - 上传图片 -
    -
    - -
    -
    -require(['jquery'], function () {
    -    var tpl = '';
    -    $('[name="file_2"]').on('change', function () {
    -        var input = this, values = [], srcs = this.value.split('|');
    -        $(this).prevAll('.uploadimage').map(function () {
    -            values.push($(this).attr('data-tips-image'));
    -        }), $(this).prevAll('.uploadimage').remove();
    -        values.reverse();
    -        for (var i in srcs) {
    -            values.push(srcs[i]);
    -        }
    -        this.value = values.join('|');
    -        for (var i in values) {
    -            var $tpl = $(tpl).attr('data-tips-image', values[i]).css('backgroundImage', 'url(' + values[i] + ')');
    -            $tpl.data('input', input).data('srcs', values).data('index', i);
    -            $tpl.on('click', 'a', function (e) {
    -                e.stopPropagation();
    -                var $cur = $(this).parent();
    -                $.msg.confirm('确定要移除这张图片吗?', function () {
    -                    var data = $cur.data('srcs');
    -                    delete data[$cur.data('index')];
    -                    $cur.data('input').value = data.join('|');
    -                    $cur.remove();
    -                });
    -            });
    -            $(this).before($tpl);
    -        }
    -    });
    -});
    -    
    -
    - -
    -
    - 单文件上传 -
    -

    - 1. data-file 指定上传文件模式,可选择 ( one | mut ),one : 表示单文件上传, mut : 表示多文件上传。 -

    -

    - 2. data-type 指定上传文件后缀,多种后缀时使用英文逗号分割,如:zip,rar。 -

    -

    - 3. data-uptype 指定上传文件存储方式,可选择( local | qiniu | oss )。local : 文件存储在本地服务器, qiniu : 文件存储在七牛云, oss : - 文件存储在阿里云OSS。 -

    -

    - 4. data-field 绑定上传成功后返回URL地址的 input,同时会触发 input 的 change 事件,可以监听并进行处理。 -

    -
    -
    -
    - - - - -
    -
    - - -
    -
    -
    -
    - - - - -
    -
    - -
    -
    -
    -// 监听 input 的 onchange 事件
    -// 当有文件上传时,会触发 change 事件
    -require(['jquery'], function () {
    -    $('[name="file_3"]').on('change', function () {
    -        alert('文件上传后的地址:' + this.value);
    -    });
    -});
    -    
    - - -
    - - -{/block} \ No newline at end of file diff --git a/application/demo/view/plugs.region.html b/application/demo/view/plugs.region.html deleted file mode 100644 index 231278c92..000000000 --- a/application/demo/view/plugs.region.html +++ /dev/null @@ -1,65 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="content"} - -
    -
    - PCAS 单文件JS省市区插件(也适用于Wap端) -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -require(['pcasunzips'], function () {
    -    /**   // 省市联动
    -    *   new PCAS("Province","City")
    -    *   new PCAS("Province","City","吉林省")
    -    *   new PCAS("Province","City","吉林省","吉林市")
    -    *   // 省市地区联动
    -    *   new PCAS("Province","City","Area")
    -    *   new PCAS("Province","City","Area","吉林省")
    -    *   new PCAS("Province","City","Area","吉林省","松原市")
    -    *   new PCAS("Province","City","Area","吉林省","松原市","宁江区")
    -    *   // 省、市、地区对象取得的值均为实际值。
    -    *   // 注:省、市、地区提示信息选项的值为""(空字符串)
    -    */
    -    // 实例化PCAS插件
    -    new PCAS('province', 'city', 'area', '广东省', '广州市', '海珠区');
    -});
    -    
    - -
    - - -{/block} \ No newline at end of file diff --git a/application/extra/mines.php b/application/extra/mines.php deleted file mode 100644 index bc0912c99..000000000 --- a/application/extra/mines.php +++ /dev/null @@ -1,161 +0,0 @@ - ['application/mac-binhex40', 'application/mac-binhex', 'application/x-binhex40', 'application/x-mac-binhex40'], - 'cpt' => 'application/mac-compactpro', - 'csv' => ['text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel', 'text/plain'], - 'bin' => ['application/macbinary', 'application/mac-binary', 'application/octet-stream', 'application/x-binary', 'application/x-macbinary'], - 'dms' => 'application/octet-stream', - 'lha' => 'application/octet-stream', - 'lzh' => 'application/octet-stream', - 'exe' => ['application/octet-stream', 'application/x-msdownload'], - 'class' => 'application/octet-stream', - 'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'], - 'so' => 'application/octet-stream', - 'sea' => 'application/octet-stream', - 'dll' => 'application/octet-stream', - 'oda' => 'application/oda', - 'pdf' => ['application/pdf', 'application/force-download', 'application/x-download', 'binary/octet-stream'], - 'ai' => ['application/pdf', 'application/postscript'], - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'mif' => 'application/vnd.mif', - 'xls' => ['application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'application/x-ms-excel', 'application/x-excel', 'application/x-dos_ms_excel', 'application/xls', 'application/x-xls', 'application/excel', 'application/download', 'application/vnd.ms-office', 'application/msword'], - 'ppt' => ['application/powerpoint', 'application/vnd.ms-powerpoint', 'application/vnd.ms-office', 'application/msword'], - 'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/x-zip', 'application/zip'], - 'wbxml' => 'application/wbxml', - 'wmlc' => 'application/wmlc', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dxr' => 'application/x-director', - 'dvi' => 'application/x-dvi', - 'gtar' => 'application/x-gtar', - 'gz' => 'application/x-gzip', - 'gzip' => 'application/x-gzip', - 'php' => ['application/x-httpd-php', 'application/php', 'application/x-php', 'text/php', 'text/x-php', 'application/x-httpd-php-source'], - 'php4' => 'application/x-httpd-php', - 'php3' => 'application/x-httpd-php', - 'phtml' => 'application/x-httpd-php', - 'phps' => 'application/x-httpd-php-source', - 'js' => ['application/x-javascript', 'text/plain'], - 'swf' => 'application/x-shockwave-flash', - 'sit' => 'application/x-stuffit', - 'tar' => 'application/x-tar', - 'tgz' => ['application/x-tar', 'application/x-gzip-compressed'], - 'z' => 'application/x-compress', - 'xhtml' => 'application/xhtml+xml', - 'xht' => 'application/xhtml+xml', - 'zip' => ['application/x-zip', 'application/zip', 'application/x-zip-compressed', 'application/s-compressed', 'multipart/x-zip'], - 'rar' => ['application/x-rar', 'application/rar', 'application/x-rar-compressed'], - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mpga' => 'audio/mpeg', - 'mp2' => 'audio/mpeg', - 'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'], - 'aif' => ['audio/x-aiff', 'audio/aiff'], - 'aiff' => ['audio/x-aiff', 'audio/aiff'], - 'aifc' => 'audio/x-aiff', - 'ram' => 'audio/x-pn-realaudio', - 'rm' => 'audio/x-pn-realaudio', - 'rpm' => 'audio/x-pn-realaudio-plugin', - 'ra' => 'audio/x-realaudio', - 'rv' => 'video/vnd.rn-realvideo', - 'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'], - 'bmp' => ['image/bmp', 'image/x-bmp', 'image/x-bitmap', 'image/x-xbitmap', 'image/x-win-bitmap', 'image/x-windows-bmp', 'image/ms-bmp', 'image/x-ms-bmp', 'application/bmp', 'application/x-bmp', 'application/x-win-bitmap'], - 'gif' => 'image/gif', - 'jpeg' => ['image/jpeg', 'image/pjpeg'], - 'jpg' => ['image/jpeg', 'image/pjpeg'], - 'jpe' => ['image/jpeg', 'image/pjpeg'], - 'png' => ['image/png', 'image/x-png'], - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'css' => ['text/css', 'text/plain'], - 'html' => ['text/html', 'text/plain'], - 'htm' => ['text/html', 'text/plain'], - 'shtml' => ['text/html', 'text/plain'], - 'txt' => 'text/plain', - 'text' => 'text/plain', - 'log' => ['text/plain', 'text/x-log'], - 'rtx' => 'text/richtext', - 'rtf' => 'text/rtf', - 'xml' => ['application/xml', 'text/xml', 'text/plain'], - 'xsl' => ['application/xml', 'text/xsl', 'text/xml'], - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mpe' => 'video/mpeg', - 'qt' => 'video/quicktime', - 'mov' => 'video/quicktime', - 'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'], - 'movie' => 'video/x-sgi-movie', - 'doc' => ['application/msword', 'application/vnd.ms-office'], - 'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword', 'application/x-zip'], - 'dot' => ['application/msword', 'application/vnd.ms-office'], - 'dotx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/msword'], - 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/vnd.ms-excel', 'application/msword', 'application/x-zip'], - 'word' => ['application/msword', 'application/octet-stream'], - 'xl' => 'application/excel', - 'eml' => 'message/rfc822', - 'json' => ['application/json', 'text/json'], - 'pem' => ['application/x-x509-user-cert', 'application/x-x509-ca-cert', 'application/x-pem-file', 'application/pkix-cert'], - 'crt' => ['application/x-x509-user-cert', 'application/x-x509-ca-cert', 'application/pkix-cert'], - 'p10' => ['application/x-pkcs10', 'application/pkcs10'], - 'p12' => 'application/x-pkcs12', - 'p7a' => 'application/x-pkcs7-signature', - 'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'], - 'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'], - 'p7r' => 'application/x-pkcs7-certreqresp', - 'p7s' => 'application/pkcs7-signature', - 'crl' => ['application/pkix-crl', 'application/pkcs-crl'], - 'der' => 'application/x-x509-ca-cert', - 'kdb' => 'application/octet-stream', - 'pgp' => 'application/pgp', - 'gpg' => 'application/gpg-keys', - 'sst' => 'application/octet-stream', - 'csr' => 'application/octet-stream', - 'rsa' => 'application/x-pkcs7', - 'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'], - '3g2' => 'video/3gpp2', - '3gp' => ['video/3gp', 'video/3gpp'], - 'mp4' => 'video/mp4', - 'm4a' => 'audio/x-m4a', - 'f4v' => 'video/mp4', - 'webm' => 'video/webm', - 'webp' => 'image/webp', - 'aac' => 'audio/x-acc', - 'm4u' => 'application/vnd.mpegurl', - 'm3u' => 'text/plain', - 'xspf' => 'application/xspf+xml', - 'vlc' => 'application/videolan', - 'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'], - 'au' => 'audio/x-au', - 'ac3' => 'audio/ac3', - 'flac' => 'audio/x-flac', - 'ogg' => 'audio/ogg', - 'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'], - 'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'], - 'ics' => 'text/calendar', - 'ical' => 'text/calendar', - 'zsh' => 'text/x-scriptzsh', - '7zip' => ['application/x-compressed', 'application/x-zip-compressed', 'application/zip', 'multipart/x-zip'], - 'cdr' => ['application/cdr', 'application/coreldraw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'], - 'wma' => ['audio/x-ms-wma', 'video/x-ms-asf'], - 'jar' => ['application/java-archive', 'application/x-java-application', 'application/x-jar', 'application/x-compressed'], - 'svg' => ['image/svg+xml', 'application/xml', 'text/xml'], - 'vcf' => 'text/x-vcard', - 'srt' => ['text/srt', 'text/plain'], - 'vtt' => ['text/vtt', 'text/plain'], - 'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'], -]; diff --git a/application/extra/view/admin.content.html b/application/extra/view/admin.content.html deleted file mode 100644 index e8ab18e29..000000000 --- a/application/extra/view/admin.content.html +++ /dev/null @@ -1,22 +0,0 @@ -
    - {block name="style"}{/block} - {if isset($title)} -
    -
    {$title}
    - {block name="button"}{/block} -
    - {/if} -
    - {if isset($alert)} - - {/if} - {block name="content"}{/block} -
    - {block name='script'}{/block} -
    \ No newline at end of file diff --git a/application/extra/view/admin.main.html b/application/extra/view/admin.main.html deleted file mode 100644 index 601f61184..000000000 --- a/application/extra/view/admin.main.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - {block name="title"}{$title|default=''}{if !empty($title)} · {/if}{:sysconf('site_name')}{/block} - - - - - - - {block name="style"}{/block} - - - - - {block name="bodyTag"} - - {block name="body"}{/block} - {block name="script"}{/block} - - {/block} - \ No newline at end of file diff --git a/application/extra/view/admin.main.left.html b/application/extra/view/admin.main.left.html deleted file mode 100644 index 1a8d0170b..000000000 --- a/application/extra/view/admin.main.left.html +++ /dev/null @@ -1,52 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/application/extra/view/admin.main.top.html b/application/extra/view/admin.main.top.html deleted file mode 100644 index 6ea5884e4..000000000 --- a/application/extra/view/admin.main.top.html +++ /dev/null @@ -1,64 +0,0 @@ - \ No newline at end of file diff --git a/application/extra/view/handler.exception.html b/application/extra/view/handler.exception.html deleted file mode 100644 index 7b1876b23..000000000 --- a/application/extra/view/handler.exception.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - <?php echo \think\Lang::get('System Error'); ?> - - - - - - - -
    - -
    -

    []

    - -

    - -

    -
    -
    - - diff --git a/application/extra/view/handler.jump.html b/application/extra/view/handler.jump.html deleted file mode 100644 index d9bfc9e58..000000000 --- a/application/extra/view/handler.jump.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - 跳转提示 - - - -
    - - -

    :)

    -

    - - -

    :(

    -

    - - -

    -

    - 页面自动 跳转 等待时间: -

    -
    - - - diff --git a/application/index/controller/Index.php b/application/index/controller/Index.php index 7172d75da..f7c6b564b 100644 --- a/application/index/controller/Index.php +++ b/application/index/controller/Index.php @@ -14,34 +14,20 @@ namespace app\index\controller; -use think\Controller; +use controller\BasicAdmin; +use service\WechatService; /** - * 网站入口控制器 - * Class Index - * @package app\index\controller + * 应用入口控制器 * @author Anyon - * @date 2017/04/05 10:38 */ -class Index extends Controller +class Index extends BasicAdmin { - /** - * 网站入口 - */ public function index() { - $this->redirect('@admin'); + $this->redirect('@admin/login'); } - public function qrc() - { - $wechat = load_wechat('Extends'); - for ($i = 10; $i < 90; $i++) { - $qrc = $wechat->getQRCode($i, 1); - print_r($qrc); - } - - } } diff --git a/application/index/controller/Wap.php b/application/index/controller/Wap.php deleted file mode 100644 index edbd60a3d..000000000 --- a/application/index/controller/Wap.php +++ /dev/null @@ -1,120 +0,0 @@ -oAuth(true)); // 获取用户详情信息 - dump($this->fansinfo); // 输出用户信息 - } - - /** - * 微信二维码支付DEMO - * @return \think\response\Json|\think\response\View - */ - public function payqrc() - { - $method = '_payqrc_' . strtolower($this->request->get('action')); - if (method_exists($this, $method)) { - return $this->$method(); - } - return view(); - } - - /** - * 获取二维码支付 - * @return \think\response\Json - */ - protected function _payqrc_payqrc() - { - list($pay, $order_no) = [load_wechat('pay'), session('pay-test-order-no')]; - if (empty($order_no)) { - $order_no = DataService::createSequence(10, 'wechat-pay-test'); - session('pay-test-order-no', $order_no); - } - if (PayService::isPay($order_no)) { - return json(['code' => 2, 'order_no' => $order_no]); - } - $url = PayService::createWechatPayQrc($pay, $order_no, 1, '微信扫码支付测试!'); - if ($url !== false) { - return json(['code' => 1, 'url' => $url, 'order_no' => $order_no]); - } - $this->error("生成支付二维码失败,{$pay->errMsg}[{$pay->errCode}]"); - } - - /** - * 重置测试订单号 - */ - protected function _payqrc_reset() - { - session('pay-test-order-no', null); - } - - /** - * 微信JSAPI支付DEMO - * @return \think\response\Json|\think\response\View - */ - public function payjs() - { - $this->openid = $this->oAuth(false); - $method = '_payjs_' . strtolower($this->request->get('action')); - if (method_exists($this, $method)) { - return $this->$method(); - } - return view(); - } - - /** - * 获取JSAPI支付参数 - * @return \think\response\Json - */ - protected function _payjs_options() - { - $order_no = session('pay-test-order-no'); - if (empty($order_no)) { - $order_no = DataService::createSequence(10, 'wechat-pay-test'); - session('pay-test-order-no', $order_no); - } - if (PayService::isPay($order_no)) { - return json(['code' => 2, 'order_no' => $order_no]); - } - $pay = load_wechat('pay'); - $options = PayService::createWechatPayJsPicker($pay, $this->openid, $order_no, 1, 'JSAPI支付测试'); - if ($options === false) { - $options = ['code' => 3, 'msg' => "创建支付失败,{$pay->errMsg}[$pay->errCode]"]; - } - $options['order_no'] = $order_no; - return json($options); - } - - /** - * 重置测试订单号 - */ - protected function _payjs_reset() - { - session('pay-test-order-no', null); - } - -} diff --git a/application/index/view/wap.payjs.html b/application/index/view/wap.payjs.html deleted file mode 100644 index 93d771f4d..000000000 --- a/application/index/view/wap.payjs.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - JSAPI支付DEMO - - - - - - -
    -

    JSAPI支付DEMO

    -
    -

    金额: 0.01元

    -
    -

    -
    -
    -
    - -
    -
    - 扫码支付测试 -
    - - - - - - - \ No newline at end of file diff --git a/application/index/view/wap.payqrc.html b/application/index/view/wap.payqrc.html deleted file mode 100644 index 9cdad91c2..000000000 --- a/application/index/view/wap.payqrc.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - 扫码支付DEMO - - - - - - -
    -

    扫码支付DEMO

    -
    -

    -

    请使用其它手机微信扫描二维码。

    -
    -

    金额: 0.01元

    -
    -

    -
    - - JSAPI支付测试 -
    - - - - - - \ No newline at end of file diff --git a/application/route.php b/application/route.php deleted file mode 100644 index ac7d6f884..000000000 --- a/application/route.php +++ /dev/null @@ -1,64 +0,0 @@ - function () { - return json(['code' => 0, 'msg' => '测试环境禁修改用户密码!']); - }, - 'admin/user/pass' => function () { - return json(['code' => 0, 'msg' => '测试环境禁修改用户密码!']); - }, - 'admin/config/index' => function () { - return json(['code' => 0, 'msg' => '测试环境禁修改系统配置操作!']); - }, - 'admin/config/file' => function () { - return json(['code' => 0, 'msg' => '测试环境禁修改文件配置操作!']); - }, - 'admin/menu/index' => function () { - return json(['code' => 0, 'msg' => '测试环境禁排序菜单操作!']); - }, - 'admin/menu/add' => function () { - return json(['code' => 0, 'msg' => '测试环境禁添加菜单操作!']); - }, - 'admin/menu/edit' => function () { - return json(['code' => 0, 'msg' => '测试环境禁编辑菜单操作!']); - }, - 'admin/menu/forbid' => function () { - return json(['code' => 0, 'msg' => '测试环境禁止禁用菜单操作!']); - }, - 'admin/menu/del' => function () { - return json(['code' => 0, 'msg' => '测试环境禁止删除菜单操作!']); - }, - 'wechat/config/index' => function () { - return json(['code' => 0, 'msg' => '测试环境禁止修改微信配置操作!']); - }, - 'wechat/config/pay' => function () { - return json(['code' => 0, 'msg' => '测试环境禁止修改微信支付操作!']); - }, - 'admin/node/save' => function () { - return json(['code' => 0, 'msg' => '测试环境禁止修改节点数据操作!']); - }, - 'wechat/menu/edit' => function () { - return json(['code' => 0, 'msg' => '测试环境禁止修改微信菜单操作!']); - }, -]); - -think\Route::get([ - 'wechat/menu/cancel' => function () { - return json(['code' => 0, 'msg' => '测试环境禁止删除微信菜单操作!']); - }, -]); - -return []; diff --git a/application/tags.php b/application/tags.php index d497b7c87..c0dffdc11 100644 --- a/application/tags.php +++ b/application/tags.php @@ -12,19 +12,28 @@ // | github开源项目:https://github.com/zoujingli/ThinkAdmin // +---------------------------------------------------------------------- +namespace think; + +use think\exception\HttpResponseException; + return [ - // 应用初始化 - 'app_init' => [], - // 应用开始 - 'app_begin' => [], - // 模块初始化 - 'module_init' => [], - // 操作开始执行 - 'action_begin' => ['hook\\AccessAuth'], - // 视图内容过滤 - 'view_filter' => ['hook\\FilterView'], - // 日志写入 - 'log_write' => [], - // 应用结束 - 'app_end' => [], + // 控制器开始前,进行权限检查 + 'action_begin' => function () { + $request = app('request'); + list($module, $controller, $action) = [$request->module(), $request->controller(), $request->action()]; + $node = strtolower("{$module}/{$controller}/{$action}"); + $info = Db::name('SystemNode')->cache(true, 30)->where(['node' => $node])->find(); + $access = ['is_menu' => intval(!empty($info['is_menu'])), 'is_auth' => intval(!empty($info['is_auth'])), 'is_login' => empty($info['is_auth']) ? intval(!empty($info['is_login'])) : 1]; + // 登录状态检查 + if (!empty($access['is_login']) && !session('user')) { + $msg = ['code' => 0, 'msg' => '抱歉,您还没有登录获取访问权限!', 'url' => url('@admin/login')]; + throw new HttpResponseException($request->isAjax() ? json($msg) : redirect($msg['url'])); + } + // 访问权限检查 + if (!empty($access['is_auth']) && !auth($node)) { + throw new HttpResponseException(json(['code' => 0, 'msg' => '抱歉,您没有访问该模块的权限!'])); + } + // 模板常量声明 + app('view')->init(config('template.'))->assign(['classuri' => "{$module}/{$controller}"]); + }, ]; diff --git a/application/wechat/controller/Api.php b/application/wechat/controller/Api.php deleted file mode 100644 index b28808a20..000000000 --- a/application/wechat/controller/Api.php +++ /dev/null @@ -1,263 +0,0 @@ - - */ -class Api extends Controller -{ - - /** - * 微信openid - * @var string - */ - protected $openid; - - /** - * 微信消息对象 - * @var WechatReceive - */ - protected $wechat; - - /** - * 微信消息接口 - * @return string - */ - public function index() - { - // 实例接口对象 - $this->wechat = &load_wechat('Receive'); - // 验证接口请求 - if ($this->wechat->valid() === false) { - $msg = "{$this->wechat->errMsg}[{$this->wechat->errCode}]"; - Log::error($msg); - return $msg; - } - // 获取消息来源用户OPENID - $this->openid = $this->wechat->getRev()->getRevFrom(); - // 获取并同步粉丝信息到数据库 - $this->_updateFansInfo(true); - // 分别执行对应类型的操作 - switch ($this->wechat->getRev()->getRevType()) { - case WechatReceive::MSGTYPE_TEXT: - return $this->_keys("WechatKeys#keys#" . $this->wechat->getRevContent()); - case WechatReceive::MSGTYPE_EVENT: - return $this->_event(); - case WechatReceive::MSGTYPE_IMAGE: - return $this->_image(); - case WechatReceive::MSGTYPE_LOCATION: - return $this->_location(); - default: - return 'success'; - } - } - - /** - * 关键字处理 - * @param string $keys - * @param bool $isForce - * @return string - */ - private function _keys($keys, $isForce = false) - { - list($table, $field, $value) = explode('#', $keys . '##'); - if (is_array($info = Db::name($table)->where($field, $value)->find()) && isset($info['type'])) { - // 数据状态检查 - if (array_key_exists('status', $info) && empty($info['status'])) { - return 'success'; - } - switch ($info['type']) { - case 'customservice': // 多客服 - $this->wechat->sendCustomMessage(['touser' => $this->openid, 'msgtype' => 'text', 'text' => ['content' => $info['content']]]); - return $this->wechat->transfer_customer_service()->reply(false, true); - case 'keys': // 关键字 - if (empty($info['content']) && empty($info['name'])) { - return 'success'; - } - return $this->_keys('wechat_keys#keys#' . (empty($info['content']) ? $info['name'] : $info['content'])); - case 'text': // 文本消息 - if (empty($info['content']) && empty($info['name'])) { - return 'success'; - } - return $this->wechat->text($info['content'])->reply(false, true); - case 'news': // 图文消息 - if (empty($info['news_id'])) { - return 'success'; - } - return $this->_news($info['news_id']); - case 'music': // 音频消息 - if (empty($info['music_url']) || empty($info['music_title']) || empty($info['music_desc'])) { - return 'success'; - } - $media_id = empty($info['music_image']) ? '' : WechatService::uploadForeverMedia($info['music_image'], 'image'); - if (empty($media_id)) { - return 'success'; - } - return $this->wechat->music($info['music_title'], $info['music_desc'], $info['music_url'], $info['music_url'], $media_id)->reply(false, true); - case 'voice': // 语音消息 - if (empty($info['voice_url'])) { - return 'success'; - } - $media_id = WechatService::uploadForeverMedia($info['voice_url'], 'voice'); - if (empty($media_id)) { - return 'success'; - } - return $this->wechat->voice($media_id)->reply(false, true); - case 'image': // 图文消息 - if (empty($info['image_url'])) { - return 'success'; - } - $media_id = WechatService::uploadForeverMedia($info['image_url'], 'image'); - if (empty($media_id)) { - return 'success'; - } - return $this->wechat->image($media_id)->reply(false, true); - case 'video': // 视频消息 - if (empty($info['video_url']) || empty($info['video_desc']) || empty($info['video_title'])) { - return 'success'; - } - $data = ['title' => $info['video_title'], 'introduction' => $info['video_desc']]; - $media_id = WechatService::uploadForeverMedia($info['video_url'], 'video', true, $data); - return $this->wechat->video($media_id, $info['video_title'], $info['video_desc'])->reply(false, true); - } - } - if ($isForce) { - return 'success'; - } - return $this->_keys('wechat_keys#keys#default', true); - } - - /** - * 回复图文 - * @param int $news_id - * @return bool|string - */ - protected function _news($news_id = 0) - { - if (is_array($newsinfo = WechatService::getNewsById($news_id)) && !empty($newsinfo['articles'])) { - $newsdata = []; - foreach ($newsinfo['articles'] as $vo) { - $newsdata[] = [ - 'Title' => $vo['title'], - 'Description' => $vo['digest'], - 'PicUrl' => $vo['local_url'], - 'Url' => url("@wechat/review", '', true, true) . "?content={$vo['id']}&type=article", - ]; - } - return $this->wechat->news($newsdata)->reply(false, true); - } - return 'success'; - } - - /** - * 事件处理 - */ - protected function _event() - { - $event = $this->wechat->getRevEvent(); - switch (strtolower($event['event'])) { - case 'subscribe': // 粉丝关注事件 - $this->_updateFansInfo(true); - $this->_spread($event['key']); - return $this->_keys('wechat_keys#keys#subscribe', true); - case 'unsubscribe':// 粉丝取消关注 - $this->_updateFansInfo(false); - return 'success'; - case 'click': // 点击菜单事件 - return $this->_keys($event['key']); - case 'scancode_push': - case 'scancode_waitmsg': // 扫码推事件 - $scanInfo = $this->wechat->getRev()->getRevScanInfo(); - if (isset($scanInfo['ScanResult'])) { - return $this->_keys($scanInfo['ScanResult']); - } - return 'success'; - case 'scan': - if (!empty($event['key'])) { - return $this->_spread($event['key']); - } - return 'success'; - } - return 'success'; - } - - /** - * 推荐好友扫码关注 - * @param string $event - * @return mixed - */ - private function _spread($event) - { - $key = preg_replace('|^.*?(\d+).*?$|', '$1', "{$event}"); - // 检测推荐是否有效 - $fans = Db::name('WechatFans')->where('id', $key)->find(); - if (!is_array($fans) || !isset($fans['openid']) || $fans['openid'] === $this->openid) { - return false; - } - // 标识推荐关系 - $data = ['spread_openid' => $fans['openid'], 'spread_at' => date('Y-m-d H:i:s')]; - Db::name('WechatFans')->where("openid='{$this->openid}' and (spread_openid is null or spread_openid='')")->setField($data); - // @todo 推荐成功的奖励 - } - - /** - * 位置事情回复 - * @return string - */ - private function _location() - { - return 'success'; - } - - /** - * 图片事件处理 - */ - private function _image() - { - return 'success'; - } - - /** - * 同步粉丝状态 - * @param bool $subscribe 关注状态 - */ - protected function _updateFansInfo($subscribe = true) - { - if ($subscribe) { - $fans = WechatService::getFansInfo($this->openid); - if (empty($fans) || empty($fans['subscribe'])) { - $wechat = &load_wechat('User'); - $userInfo = $wechat->getUserInfo($this->openid); - $userInfo['subscribe'] = intval($subscribe); - WechatService::setFansInfo($userInfo, $wechat->appid); - } - } else { - $data = ['subscribe' => '0', 'appid' => $this->wechat->appid, 'openid' => $this->openid]; - DataService::save('wechat_fans', $data, 'openid'); - } - } - -} diff --git a/application/wechat/controller/Block.php b/application/wechat/controller/Block.php new file mode 100644 index 000000000..5eae5a96f --- /dev/null +++ b/application/wechat/controller/Block.php @@ -0,0 +1,114 @@ + + * @date 2017/03/27 14:43 + */ +class Block extends BasicAdmin +{ + + /** + * 定义当前默认数据表 + * @var string + */ + public $table = 'WechatFans'; + + /** + * 黑名单列表 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception + */ + public function index() + { + $this->title = '微信粉丝黑名单管理'; + $get = $this->request->get(); + $db = Db::name($this->table)->where(['is_black' => '1'])->order('subscribe_time desc'); + (isset($get['sex']) && $get['sex'] !== '') && $db->where('sex', $get['sex']); + foreach (['nickname', 'country', 'province', 'city'] as $key) { + (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%"); + } + if (isset($get['tag']) && $get['tag'] !== '') { + $db->where("concat(',',tagid_list,',') like :tag", ['tag' => "%,{$get['tag']},%"]); + } + if (isset($get['date']) && $get['date'] !== '') { + list($start, $end) = explode(' - ', $get['date']); + $db->whereBetween('subscribe_at', ["{$start} 00:00:00", "{$end} 23:59:59"]); + } + return parent::_list($db); + } + + /** + * 列表数据处理 + * @param array $list + */ + protected function _data_filter(&$list) + { + $tags = Db::name('WechatFansTags')->column('id,name'); + foreach ($list as &$vo) { + list($vo['tags_list'], $vo['nickname']) = [[], ToolsService::emojiDecode($vo['nickname'])]; + foreach (explode(',', $vo['tagid_list']) as $tag) { + if ($tag !== '' && isset($tags[$tag])) { + $vo['tags_list'][$tag] = $tags[$tag]; + } elseif ($tag !== '') { + $vo['tags_list'][$tag] = $tag; + } + } + } + $this->assign('tags', $tags); + } + + /** + * 取消黑名 + */ + public function backdel() + { + $openids = $this->_getActionOpenids(); + try { + WechatService::user()->batchUnblackList($openids); + Db::name($this->table)->whereIn('openid', $openids)->setField('is_black', '0'); + } catch (\Exception $e) { + $this->error("设备黑名单失败,请稍候再试!" . $e->getMessage()); + } + $this->success("已成功将 " . count($openids) . " 名粉丝从黑名单中移除!", ''); + } + + /** + * 获取当前操作用户openid数组 + * @return array + */ + private function _getActionOpenids() + { + $ids = $this->request->post('id', ''); + empty($ids) && $this->error('没有需要操作的数据!'); + $openids = Db::name($this->table)->whereIn('id', explode(',', $ids))->column('openid'); + empty($openids) && $this->error('没有需要操作的数据!'); + return $openids; + } + +} diff --git a/application/wechat/controller/Config.php b/application/wechat/controller/Config.php index 72ccd5ef5..caacc43ee 100644 --- a/application/wechat/controller/Config.php +++ b/application/wechat/controller/Config.php @@ -15,9 +15,8 @@ namespace app\wechat\controller; use controller\BasicAdmin; -use service\DataService; use service\LogService; -use service\PayService; +use service\WechatService; /** * 微信配置管理 @@ -37,97 +36,38 @@ class Config extends BasicAdmin /** * 微信基础参数配置 - * @return \think\response\View + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function index() { if ($this->request->isGet()) { - return view('', ['title' => '微信接口配置']); + $code = encode(url('@admin', '', true, true) . '#' . $this->request->url()); + $assign = [ + 'title' => '微信接口配置', + 'appuri' => url("@wechat/api.push", '', true, true), + 'appid' => $this->request->get('appid', sysconf('wechat_appid')), + 'appkey' => $this->request->get('appkey', sysconf('wechat_appkey')), + 'authurl' => "http://wm.cuci.cc/wechat/api.push/auth/{$code}.html", + 'wechat' => WechatService::instance('config')->getConfig(), + ]; + return $this->fetch('', $assign); } - foreach ($this->request->post() as $key => $vo) { - sysconf($key, $vo); - } - LogService::write('微信管理', '修改微信接口参数成功'); - $this->success('数据修改成功!', ''); - } - - /** - * 微信商户参数配置 - * @return \think\response\View - */ - public function pay() - { - if ($this->request->isGet()) { - $method = strtolower('_pay_' . $this->request->get('action')); - if (method_exists($this, $method)) { - return $this->$method(); + try { + sysconf('wechat_appid', $this->request->post('wechat_appid')); + sysconf('wechat_appkey', $this->request->post('wechat_appkey')); + $apiurl = $this->request->post('wechat_appurl'); + if (!empty($apiurl)) { + if (!WechatService::instance('config')->setApiNotifyUri($apiurl)) { + $this->error('远程服务端接口更新失败,请稍候再试!'); + } } - return view('', ['title' => '微信支付配置']); + LogService::write('微信管理', '修改微信接口参数成功'); + } catch (\Exception $e) { + $this->error('微信授权保存失败 , 请稍候重试 ! ' . $e->getMessage()); } - $data = $this->request->post(); - foreach ($data as $key => $vo) { - if (in_array($key, ['wechat_cert_key_md5', 'wechat_cert_cert_md5']) && !empty($vo)) { - $filename = ROOT_PATH . 'static/upload/' . join('/', str_split($vo, 16)) . '.pem'; - !file_exists($filename) && $this->error('支付双向证书上传失败,请重新上传!'); - $data[str_replace('_md5', '', $key)] = $filename; - } - } - unset($data['wechat_cert_key_md5'], $data['wechat_cert_cert_md5']); - foreach ($data as $key => $vo) { - sysconf($key, $vo); - } - LogService::write('微信管理', '修改微信支付参数成功'); - $this->success('数据修改成功!', ''); - } - - /** - * 生成测试支付二维码 - * @return \think\response\Json - */ - protected function _pay_payqrc() - { - $pay = &load_wechat('pay'); - // 生成订单号 - $order_no = session('pay-test-order-no'); - if (empty($order_no)) { - $order_no = DataService::createSequence(10, 'wechat-pay-test'); - session('pay-test-order-no', $order_no); - } - // 该订单号已经支付 - if (PayService::isPay($order_no)) { - return json(['code' => 2, 'order_no' => $order_no]); - } - // 订单号未支付,生成支付二维码URL - $url = PayService::createWechatPayQrc($pay, $order_no, 1, '微信扫码支付测试!'); - if ($url !== false) { - return json(['code' => 1, 'url' => $url, 'order_no' => $order_no]); - } - // 生成支付二维码URL失败 - $this->error("生成支付二维码失败, {$pay->errMsg}[{$pay->errCode}]"); - } - - /** - * 支付测试退款 - */ - protected function _pay_refund() - { - $order_no = session('pay-test-order-no'); - if (empty($order_no)) { - $this->error('测试订单号不存在, 请重新开始支付测试!'); - } - if (!PayService::isPay($order_no)) { - $this->error('测试订单未支付或未收到微信支付通过!'); - } - $pay = &load_wechat('pay'); - if (!file_exists($pay->ssl_cer) || !file_exists($pay->ssl_key)) { - $this->error('微信支付双向证书异常, 无法完成退款操作!'); - } - $refund_no = DataService::createSequence(10, 'wechat-pay-test'); - if (false !== PayService::putWechatRefund($pay, $order_no, 1, $refund_no)) { - session('pay-test-order-no', null); - $this->success('测试退款操作成功, 请查看微信通知!', ''); - } - $this->error("操作退款失败, {$pay->errMsg}[{$pay->errCode}]"); + $this->success('微信授权数据修改成功!', ''); } } diff --git a/application/wechat/controller/Fans.php b/application/wechat/controller/Fans.php index 2b7909c0c..cc25ac55f 100644 --- a/application/wechat/controller/Fans.php +++ b/application/wechat/controller/Fans.php @@ -14,6 +14,8 @@ namespace app\wechat\controller; +use app\wechat\service\FansService; +use app\wechat\service\TagsService; use controller\BasicAdmin; use service\LogService; use service\ToolsService; @@ -39,25 +41,33 @@ class Fans extends BasicAdmin /** * 显示粉丝列表 * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function index() { $this->title = '微信粉丝管理'; $get = $this->request->get(); - $db = Db::name($this->table)->where('is_back', '0')->order('subscribe_time desc'); + $db = Db::name($this->table)->where(['is_black' => '0'])->order('subscribe_time desc'); (isset($get['sex']) && $get['sex'] !== '') && $db->where('sex', $get['sex']); foreach (['nickname', 'country', 'province', 'city'] as $key) { - (isset($get[$key]) && $get[$key] !== '') && $db->where($key, 'like', "%{$get[$key]}%"); + (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%"); } if (isset($get['tag']) && $get['tag'] !== '') { $db->where("concat(',',tagid_list,',') like :tag", ['tag' => "%,{$get['tag']},%"]); } + if (isset($get['date']) && $get['date'] !== '') { + list($start, $end) = explode(' - ', $get['date']); + $db->whereBetween('subscribe_at', ["{$start} 00:00:00", "{$end} 23:59:59"]); + } return parent::_list($db); } /** * 列表数据处理 - * @param type $list + * @param array $list */ protected function _data_filter(&$list) { @@ -75,72 +85,46 @@ class Fans extends BasicAdmin $this->assign('tags', $tags); } - /** - * 黑名单列表 - */ - public function back() - { - $this->title = '微信粉丝黑名单管理'; - $get = $this->request->get(); - $db = Db::name($this->table)->where('is_back', '1')->order('subscribe_time desc'); - (isset($get['sex']) && $get['sex'] !== '') && $db->where('sex', $get['sex']); - foreach (['nickname', 'country', 'province', 'city'] as $key) { - (isset($get[$key]) && $get[$key] !== '') && $db->where($key, 'like', "%{$get[$key]}%"); - } - if (isset($get['tag']) && $get['tag'] !== '') { - $db->where("concat(',',tagid_list,',') like :tag", ['tag' => "%,{$get['tag']},%"]); - } - return parent::_list($db); - } - /** * 设置黑名单 */ public function backadd() { - $wechat = load_wechat('User'); - $openids = $this->_getActionOpenids(); - if (false !== $wechat->addBacklist($openids)) { - Db::name($this->table)->where('openid', 'in', $openids)->setField('is_back', '1'); - $this->success("已成功将 " . count($openids) . " 名粉丝移到黑名单!", ''); + try { + $openids = $this->_getActionOpenids(); + WechatService::user()->batchBlackList($openids); + Db::name($this->table)->whereIn('openid', $openids)->setField('is_black', '1'); + } catch (\Exception $e) { + $this->error("设置黑名单失败,请稍候再试!"); } - $this->error("设备黑名单失败,请稍候再试!{$wechat->errMsg}[{$wechat->errCode}]"); + $this->success('设置黑名单成功!', ''); } /** * 标签选择 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function tagset() { $tags = $this->request->post('tags', ''); $fans_id = $this->request->post('fans_id', ''); - $fans = Db::name('WechatFans')->where('id', $fans_id)->find(); + $fans = Db::name('WechatFans')->where(['id' => $fans_id])->find(); empty($fans) && $this->error('需要操作的数据不存在!'); - $wechat = load_wechat('User'); - foreach (explode(',', $fans['tagid_list']) as $tagid) { - is_numeric($tagid) && $wechat->batchDeleteUserTag($tagid, [$fans['openid']]); + try { + $wechat = WechatService::tags(); + foreach (explode(',', $fans['tagid_list']) as $tagid) { + is_numeric($tagid) && $wechat->batchUntagging([$fans['openid']], $tagid); + } + foreach (explode(',', $tags) as $tagid) { + is_numeric($tagid) && $wechat->batchTagging([$fans['openid']], $tagid); + } + Db::name('WechatFans')->where(['id' => $fans_id])->setField('tagid_list', $tags); + } catch (\Exception $e) { + $this->error('粉丝标签设置失败, 请稍候再试!'); } - foreach (explode(',', $tags) as $tagid) { - is_numeric($tagid) && $wechat->batchAddUserTag($tagid, [$fans['openid']]); - } - if (false !== Db::name('WechatFans')->where('id', $fans_id)->setField('tagid_list', $tags)) { - $this->success('粉丝标签成功!', ''); - } - $this->error('粉丝标签设置失败, 请稍候再试!'); - } - - /** - * 取消黑名 - */ - public function backdel() - { - $wechat = load_wechat('User'); - $openids = $this->_getActionOpenids(); - if (false !== $wechat->delBacklist($openids)) { - Db::name($this->table)->where('openid', 'in', $openids)->setField('is_back', '0'); - $this->success("已成功将 " . count($openids) . " 名粉丝从黑名单中移除!", ''); - } - $this->error("设备黑名单失败,请稍候再试!{$wechat->errMsg}[{$wechat->errCode}]"); + $this->success('粉丝标签成功!', ''); } /** @@ -150,12 +134,13 @@ class Fans extends BasicAdmin { $tagid = $this->request->post('tag_id', 0); empty($tagid) && $this->error('没有可能操作的标签ID'); - $openids = $this->_getActionOpenids(); - $wechat = load_wechat('User'); - if (false !== $wechat->batchAddUserTag($tagid, $openids)) { - $this->success('设置粉丝标签成功!', ''); + try { + $openids = $this->_getActionOpenids(); + WechatService::tags()->batchTagging($openids, $tagid); + } catch (\Exception $e) { + $this->error("设置粉丝标签失败, 请稍候再试! " . $e->getMessage()); } - $this->error("设置粉丝标签失败, 请稍候再试! {$wechat->errMsg}[{$wechat->errCode}]"); + $this->success('设置粉丝标签成功!', ''); } /** @@ -165,12 +150,13 @@ class Fans extends BasicAdmin { $tagid = $this->request->post('tag_id', 0); empty($tagid) && $this->error('没有可能操作的标签ID'); - $openids = $this->_getActionOpenids(); - $wechat = load_wechat('User'); - if (false !== $wechat->batchDeleteUserTag($tagid, $openids)) { - $this->success('删除粉丝标签成功!', ''); + try { + $openids = $this->_getActionOpenids(); + WechatService::tags()->batchUntagging($openids, $tagid); + } catch (\Exception $e) { + $this->error("删除粉丝标签失败, 请稍候再试! "); } - $this->error("删除粉丝标签失败, 请稍候再试! {$wechat->errMsg}[{$wechat->errCode}]"); + $this->success('删除粉丝标签成功!', ''); } /** @@ -181,7 +167,7 @@ class Fans extends BasicAdmin { $ids = $this->request->post('id', ''); empty($ids) && $this->error('没有需要操作的数据!'); - $openids = Db::name($this->table)->where('id', 'in', explode(',', $ids))->column('openid'); + $openids = Db::name($this->table)->whereIn('id', explode(',', $ids))->column('openid'); empty($openids) && $this->error('没有需要操作的数据!'); return $openids; } @@ -191,13 +177,15 @@ class Fans extends BasicAdmin */ public function sync() { - Db::name($this->table)->where('1=1')->delete(); - if (WechatService::syncAllFans('')) { - WechatService::syncBlackFans(''); + try { + Db::name($this->table)->where('1=1')->delete(); + FansService::sync(); + TagsService::sync(); LogService::write('微信管理', '同步全部微信粉丝成功'); - $this->success('同步获取所有粉丝成功!', ''); + } catch (\Exception $e) { + $this->error('同步粉丝记录失败,请稍候再试!'); } - $this->error('同步获取粉丝失败,请稍候再试!'); + $this->success('同步获取所有粉丝成功!', ''); } } diff --git a/application/wechat/controller/Keys.php b/application/wechat/controller/Keys.php index dfb42d4c9..e546f04e6 100644 --- a/application/wechat/controller/Keys.php +++ b/application/wechat/controller/Keys.php @@ -16,11 +16,12 @@ namespace app\wechat\controller; use controller\BasicAdmin; use service\DataService; +use service\WechatService; use think\Db; /** * 微信文章管理 - * Class Article + * Class Keys * @package app\wechat\controller * @author Anyon * @date 2017/03/27 14:43 @@ -36,28 +37,35 @@ class Keys extends BasicAdmin /** * 显示关键字列表 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function index() { $this->assign('title', '微信关键字'); - $db = Db::name($this->table)->where('keys', 'not in', ['subscribe', 'default']); - return $this->_list($db); + $db = Db::name($this->table)->whereNotIn('keys', ['subscribe', 'default']); + return $this->_list($db->order('id desc')); } /** * 列表数据处理 * @param array $data + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException */ protected function _index_data_filter(&$data) { $types = [ 'keys' => '关键字', 'image' => '图片', 'news' => '图文', - 'music' => '音乐', 'text' => '文字', 'video' => '视频', 'voice' => '语音' + 'music' => '音乐', 'text' => '文字', 'video' => '视频', 'voice' => '语音', ]; - $wechat = load_wechat('Extends'); + $wechat = WechatService::qrcode(); foreach ($data as &$vo) { - $result = $wechat->getQRCode($vo['keys'], 1); - $vo['qrc'] = $wechat->getQRUrl($result['ticket']); + $result = $wechat->create($vo['keys']); + $vo['qrc'] = $wechat->url($result['ticket']); $vo['type'] = isset($types[$vo['type']]) ? $types[$vo['type']] : $vo['type']; } } @@ -65,6 +73,10 @@ class Keys extends BasicAdmin /** * 添加关键字 * @return string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function add() { @@ -73,21 +85,10 @@ class Keys extends BasicAdmin } /** - * 编辑关键字 - * @return string + * 添加数据处理 + * @param array $data */ - public function edit() - { - $this->title = '编辑关键字规则'; - return $this->_form($this->table, 'form'); - } - - - /** - * 表单处理 - * @param $data - */ - protected function _form_filter($data) + protected function _add_form_filter(array $data) { if ($this->request->isPost() && isset($data['keys'])) { $db = Db::name($this->table)->where('keys', $data['keys']); @@ -96,8 +97,33 @@ class Keys extends BasicAdmin } } + /** + * 编辑关键字 + * @return string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit() + { + $this->title = '编辑关键字规则'; + return $this->_form($this->table, 'form'); + } + + /** + * 编辑数据处理 + * @param array $data + */ + protected function _edit_form_filter(array $data) + { + $this->_add_form_filter($data); + } + /** * 删除关键字 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function del() { @@ -107,9 +133,10 @@ class Keys extends BasicAdmin $this->error("关键字删除失败,请稍候再试!"); } - /** * 关键字禁用 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function forbid() { @@ -121,6 +148,8 @@ class Keys extends BasicAdmin /** * 关键字禁用 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function resume() { @@ -132,6 +161,11 @@ class Keys extends BasicAdmin /** * 关注默认回复 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function subscribe() { @@ -141,19 +175,26 @@ class Keys extends BasicAdmin /** * 关注默认回复表单处理 - * @param $data + * @param array $data + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ protected function _subscribe_form_filter(&$data) { if ($this->request->isGet()) { - $data = Db::name($this->table)->where('keys', 'subscribe')->find(); + $data = Db::name($this->table)->where(['keys' => 'subscribe'])->find(); } $data['keys'] = 'subscribe'; } - /** * 无配置默认回复 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function defaults() { @@ -161,16 +202,35 @@ class Keys extends BasicAdmin return $this->_form($this->table, 'form'); } - /** * 无配置默认回复表单处理 - * @param $data + * @param array $data + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ protected function _defaults_form_filter(&$data) { if ($this->request->isGet()) { - $data = Db::name($this->table)->where('keys', 'default')->find(); + $data = Db::name($this->table)->where(['keys' => 'default'])->find(); } $data['keys'] = 'default'; } + + /** + * 编辑结果处理 + * @param $result + */ + protected function _form_result($result) + { + if ($result !== false) { + list($url, $keys) = ['', $this->request->post('keys')]; + if (!in_array($keys, ['subscribe', 'default'])) { + $url = url('@admin') . '#' . url('wechat/keys/index') . '?spm=' . $this->request->get('spm'); + } + $this->success('恭喜, 关键字保存成功!', $url); + } + $this->error('关键字保存失败, 请稍候再试!'); + } + } diff --git a/application/wechat/controller/Menu.php b/application/wechat/controller/Menu.php index 57c0c650f..b8d13adaf 100644 --- a/application/wechat/controller/Menu.php +++ b/application/wechat/controller/Menu.php @@ -17,6 +17,7 @@ namespace app\wechat\controller; use controller\BasicAdmin; use service\LogService; use service\ToolsService; +use service\WechatService; use think\Db; /** @@ -57,7 +58,12 @@ class Menu extends BasicAdmin ]; /** - * 显示列表操作 + * 显示菜单列表 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function index() { @@ -81,26 +87,31 @@ class Menu extends BasicAdmin if ($this->request->isPost()) { $post = $this->request->post(); !isset($post['data']) && $this->error('访问出错,请稍候再试!'); - $data = $post['data']; - if (empty($data)) { - Db::name($this->table)->where('1=1')->delete(); - load_wechat('Menu')->deleteMenu(); + // 删除菜单 + if (empty($post['data'])) { + try { + Db::name($this->table)->where('1=1')->delete(); + WechatService::menu()->delete(); + } catch (\Exception $e) { + $this->error('删除取消微信菜单失败,请稍候再试!' . $e->getMessage()); + } $this->success('删除并取消微信菜单成功!', ''); } - foreach ($data as &$vo) { - if (isset($vo['content'])) { - $vo['content'] = str_replace('"', "'", $vo['content']); + // 数据过滤处理 + try { + foreach ($post['data'] as &$vo) { + isset($vo['content']) && ($vo['content'] = str_replace('"', "'", $vo['content'])); } + Db::transaction(function () use ($post) { + Db::name($this->table)->where('1=1')->delete(); + Db::name($this->table)->insertAll($post['data']); + }); + $this->_push(); + } catch (\Exception $e) { + $this->error('微信菜单发布失败,请稍候再试!' . $e->getMessage()); } - if (Db::name($this->table)->where('1=1')->delete() !== false && Db::name($this->table)->insertAll($data) !== false) { - $result = $this->_push(); - if ($result['status']) { - LogService::write('微信管理', '发布微信菜单成功'); - $this->success('保存发布菜单成功!', ''); - } - $this->error('菜单发布失败,' . $result['errmsg']); - } - $this->error('保存发布菜单失败!'); + LogService::write('微信管理', '发布微信菜单成功'); + $this->success('保存发布菜单成功!', ''); } } @@ -109,40 +120,47 @@ class Menu extends BasicAdmin */ public function cancel() { - $wehcat = load_wechat('Menu'); - if (false !== $wehcat->deleteMenu()) { - $this->success('菜单取消成功,重新关注可立即生效!', ''); + try { + WechatService::menu()->delete(); + } catch (\Exception $e) { + $this->error('菜单取消失败'); } - $this->error('菜单取消失败,' . $wehcat->getError()); + $this->success('菜单取消成功,重新关注可立即生效!', ''); } /** * 菜单推送 + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ - protected function _push() + private function _push() { - list($map, $fields) = [['status' => '1'], 'id,index,pindex,name,type,content']; - $result = (array)Db::name($this->table)->field($fields)->where($map)->order('sort ASC,id ASC')->select(); + list($map, $field) = [['status' => '1'], 'id,index,pindex,name,type,content']; + $result = (array)Db::name($this->table)->field($field)->where($map)->order('sort ASC,id ASC')->select(); foreach ($result as &$row) { empty($row['content']) && $row['content'] = uniqid(); - if ($row['type'] === 'miniprogram') : + if ($row['type'] === 'miniprogram') { list($row['appid'], $row['url'], $row['pagepath']) = explode(',', "{$row['content']},,"); - elseif ($row['type'] === 'view') : + } elseif ($row['type'] === 'view') { if (preg_match('#^(\w+:)?//#', $row['content'])) { $row['url'] = $row['content']; } else { $row['url'] = url($row['content'], '', true, true); } - elseif ($row['type'] === 'event') : + } elseif + ($row['type'] === 'event') { if (isset($this->menuType[$row['content']])) { list($row['type'], $row['key']) = [$row['content'], "wechat_menu#id#{$row['id']}"]; - }; - elseif ($row['type'] === 'media_id'): + } + } elseif ($row['type'] === 'media_id') { $row['media_id'] = $row['content']; - else : + } else { $row['key'] = "wechat_menu#id#{$row['id']}"; !in_array($row['type'], $this->menuType) && $row['type'] = 'click'; - endif; + } unset($row['content']); } $menus = ToolsService::arr2tree($result, 'index', 'pindex', 'sub_button'); @@ -157,11 +175,7 @@ class Menu extends BasicAdmin } unset($menu['type']); } - $wechat = load_wechat('Menu'); - if (false !== $wechat->createMenu(['button' => $menus])) { - return ['status' => true, 'errmsg' => '']; - } - return ['status' => false, 'errmsg' => $wechat->getError()]; + WechatService::menu()->create(['button' => $menus]); } } diff --git a/application/wechat/controller/News.php b/application/wechat/controller/News.php index 0f71174bf..74bb21803 100644 --- a/application/wechat/controller/News.php +++ b/application/wechat/controller/News.php @@ -14,13 +14,14 @@ namespace app\wechat\controller; +use app\wechat\service\MediaService; use controller\BasicAdmin; use service\DataService; use service\LogService; +use service\ToolsService; use service\WechatService; use think\Db; -use think\Log; -use think\response\View; +use think\facade\Log; /** * 微信图文管理 @@ -40,26 +41,67 @@ class News extends BasicAdmin /** * 图文列表 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function index() { - $this->assign('title', '图文列表'); - $db = Db::name($this->table)->where('is_deleted', '0')->order('id desc'); - return parent::_list($db); + $this->title = '微信图文列表'; + $db = Db::name($this->table)->where(['is_deleted' => '0']); + return parent::_list($db->order('id desc')); + } + + /** + * 图文列表数据处理 + * @param array $data + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + protected function _index_data_filter(&$data) + { + foreach ($data as &$vo) { + $vo = MediaService::getNewsById($vo['id']); + } } /** * 图文选择器 * @return string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function select() { return $this->index(); } + /** + * 图文列表数据处理 + * @param array $data + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + protected function _select_data_filter(&$data) + { + foreach ($data as &$vo) { + $vo = MediaService::getNewsById($vo['id']); + } + } + /** * 媒体资源显示 * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function image() { @@ -68,43 +110,24 @@ class News extends BasicAdmin return $this->_list(Db::name('WechatNewsMedia')->where('type', 'image')); } - /** - * 图文列表数据处理 - * @param $data - */ - protected function _index_data_filter(&$data) - { - foreach ($data as &$vo) { - $vo = WechatService::getNewsById($vo['id']); - } - } - - /** - * 图文列表数据处理 - * @param $data - */ - protected function _select_data_filter(&$data) - { - foreach ($data as &$vo) { - $vo = WechatService::getNewsById($vo['id']); - } - } - /** * 添加图文 - * @return View + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function add() { if ($this->request->isGet()) { - return view('form', ['title' => '新建图文']); + return $this->fetch('form', ['title' => '新建图文']); } if ($this->request->isPost()) { $data = $this->request->post(); if (($ids = $this->_apply_news_article($data['data'])) && !empty($ids)) { $post = ['article_id' => $ids, 'create_by' => session('user.id')]; if (DataService::save($this->table, $post, 'id') !== false) { - $this->success('图文添加成功!', ''); + $url = url('@admin') . '#' . url('@wechat/news/index') . '?spm=' . $this->request->get('spm'); + $this->success('图文添加成功!', $url); } } $this->error('图文添加失败,请稍候再试!'); @@ -113,21 +136,24 @@ class News extends BasicAdmin /** * 编辑图文 - * @return View + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function edit() { $id = $this->request->get('id', ''); if ($this->request->isGet()) { empty($id) && $this->error('参数错误,请稍候再试!'); - return view('form', ['title' => '编辑图文', 'vo' => WechatService::getNewsById($id)]); + return $this->fetch('form', ['title' => '编辑图文', 'vo' => MediaService::getNewsById($id)]); } $data = $this->request->post(); $ids = $this->_apply_news_article($data['data']); if (!empty($ids)) { $post = ['id' => $id, 'article_id' => $ids, 'create_by' => session('user.id')]; if (false !== DataService::save('wechat_news', $post, 'id')) { - $this->success('图文更新成功!', ''); + $url = url('@admin') . '#' . url('@wechat/news/index') . '?spm=' . $this->request->get('spm'); + $this->success('图文更新成功!', $url); } } $this->error('图文更新失败,请稍候再试!'); @@ -138,13 +164,17 @@ class News extends BasicAdmin * @param array $data * @param array $ids * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ protected function _apply_news_article($data, $ids = []) { foreach ($data as &$vo) { $vo['create_by'] = session('user.id'); $vo['create_at'] = date('Y-m-d H:i:s'); - $vo['digest'] = empty($vo['digest']) ? mb_substr(strip_tags(str_replace(["\s", ' '], '', $vo['content'])), 0, 120) : $vo['digest']; + if (empty($vo['digest'])) { + $vo['digest'] = mb_substr(strip_tags(str_replace(["\s", ' '], '', htmlspecialchars_decode($vo['content']))), 0, 120); + } if (empty($vo['id'])) { $result = $id = Db::name('WechatNewsArticle')->insertGetId($vo); } else { @@ -160,6 +190,8 @@ class News extends BasicAdmin /** * 删除用户 + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function del() { @@ -172,6 +204,13 @@ class News extends BasicAdmin /** * 推荐图文 * @return array + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException */ public function push() { @@ -183,19 +222,21 @@ class News extends BasicAdmin } list($ids, $db) = [explode(',', $params), Db::name('WechatFans')]; !in_array('0', $ids) && $db->where("concat(',',tagid_list,',') REGEXP '," . join(',|,', $ids) . ",'"); - return ['code' => "SUCCESS", 'data' => $db->where('subscribe', '1')->limit(200)->column('nickname')]; + $list = $db->where(['subscribe' => '1'])->limit(200)->column('nickname'); + foreach ($list as &$vo) { + $vo = ToolsService::emojiDecode($vo); + } + return ['code' => "SUCCESS", 'data' => $list]; default : $news_id = $this->request->get('id', ''); // 显示及图文 - $newsinfo = WechatService::getNewsById($news_id); + $newsinfo = MediaService::getNewsById($news_id); // Get 请求,显示选择器界面 if ($this->request->isGet()) { - $fans_tags = Db::name('WechatFansTags')->select(); - array_unshift($fans_tags, [ - 'id' => 0, 'name' => '全部', - 'count' => Db::name('WechatFans')->where('subscribe', '1')->count(), - ]); - return view('push', ['vo' => $newsinfo, 'fans_tags' => $fans_tags]); + $fans_tags = (array)Db::name('WechatFansTags')->select(); + $count = Db::name('WechatFans')->where(['subscribe' => '1'])->count(); + array_unshift($fans_tags, ['id' => 0, 'name' => '全部', 'count' => $count]); + return $this->fetch('push', ['vo' => $newsinfo, 'fans_tags' => $fans_tags]); } // Post 请求,执行图文推送操作 $post = $this->request->post(); @@ -213,39 +254,43 @@ class News extends BasicAdmin $data['filter'] = ['is_to_all' => false, 'tag_id' => join(',', $post['fans_tags'])]; $data['mpnews'] = ['media_id' => $newsinfo['media_id']]; } - $wechat = load_wechat('Receive'); - if (false !== $wechat->sendGroupMassMessage($data)) { + $wechat = WechatService::custom(); + if (false !== $wechat->massSendAll($data)) { LogService::write('微信管理', "图文[{$news_id}]推送成功"); $this->success('微信图文推送成功!', ''); } - $this->error("微信图文推送失败,{$wechat->errMsg} [{$wechat->errCode}]"); + $this->error("微信图文推送失败"); } } /** * 上传永久图文 - * @param array $newsinfo + * @param array $news * @return bool + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\exception\PDOException */ - private function _uploadWechatNews(&$newsinfo) + private function _uploadWechatNews(&$news) { - foreach ($newsinfo['articles'] as &$article) { - $article['thumb_media_id'] = WechatService::uploadForeverMedia($article['local_url']); + foreach ($news['articles'] as &$article) { + $article['thumb_media_id'] = MediaService::uploadForeverMedia($article['local_url']); $article['content'] = preg_replace_callback("//i", function ($matches) { - $src = WechatService::uploadImage($matches[2]); + $src = MediaService::uploadImage($matches[2]); return ""; }, htmlspecialchars_decode($article['content'])); } - $wechat = load_wechat('media'); + $wechat = WechatService::media(); // 如果已经上传过,先删除之前的历史记录 - !empty($newsinfo['media_id']) && $wechat->delForeverMedia($newsinfo['media_id']); + !empty($news['media_id']) && $wechat->delMaterial($news['media_id']); // 上传图文到微信服务器 - $result = $wechat->uploadForeverArticles(['articles' => $newsinfo['articles']]); + $result = $wechat->addNews(['articles' => $news['articles']]); if (isset($result['media_id'])) { - $newsinfo['media_id'] = $result['media_id']; - return Db::name('WechatNews')->where('id', $newsinfo['id'])->setField('media_id', $result['media_id']); + $news['media_id'] = $result['media_id']; + return Db::name('WechatNews')->where(['id' => $news['id']])->update(['media_id' => $result['media_id']]); } - Log::error("上传永久图文失败, {$wechat->errMsg}[{$wechat->errCode}]"); + Log::error("上传永久图文失败"); return false; } diff --git a/application/wechat/controller/Notify.php b/application/wechat/controller/Notify.php deleted file mode 100644 index 4e03196e8..000000000 --- a/application/wechat/controller/Notify.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @date 2017/04/05 14:02 - */ -class Notify extends Controller -{ - - public function index() - { - // 实例支付接口 - $pay = load_wechat('Pay'); - - // 获取支付通知 - $notifyInfo = $pay->getNotify(); - - // 支付通知数据获取失败 - if ($notifyInfo === false) { - // 接口失败的处理 - Log::error("微信支付通知消息验证失败,{$pay->errCode}[{$pay->errCode}]"); - return $pay->errMsg; - } else { - //支付通知数据获取成功 - if ($notifyInfo['result_code'] == 'SUCCESS' && $notifyInfo['return_code'] == 'SUCCESS') { - // 记录支付通知数据 - if (!Db::name('WechatPayNotify')->insert($notifyInfo)) { - $pay->replyXml(['return_code' => 'ERROR', 'return_msg' => '系统记录微信通知时发生异常!']); - } - $prepayMap = ['out_trade_no' => $notifyInfo['out_trade_no']]; - $prepayData = Db::name('WechatPayPrepayid')->where($prepayMap)->find(); - if (empty($prepayData)) { - $pay->replyXml(['return_code' => 'ERROR', 'return_msg' => '系统中未发现对应的预支付记录!']); - } - $prepayUpdateData = ['transaction_id' => $notifyInfo['transaction_id'], 'is_pay' => 1, 'pay_at' => date('Y-m-d H:i:s')]; - if (false === Db::name('WechatPayPrepayid')->where($prepayMap)->update($prepayUpdateData)) { - $pay->replyXml(['return_code' => 'ERROR', 'return_msg' => '更新系统预支付记录失败!']); - } - // 支付状态完全成功,可以更新订单的支付状态了 - // @todo 这里去完成你的订单状态修改操作 - // 回复xml,replyXml方法是终态方法 - $pay->replyXml(['return_code' => 'SUCCESS', 'return_msg' => '系统业务处理成功!']); - } - } - } - -} diff --git a/application/wechat/controller/Review.php b/application/wechat/controller/Review.php index 998922257..5ea6bd62c 100644 --- a/application/wechat/controller/Review.php +++ b/application/wechat/controller/Review.php @@ -14,27 +14,32 @@ namespace app\wechat\controller; -use service\FileService; -use service\WechatService; +use app\wechat\service\MediaService; use think\Controller; use think\Db; +/** + * 微信素材预览 + * Class Review + * @package app\wechat\controller + */ class Review extends Controller { /** * 显示手机预览 * @return string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function index() { - $get = $this->request->get(); $content = str_replace("\n", "
    ", $this->request->get('content', '')); // 内容 $type = $this->request->get('type', 'text'); // 类型 - $this->assign('type', $type); // 图文处理 if ($type === 'news' && is_numeric($content) && !empty($content)) { - $news = WechatService::getNewsById($content); + $news = MediaService::getNewsById($content); $this->assign('articles', $news['articles']); } // 文章预览 @@ -43,26 +48,14 @@ class Review extends Controller if (!empty($article['content_source_url'])) { $this->redirect($article['content_source_url']); } + $article['content'] = htmlspecialchars_decode($article['content']); $this->assign('vo', $article); } - $this->assign($get); + $this->assign('type', $type); $this->assign('content', $content); + $this->assign($this->request->get()); // 渲染模板并显示 - return view(); - } - - /** - * 微信图片显示 - */ - public function img() - { - $url = $this->request->get('url', ''); - $filename = FileService::getFileName($url, 'jpg', 'tmp/'); - if (false === ($img = FileService::getFileUrl($filename))) { - $info = FileService::save($filename, file_get_contents($url)); - $img = (is_array($info) && isset($info['url'])) ? $info['url'] : $url; - } - $this->redirect($img); + return $this->fetch(); } } diff --git a/application/wechat/controller/Tags.php b/application/wechat/controller/Tags.php index 7d9ae77e9..52350718f 100644 --- a/application/wechat/controller/Tags.php +++ b/application/wechat/controller/Tags.php @@ -14,6 +14,7 @@ namespace app\wechat\controller; +use app\wechat\service\TagsService; use controller\BasicAdmin; use service\DataService; use service\LogService; @@ -24,8 +25,6 @@ use think\Db; * 微信粉丝标签管理 * Class Tags * @package app\wechat\controller - * @author Anyon - * @date 2017/03/27 14:43 */ class Tags extends BasicAdmin { @@ -39,22 +38,31 @@ class Tags extends BasicAdmin /** * 显示粉丝标签列表 * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ public function index() { $this->title = '微信粉丝标签管理'; - $get = $this->request->get(); - $db = Db::name($this->table)->order('id asc'); + list($get, $db) = [$this->request->get(), Db::name($this->table)]; foreach (['name'] as $key) { - if (isset($get[$key]) && $get[$key] !== '') { - $db->where($key, 'like', "%{$get[$key]}%"); - } + (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%"); } - return parent::_list($db); + return parent::_list($db->order('id asc')); } /** * 添加粉丝标签 + * @return array|string + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException */ public function add() { @@ -63,18 +71,27 @@ class Tags extends BasicAdmin } $name = $this->request->post('name', ''); empty($name) && $this->error('粉丝标签名不能为空!'); - (Db::name($this->table)->where('name', $name)->count() > 0) && $this->error('粉丝标签标签名已经存在, 请使用其它标签名!'); - $wechat = load_wechat('User'); + if (Db::name($this->table)->where('name', $name)->count() > 0) { + $this->error('粉丝标签标签名已经存在, 请使用其它标签名!'); + } + $wechat = WechatService::tags(); if (false === ($result = $wechat->createTags($name)) && isset($result['tag'])) { - $this->error("添加粉丝标签失败. {$wechat->errMsg}[{$wechat->errCode}]"); + $this->error("添加粉丝标签失败. "); } $result['tag']['count'] = 0; - DataService::save($this->table, $result['tag'], 'id') && $this->success('添加粉丝标签成功!', ''); + if (DataService::save($this->table, $result['tag'], 'id')) { + $this->success('添加粉丝标签成功!', ''); + } $this->error('粉丝标签添加失败, 请稍候再试!'); } /** * 编辑粉丝标签 + * @return array|string + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public function edit() { @@ -83,29 +100,56 @@ class Tags extends BasicAdmin return parent::_form($this->table, 'form', 'id'); } // 接收提交的数据 - list($name, $id) = [$this->request->post('name', ''), $this->request->post('id', '0')]; - $info = Db::name($this->table)->where('name', $name)->find(); + $id = $this->request->post('id', '0'); + $name = $this->request->post('name', ''); + $info = Db::name($this->table)->where(['name' => $name])->find(); if (!empty($info)) { if (intval($info['id']) === intval($id)) { $this->error('粉丝标签名没有改变, 无需修改!'); } $this->error('标签已经存在, 使用其它名称再试!'); } - $wechat = load_wechat('User'); - $data = ['id' => $id, 'name' => $name]; - if (false !== $wechat->updateTag($id, $name) && false !== DataService::save($this->table, $data, 'id')) { - $this->success('编辑标签成功!', ''); + try { + WechatService::tags()->updateTags($id, $name); + DataService::save($this->table, ['id' => $id, 'name' => $name], 'id'); + } catch (\Exception $e) { + $this->error('编辑标签失败, 请稍后再试!' . $e->getMessage()); } - $this->error('编辑标签失败, 请稍后再试!' . $wechat->errMsg); + $this->success('编辑标签成功!', ''); + } + + + /** + * 删除粉丝标签 + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function del() + { + $wechat = WechatService::tags(); + foreach (explode(',', $this->request->post('id', '')) as $id) { + if ($wechat->deleteTags($id)) { + Db::name('WechatFansTags')->where(['id' => $id])->delete(); + } else { + $this->error('移除粉丝标签失败,请稍候再试!'); + } + } + $this->success('移除粉丝标签成功!', ''); } /** * 同步粉丝标签列表 + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\exception\PDOException */ public function sync() { Db::name($this->table)->where('1=1')->delete(); - if (WechatService::syncFansTags()) { + if (TagsService::sync()) { LogService::write('微信管理', '同步全部微信粉丝标签成功'); $this->success('同步获取所有粉丝标签成功!', ''); } diff --git a/application/wechat/controller/api/Push.php b/application/wechat/controller/api/Push.php new file mode 100644 index 000000000..9e9ea9c4a --- /dev/null +++ b/application/wechat/controller/api/Push.php @@ -0,0 +1,256 @@ + + */ +class Push +{ + + /** + * 当前公众号APPID + * @var string + */ + protected $appid; + + /** + * 当前微信用户openid + * @var string + */ + protected $openid; + + /** + * 当前微信消息对象 + * @var array + */ + protected $receive; + + /** + * 微信消息接口 + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function __construct() + { + $request = app('request'); + $this->appid = $request->post('appid', '', null); + $this->openid = $request->post('openid', '', null); + $this->receive = unserialize($request->post('receive', '', null)); + p($this->receive); + if (empty($this->appid) || empty($this->openid) || empty($this->receive)) { + throw new Exception('微信API实例缺失必要参数[appid,openid,event].'); + } + if ($this->appid !== sysconf('wechat_appid')) { + throw new Exception('微信API实例APPID验证失败.'); + } + // text,event,image,location + if (method_exists($this, ($method = $this->receive['MsgType']))) { + $this->$method(); + } + } + + /** + * 文件消息处理 + * @return bool + * @throws \Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + protected function text() + { + return $this->keys("wechat_keys#keys#{$this->receive['Content']}"); + } + + /** + * 事件消息处理 + * @return bool|string + * @throws \Exception + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + protected function event() + { + switch (strtolower($this->receive['Event'])) { + case 'subscribe': + $this->updateFansinfo(true); + if (isset($this->receive['EventKey']) && is_string($this->receive['EventKey'])) { + if (($key = preg_replace('/^qrscene_/i', '', $this->receive['EventKey']))) { + [$this->updateSpread($key), $this->keys("wechat_keys#keys#{$key}")]; + } + } + return $this->keys('wechat_keys#keys#subscribe', true); + case 'unsubscribe': + return $this->updateFansinfo(false); + case 'click': + return $this->keys($this->receive['EventKey']); + case 'scancode_push': + case 'scancode_waitmsg': + if (isset($this->receive['ScanCodeInfo'])) { + $this->receive['ScanCodeInfo'] = (array)$this->receive['ScanCodeInfo']; + if (!empty($this->receive['ScanCodeInfo']['ScanResult'])) { + return $this->keys("wechat_keys#keys#{$this->receive['ScanCodeInfo']['ScanResult']}"); + } + } + return false; + case 'scan': + if (!empty($this->receive['EventKey'])) { + return $this->keys("wechat_keys#keys#{$this->receive['EventKey']}"); + } + return false; + } + return false; + } + + /** + * 关键字处理 + * @param string $rule 关键字规则 + * @param bool $isLastReply 强制结束 + * @return bool + * @throws \Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + protected function keys($rule, $isLastReply = false) + { + list($table, $field, $value) = explode('#', $rule . '##'); + $info = Db::name($table)->where($field, $value)->find(); + p($info); + if (empty($info['type']) || (array_key_exists('status', $info) && empty($info['status']))) { + // 切换默认回复 + return $isLastReply ? false : $this->keys('wechat_keys#keys#default', true); + } + switch ($info['type']) { + case 'customservice': + return $this->sendMessage('text', ['content' => $info['content']]); + case 'keys': + $content = empty($info['content']) ? $info['name'] : $info['content']; + return $this->keys("wechat_keys#keys#{$content}"); + case 'text': + return $this->sendMessage('text', ['content' => $info['content']]); + case 'news': + list($news, $data) = [MediaService::getNewsById($info['news_id']), []]; + if (empty($news['articles'])) { + return false; + } + foreach ($news['articles'] as $vo) { + $url = url("@wechat/review", '', true, true) . "?content={$vo['id']}&type=article"; + $data[] = ['url' => $url, 'title' => $vo['title'], 'picurl' => $vo['local_url'], 'description' => $vo['digest']]; + } + return $this->sendMessage('news', ['articles' => $data]); + case 'music': + if (empty($info['music_url']) || empty($info['music_title']) || empty($info['music_desc'])) { + return false; + } + $media_id = empty($info['music_image']) ? '' : MediaService::uploadForeverMedia($info['music_image'], 'image'); + $data = ['title' => $info['music_title'], 'description' => $info['music_desc'], 'musicurl' => $info['music_url'], 'hqmusicurl' => $info['music_url'], 'thumb_media_id' => $media_id]; + return $this->sendMessage('music', $data); + case 'voice': + if (empty($info['voice_url']) || !($media_id = MediaService::uploadForeverMedia($info['voice_url'], 'voice'))) { + return false; + } + return $this->sendMessage('voice', ['media_id' => $media_id]); + case 'image': + if (empty($info['image_url']) || !($media_id = MediaService::uploadForeverMedia($info['image_url'], 'image'))) { + return false; + } + return $this->sendMessage('image', ['media_id' => $media_id]); + case 'video': + if (empty($info['video_url']) || empty($info['video_desc']) || empty($info['video_title'])) { + return false; + } + $videoData = ['title' => $info['video_title'], 'introduction' => $info['video_desc']]; + if (!($media_id = MediaService::uploadForeverMedia($info['video_url'], 'video', $videoData))) { + return false; + } + $data = ['media_id' => $media_id, 'title' => $info['video_title'], 'description' => $info['video_desc']]; + return $this->sendMessage('video', $data); + default: + return false; + } + } + + /** + * 发送消息到公众号 + * @param string $type 消息类型(text|image|voice|video|music|news|mpnews|wxcard) + * @param array $data 消息内容 + * @return array|bool + * @throws \Exception + */ + protected function sendMessage($type, $data) + { + $msgData = ['touser' => $this->openid, 'msgtype' => $type, "{$type}" => $data]; + return WechatService::custom()->send($msgData); + } + + /** + * 更新推荐二维码关系 + * @param string $key + * @return bool + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + protected function updateSpread($key) + { + // 检测推荐是否有效 + $fans = Db::name('WechatFans')->where(['openid' => $key])->find(); + if (empty($fans['openid']) || $fans['openid'] === $this->openid) { + return false; + } + // 标识推荐关系 + $data = ['spread_openid' => $fans['openid'], 'spread_at' => date('Y-m-d H:i:s')]; + $where = "openid='{$this->openid}' and (spread_openid is null or spread_openid='')"; + return Db::name('WechatFans')->where($where)->update($data) !== false; + } + + /** + * 同步粉丝状态 + * @param bool $subscribe 关注状态 + * @return string + * @throws \Exception + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + protected function updateFansinfo($subscribe = true) + { + if ($subscribe) { + $userInfo = WechatService::user()->getUserInfo($this->openid); + $userInfo['subscribe'] = intval($subscribe); + FansService::set($userInfo); + } else { + $fans = ['subscribe' => '0', 'openid' => $this->openid, 'appid' => $this->appid]; + DataService::save('WechatFans', $fans, 'openid', ['appid' => $this->appid]); + } + } + +} diff --git a/application/wechat/service/FansService.php b/application/wechat/service/FansService.php new file mode 100644 index 000000000..d7732cbb7 --- /dev/null +++ b/application/wechat/service/FansService.php @@ -0,0 +1,123 @@ + $openid, 'appid' => sysconf('wechat_appid')]; + $user = Db::name('WechatFans')->where($map)->find(); + foreach (['country', 'province', 'city', 'nickname', 'remark'] as $k) { + isset($user[$k]) && $user[$k] = ToolsService::emojiDecode($user[$k]); + } + return $user; + } + + /** + * 同步所有粉丝记录 + * @param string $next_openid + * @return bool + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function sync($next_openid = '') + { + $wechat = WechatService::user(); + $result = $wechat->getUserList($next_openid); + if (empty($result['data']['openid'])) { + return false; + } + foreach (array_chunk($result['data']['openid'], 100) as $openids) { + foreach ($wechat->getBatchUserInfo($openids)['user_info_list'] as $user) { + if (false === self::set($user)) { + return false; + } + if ($result['next_openid'] === $user['openid']) { + unset($result['next_openid']); + } + } + } + return empty($result['next_openid']) ? true : self::sync($result['next_openid']); + } + + /** + * 同步获取黑名单信息 + * @param string $next_openid + * @return bool + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function syncBlack($next_openid = '') + { + $wechat = WechatService::user(); + $result = $wechat->getBlackList($next_openid); + foreach (array_chunk($result['data']['openid'], 100) as $openids) { + $info = $wechat->getBatchUserInfo($openids); + foreach ($info as $user) { + $user['is_black'] = '1'; + if (self::set($user) && $result['next_openid'] === $user['openid']) { + unset($result['next_openid']); + } + } + } + return empty($result['next_openid']) ? true : self::syncBlack($result['next_openid']); + } + +} \ No newline at end of file diff --git a/application/wechat/service/MediaService.php b/application/wechat/service/MediaService.php new file mode 100644 index 000000000..342c0dbf7 --- /dev/null +++ b/application/wechat/service/MediaService.php @@ -0,0 +1,101 @@ +where(['id' => $id])->where($where)->find(); + $article_ids = explode(',', $data['article_id']); + $articles = Db::name('WechatNewsArticle')->whereIn('id', $article_ids)->select(); + $data['articles'] = []; + foreach ($article_ids as $article_id) { + foreach ($articles as $article) { + if (intval($article['id']) === intval($article_id)) { + unset($article['create_by'], $article['create_at']); + $article['content'] = htmlspecialchars_decode($article['content']); + $data['articles'][] = $article; + } + } + } + return $data; + } + + /** + * 上传图片到微信服务器 + * @param string $local_url 图文地址 + * @return string + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + */ + public static function uploadImage($local_url) + { + $media_url = Db::name('WechatNewsImage')->where(['md5' => md5($local_url)])->value('media_url'); + if (!empty($media_url)) { + return $media_url; + } + $result = WechatService::wechat()->upFile(base64_encode(file_get_contents($local_url)), $local_url); + $info = WechatService::media()->uploadImg($result['file']); + WechatService::wechat()->rmFile($local_url); + $data = ['local_url' => $local_url, 'media_url' => $info['url'], 'md5' => md5($local_url)]; + Db::name('WechatNewsImage')->insert($data); + return $info['url']; + } + + /** + * 上传图片永久素材,返回素材media_id + * @param string $local_url 文件URL地址 + * @param string $type 文件类型 + * @param array $video_info 视频信息 + * @return string|null + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function uploadForeverMedia($local_url, $type = 'image', $video_info = []) + { + $map = ['md5' => md5($local_url), 'appid' => sysconf('wechat_appid')]; + if (($media_id = Db::name('WechatNewsMedia')->where($map)->value('media_id'))) { + return $media_id; + } + $result = WechatService::wechat()->upFile(base64_encode(file_get_contents($local_url)), $local_url); + $result = WechatService::media()->addMaterial($result['file'], $type, $video_info); + WechatService::wechat()->rmFile($local_url); + $data = ['md5' => $map['md5'], 'type' => $type, 'appid' => $map['appid'], 'media_id' => $result['media_id'], 'local_url' => $local_url]; + isset($result['url']) && $data['media_url'] = $result['url']; + Db::name('WechatNewsMedia')->insert($data); + return $data['media_id']; + } + +} \ No newline at end of file diff --git a/application/wechat/service/TagsService.php b/application/wechat/service/TagsService.php new file mode 100644 index 000000000..52ec49ffc --- /dev/null +++ b/application/wechat/service/TagsService.php @@ -0,0 +1,69 @@ +getUserTagId($openid); + if (!is_array($tagsid)) { + return false; + } + $data = ['openid' => $openid, 'tagid_list' => join(',', $tagsid)]; + return DataService::save('WechatFans', $data, 'openid', ['appid' => sysconf('wechat_appid')]); + } + + /** + * 从微信服务器获取所有标签 + * @return bool + * @throws \WeChat\Exceptions\InvalidResponseException + * @throws \WeChat\Exceptions\LocalCacheException + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function sync() + { + $appid = sysconf('wechat_appid'); + $result = WechatService::tags()->getTags(); + Db::name('WechatFansTags')->where(['appid' => $appid])->delete(); + foreach (array_chunk($result['tags'], 100) as $list) { + foreach ($list as &$vo) { + $vo['appid'] = $appid; + } + Db::name('WechatFansTags')->insertAll($list); + } + return true; + } + +} \ No newline at end of file diff --git a/application/wechat/view/block/index.html b/application/wechat/view/block/index.html new file mode 100644 index 000000000..77e7daa21 --- /dev/null +++ b/application/wechat/view/block/index.html @@ -0,0 +1,160 @@ +{extend name='admin@public/content'} + +{block name="button"} + + + + + + + + + +{/block} + +{block name="content"} + + + + +
    + +

    没 有 记 录 哦!

    + + + + + + + + + + + + + + + + {foreach $list as $key=>$vo} + + + + + + + + + + {/foreach} + +
    + + 用户昵称性别用户标签所在区域关注时间操作
    + + + + {$vo.nickname|default='未设置微信昵称'} + + {$vo.sex==1?'男':($vo.sex==2?'女':'未知')} + + {if auth("$classuri/tagset")} + + + {/if} + {if empty($vo.tags_list)} + 尚未设置标签 + {else} + {foreach $vo.tags_list as $k=>$tag}{$tag}{/foreach} + {/if} + + {$vo.country|default='未设置区域信息'|raw}{$vo.province}{$vo.city} + {$vo.subscribe_at|format_datetime} + {if auth("$classuri/backdel")}移出黑名单{/if} +
    + {if isset($page)}

    {$page|raw}

    {/if} + +
    +{/block} + +{block name="script"} + + + +{if auth("$classuri/tagset")}{include file='wechat@fans/tags_inc'}{/if} +{/block} + diff --git a/application/wechat/view/config.index.html b/application/wechat/view/config.index.html deleted file mode 100644 index 2c5e6d85a..000000000 --- a/application/wechat/view/config.index.html +++ /dev/null @@ -1,72 +0,0 @@ -{extend name="extra@admin/content"} - -{block name="content"} -
    - -
    - -
    - -

    - 请复制此URL地址填写在公众号平台 [ 开发 >> 基本配置 ] 中 [ URL ( 服务器地址 ) ] -
    注意:URL主域名必需备案,微信服务接口只支持 80 端口 ( http ) 和 443 端口 ( https ) -

    -
    -
    - -
    - -
    - -
    - -

    - 公众号应用ID是所有接口必要参数,可以在公众号平台 [ 开发 >> 基本配置 ] 页面获取。 -

    -
    -
    - - -
    - -
    - -

    - 公众号应用密钥是所有接口必要参数,可以在公众号平台 [ 开发 >> 基本配置 ] 页面授权后获取。 -

    -
    -
    - - -
    - -
    - -

    - 公众号平台与系统对接认证Token,请优先填写此参数并保存,然后再在微信公众号平台操作对接。 -

    -
    -
    - - -
    - -
    - -

    - 公众号平台接口设置为加密模式,消息加密密钥必需填写并保持与公众号平台一致。 -

    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -{/block} diff --git a/application/wechat/view/config.pay.html b/application/wechat/view/config.pay.html deleted file mode 100644 index 40746bb83..000000000 --- a/application/wechat/view/config.pay.html +++ /dev/null @@ -1,132 +0,0 @@ -{extend name="extra@admin/content"} - -{block name="style"} - -{/block} - -{block name="content"} -
    - -
    - -
    - -

    - 注意:商户ID必需与微信接口配置公众号APPID对应,否则无法使用支付功能! -

    -
    -
    - -
    - -
    - -

    - 微信支付商户密钥需要在商户平台配置,必需填写密钥之后才能正常使用微信支付功能。 -

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

    - 企业打款、企业红包、订单退款等操作需要使用双向证书,可在微信商户平台下载证书! -

    -
    -
    - -
    - -
    -
    - - -
    -
    - -
    -{/block} - -{block name="script"} - -{/block} diff --git a/application/wechat/view/config/index.html b/application/wechat/view/config/index.html new file mode 100644 index 000000000..ab72ceb93 --- /dev/null +++ b/application/wechat/view/config/index.html @@ -0,0 +1,67 @@ +{extend name="admin@public/content"} + +{block name="content"} +
    + + +
    +
    +
    +
    + +
    +
    +

    微信昵称:{$wechat.nick_name}

    +

    微信类型:{$wechat.service_type_info == 2 ? '服务号' : '订阅号'} / + {$wechat.verify_type_info == -1 ? '未认证' : '已认证'}

    +

    注册公司:{$wechat.principal_name}

    +

    授权绑定:{$wechat.create_at|format_datetime}

    +
    +
    +
    +
    + + +
    + +
    + +

    点击连接将跳转到微信第三方平台进行公众号授权。

    +
    +
    + +
    + +
    + +
    + +

    公众号 appid 通过微信第三方授权自动获取. 若没有值请进行微信第三方授权。

    +
    +
    + +
    + +
    + +

    公众号服务平台接口密钥, 通过微信第三方授权自动获取, 若没有值请进行微信第三方授权。

    +
    +
    + +
    + +
    + +

    公众号服务平台接口通知URL, 公众号消息接收与回复等。

    +
    +
    + +
    + +
    + +
    + +
    + +{/block} diff --git a/application/wechat/view/fans.back.html b/application/wechat/view/fans.back.html deleted file mode 100644 index 32f9d9532..000000000 --- a/application/wechat/view/fans.back.html +++ /dev/null @@ -1,201 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="button"} - -
    - -
    - -
    - -
    - -{/block} - -{block name="content"} - - - - - -
    - {if !empty($list)} - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - {/foreach} - -
    - - 用户昵称性别标签区域关注时间
    - - - - {$vo.nickname|default="未设置微信昵称"} - {$vo.sex==1?'男':($vo.sex==2?'女':'未知')} - - + - - - {if empty($vo.tags_list)} - 尚未设置标签 - {else} - {foreach $vo.tags_list as $k=>$tag} - - {$tag} - - {/foreach} - {/if} - - {$vo.country|default='未设置区域信息'}{$vo.province}{$vo.city}{$vo.subscribe_at}
    - {if isset($page)}

    {$page}

    {/if} - {else} -

    暂时无记录

    - {/if} -
    - -
    -
    -
    - {foreach $tags as $key=>$tag} -
    - -
    - {/foreach} -
    -
    -
    - - -
    -
    -
    -{/block} - -{block name="script"} -{if auth("$classuri/tagset")} - -{/if} -{/block} diff --git a/application/wechat/view/fans.index.html b/application/wechat/view/fans.index.html deleted file mode 100644 index fb3aa5ebe..000000000 --- a/application/wechat/view/fans.index.html +++ /dev/null @@ -1,207 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="button"} - -
    - -
    - -
    - -
    - -{/block} - -{block name="content"} - - - - - -
    - {if !empty($list)} - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - {/foreach} - -
    - - 用户昵称性别标签区域关注时间
    - - - - {$vo.nickname|default="未设置微信昵称"} - {$vo.sex==1?'男':($vo.sex==2?'女':'未知')} - - + - - - {if empty($vo.tags_list)} - 尚未设置标签 - {else} - {foreach $vo.tags_list as $k=>$tag} - - {$tag} - - {/foreach} - {/if} - - {$vo.country|default='未设置区域信息'}{$vo.province}{$vo.city}{$vo.subscribe_at}
    - {if isset($page)}

    {$page}

    {/if} - {else} -

    暂时无记录

    - {/if} -
    - -
    -
    -
    - {foreach $tags as $key=>$tag} -
    - -
    - {/foreach} -
    -
    -
    - - -
    -
    -
    -{/block} - -{block name="script"} -{if auth("$classuri/tagset")} - -{/if} -{/block} diff --git a/application/wechat/view/fans/index.html b/application/wechat/view/fans/index.html new file mode 100644 index 000000000..433638048 --- /dev/null +++ b/application/wechat/view/fans/index.html @@ -0,0 +1,160 @@ +{extend name='admin@public/content'} + +{block name="button"} + + + + + + + + + +{/block} + +{block name="content"} + + + + +
    + +

    没 有 记 录 哦!

    + + + + + + + + + + + + + + + + {foreach $list as $key=>$vo} + + + + + + + + + + {/foreach} + +
    + + 用户昵称性别标签区域关注时间
    + + + + {$vo.nickname|default='未设置微信昵称'} + + {$vo.sex==1?'男':($vo.sex==2?'女':'未知')} + + {if auth("$classuri/tagset")} + + + {/if} + {if empty($vo.tags_list)} + 尚未设置标签 + {else} + {foreach $vo.tags_list as $k=>$tag}{$tag}{/foreach} + {/if} + + {$vo.country|default='未设置区域信息'|raw}{$vo.province}{$vo.city} + {$vo.subscribe_at|format_datetime} + {if auth("$classuri/backdel")}拉黑{/if} +
    + {if isset($page)}

    {$page|raw}

    {/if} + +
    +{/block} + +{block name="script"} + + + + +{if auth("$classuri/tagset")}{include file='wechat@fans/tags_inc'}{/if} +{/block} diff --git a/application/wechat/view/fans/tags_inc.html b/application/wechat/view/fans/tags_inc.html new file mode 100644 index 000000000..85dab465e --- /dev/null +++ b/application/wechat/view/fans/tags_inc.html @@ -0,0 +1,80 @@ + + +
    +
    +
    + {foreach $tags as $key=>$tag} +
    + +
    + {/foreach} +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/application/wechat/view/keys.index.html b/application/wechat/view/keys.index.html deleted file mode 100644 index 1b10598e3..000000000 --- a/application/wechat/view/keys.index.html +++ /dev/null @@ -1,203 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="button"} -
    - - -
    -{/block} - -{block name='content'} -
    - {if !empty($list)} - - - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - - - {/foreach} - -
    - - - - 关键字回复类型回复内容更新时间状态操作
    - - - - - {if !empty($vo.qrc)} - - {/if} - {$vo.keys} - {$vo.type} - {if $vo.type eq '音乐'} - 预览 - - {elseif $vo.type eq '文字'} - 预览 - - {elseif $vo.type eq '图片'} - 预览 - - {elseif $vo.type eq '图文'} - 预览 - - {elseif $vo.type eq '视频'} - 预览 - - {else} - {$vo.content} - {/if} - {$vo.create_at} - {if $vo.status eq 0} - 已禁用 - {elseif $vo.status eq 1} - 使用中 - {/if} - - - {if auth("$classuri/edit")} - | - 编辑 - {/if} - - {if $vo.status eq 1 and auth("$classuri/forbid")} - | - 禁用 - {elseif auth("$classuri/resume")} - | - 启用 - {/if} - - {if auth("$classuri/del")} - | - 删除 - {/if} - -
    - {if isset($page)}

    {$page}

    {/if} - {else} -

    暂时无记录

    - {/if} - -
    - -{/block} - -{block name="script"} - -{/block} \ No newline at end of file diff --git a/application/wechat/view/keys.form.html b/application/wechat/view/keys/form.html similarity index 60% rename from application/wechat/view/keys.form.html rename to application/wechat/view/keys/form.html index 8d33ec24b..f585d4ab3 100644 --- a/application/wechat/view/keys.form.html +++ b/application/wechat/view/keys/form.html @@ -1,296 +1,292 @@ -{extend name='extra@admin/content'} - -{block name="style"} - -{/block} - -{block name="content"} - - -
    -
    公众号
    -
    - -
    -
    - - -
    -
    -
    -
    - {$title} -
    - {if !isset($vo.keys) or ($vo.keys neq 'default' and $vo.keys neq 'subscribe')} -
    - -
    - -
    -
    - {else} -
    - -
    -
    - {if !isset($vo.status) or $vo.status neq 0} - - - {else} - - - {/if} -
    -
    -
    - {/if} - -
    - -
    - -
    -
    - -
    - -
    - -
    -
    - -
    - -
    - 选择图文 - -
    -
    - -
    - -
    - -

    文件最大2Mb,支持bmp/png/jpeg/jpg/gif格式

    - - 上传图片 -
    -
    - -
    - -
    -
    - - -
    -

    文件最大2Mb,播放长度不超过60s,mp3/wma/wav/amr格式

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

    文件最大64KB,只支持JPG格式

    - - 上传图片 -
    -
    - -
    - -
    - -
    -
    - -
    - -
    -
    - - -
    -

    文件最大10MB,只支持MP4格式

    -
    -
    - -
    - -
    - -
    -
    - -
    -
    - - {if !isset($vo.keys) || !in_array($vo.keys,['default','subscribe'])} - - {/if} -
    - - {if isset($vo['id'])}{/if} -
    -
    -
    -
    -
    -{/block} - -{block name="script"} - +{extend name='admin@public/content'} + +{block name="style"} + +{/block} + +{block name="content"} + + +
    +
    公众号
    +
    + +
    +
    + + +
    +
    +
    +
    + {$title} +
    + +
    + +
    + +
    +
    + + + +
    + +
    +
    + {foreach ['1'=>'启用','0'=>'禁用'] as $k=>$v} + + {/foreach} +
    +
    +
    +
    + +
    + {foreach ['text'=>'文字','news'=>'图文','image'=>'图片','music'=>'音乐','video'=>'视频'] as $k=>$v} + + {/foreach} +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + 选择图文 + +
    +
    + +
    + +
    + +

    文件最大2Mb,支持bmp/png/jpeg/jpg/gif格式

    + + 上传图片 +
    +
    + +
    + +
    +
    + + +
    +

    文件最大2Mb,播放长度不超过60s,mp3/wma/wav/amr格式

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

    文件最大64KB,只支持JPG格式

    + + 上传图片 +
    +
    + +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +

    文件最大10MB,只支持MP4格式

    +
    +
    +
    + +
    + +
    +
    + +
    +
    + + + + +
    + {if isset($vo['id'])}{/if} +
    +
    +
    +
    +
    +{/block} + +{block name="script"} + {/block} \ No newline at end of file diff --git a/application/wechat/view/keys/index.html b/application/wechat/view/keys/index.html new file mode 100644 index 000000000..42c4f161d --- /dev/null +++ b/application/wechat/view/keys/index.html @@ -0,0 +1,187 @@ +{extend name='admin@public/content'} + +{block name="button"} + + + + + + + + + +{/block} + +{block name='content'} +
    + {if empty($list)} +

    没 有 记 录 哦!

    + {else} + + + + + + + + + + + + + + + + {foreach $list as $key=>$vo} + + + + + + + + + + + {/foreach} + +
    + + + + 关键字类型预览添加时间状态
    + + + + + {if !empty($vo.qrc)}{/if} + {$vo.keys} + {$vo.type} + {if $vo.type eq '音乐'} + 预览 + {elseif $vo.type eq '文字'} + 预览 + {elseif $vo.type eq '图片'} + 预览 + {elseif $vo.type eq '图文'} + 预览 + {elseif $vo.type eq '视频'} + 预览 + {else} + {$vo.content} + {/if} + {$vo.create_at|format_datetime} + {if $vo.status eq 0}已禁用{elseif $vo.status eq 1}使用中{/if} + + + {if auth("$classuri/edit")} + | + 编辑 + {/if} + + {if $vo.status eq 1 and auth("$classuri/forbid")} + | + 禁用 + {elseif auth("$classuri/resume")} + | + 启用 + {/if} + + {if auth("$classuri/del")} + | + 删除 + {/if} + +
    + {if isset($page)}

    {$page|raw}

    {/if} + {/if} +
    + +{/block} + +{block name="script"} + +{/block} \ No newline at end of file diff --git a/application/wechat/view/menu.index.html b/application/wechat/view/menu/index.html similarity index 57% rename from application/wechat/view/menu.index.html rename to application/wechat/view/menu/index.html index 02f9f699b..ef0e78d33 100644 --- a/application/wechat/view/menu.index.html +++ b/application/wechat/view/menu/index.html @@ -1,347 +1,333 @@ -{extend name='extra@admin/content'} - -{block name="style"} - -{/block} - -{block name='content'} -
    -
    公众号
    -
    - -
    -
    - -
    - - -
    -
    - {if auth("$classuri/edit")} - - {/if} - {if auth("$classuri/cancel")} - - {/if} -
    -{/block} - -{block name="script"} - +{extend name='admin@public/content'} + +{block name="style"} + +{/block} + +{block name='content'} +
    +
    公众号
    +
    + +
    +
    + +
    + + + + + +
    +
    + + + + + + +
    + +{/block} + +{block name="script"} + {/block} \ No newline at end of file diff --git a/application/wechat/view/news.form.html b/application/wechat/view/news.form.html deleted file mode 100644 index 36aa7cd99..000000000 --- a/application/wechat/view/news.form.html +++ /dev/null @@ -1,278 +0,0 @@ -{extend name='extra@admin/content'} - -{block name='content'} - - -
    -
    图文列表
    -
    - {if empty($vo['articles']) eq false} - {foreach $vo.articles as $key=>$value} -
    - - {$value.title} -
    -
    - {/foreach} - {else} -
    - - -
    -
    - {/if} - -
    -
    - - - -
    -
    图文内容编辑
    -
    -
    - -
    -
    -
    - 标题 - - -
    -
    -
    - -
    -
    -
    - 作者 - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    - 上传图片 - 选择图片 -

    - -
    -
    -

    封面大图片建议尺寸:900像素 * 500像素

    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - -{/block} - - -{block name='script'} - -{/block} - -{block name="style"} - -{/block} \ No newline at end of file diff --git a/application/wechat/view/news.image.html b/application/wechat/view/news.image.html deleted file mode 100644 index db6528613..000000000 --- a/application/wechat/view/news.image.html +++ /dev/null @@ -1,43 +0,0 @@ -{extend name='extra@admin/main'} - -{block name="body"} - -
    - {foreach $list as $key=>$vo} - - {/foreach} -
    -
    - -{if isset($page)}{$page}{/if} - -{/block} diff --git a/application/wechat/view/news.push.html b/application/wechat/view/news.push.html deleted file mode 100644 index 8d633c184..000000000 --- a/application/wechat/view/news.push.html +++ /dev/null @@ -1,112 +0,0 @@ -
    - -
    -
    微信图文
    -
    - {foreach $vo.articles as $key=>$value} -
    -
    - {$value.title} -
    -
    - {/foreach} -
    -
    - -
    -
    指定粉丝标签推送 全选
    -
    - - {foreach $fans_tags as $tag} - - {/foreach} - - {literal} - - {/literal} - - -
    -
    -
    -
    - -
    - - -
    - -
    - - \ No newline at end of file diff --git a/application/wechat/view/news.select.html b/application/wechat/view/news.select.html deleted file mode 100644 index fb00a750f..000000000 --- a/application/wechat/view/news.select.html +++ /dev/null @@ -1,62 +0,0 @@ -{extend name='extra@admin/main'} - -{block name="body"} -
    - {foreach $list as $key=>$vo} -
    - {foreach $vo.articles as $key=>$value} -
    -
    - image -
    - {$value.title} -
    - {/foreach} -
    - {/foreach} -
    - -{if isset($page)}

    {$page}

    {/if} - -{/block} - -{block name='style'} - -{/block} - -{block name="script"} - -{/block} \ No newline at end of file diff --git a/application/wechat/view/news/form.html b/application/wechat/view/news/form.html new file mode 100644 index 000000000..befbff4ec --- /dev/null +++ b/application/wechat/view/news/form.html @@ -0,0 +1,362 @@ +{extend name='admin@public/content'} + +{block name='content'} + + +
    +
    图文列表
    +
    + {if empty($vo['articles']) eq false} + +
    + + {$value.title} +
    +
    + + {else} +
    + + +
    +
    + {/if} + + + +
    +
    + + + +
    +
    图文内容编辑
    +
    +
    + +
    +
    +
    + 标 题 + + +
    +
    +
    + +
    +
    +
    + 作 者 + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + 上传图片 + 选择图片 +

    + +
    +
    +

    封面大图片建议尺寸:900像素 * 500像素

    +
    +
    +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + +{/block} + + +{block name='script'} + + +{/block} + +{block name="style"} + +{/block} \ No newline at end of file diff --git a/application/wechat/view/news/image.html b/application/wechat/view/news/image.html new file mode 100644 index 000000000..fc4a13b33 --- /dev/null +++ b/application/wechat/view/news/image.html @@ -0,0 +1,42 @@ +{extend name='admin@public/main'} + +{block name='style'} + +{/block} + +{block name="body"} +
    + {foreach $list as $key=>$vo} + + {/foreach} +
    +
    +
    +
    + {if isset($page)}{$page|raw}{/if} +
    +{/block} + +{block name="script"} + +{/block} diff --git a/application/wechat/view/news.index.html b/application/wechat/view/news/index.html similarity index 87% rename from application/wechat/view/news.index.html rename to application/wechat/view/news/index.html index 5cb530dae..8930a12cf 100644 --- a/application/wechat/view/news.index.html +++ b/application/wechat/view/news/index.html @@ -1,170 +1,168 @@ -{extend name='extra@admin/content'} - -{block name="button"} -
    - -
    -{/block} - -{block name='content'} - -
    - {foreach $list as $vo} -
    - - {foreach $vo.articles as $k => $v} - {if $k < 1} -
    - {if $v.title}

    {$v.title}

    {/if} -
    -
    - {else} -
    -
    {$v.title}
    -
    -
    -
    -
    - {/if} - {/foreach} -
    - {/foreach} -
    - {if empty($list)} -

    暂时无记录

    - {/if} -
    -{if isset($page)}

    {$page}

    {/if} - -{/block} - -{block name='script'} - -{/block} - -{block name="style"} - +{extend name='admin@public/content'} + +{block name="button"} + +{/block} + +{block name='content'} + +
    + {foreach $list as $vo} +
    + + {foreach $vo.articles as $k => $v} + {if $k < 1} +
    + {if $v.title}

    {$v.title}

    {/if} +
    +
    + {else} +
    +
    {$v.title}
    +
    +
    +
    + {/if} + {/foreach} +
    + {/foreach} +
    + {if empty($list)} +

    没 有 记 录 哦!

    + {/if} +
    +{if isset($page)}

    {$page|raw}

    {/if} + +{/block} + +{block name='script'} + +{/block} + +{block name="style"} + {/block} \ No newline at end of file diff --git a/application/wechat/view/news/push.html b/application/wechat/view/news/push.html new file mode 100644 index 000000000..67fba17b0 --- /dev/null +++ b/application/wechat/view/news/push.html @@ -0,0 +1,192 @@ +
    + +
    +
    微信图文
    +
    + {foreach $vo.articles as $key=>$value} +
    +
    + {$value.title} +
    +
    + {/foreach} +
    +
    + +
    +
    指定粉丝标签推送 全选
    +
    + + {foreach $fans_tags as $tag} + + {/foreach} + + {literal} + + {/literal} +
    +
    +
    +
    + +
    + + +
    + +
    + + + + \ No newline at end of file diff --git a/application/wechat/view/news/select.html b/application/wechat/view/news/select.html new file mode 100644 index 000000000..ebb2a4543 --- /dev/null +++ b/application/wechat/view/news/select.html @@ -0,0 +1,80 @@ +{extend name='admin@public/main'} + +{block name='style'} + +{/block} + +{block name="body"} +
    + {foreach $list as $vo} +
    + {foreach $vo.articles as $k => $v} + {if $k < 1} +
    + {if $v.title}

    {$v.title}

    {/if} +
    +
    + {else} +
    +
    {$v.title}
    +
    +
    +
    + {/if} + {/foreach} +
    + {/foreach} +
    + {if empty($list)}

    没 有 记 录 哦!

    {/if} +
    +
    +
    {if isset($page)}{$page|raw}{/if}
    +{/block} + + +{block name="script"} + +{/block} \ No newline at end of file diff --git a/application/wechat/view/review.index.html b/application/wechat/view/review/index.html similarity index 93% rename from application/wechat/view/review.index.html rename to application/wechat/view/review/index.html index cc62e8561..073164fd8 100644 --- a/application/wechat/view/review.index.html +++ b/application/wechat/view/review/index.html @@ -1,178 +1,177 @@ - - - - - - - - - - - - - - - {if ($type eq 'text') or ($type eq 'image') or ($type eq 'music')} -
    -
    {:date('H:i')}
    -
    -
    - -
    -
    - {if $type eq 'text'} -
    -
    -
    - {$content|default=''} -
    - {elseif $type eq 'image'} -
    -
    -
    - -
    - {elseif $type eq 'music'} -
    -
    -
    - - - - - - - - -
    - {$title|default=''} - -
    -
    - -
    -
    - {$desc|default=''}           -
    -
    - {/if} -
    -
    -
    - {elseif $type eq 'article'} -
    -
    -
    {$vo.title|default=''}
    -
    -
    - {:date('Y-m-d',strtotime($vo['create_at']))} - {$vo.author|default=''} -
    -
    - {if $vo.show_cover_pic eq 1} -
    - {/if} -
    {$vo.content|default=''}
    - {if $vo.content_source_url} - - {/if} -
    -
    - {elseif $type eq 'video'} -
    -
    {:date('H:i')}
    -
    -
    -
    -
    -
    - {$title|default=''} -
    -
    {:date('m月d日')}
    -
    - -
    - -
    -
    -
    -
    -
    - - {elseif $type eq 'news'} -
    -
    {:date('H:i')}
    -
    -
    -
    - {if !empty($articles)} - {foreach $articles as $key=>$vo} - {if count($articles) gt 1} - {if $key < 1} -
    -
    -
    - {$vo.title|default=''} -
    -
    - {else} - - - - - -
    {$vo.title}
    - {/if} - {else} -
    -
    - {$vo.title|default=''} -
    -
    - {:date('m月d日')} -
    -
    -
    - {:str_replace([' ',"\n"],'',strip_tags($vo.digest))} ... -
    -
    - - {/if} - {/foreach} - {/if} -
    -
    -
    -
    - - {/if} - + + + + + + + + + + + + + + + {if ($type eq 'text') or ($type eq 'image') or ($type eq 'music')} +
    +
    {:date('H:i')}
    +
    +
    + +
    +
    + {if $type eq 'text'} +
    +
    +
    + {$content|default=''} +
    + {elseif $type eq 'image'} +
    +
    +
    + +
    + {elseif $type eq 'music'} +
    +
    +
    + + + + + + + + +
    + {$title|default=''} + +
    +
    + +
    +
    + {$desc|default=''}           +
    +
    + {/if} +
    +
    +
    + {elseif $type eq 'article'} +
    +
    +
    {$vo.title|default=''}
    +
    +
    + {:date('Y-m-d',strtotime($vo['create_at']))} + {$vo.author|default=''} +
    +
    + {if $vo.show_cover_pic eq 1} +
    + {/if} +
    {$vo.content|default=''|raw}
    + {if $vo.content_source_url} + + {/if} +
    +
    + {elseif $type eq 'video'} +
    +
    {:date('H:i')}
    +
    +
    +
    +
    +
    + {$title|default=''} +
    +
    {:date('m月d日')}
    +
    + +
    + +
    +
    +
    +
    +
    + + {elseif $type eq 'news'} +
    +
    {:date('H:i')}
    +
    +
    +
    + {if !empty($articles)} + {foreach $articles as $key=>$vo} + {if count($articles) gt 1} + {if $key < 1} +
    +
    +
    + {$vo.title|default=''} +
    +
    + {else} + + + + + +
    {$vo.title}
    + {/if} + {else} +
    +
    + {$vo.title|default=''} +
    +
    + {:date('m月d日')} +
    +
    +
    + {:str_replace([' ',"\n"],'',strip_tags($vo.digest))} ... +
    +
    + + {/if} + {/foreach} + {/if} +
    +
    +
    +
    + + {/if} + \ No newline at end of file diff --git a/application/wechat/view/tags.index.html b/application/wechat/view/tags.index.html deleted file mode 100644 index ddf1026fd..000000000 --- a/application/wechat/view/tags.index.html +++ /dev/null @@ -1,89 +0,0 @@ -{extend name='extra@admin/content'} - -{block name="button"} -
    - - -
    -{/block} - -{block name="content"} - - - - - -
    - - - - - - - - - - - - - - {foreach $list as $key=>$vo} - - - - - - - - - {/foreach} - {if empty($list)} - - {/if} - -
    - - ID标签类型粉丝数操作
    - - {$vo.id|default='0'}{$vo.name|default=''}{$vo.id < 100 ? "系统标签" : "自定义标签"}{$vo.count|default=''} - - {if auth("$classuri/edit")} - {if $vo.id >= 100} - | - 编辑 - {else} - 编辑 - {/if} - {/if} - - {if auth("$classuri/del")} - | - {if $vo.id >= 100} - 删除 - {else} - 删除 - {/if} - {/if} - -
    没 有 记 录 了 哦 !
    - {if isset($page)}

    {$page}

    {/if} -
    -{/block} \ No newline at end of file diff --git a/application/wechat/view/tags.form.html b/application/wechat/view/tags/form.html similarity index 75% rename from application/wechat/view/tags.form.html rename to application/wechat/view/tags/form.html index 441699758..39ed800e6 100644 --- a/application/wechat/view/tags.form.html +++ b/application/wechat/view/tags/form.html @@ -1,22 +1,18 @@ -
    - -
    - -
    - -
    -
    - -
    - -
    - - {if isset($vo['id'])}{/if} - - - - - -
    - -
    +
    + +
    + +
    + +
    +
    + +
    + +
    + {if isset($vo['id'])}{/if} + + +
    + +
    diff --git a/application/wechat/view/tags/index.html b/application/wechat/view/tags/index.html new file mode 100644 index 000000000..a53e2842f --- /dev/null +++ b/application/wechat/view/tags/index.html @@ -0,0 +1,89 @@ +{extend name='admin@public/content'} + +{block name="button"} + + + + + + + + + + + + + +{/block} + +{block name="content"} + + + + +
    + {if empty($list)} +

    没 有 记 录 哦!

    + {else} + + + + + + + + + + + + + + {foreach $list as $key=>$vo} + + + + + + + + + {/foreach} + +
    + + ID标签名称标签类型粉丝数操作
    + + {$vo.id|default='0'}{$vo.name|default=''}{$vo.id < 100 ? "系统标签" : "自定义标签"}{$vo.count|default=''} + + {if auth("$classuri/edit")} + | + + 编辑 + + 编辑 + + {/if} + + {if auth("$classuri/del")} + | + + 删除 + + 删除 + + {/if} +
    + {if isset($page)}

    {$page|raw}

    {/if} + {/if} +
    +{/block} \ No newline at end of file diff --git a/composer.json b/composer.json index 6204f57f5..943a0ab45 100644 --- a/composer.json +++ b/composer.json @@ -1,36 +1,39 @@ -{ - "name": "topthink/think", - "description": "the new thinkphp framework", - "type": "project", - "keywords": [ - "framework", - "thinkphp", - "ORM" - ], - "homepage": "http://thinkphp.cn/", - "license": "Apache-2.0", - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "require": { - "php": ">=5.4.0", - "qiniu/php-sdk": "^7.0", - "zoujingli/wechat-php-sdk": "dev-master", - "zoujingli/ip2region": "^1.0", - "topthink/framework": "5.0.*", - "topthink/think-captcha": "^1.0", - "topthink/think-mongo": "^1.1", - "topthink/think-queue": "^1.0", - "endroid/qrcode": "^1.9", - "aliyuncs/oss-sdk-php": "^2.2" - }, - "extra": { - "think-path": "thinkphp" - }, - "config": { - "preferred-install": "dist" - } -} +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=5.6.0", + "endroid/qrcode": "^1.9", + "topthink/framework": "5.1.*", + "zoujingli/ip2region": "dev-master", + "aliyuncs/oss-sdk-php": "^2.2", + "zoujingli/weopen-developer": "dev-master", + "topthink/think-captcha": "^2.0", + "qiniu/php-sdk": "^7.2" + }, + "autoload": { + "psr-4": { + "app\\": "application" + } + }, + "extra": { + "think-path": "thinkphp" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 000000000..fe4e57e6f --- /dev/null +++ b/config/app.php @@ -0,0 +1,111 @@ + true, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => 'htmlentities,trim', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 路由使用完整匹配 + 'route_complete_match' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 使用注解路由 + 'route_annotation' => false, + // 域名部署 + 'url_domain_deploy' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => env('think_path') . 'tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => env('think_path') . 'tpl/dispatch_jump.tpl', + // 异常页面的模板文件 + 'exception_tmpl' => env('think_path') . 'tpl/think_exception.tpl', + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', +]; diff --git a/vendor/zoujingli/wechat-php-sdk/include.php b/config/cache.php similarity index 65% rename from vendor/zoujingli/wechat-php-sdk/include.php rename to config/cache.php index 6057cbe92..ca1d9e077 100644 --- a/vendor/zoujingli/wechat-php-sdk/include.php +++ b/config/cache.php @@ -1,17 +1,20 @@ - 0, + 'prefix' => '', + 'path' => '', + 'type' => 'File', +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 000000000..12d5969b2 --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,30 @@ + '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly 设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, +]; diff --git a/application/database.php b/config/database.php similarity index 90% rename from application/database.php rename to config/database.php index 7472ab7b1..759d36015 100644 --- a/application/database.php +++ b/config/database.php @@ -16,13 +16,13 @@ return [ // 数据库类型 'type' => 'mysql', // 服务器地址 - 'hostname' => '127.0.0.1', + 'hostname' => 'thinkadmin.top', // 数据库名 - 'database' => 'ThinkAdmin', + 'database' => 'admin_v3', // 用户名 - 'username' => 'ThinkAdmin', + 'username' => 'admin_v3', // 密码 - 'password' => 'ThinkAdmin', + 'password' => '', // 端口 'hostport' => '3306', // 连接dsn @@ -53,8 +53,6 @@ return [ 'datetime_format' => 'Y-m-d H:i:s', // 是否需要进行SQL性能分析 'sql_explain' => false, - // Builder类 - 'builder' => '', // Query类 'query' => '\\think\\db\\Query', ]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 000000000..a87698208 --- /dev/null +++ b/config/log.php @@ -0,0 +1,20 @@ + '', + 'type' => 'File', + 'file_size' => 1024 * 1024 * 10, + 'apart_level' => ['emergency', 'alert', 'critical', 'error', 'sql'], +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 000000000..9c99b174d --- /dev/null +++ b/config/session.php @@ -0,0 +1,29 @@ + '', + 'type' => '', + 'prefix' => 'ta', + 'auto_start' => true, + 'path' => $session_path, + 'name' => $session_name, + 'var_session_id' => $session_name, +]; \ No newline at end of file diff --git a/config/template.php b/config/template.php new file mode 100644 index 000000000..ebd97e0ed --- /dev/null +++ b/config/template.php @@ -0,0 +1,43 @@ +root(); +$uriRoot = rtrim(preg_match('/\.php$/', $appRoot) ? dirname($appRoot) : $appRoot, '/'); + +return [ + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + // 定义模板替换字符串 + 'tpl_replace_string' => [ + '__APP__' => $appRoot, + '__ROOT__' => $uriRoot, + '__STATIC__' => $uriRoot . "/static", + ], +]; + diff --git a/application/extra/queue.php b/config/trace.php similarity index 96% rename from application/extra/queue.php rename to config/trace.php index 6a0697a67..b1d2ade4d 100644 --- a/application/extra/queue.php +++ b/config/trace.php @@ -13,5 +13,5 @@ // +---------------------------------------------------------------------- return [ - 'connector' => 'Sync' + 'type' => 'Html', ]; diff --git a/extend/controller/BasicAdmin.php b/extend/controller/BasicAdmin.php index 776e12395..858f35d2a 100644 --- a/extend/controller/BasicAdmin.php +++ b/extend/controller/BasicAdmin.php @@ -47,6 +47,10 @@ class BasicAdmin extends Controller * @param array $where 查询规则 * @param array $extendData 扩展数据 * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ protected function _form($dbQuery = null, $tplFile = '', $pkField = '', $where = [], $extendData = []) { @@ -56,7 +60,7 @@ class BasicAdmin extends Controller // 非POST请求, 获取数据并显示表单页面 if (!$this->request->isPost()) { $vo = ($pkValue !== null) ? array_merge((array)$db->where($pk, $pkValue)->where($where)->find(), $extendData) : $extendData; - if (false !== $this->_callback('_form_filter', $vo)) { + if (false !== $this->_callback('_form_filter', $vo, [])) { empty($this->title) || $this->assign('title', $this->title); return $this->fetch($tplFile, ['vo' => $vo]); } @@ -64,9 +68,9 @@ class BasicAdmin extends Controller } // POST请求, 数据自动存库 $data = array_merge($this->request->post(), $extendData); - if (false !== $this->_callback('_form_filter', $data)) { + if (false !== $this->_callback('_form_filter', $data, [])) { $result = DataService::save($db, $data, $pk, $where); - if (false !== $this->_callback('_form_result', $result)) { + if (false !== $this->_callback('_form_result', $result, $data)) { if ($result !== false) { $this->success('恭喜, 数据保存成功!', ''); } @@ -81,17 +85,74 @@ class BasicAdmin extends Controller * @param bool $isPage 是启用分页 * @param bool $isDisplay 是否直接输出显示 * @param bool $total 总记录数 - * @param array $result + * @param array $result 结果集 * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\Exception */ protected function _list($dbQuery = null, $isPage = true, $isDisplay = true, $total = false, $result = []) + { + $db = is_null($dbQuery) ? Db::name($this->table) : (is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery); + // 列表排序默认处理 + if ($this->request->isPost() && $this->request->post('action') === 'resort') { + foreach ($this->request->post() as $key => $value) { + if (preg_match('/^_\d{1,}$/', $key) && preg_match('/^\d{1,}$/', $value)) { + list($where, $update) = [['id' => trim($key, '_')], ['sort' => $value]]; + if (false === Db::name($db->getTable())->where($where)->update($update)) { + $this->error('列表排序失败, 请稍候再试'); + } + } + } + $this->success('列表排序成功, 正在刷新列表', ''); + } + // 列表数据查询与显示 + if (null === $db->getOptions('order')) { + in_array('sort', $db->getTableFields($db->getTable())) && $db->order('sort asc'); + } + if ($isPage) { + $rows = intval($this->request->get('rows', cookie('page-rows'))); + cookie('page-rows', $rows = $rows >= 10 ? $rows : 20); + // 分页数据处理 + $query = $this->request->get('', '', 'urlencode'); + $page = $db->paginate($rows, $total, ['query' => $query]); + if (($totalNum = $page->total()) > 0) { + list($rowsHTML, $pageHTML, $maxNum) = [[], [], $page->lastPage()]; + foreach ([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200] as $num) { + list($query['rows'], $query['page']) = [$num, '1']; + $url = url('@admin') . '#' . $this->request->baseUrl() . '?' . http_build_query($query); + $rowsHTML[] = ""; + } + for ($i = 1; $i <= $maxNum; $i++) { + list($query['rows'], $query['page']) = [$rows, $i]; + $url = url('@admin') . '#' . $this->request->baseUrl() . '?' . http_build_query($query); + $pageHTML[] = ""; + } + list($pattern, $replacement) = [['|href="(.*?)"|', '|pagination|'], ['data-open="$1"', 'pagination pull-right']]; + $html = "共 {$totalNum} 条记录,每页显示 条,共 " . ceil($totalNum / $rows) . " 页当前显示第 页。"; + list($result['total'], $result['list'], $result['page']) = [$totalNum, $page->all(), $html . preg_replace($pattern, $replacement, $page->render())]; + } else { + list($result['total'], $result['list'], $result['page']) = [$totalNum, $page->all(), $page->render()]; + } + } else { + $result['list'] = $db->select(); + } + if (false !== $this->_callback('_data_filter', $result['list'], []) && $isDisplay) { + !empty($this->title) && $this->assign('title', $this->title); + return $this->fetch('', $result); + } + return $result; + } + + protected function _list_modal($dbQuery = null, $isPage = true, $isDisplay = true, $total = false, $result = [], $template = '') { $db = is_null($dbQuery) ? Db::name($this->table) : (is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery); // 列表排序默认处理 if ($this->request->isPost() && $this->request->post('action') === 'resort') { $data = $this->request->post(); unset($data['action']); - foreach ($data as $key => &$value) { + foreach ($data as $key => $value) { if (false === $db->where('id', intval(ltrim($key, '_')))->setField('sort', $value)) { $this->error('列表排序失败, 请稍候再试'); } @@ -100,21 +161,23 @@ class BasicAdmin extends Controller } // 列表数据查询与显示 if (null === $db->getOptions('order')) { - $fields = $db->getTableFields($db->getTable()); - in_array('sort', $fields) && $db->order('sort asc'); + in_array('sort', $db->getTableFields($db->getTable())) && $db->order('sort asc'); } if ($isPage) { - $rows = intval($this->request->get('rows', cookie('rows'))); - cookie('rows', $rows >= 10 ? $rows : 20); - $page = $db->paginate($rows, $total, ['query' => $this->request->get('', '', 'urlencode')]); - list($pattern, $replacement) = [['|href="(.*?)"|', '|pagination|'], ['data-open="$1"', 'pagination pull-right']]; - list($result['list'], $result['page']) = [$page->all(), preg_replace($pattern, $replacement, $page->render())]; + $rows = 10; + // 分页数据处理 + $query = $this->request->get('', '', 'urlencode'); + $page = $db->paginate($rows, $total, ['query' => $query]); + list($totalNum) = [$page->total()]; + list($pattern, $replacement) = [['|href="(.*?)"|', '|pagination|'], ['href="javascript:;" onclick="pageto(this)"', 'pagination pull-right']]; + $html = "共 {$totalNum} 条记录," . " 共 " . ceil($totalNum / $rows) . " 页。"; + list($result['total'], $result['list'], $result['page']) = [$totalNum, $page->all(), $html . preg_replace($pattern, $replacement, $page->render())]; } else { $result['list'] = $db->select(); } - if (false !== $this->_callback('_data_filter', $result['list']) && $isDisplay) { + if ($isDisplay && (false !== $this->_callback('_data_filter', $result['list'], []))) { !empty($this->title) && $this->assign('title', $this->title); - return $this->fetch('', $result); + return $this->fetch($template, $result); } return $result; } @@ -122,13 +185,14 @@ class BasicAdmin extends Controller /** * 当前对象回调成员方法 * @param string $method - * @param array|bool $data + * @param array|bool $data1 + * @param array|bool $data2 * @return bool */ - protected function _callback($method, &$data) + protected function _callback($method, &$data1, $data2) { foreach ([$method, "_" . $this->request->action() . "{$method}"] as $_method) { - if (method_exists($this, $_method) && false === $this->$_method($data)) { + if (method_exists($this, $_method) && false === $this->$_method($data1, $data2)) { return false; } } diff --git a/extend/controller/BasicApi.php b/extend/controller/BasicApi.php deleted file mode 100644 index 91d0c9a04..000000000 --- a/extend/controller/BasicApi.php +++ /dev/null @@ -1,67 +0,0 @@ -request = is_null($request) ? Request::instance() : $request; - } - - /** - * 输出返回数据 - * @param string $msg 提示消息内容 - * @param string $code 业务状态码 - * @param mixed $data 要返回的数据 - * @param string $type 返回类型 JSON XML - * @return Response - */ - public function response($msg, $code = 'SUCCESS', $data = [], $type = 'json') - { - $result = ['msg' => $msg, 'code' => $code, 'data' => $data, 'type' => strtolower($type)]; - return Response::create($result, $type)->header(ToolsService::corsRequestHander())->code(200); - } - -} diff --git a/extend/controller/BasicApp.php b/extend/controller/BasicApp.php new file mode 100644 index 000000000..1f4fd55f1 --- /dev/null +++ b/extend/controller/BasicApp.php @@ -0,0 +1,145 @@ +request = app('request'); + $this->unionid = $this->request->post('unionid', ''); + $this->deviceid = $this->request->post('deviceid', ''); + $this->_initialize(); + } + + /** + * App接口初始化 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function _initialize() + { + if (empty($this->deviceid) && empty($this->unionid)) { + $this->error("接口异常,缺少必要参数!"); + } + return $this->checkLogin(); + } + + /** + * 检查用户登录 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + protected function checkLogin() + { + $this->member = $this->initMember(); + empty($this->member) && $this->error('还没有登录哦,请登录后再来访问!'); + } + + /** + * 初始化会员数据 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + protected function initMember() + { + $where = "(deviceid='{$this->deviceid}' and deviceid<>'') or (unionid='{$this->unionid}' and unionid<>'')"; + $this->member = Db::name('Member')->where(['status' => '1'])->where($where)->find(); + if (!empty($this->member)) { + $where = ['level' => $this->member['level']]; + $this->member['nickname'] = ToolsService::emojiDecode($this->member['nickname']); + $this->member['level_title'] = Db::name('MemberLevel')->where($where)->value('title', ''); + // 计算会员的积分排行榜 + $rateWhere = ['is_pass' => '1', 'total_integral' => ['>', $this->member['total_integral']]]; + $rateCount = Db::name('Member')->where($rateWhere)->count(); + $count = Db::name('Member')->where(['is_pass' => '1'])->count(); + $this->member['integral_rate'] = number_format(floatval(100 - $rateCount * 100 / $count), 2); + } + return $this->member; + } + + /** + * 返回成功的操作 + * @param string $msg 消息内容 + * @param array $data 返回数据 + */ + protected function success($msg, $data = []) + { + $result = ['code' => 1, 'msg' => $msg, 'data' => $data]; + throw new HttpResponseException(Response::create($result, 'json', 200, ToolsService::corsRequestHander())); + } + + /** + * 返回失败的请求 + * @param string $msg 消息内容 + * @param array $data 返回数据 + */ + protected function error($msg, $data = []) + { + $result = ['code' => 0, 'msg' => $msg, 'data' => $data]; + throw new HttpResponseException(Response::create($result, 'json', 200, ToolsService::corsRequestHander())); + } + +} diff --git a/extend/controller/BasicPda.php b/extend/controller/BasicPda.php new file mode 100644 index 000000000..e39c7d84c --- /dev/null +++ b/extend/controller/BasicPda.php @@ -0,0 +1,122 @@ +request = app('request'); + $this->deviceid = $this->request->post('deviceid', ''); + $this->_initialize(); + } + + /** + * PDA接口初始化 + */ + public function _initialize() + { + empty($this->deviceid) && $this->error("接口异常,缺少必要参数!"); + sysconf('depot_pda_secret_key') !== $this->request->post('appkey') && $this->error("PDA密钥有误!"); + $this->checkLogin && !$this->checkLogin() && $this->error('请登录后再操作!'); + } + + /** + * 检查用户登录 + * @return bool + */ + protected function checkLogin() + { + return !!$this->initUser(); + } + + /** + * 初始化用户数据 + * @return array|false|\PDOStatement|string|\think\Model + */ + protected function initUser() + { + $where = "deviceid='{$this->deviceid}' and deviceid<>''"; + $this->user = Db::name('GoodsDepotKeeper')->where(['status' => '1', 'is_deleted' => 0])->where($where)->find(); + if (!empty($this->user)) { + $this->user['depot'] = Db::name('GoodsDepot')->where(['id' => $this->user['depot_id']])->value('name'); + } + return $this->user; + } + + /** + * 返回成功的操作 + * @param string $msg 消息内容 + * @param array $data 返回数据 + */ + protected function success($msg, $data = []) + { + $result = ['code' => 1, 'msg' => $msg, 'data' => $data]; + throw new HttpResponseException(Response::create($result, 'json', 200, ToolsService::corsRequestHander())); + } + + /** + * 返回失败的请求 + * @param string $msg 消息内容 + * @param array $data 返回数据 + */ + protected function error($msg, $data = []) + { + $result = ['code' => 0, 'msg' => $msg, 'data' => $data]; + throw new HttpResponseException(Response::create($result, 'json', 200, ToolsService::corsRequestHander())); + } + +} diff --git a/extend/controller/BasicWechat.php b/extend/controller/BasicWechat.php index c6ef388e0..11c83de80 100644 --- a/extend/controller/BasicWechat.php +++ b/extend/controller/BasicWechat.php @@ -16,7 +16,6 @@ namespace controller; use service\WechatService; use think\Controller; -use think\Log; /** * 微信基础控制器 @@ -33,99 +32,26 @@ class BasicWechat extends Controller protected $openid; /** - * 当前粉丝信息 - * @var array + * 获取粉丝用户OPENID + * @return bool|string + * @throws \think\Exception + * @throws \think\exception\PDOException */ - protected $fansinfo; - - /** - * 当前访问网址 - * @var string - */ - protected $url; - - /** - * 是否默认开启网页授权 - * @var bool - */ - protected $checkAuth = true; - - /** - * 初始化方法 - */ - public function _initialize() + protected function getOpenid() { - // 当前完整URL地址 - $this->url = $this->request->url(true); - // 网页授权,并获粉丝信息 - $this->assign('jsSign', load_wechat('script')->getJsSign($this->url)); - // 检查启用网页授权 - $this->checkAuth && $this->oAuth(); + return WechatService::webOauth(0)['openid']; } /** - * 微信网页授权 - * @param bool $fullMode 获取完整 - * @return string + * 获取微信粉丝信息 + * @return bool|array + * @throws \Exception + * @throws \think\Exception + * @throws \think\exception\PDOException */ - protected function oAuth($fullMode = true) + protected function getFansinfo() { - // 本地开发调试用户 openid - if (in_array($this->request->host(), ['127.0.0.1', 'localhost'])) { - session('openid', 'oBWB3wWVNujb-PJlmPmxC5CBTNF0'); - } - // 检查缓存中 openid 信息是否完整 - if (($this->openid = session('openid'))) { - if (!$fullMode) { - return $this->openid; - } - $this->fansinfo = WechatService::getFansInfo($this->openid); - if (!empty($this->fansinfo) && $this->fansinfo['expires_in'] > time()) { - $this->assign('fansinfo', $this->fansinfo); - return $this->openid; - } - } - // 发起微信网页授权 - $wxoauth_url = $this->url; - if (!($redirect_url = $this->request->get('redirectcode', false, 'decode'))) { - $split = stripos($this->url, '?') === false ? '?' : '&'; - $wxoauth_url = "{$this->url}{$split}redirectcode=" . encode($this->url); - } - // 微信网页授权处理 - $wechat = &load_wechat('Oauth'); - if (!$this->request->get('code', false)) { - $this->redirect($wechat->getOauthRedirect($wxoauth_url, 'webOauth', 'snsapi_base')); - } - if (false === ($result = $wechat->getOauthAccessToken()) || empty($result['openid'])) { - Log::error("微信网页授权失败, {$wechat->errMsg}[{$wechat->errCode}]"); - $this->error("微信网页授权失败, {$wechat->errMsg}[{$wechat->errCode}]"); - } - session('openid', $this->openid = $result['openid']); - empty($fullMode) && $this->redirect($redirect_url); - // 微信粉丝信息处理 - $this->fansinfo = WechatService::getFansInfo($this->openid); - if (empty($this->fansinfo['expires_in']) || intval($this->fansinfo['expires_in']) < time()) { - /* 使用普通授权, 获取用户资料; 未关注时重新使用高级授权 */ - if ($result['scope'] === 'snsapi_base') : - $user = load_wechat('User')->getUserInfo($this->openid); - empty($user['subscribe']) && $this->redirect($wechat->getOauthRedirect($wxoauth_url, 'webOauth', 'snsapi_userinfo')); - /* 使用高级授权, 获取完整用户资料 */ - elseif ($result['scope'] === 'snsapi_userinfo') : - $user = $wechat->getOauthUserinfo($result['access_token'], $this->openid); - endif; - /* 授权结果处理, 更新粉丝信息 */ - if ((empty($user) || !array_key_exists('nickname', $user))) : - Log::error("微信网页授权获取用户信息失败, {$wechat->errMsg}[{$wechat->errCode}]"); - $this->error("微信网页授权获取用户信息失败, {$wechat->errMsg}[{$wechat->errCode}]"); - endif; - $user['expires_in'] = $result['expires_in'] + time() - 100; - $user['refresh_token'] = $result['refresh_token']; - $user['access_token'] = $result['access_token']; - // 用户特权信息不处理 - unset($user['privilege']); - WechatService::setFansInfo($user, $wechat->appid) or $this->error('微信网页授权用户保存失败!'); - } - $this->redirect($redirect_url); + return WechatService::webOauth(1)['fansinfo']; } } diff --git a/extend/hook/AccessAuth.php b/extend/hook/AccessAuth.php deleted file mode 100644 index 51f8ac604..000000000 --- a/extend/hook/AccessAuth.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @date 2017/05/12 11:59 - */ -class AccessAuth -{ - - /** - * 当前请求对象 - * @var Request - */ - protected $request; - - /** - * 行为入口 - * @param $params - */ - public function run(&$params) - { - $this->request = Request::instance(); - list($module, $controller, $action) = [$this->request->module(), $this->request->controller(), $this->request->action()]; - $node = strtolower("{$module}/{$controller}/{$action}"); - $info = Db::name('SystemNode')->where('node', $node)->find(); - $access = [ - 'is_menu' => intval(!empty($info['is_menu'])), - 'is_auth' => intval(!empty($info['is_auth'])), - 'is_login' => empty($info['is_auth']) ? intval(!empty($info['is_login'])) : 1 - ]; - // 用户登录状态检查 - if (!empty($access['is_login']) && !session('user')) { - if ($this->request->isAjax()) { - $this->response('抱歉,您还没有登录获取访问权限!', 0, url('@admin/login')); - } - throw new HttpResponseException(redirect('@admin/login')); - } - // 访问权限节点检查 - if (!empty($access['is_auth']) && !auth($node)) { - $this->response('抱歉,您没有访问该模块的权限!', 0); - } - // 权限正常, 默认赋值 - $view = View::instance(Config::get('template'), Config::get('view_replace_str')); - $view->assign('classuri', strtolower("{$module}/{$controller}")); - } - - /** - * 返回消息对象 - * @param string $msg 消息内容 - * @param int $code 返回状态码 - * @param string $url 跳转URL地址 - * @param array $data 数据内容 - * @param int $wait - */ - protected function response($msg, $code = 0, $url = '', $data = [], $wait = 3) - { - $result = ['code' => $code, 'msg' => $msg, 'data' => $data, 'url' => $url, 'wait' => $wait]; - throw new HttpResponseException(json($result)); - } - -} diff --git a/extend/hook/FilterView.php b/extend/hook/FilterView.php deleted file mode 100644 index f1a44354a..000000000 --- a/extend/hook/FilterView.php +++ /dev/null @@ -1,75 +0,0 @@ - - * @date 2017/04/25 11:59 - */ -class FilterView -{ - - /** - * 当前请求对象 - * @var Request - */ - protected $request; - - /** - * 行为入口 - * @param $params - */ - public function run(&$params) - { - $this->request = Request::instance(); - list($appRoot, $uriSelf) = [$this->request->root(true), $this->request->url(true)]; - $uriRoot = preg_match('/\.php$/', $appRoot) ? dirname($appRoot) : $appRoot; - $uriStatic = "{$uriRoot}/static"; - $replace = ['__APP__' => $appRoot, '__SELF__' => $uriSelf, '__PUBLIC__' => $uriRoot, '__STATIC__' => $uriStatic]; - $params = str_replace(array_keys($replace), array_values($replace), $params); - !IS_CLI && $this->baidu($params); - } - - /** - * 百度统计实现代码 - * @param $params - */ - public function baidu(&$params) - { - if (($key = sysconf('tongji_baidu_key'))) { - $https = $this->request->isSsl() ? 'https' : 'http'; - $script = << -\n\n -SCRIPT; - $params = preg_replace('||i', "{$script}", $params); - } - } - -} diff --git a/extend/service/DataService.php b/extend/service/DataService.php index 9956970fb..43e6c6622 100644 --- a/extend/service/DataService.php +++ b/extend/service/DataService.php @@ -15,7 +15,7 @@ namespace service; use think\Db; -use think\Request; +use think\db\Query; /** * 基础数据服务 @@ -27,23 +27,6 @@ use think\Request; class DataService { - /** - * 数据签名 - * @param array $data - * @param string $apikey - * @param string $prefix - * @return string - */ - public static function sign(&$data, $apikey = '', $prefix = '') - { - $data['_SIGNSTR_'] = strtoupper(isset($data['_SIGNSTR_']) ? $data['_SIGNSTR_'] : substr(md5(uniqid()), 22)); - ksort($data); - foreach (array_values($data) as $string) { - is_array($string) || ($prefix .= "{$string}"); - } - return strtoupper(md5($prefix . $apikey . $data['_SIGNSTR_'])); - } - /** * 删除指定序号 * @param string $sequence @@ -73,10 +56,8 @@ class DataService $sequence .= ($i <= 1 ? rand(1, 9) : rand(0, 9)); } $data = ['sequence' => $sequence, 'type' => strtoupper($type)]; - if (Db::name('SystemSequence')->where($data)->count() < 1) { - if (Db::name('SystemSequence')->insert($data) !== false) { - return $sequence; - } + if (Db::name('SystemSequence')->where($data)->count() < 1 && Db::name('SystemSequence')->insert($data) !== false) { + return $sequence; } } return null; @@ -84,7 +65,7 @@ class DataService /** * 数据增量保存 - * @param \think\db\Query|string $dbQuery 数据查询对象 + * @param Query|string $dbQuery 数据查询对象 * @param array $data 需要保存或更新的数据 * @param string $key 条件主键限制 * @param array $where 其它的where条件 @@ -95,16 +76,16 @@ class DataService public static function save($dbQuery, $data, $key = 'id', $where = []) { $db = is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery; - $where[$key] = isset($data[$key]) ? $data[$key] : ''; - if ($db->where($where)->count() > 0) { - return $db->strict(false)->where($where)->update($data) !== false; + $where[] = [$key, 'eq', isset($data[$key]) ? $data[$key] : '']; + if (Db::name($db->getTable())->where($where)->count() > 0) { + return Db::name($db->getTable())->strict(false)->where($where)->update($data) !== false; } - return $db->strict(false)->insert($data) !== false; + return Db::name($db->getTable())->strict(false)->insert($data) !== false; } /** * 更新数据表内容 - * @param \think\db\Query|string $dbQuery 数据查询对象 + * @param Query|string $dbQuery 数据查询对象 * @param array $where 额外查询条件 * @return bool|null * @throws \think\Exception @@ -112,24 +93,20 @@ class DataService */ public static function update(&$dbQuery, $where = []) { - $request = Request::instance(); + $request = app('request'); $db = is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery; - $ids = explode(',', $request->post('id', '')); - $field = $request->post('field', ''); - $value = $request->post('value', ''); - $pk = $db->getPk(['table' => $db->getTable()]); - $where[empty($pk) ? 'id' : $pk] = ['in', $ids]; + list($pk, $table) = [$db->getPk(), $db->getTable()]; + list($field, $value) = [$request->post('field', ''), $request->post('value', '')]; + $where[] = [empty($pk) ? 'id' : $pk, 'in', explode(',', $request->post('id', ''))]; // 删除模式,如果存在 is_deleted 字段使用软删除 if ($field === 'delete') { - if (method_exists($db, 'getTableFields')) { - if (in_array('is_deleted', $db->getTableFields($db->getTable()))) { - return false !== $db->where($where)->update(['is_deleted' => 1]); - } + if (method_exists($db, 'getTableFields') && in_array('is_deleted', $db->getTableFields())) { + return Db::name($table)->where($where)->update(['is_deleted' => '1']) !== false; } - return false !== $db->where($where)->delete(); + return Db::name($table)->where($where)->delete() !== false; } // 更新模式,更新指定字段内容 - return false !== $db->where($where)->update([$field => $value]); + return Db::name($table)->where($where)->update([$field => $value]) !== false; } } diff --git a/extend/service/ExtendService.php b/extend/service/ExtendService.php index 60bfeb0e5..dbb366cf2 100644 --- a/extend/service/ExtendService.php +++ b/extend/service/ExtendService.php @@ -31,14 +31,19 @@ class ExtendService * @param string $content 短信内容 * @param string $productid 短信通道ID * @return bool + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function sendSms($phone, $content, $productid = '676767') { $tkey = date("YmdHis"); $data = [ - 'username' => sysconf('sms_username'), 'tkey' => $tkey, - 'content' => $content, 'mobile' => $phone, 'productid' => $productid, - 'password' => md5(md5(sysconf('sms_password')) . $tkey), + 'tkey' => $tkey, + 'mobile' => $phone, + 'content' => $content, + 'username' => sysconf('sms_username'), + 'productid' => $productid, + 'password' => md5(md5(sysconf('sms_password')) . $tkey), ]; $result = HttpService::post('http://www.ztsms.cn/sendNSms.do', $data); list($code, $msg) = explode(',', $result . ','); @@ -50,12 +55,15 @@ class ExtendService /** * 查询短信余额 * @return array + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function querySmsBalance() { $tkey = date("YmdHis"); $data = [ - 'username' => sysconf('sms_username'), 'tkey' => $tkey, + 'tkey' => $tkey, + 'username' => sysconf('sms_username'), 'password' => md5(md5(sysconf('sms_password')) . $tkey), ]; $result = HttpService::post('http://www.ztsms.cn/balanceN.do', $data); @@ -79,7 +87,7 @@ class ExtendService { list($result, $client_ip) = [[], Request::instance()->ip()]; $header = ['Host' => 'www.kuaidi100.com', 'CLIENT-IP' => $client_ip, 'X-FORWARDED-FOR' => $client_ip]; - $autoResult = HttpService::get("http://www.kuaidi100.com/autonumber/autoComNum?text={$code}", [], 30, $header); + $autoResult = HttpService::get("http://www.kuaidi100.com/autonumber/autoComNum?text={$code}", [], ['header' => $header, 'timeout' => 30]); foreach (json_decode($autoResult)->auto as $vo) { $result[$vo->comCode] = self::express($vo->comCode, $code); } @@ -97,7 +105,67 @@ class ExtendService list($microtime, $client_ip) = [microtime(true), Request::instance()->ip()]; $header = ['Host' => 'www.kuaidi100.com', 'CLIENT-IP' => $client_ip, 'X-FORWARDED-FOR' => $client_ip]; $location = "http://www.kuaidi100.com/query?type={$express_code}&postid={$express_no}&id=1&valicode=&temp={$microtime}"; - return json_decode(HttpService::get($location, [], 30, $header), true); + return json_decode(HttpService::get($location, [], ['header' => $header, 'timeout' => 30]), true); } -} \ No newline at end of file + /** + * 下载CSV文件 + * @param string $filename 导出文件名 + * @param array $options 数据规则,如:['用户名'=>'username','性别'=>'sex'] + * @param \think\Db\Query $query + * @throws \think\exception\DbException + */ + public static function downloadCsv($filename, $options, $query) + { + self::downloadCsvHeader($filename, array_keys($options)); + $query->chunk(1000, function ($list) use ($options) { + self::downloadCsvBody($list, array_values($options)); + }); + } + + /** + * 写入CSV文件头部 + * @param string $filename 导出文件 + * @param array $headers CSV 头部(一级数组) + */ + public static function downloadCsvHeader($filename, $headers) + { + header('Content-Type: application/octet-stream'); + header("Content-Disposition: attachment; filename=" . iconv('utf-8', 'gbk//TRANSLIT', $filename)); + echo @iconv('utf-8', 'gbk//TRANSLIT', '"' . implode('","', $headers) . "\"\n"); + } + + /** + * 写入CSV文件内容 + * @param array $list 数据列表(二维数组或多维数组) + * @param array $rules 数据规则(一维数组) + */ + public static function downloadCsvBody($list, $rules) + { + foreach ($list as $data) { + $rows = []; + foreach ($rules as $rule) { + $item = self::parseKeyDot($data, $rule); + $rows[] = $item === $data ? '' : $item; + } + echo @iconv('utf-8', 'gbk//TRANSLIT', '"' . implode('","', $rows) . "\"\n"); + flush(); + } + } + + /** + * 根据数组key查询(可带点规则) + * @param array $data 数据 + * @param string $rule 规则,如: order.order_no + * @return mixed + */ + private static function parseKeyDot($data, $rule) + { + list($temp, $attr) = [$data, explode('.', trim($rule, '.'))]; + while ($key = array_shift($attr)) { + $temp = isset($temp[$key]) ? $temp[$key] : $temp; + } + return is_string($temp) ? $temp : ''; + } + +} diff --git a/extend/service/FileService.php b/extend/service/FileService.php index 0f5e87253..889ec0849 100644 --- a/extend/service/FileService.php +++ b/extend/service/FileService.php @@ -20,8 +20,7 @@ use OSS\OssClient; use Qiniu\Auth; use Qiniu\Storage\BucketManager; use Qiniu\Storage\UploadManager; -use think\Config; -use think\Log; +use think\facade\Log; /** * 系统文件服务 @@ -35,20 +34,37 @@ class FileService /** * 根据文件后缀获取文件MINE - * @param array $exts 文件后缀 + * @param array $ext 文件后缀 * @param array $mine 文件后缀MINE信息 * @return string */ - public static function getFileMine($exts, $mine = []) + public static function getFileMine($ext, $mine = []) { - $mines = Config::get('mines'); - foreach (is_string($exts) ? explode(',', $exts) : $exts as $_e) { - if (isset($mines[strtolower($_e)])) { - $_exinfo = $mines[strtolower($_e)]; - $mine[] = is_array($_exinfo) ? join(',', $_exinfo) : $_exinfo; - } + $mines = self::getMines(); + foreach (is_string($ext) ? explode(',', $ext) : $ext as $e) { + $mine[] = isset($mines[strtolower($e)]) ? $mines[strtolower($e)] : 'application/octet-stream'; } - return join(',', $mine); + return join(',', array_unique($mine)); + } + + /** + * 获取所有文件扩展的mine + * @return mixed + */ + public static function getMines() + { + $mines = cache('all_ext_mine'); + if (empty($mines)) { + $content = file_get_contents('http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types'); + preg_match_all('#^([^\s]{2,}?)\s+(.+?)$#ism', $content, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + foreach (explode(" ", $match[2]) as $ext) { + $mines[$ext] = $match[1]; + } + } + cache('all_ext_mine', $mines); + } + return $mines; } /** @@ -56,6 +72,9 @@ class FileService * @param string $filename * @param string|null $storage * @return bool|string + * @throws OssException + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function getFileUrl($filename, $storage = null) { @@ -86,6 +105,8 @@ class FileService * 根据配置获取到七牛云文件上传目标地址 * @param bool $isClient * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function getUploadQiniuUrl($isClient = true) { @@ -94,35 +115,38 @@ class FileService switch ($region) { case '华东': if ($isHttps) { - return $isClient ? 'https://upload.qbox.me' : 'https://up.qbox.me'; + return $isClient ? 'https:///upload.qiniup.com' : 'https://upload.qiniup.com'; } - return $isClient ? 'http://upload.qiniu.com' : 'http://up.qiniu.com'; + return $isClient ? 'http:///upload.qiniup.com' : 'http://upload.qiniup.com'; case '华北': if ($isHttps) { - return $isClient ? 'https://upload-z1.qbox.me' : 'https://up-z1.qbox.me'; + return $isClient ? 'https://upload-z1.qiniup.com' : 'https://up-z1.qiniup.com'; } - return $isClient ? 'http://upload-z1.qiniu.com' : 'http://up-z1.qiniu.com'; + return $isClient ? 'http://upload-z1.qiniup.com' : 'http://up-z1.qiniup.com'; case '北美': if ($isHttps) { - return $isClient ? 'https://upload-na0.qbox.me' : 'https://up-na0.qbox.me'; + return $isClient ? 'https://upload-na0.qiniup.com' : 'https://up-na0.qiniup.com'; } - return $isClient ? 'http://upload-na0.qiniu.com' : 'http://up-na0.qiniu.com'; + return $isClient ? 'http://upload-na0.qiniup.com' : 'http://up-na0.qiniup.com'; case '华南': default: if ($isHttps) { - return $isClient ? 'https://upload-z2.qbox.me' : 'https://up-z2.qbox.me'; + return $isClient ? 'https://upload-z2.qiniup.com' : 'https://up-z2.qiniup.com'; } - return $isClient ? 'http://upload-z2.qiniu.com' : 'http://up-z2.qiniu.com'; + return $isClient ? 'http://upload-z2.qiniup.com' : 'http://up-z2.qiniup.com'; } } /** * 获取AliOSS上传地址 * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function getUploadOssUrl() { - return (request()->isSsl() ? 'https' : 'http') . '://' . sysconf('storage_oss_domain'); + $protocol = request()->isSsl() ? 'https' : 'http'; + return "{$protocol}://" . sysconf('storage_oss_domain'); } /** @@ -139,31 +163,50 @@ class FileService /** * 获取七牛云URL前缀 * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function getBaseUriQiniu() { - return (sysconf('storage_qiniu_is_https') ? 'https' : 'http') . '://' . sysconf('storage_qiniu_domain') . '/'; + switch (strtolower(sysconf('storage_qiniu_is_https'))) { + case 'https': + return 'https://' . sysconf('storage_qiniu_domain') . '/'; + case 'http': + return 'http://' . sysconf('storage_qiniu_domain') . '/'; + default: + return '//' . sysconf('storage_qiniu_domain') . '/'; + } } /** - * 获取AliOss URL前缀 + * 获取阿里云对象存储URL前缀 * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function getBaseUriOss() { - return (sysconf('storage_oss_is_https') ? 'https' : 'http') . '://' . sysconf('storage_oss_domain') . '/'; + switch (strtolower(sysconf('storage_oss_is_https'))) { + case 'https': + return 'https://' . sysconf('storage_oss_domain') . '/'; + case 'http': + return 'http://' . sysconf('storage_oss_domain') . '/'; + default: + return '//' . sysconf('storage_oss_domain') . '/'; + } } /** * 获取文件相对名称 - * @param string $location 文件标识 + * @param string $local_url 文件标识 * @param string $ext 文件后缀 - * @param string $pre 文件前缀 + * @param string $pre 文件前缀(若有值需要以/结尾) * @return string */ - public static function getFileName($location, $ext = '', $pre = '') + public static function getFileName($local_url, $ext = '', $pre = '') { - return $pre . join('/', str_split(md5($location), 16)) . '.' . $ext; + empty($ext) && $ext = strtolower(pathinfo($local_url, 4)); + return $pre . join('/', str_split(md5($local_url), 16)) . '.' . ($ext ? $ext : 'tmp'); } /** @@ -171,12 +214,15 @@ class FileService * @param string $filename * @param string|null $storage * @return bool + * @throws OssException + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function hasFile($filename, $storage = null) { switch (empty($storage) ? sysconf('storage_type') : $storage) { case 'local': - return file_exists(ROOT_PATH . 'static/upload/' . $filename); + return file_exists(env('root_path') . 'static/upload/' . $filename); case 'qiniu': $auth = new Auth(sysconf('storage_qiniu_access_key'), sysconf('storage_qiniu_secret_key')); $bucketMgr = new BucketManager($auth); @@ -194,12 +240,15 @@ class FileService * @param string $filename * @param string|null $storage * @return string|null + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws OssException */ public static function readFile($filename, $storage = null) { switch (empty($storage) ? sysconf('storage_type') : $storage) { case 'local': - $file = ROOT_PATH . 'static/upload/' . $filename; + $file = env('root_path') . 'static/upload/' . $filename; return file_exists($file) ? file_get_contents($file) : ''; case 'qiniu': $auth = new Auth(sysconf('storage_qiniu_access_key'), sysconf('storage_qiniu_secret_key')); @@ -218,6 +267,8 @@ class FileService * @param string $content * @param string|null $file_storage * @return array|false + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function save($filename, $content, $file_storage = null) { @@ -238,14 +289,14 @@ class FileService public static function local($filename, $content) { try { - $filepath = ROOT_PATH . 'static/upload/' . $filename; - !file_exists(dirname($filepath)) && mkdir(dirname($filepath), '0755', true); - if (file_put_contents($filepath, $content)) { + $realfile = env('root_path') . 'static/upload/' . $filename; + !file_exists(dirname($realfile)) && mkdir(dirname($realfile), 0755, true); + if (file_put_contents($realfile, $content)) { $url = pathinfo(request()->baseFile(true), PATHINFO_DIRNAME) . '/static/upload/' . $filename; - return ['file' => $filepath, 'hash' => md5_file($filepath), 'key' => "static/upload/{$filename}", 'url' => $url]; + return ['file' => $realfile, 'hash' => md5_file($realfile), 'key' => "static/upload/{$filename}", 'url' => $url]; } } catch (Exception $err) { - Log::error('本地文件存储失败, ' . var_export($err, true)); + Log::error('本地文件存储失败, ' . $err->getMessage()); } return null; } @@ -255,6 +306,8 @@ class FileService * @param string $filename * @param string $content * @return array|null + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function qiniu($filename, $content) { @@ -263,7 +316,7 @@ class FileService $uploadMgr = new UploadManager(); list($result, $err) = $uploadMgr->put($token, $filename, $content); if ($err !== null) { - Log::error('七牛云文件上传失败, ' . var_export($err, true)); + Log::error('七牛云文件上传失败, ' . $err->getMessage()); return null; } $result['file'] = $filename; @@ -276,38 +329,49 @@ class FileService * @param string $filename * @param string $content * @return array|null + * @throws \think\Exception + * @throws \think\exception\PDOException */ public static function oss($filename, $content) { try { - $ossClient = new OssClient(sysconf('storage_oss_keyid'), sysconf('storage_oss_secret'), self::getBaseUriOss(), true); + $endpoint = 'http://' . sysconf('storage_oss_domain'); + $ossClient = new OssClient(sysconf('storage_oss_keyid'), sysconf('storage_oss_secret'), $endpoint, true); $result = $ossClient->putObject(sysconf('storage_oss_bucket'), $filename, $content); - return ['file' => $filename, 'hash' => $result['content-md5'], 'key' => $filename, 'url' => $result['oss-request-url']]; + $baseUrl = explode('://', $result['oss-request-url'])[1]; + if (strtolower(sysconf('storage_oss_is_https')) === 'http') { + $site_url = "http://{$baseUrl}"; + } elseif (strtolower(sysconf('storage_oss_is_https')) === 'https') { + $site_url = "https://{$baseUrl}"; + } else { + $site_url = "//{$baseUrl}"; + } + return ['file' => $filename, 'hash' => $result['content-md5'], 'key' => $filename, 'url' => $site_url]; } catch (OssException $err) { - Log::error('阿里云OSS文件上传失败, ' . var_export($err, true)); - return null; + Log::error('阿里云OSS文件上传失败, ' . $err->getMessage()); } + return null; } /** * 下载文件到本地 * @param string $url 文件URL地址 * @param bool $isForce 是否强制重新下载文件 - * @return array|null; + * @return array */ public static function download($url, $isForce = false) { try { - $filename = self::getFileName($url, strtolower(pathinfo($url, 4)), 'download/'); + $filename = self::getFileName($url, '', 'download/'); if (false === $isForce && ($siteUrl = self::getFileUrl($filename, 'local'))) { - $realfile = ROOT_PATH . 'static/upload/' . $filename; + $realfile = env('root_path') . 'static/upload/' . $filename; return ['file' => $realfile, 'hash' => md5_file($realfile), 'key' => "static/upload/{$filename}", 'url' => $siteUrl]; } return self::local($filename, file_get_contents($url)); } catch (\Exception $e) { - Log::error("FileService 文件下载失败 [ {$url} ] . {$e->getMessage()}"); - return null; + Log::error("FileService 文件下载失败 [ {$url} ] " . $e->getMessage()); } + return ['url' => $url]; } } diff --git a/extend/service/HttpService.php b/extend/service/HttpService.php index 24e6ccda4..2eccd61d0 100644 --- a/extend/service/HttpService.php +++ b/extend/service/HttpService.php @@ -14,8 +14,6 @@ namespace service; -use CURLFile; - /** * HTTP请求服务 * Class HttpService @@ -27,98 +25,94 @@ class HttpService { /** - * HTTP GET 请求 - * @param string $url 请求的URL地址 - * @param array $data GET参数 - * @param int $second 设置超时时间(默认30秒) - * @param array $header 请求Header信息 + * 以get模拟网络请求 + * @param string $url HTTP请求URL地址 + * @param array $query GET请求参数 + * @param array $options CURL参数 * @return bool|string */ - public static function get($url, $data = [], $second = 30, $header = []) + public static function get($url, $query = [], $options = []) { - if (!empty($data)) { - $url .= (stripos($url, '?') === false ? '?' : '&'); - $url .= (is_array($data) ? http_build_query($data) : $data); - } - $curl = curl_init(); - self::applyHttp($curl, $url); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_TIMEOUT, $second); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - if (!empty($header)) { - curl_setopt($curl, CURLOPT_HTTPHEADER, $header); - } - list($content, $status) = [curl_exec($curl), curl_getinfo($curl), curl_close($curl)]; - return (intval($status["http_code"]) === 200) ? $content : false; + $options['query'] = $query; + return HttpService::request('get', $url, $options); } /** - * POST 请求(支持文件上传) + * 以get模拟网络请求 * @param string $url HTTP请求URL地址 - * @param array|string $data POST提交的数据 - * @param int $second 请求超时时间 - * @param array $header 请求Header信息 + * @param array $data POST请求数据 + * @param array $options CURL参数 * @return bool|string */ - static public function post($url, $data = [], $second = 30, $header = []) + public static function post($url, $data = [], $options = []) + { + $options['data'] = $data; + return HttpService::request('post', $url, $options); + } + + /** + * CURL模拟网络请求 + * @param string $method 请求方法 + * @param string $url 请求方法 + * @param array $options 请求参数[header,data,ssl_cer,ssl_key] + * @return bool|string + */ + public static function request($method, $url, $options = []) { $curl = curl_init(); - self::applyData($data); - self::applyHttp($curl, $url); - curl_setopt($curl, CURLOPT_TIMEOUT, $second); + // GET参数设置 + if (!empty($options['query'])) { + $url .= stripos($url, '?') !== false ? '&' : '?' . http_build_query($options['query']); + } + // POST数据设置 + if (strtolower($method) === 'post') { + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, self::build($options['data'])); + } + // 请求超时设置 + $options['timeout'] = isset($options['timeout']) ? $options['timeout'] : 60; + curl_setopt($curl, CURLOPT_TIMEOUT, $options['timeout']); + // CURL头信息设置 + if (!empty($options['header'])) { + curl_setopt($curl, CURLOPT_HTTPHEADER, $options['header']); + } + // 证书文件设置 + if (!empty($options['ssl_cer']) && file_exists($options['ssl_cer'])) { + curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM'); + curl_setopt($curl, CURLOPT_SSLCERT, $options['ssl_cer']); + } + if (!empty($options['ssl_key']) && file_exists($options['ssl_key'])) { + curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM'); + curl_setopt($curl, CURLOPT_SSLKEY, $options['ssl_key']); + } curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curl, CURLOPT_POST, true); - curl_setopt($curl, CURLOPT_POSTFIELDS, $data); - if (!empty($header)) { - curl_setopt($curl, CURLOPT_HTTPHEADER, $header); - } list($content, $status) = [curl_exec($curl), curl_getinfo($curl), curl_close($curl)]; return (intval($status["http_code"]) === 200) ? $content : false; } /** - * 设置SSL参数 - * @param $curl - * @param string $url - */ - private static function applyHttp(&$curl, $url) - { - if (stripos($url, "https") === 0) { - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($curl, CURLOPT_SSLVERSION, 1); - } - } - - /** - * Post 数据过滤处理 + * POST数据过滤处理 * @param array $data - * @param bool $isBuild - * @return string + * @param bool $needBuild + * @return array */ - private static function applyData(&$data, $isBuild = true) + private static function build($data, $needBuild = true) { if (!is_array($data)) { - return null; + return $data; } - foreach ($data as &$value) { - is_array($value) && $isBuild = true; - if (!(is_string($value) && strlen($value) > 0 && $value[0] === '@')) { - continue; - } - if (!file_exists(($file = realpath(trim($value, '@'))))) { - continue; - } - list($isBuild, $mime) = [false, FileService::getFileMine(pathinfo($file, 4))]; - if (class_exists('CURLFile', false)) { - $value = new CURLFile($file, $mime); - } else { - $value = "{$value};type={$mime}"; + foreach ($data as $key => $value) { + if (is_string($value) && class_exists('CURLFile', false) && stripos($value, '@') === 0) { + if (($filename = realpath(trim($value, '@'))) && file_exists($filename)) { + list($needBuild, $data[$key]) = [false, new \CURLFile($filename)]; + } } } - $isBuild && $data = http_build_query($data); + return $needBuild ? http_build_query($data) : $data; } } diff --git a/extend/service/LogService.php b/extend/service/LogService.php index 2c4a9383c..700c1303c 100644 --- a/extend/service/LogService.php +++ b/extend/service/LogService.php @@ -15,7 +15,7 @@ namespace service; use think\Db; -use think\Request; +use think\db\Query; /** * 操作日志服务 @@ -29,7 +29,7 @@ class LogService /** * 获取数据操作对象 - * @return \think\db\Query + * @return Query */ protected static function db() { @@ -44,9 +44,15 @@ class LogService */ public static function write($action = '行为', $content = "内容描述") { - $request = Request::instance(); + $request = app('request'); $node = strtolower(join('/', [$request->module(), $request->controller(), $request->action()])); - $data = ['ip' => $request->ip(), 'node' => $node, 'username' => session('user.username') . '', 'action' => $action, 'content' => $content]; + $data = [ + 'ip' => $request->ip(), + 'node' => $node, + 'action' => $action, + 'content' => $content, + 'username' => session('user.username') . '', + ]; return self::db()->insert($data) !== false; } diff --git a/extend/service/NodeService.php b/extend/service/NodeService.php index d51a51291..a448c562f 100644 --- a/extend/service/NodeService.php +++ b/extend/service/NodeService.php @@ -29,16 +29,19 @@ class NodeService /** * 应用用户权限节点 * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException */ public static function applyAuthNode() { cache('need_access_node', null); if (($userid = session('user.id'))) { - session('user', Db::name('SystemUser')->where('id', $userid)->find()); + session('user', Db::name('SystemUser')->where(['id' => $userid])->find()); } if (($authorize = session('user.authorize'))) { - $where = ['id' => ['in', explode(',', $authorize)], 'status' => '1']; - $authorizeids = Db::name('SystemAuth')->where($where)->column('id'); + $where = ['status' => '1']; + $authorizeids = Db::name('SystemAuth')->whereIn('id', explode(',', $authorize))->where($where)->column('id'); if (empty($authorizeids)) { return session('user.nodes', []); } @@ -70,14 +73,14 @@ class NodeService public static function checkAuthNode($node) { list($module, $controller, $action) = explode('/', str_replace(['?', '=', '&'], '/', $node . '///')); - $auth_node = strtolower(trim("{$module}/{$controller}/{$action}", '/')); + $currentNode = strtolower(trim("{$module}/{$controller}/{$action}", '/')); if (session('user.username') === 'admin' || stripos($node, 'admin/index') === 0) { return true; } - if (!in_array($auth_node, self::getAuthNode())) { + if (!in_array($currentNode, self::getAuthNode())) { return true; } - return in_array($auth_node, (array)session('user.nodes')); + return in_array($currentNode, (array)session('user.nodes')); } /** @@ -88,8 +91,8 @@ class NodeService public static function get($nodes = []) { $alias = Db::name('SystemNode')->column('node,is_menu,is_auth,is_login,title'); - $ignore = ['index', 'wechat/api', 'wechat/notify', 'wechat/review', 'admin/plugs', 'admin/login', 'admin/index']; - foreach (self::getNodeTree(APP_PATH) as $thr) { + $ignore = ['index', 'wechat/review', 'admin/plugs', 'admin/login', 'admin/index']; + foreach (self::getNodeTree(env('app_path')) as $thr) { foreach ($ignore as $str) { if (stripos($thr, $str) === 0) { continue 2; @@ -102,33 +105,31 @@ class NodeService $nodes[$thr] = array_merge(isset($alias[$thr]) ? $alias[$thr] : ['node' => $thr, 'title' => '', 'is_menu' => 0, 'is_auth' => 0, 'is_login' => 0], ['pnode' => $two]); } foreach ($nodes as &$node) { - list($node['is_auth'], $node['is_menu'], $node['is_login']) = [ - intval($node['is_auth']), intval($node['is_menu']), - empty($node['is_auth']) ? intval($node['is_login']) : 1 - ]; + list($node['is_auth'], $node['is_menu'], $node['is_login']) = [intval($node['is_auth']), intval($node['is_menu']), empty($node['is_auth']) ? intval($node['is_login']) : 1]; } return $nodes; } /** * 获取节点列表 - * @param string $path 路径 + * @param string $dirPath 路径 * @param array $nodes 额外数据 * @return array */ - public static function getNodeTree($path, $nodes = []) + public static function getNodeTree($dirPath, $nodes = []) { - foreach (self::_getFilePaths($path) as $vo) { - if (!preg_match('|/(\w+)/controller/(\w+)|', str_replace(DS, '/', $vo), $matches) || count($matches) !== 3) { + foreach (self::scanDirFile($dirPath) as $filename) { + $matches = []; + if (!preg_match('|/(\w+)/controller/(\w+)|', str_replace(DIRECTORY_SEPARATOR, '/', $filename), $matches) || count($matches) !== 3) { continue; } - $className = config('app_namespace') . str_replace('/', '\\', $matches[0]); + $className = env('app_namespace') . str_replace('/', '\\', $matches[0]); if (!class_exists($className)) { continue; } - foreach (get_class_methods($className) as $actionName) { - if ($actionName[0] !== '_') { - $nodes[] = strtolower("{$matches[1]}/{$matches[2]}/{$actionName}"); + foreach (get_class_methods($className) as $funcName) { + if (strpos($funcName, '_') !== 0 && $funcName !== 'initialize') { + $nodes[] = strtolower("{$matches[1]}/{$matches[2]}/{$funcName}"); } } } @@ -137,19 +138,22 @@ class NodeService /** * 获取所有PHP文件 - * @param string $path 目录 + * @param string $dirPath 目录 * @param array $data 额外数据 - * @param string $ext 文件后缀 + * @param string $ext 有文件后缀 * @return array */ - private static function _getFilePaths($path, $data = [], $ext = 'php') + private static function scanDirFile($dirPath, $data = [], $ext = 'php') { - foreach (scandir($path) as $dir) { - if ($dir[0] === '.') { + foreach (scandir($dirPath) as $dir) { + if (strpos($dir, '.') === 0) { continue; } - if (($tmp = realpath($path . DS . $dir)) && (is_dir($tmp) || pathinfo($tmp, 4) === $ext)) { - is_dir($tmp) ? $data = array_merge($data, self::_getFilePaths($tmp)) : $data[] = $tmp; + $tmpPath = realpath($dirPath . DIRECTORY_SEPARATOR . $dir); + if (is_dir($tmpPath)) { + $data = array_merge($data, self::scanDirFile($tmpPath)); + } elseif (pathinfo($tmpPath, 4) === $ext) { + $data[] = $tmpPath; } } return $data; diff --git a/extend/service/PayService.php b/extend/service/PayService.php deleted file mode 100644 index 905dd29df..000000000 --- a/extend/service/PayService.php +++ /dev/null @@ -1,146 +0,0 @@ - - * @date 2016/10/25 14:49 - */ -class PayService -{ - - /** - * 查询订单是否已经支付 - * @param string $order_no - * @return bool - */ - public static function isPay($order_no) - { - $map = ['order_no' => $order_no, 'is_pay' => '1']; - return Db::name('WechatPayPrepayid')->where($map)->count() > 0; - } - - /** - * 创建微信二维码支付(扫码支付模式二) - * @param WechatPay $pay 支付SDK - * @param string $order_no 系统订单号 - * @param int $fee 支付金额 - * @param string $title 订单标题 - * @param string $from 订单来源 - * @return false|string - */ - public static function createWechatPayQrc(WechatPay $pay, $order_no, $fee, $title, $from = 'wechat') - { - $prepayid = self::createWechatPrepayid($pay, null, $order_no, $fee, $title, 'NATIVE', $from); - if ($prepayid === false) { - return false; - } - $filename = FileService::getFileName($prepayid, 'png', 'qrc/'); - if (!FileService::hasFile($filename, 'local')) { - $qrCode = new QrCode($prepayid); - if (null === FileService::save($filename, $qrCode->get(), 'local')) { - return false; - } - } - return FileService::getFileUrl($filename, 'local'); - } - - /** - * 创建微信JSAPI支付签名包 - * @param WechatPay $pay 支付SDK - * @param string $openid 微信用户openid - * @param string $order_no 系统订单号 - * @param int $fee 支付金额 - * @param string $title 订单标题 - * @return bool|array - */ - public static function createWechatPayJsPicker(WechatPay $pay, $openid, $order_no, $fee, $title) - { - if (($prepayid = self::createWechatPrepayid($pay, $openid, $order_no, $fee, $title, 'JSAPI')) === false) { - return false; - } - return $pay->createMchPay($prepayid); - } - - /** - * 微信退款操作 - * @param WechatPay $pay 支付SDK - * @param string $order_no 系统订单号 - * @param int $fee 退款金额 - * @param string|null $refund_no 退款订单号 - * @param string $refund_account - * @return bool - */ - public static function putWechatRefund(WechatPay $pay, $order_no, $fee = 0, $refund_no = null, $refund_account = '') - { - $map = ['order_no' => $order_no, 'is_pay' => '1', 'appid' => $pay->appid]; - $notify = Db::name('WechatPayPrepayid')->where($map)->find(); - if (empty($notify)) { - Log::error("内部订单号{$order_no}验证退款失败"); - return false; - } - if (false !== $pay->refund($notify['out_trade_no'], $notify['transaction_id'], is_null($refund_no) ? "T{$order_no}" : $refund_no, $notify['fee'], empty($fee) ? $notify['fee'] : $fee, '', $refund_account)) { - $data = ['out_trade_no' => $notify['out_trade_no'], 'is_refund' => "1", 'refund_at' => date('Y-m-d H:i:s'), 'expires_in' => time() + 7000]; - if (DataService::save('wechat_pay_prepayid', $data, 'out_trade_no')) { - return true; - } - Log::error("内部订单号{$order_no}退款成功,系统更新异常"); - return false; - } - Log::error("内部订单号{$order_no}退款失败,{$pay->errMsg}"); - return false; - } - - /** - * 创建微信预支付码 - * @param WechatPay $pay 支付SDK - * @param string $openid 支付者Openid - * @param string $order_no 实际订单号 - * @param int $fee 实际订单支付费用 - * @param string $title 订单标题 - * @param string $trade_type 付款方式 - * @param string $from 订单来源 - * @return bool|string - */ - public static function createWechatPrepayid(WechatPay $pay, $openid, $order_no, $fee, $title, $trade_type = 'JSAPI', $from = 'wechat') - { - $map = ['order_no' => $order_no, 'is_pay' => '1', 'expires_in' => time(), 'appid' => $pay->appid, 'trade_type' => $trade_type]; - $where = 'appid=:appid and order_no=:order_no and (is_pay=:is_pay or expires_in>:expires_in) and trade_type=:trade_type'; - $prepayinfo = Db::name('WechatPayPrepayid')->where($where, $map)->find(); - if (empty($prepayinfo) || empty($prepayinfo['prepayid'])) { - $out_trade_no = DataService::createSequence(18, 'WXPAY-OUTER-NO'); - if (!($prepayid = $pay->getPrepayId($openid, $title, $out_trade_no, $fee, url("@wechat/notify", '', true, true), $trade_type))) { - Log::error("内部订单号{$order_no}生成预支付失败,{$pay->errMsg}"); - return false; - } - $data = ['prepayid' => $prepayid, 'order_no' => $order_no, 'out_trade_no' => $out_trade_no, 'fee' => $fee, 'trade_type' => $trade_type]; - list($data['from'], $data['appid'], $data['expires_in']) = [$from, $pay->getAppid(), time() + 5400]; - if (Db::name('WechatPayPrepayid')->insert($data) > 0) { - Log::notice("内部订单号{$order_no}生成预支付成功,{$prepayid}"); - return $prepayid; - } - } - return $prepayinfo['prepayid']; - } - -} diff --git a/extend/service/SoapService.php b/extend/service/SoapService.php index 5fe7b2e04..06f94caf4 100644 --- a/extend/service/SoapService.php +++ b/extend/service/SoapService.php @@ -14,7 +14,8 @@ namespace service; -use think\Log; +use think\Exception; +use think\facade\Log; /** * Soap服务对象 @@ -30,59 +31,34 @@ class SoapService */ protected $soap; - /** - * 错误消息 - * @var string - */ - protected $error; - /** * SoapService constructor. * @param string|null $wsdl WSDL连接参数 * @param array $params Params连接参数 - * @throws \Exception + * @throws \think\Exception */ public function __construct($wsdl, $params) { set_time_limit(3600); if (!extension_loaded('soap')) { - throw new \Exception('Not support soap.'); + throw new Exception('Not support soap.'); } $this->soap = new \SoapClient($wsdl, $params); } - /** - * 属性获取转换 - * @param $name - * @return string - */ - public function __get($name) - { - switch (strtolower($name)) { - case 'errmsg': - return $this->getError(); - case 'errcode': - return $this->getErrorCode(); - case 'appid': - return $this->getAppid(); - } - return ''; - } - /** * @param string $name SOAP调用方法名 * @param array|string $arguments SOAP调用参数 * @return array|string|bool - * @throws \Exception */ public function __call($name, $arguments) { try { - return $this->soap->__call($name, $arguments); + return $this->soap->__soapCall($name, $arguments); } catch (\Exception $e) { Log::error("Soap Error. Call {$name} Method --- " . $e->getMessage()); } return false; } -} \ No newline at end of file +} diff --git a/extend/service/ToolsService.php b/extend/service/ToolsService.php index 114531d87..4acbd15f5 100644 --- a/extend/service/ToolsService.php +++ b/extend/service/ToolsService.php @@ -14,8 +14,6 @@ namespace service; -use think\Request; - /** * 系统工具服务 * Class ToolsService @@ -33,14 +31,14 @@ class ToolsService { if (request()->isOptions()) { header('Access-Control-Allow-Origin:*'); - header('Access-Control-Allow-Headers:Accept,Referer,Host,Keep-Alive,User-Agent,X-Requested-With,Cache-Control,Content-Type,Cookie,token'); header('Access-Control-Allow-Credentials:true'); header('Access-Control-Allow-Methods:GET,POST,OPTIONS'); - header('Access-Control-Max-Age:1728000'); + header('Access-Control-Allow-Headers:Accept,Referer,Host,Keep-Alive,User-Agent,X-Requested-With,Cache-Control,Cookie'); header('Content-Type:text/plain charset=UTF-8'); - header('Content-Length: 0', true); - header('status: 204'); + header('Access-Control-Max-Age:1728000'); header('HTTP/1.0 204 No Content'); + header('Content-Length:0'); + header('status:204'); exit; } } @@ -52,11 +50,9 @@ class ToolsService public static function corsRequestHander() { return [ - 'Access-Control-Allow-Origin' => '*', - 'Access-Control-Allow-Credentials' => true, + 'Access-Control-Allow-Origin' => request()->header('origin', '*'), 'Access-Control-Allow-Methods' => 'GET,POST,OPTIONS', - 'Access-Defined-X-Support' => 'service@cuci.cc', - 'Access-Defined-X-Servers' => 'Guangzhou Cuci Technology Co. Ltd', + 'Access-Control-Allow-Credentials' => "true", ]; } @@ -124,12 +120,13 @@ class ToolsService foreach (self::arr2tree($list, $id, $pid) as $attr) { $attr[$path] = "{$ppath}-{$attr[$id]}"; $attr['sub'] = isset($attr['sub']) ? $attr['sub'] : []; - $attr['spl'] = str_repeat("   ├  ", substr_count($ppath, '-')); + $attr['spt'] = substr_count($ppath, '-'); + $attr['spl'] = str_repeat("    ├ ", $attr['spt']); $sub = $attr['sub']; unset($attr['sub']); $tree[] = $attr; if (!empty($sub)) { - $tree = array_merge($tree, (array)self::arr2table($sub, $id, $pid, $path, $attr[$path])); + $tree = array_merge($tree, self::arr2table($sub, $id, $pid, $path, $attr[$path])); } } return $tree; @@ -154,21 +151,4 @@ class ToolsService return $ids; } - /** - * 物流单查询 - * @param $code - * @return array - */ - public static function express($code) - { - list($result, $client_ip) = [[], Request::instance()->ip()]; - $header = ['Host' => 'www.kuaidi100.com', 'CLIENT-IP' => $client_ip, 'X-FORWARDED-FOR' => $client_ip]; - $autoResult = HttpService::get("http://www.kuaidi100.com/autonumber/autoComNum?text={$code}", [], 30, $header); - foreach (json_decode($autoResult)->auto as $vo) { - $microtime = microtime(true); - $location = "http://www.kuaidi100.com/query?type={$vo->comCode}&postid={$code}&id=1&valicode=&temp={$microtime}"; - $result[$vo->comCode] = json_decode(HttpService::get($location, [], 30, $header), true); - } - return $result; - } } diff --git a/extend/service/WechatService.php b/extend/service/WechatService.php index 726ec3b8a..6250eee58 100644 --- a/extend/service/WechatService.php +++ b/extend/service/WechatService.php @@ -14,8 +14,7 @@ namespace service; -use think\Db; -use think\Log; +use app\wechat\service\FansService; /** * 微信数据服务 @@ -23,230 +22,85 @@ use think\Log; * @package service * @author Anyon * @date 2017/03/22 15:32 + * + * @method \WeChat\Card card() static 卡券管理 + * @method \WeChat\Custom custom() static 客服消息处理 + * @method \WeChat\Limit limit() static 接口调用频次限制 + * @method \WeChat\Media media() static 微信素材管理 + * @method \WeChat\Menu menu() static 微信素材管理 + * @method \WeChat\Oauth oauth() static 微信网页授权 + * @method \WeChat\Pay pay() static 微信支付商户 + * @method \WeChat\Product product() static 商店管理 + * @method \WeChat\Qrcode qrcode() static 二维码管理 + * @method \WeChat\Receive receive() static 公众号推送管理 + * @method \WeChat\Scan scan() static 扫一扫接入管理 + * @method \WeChat\Script script() static 微信前端支持 + * @method \WeChat\Shake shake() static 揺一揺周边 + * @method \WeChat\Tags tags() static 用户标签管理 + * @method \WeChat\Template template() static 模板消息 + * @method \WeChat\User user() static 微信粉丝管理 + * @method \WeChat\Wifi wifi() static 门店WIFI管理 + * @method void wechat static 第三方微信工具 + * @method void config static 第三方配置工具 */ class WechatService { /** - * 通过图文ID读取图文信息 - * @param int $id 本地图文ID - * @param array $where 额外的查询条件 + * 获取微信实例ID + * @param string $name 实例对象名称 + * @return SoapService + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function instance($name) + { + list($appid, $appkey) = [sysconf('wechat_appid'), sysconf('wechat_appkey')]; + $token = strtolower("{$name}-{$appid}-{$appkey}"); + $location = "http://wm.cuci.cc/wechat/api.client/soap/{$token}.html"; + $params = ['uri' => strtolower($name), 'location' => $location, 'trace' => true]; + return new SoapService(null, $params); + } + + /** + * 初始化进入授权 + * @param int $fullMode 授权公众号模式 * @return array + * @throws \think\Exception + * @throws \think\exception\PDOException */ - public static function getNewsById($id, $where = []) + public static function webOauth($fullMode = 0) { - $data = Db::name('WechatNews')->where(['id' => $id])->where($where)->find(); - $article_ids = explode(',', $data['article_id']); - $articles = Db::name('WechatNewsArticle')->whereIn('id', $article_ids)->select(); - $data['articles'] = []; - foreach ($article_ids as $article_id) { - foreach ($articles as $article) { - if (intval($article['id']) === intval($article_id)) { - unset($article['create_by'], $article['create_at']); - $article['content'] = htmlspecialchars_decode($article['content']); - $data['articles'][] = $article; - } - } + $appid = sysconf('wechat_appid'); + list($openid, $fansinfo) = [session("{$appid}_openid"), session("{$appid}_fansinfo")]; + if ((empty($fullMode) && !empty($openid)) || (!empty($fullMode) && !empty($fansinfo))) { + empty($fansinfo) || FansService::set($fansinfo); + return ['openid' => $openid, 'fansinfo' => $fansinfo]; + } + $service = self::instance('wechat'); + $result = $service->oauth(session_id(), request()->url(true), $fullMode); + session("{$appid}_openid", $result['openid']); + session("{$appid}_fansinfo", $result['fans']); + if ((empty($fullMode) && !empty($openid)) || (!empty($fullMode) && !empty($fansinfo))) { + empty($fansinfo) || FansService::set($fansinfo); + return ['openid' => $openid, 'fansinfo' => $fansinfo]; + } + if (!empty($result['url'])) { + redirect($result['url'], [], 301)->send(); } - unset($articles); - return $data; } /** - * 上传图片到微信服务器 - * @param string $local_url - * @return string|null + * 魔术静态方法实现对象 + * @param string $name + * @param array $arguments + * @return SoapService + * @throws \think\Exception + * @throws \think\exception\PDOException */ - public static function uploadImage($local_url) + public static function __callStatic($name, $arguments) { - # 检测文件上否已经上传过了 - $md5 = md5($local_url); - if (($img = Db::name('WechatNewsImage')->where(['md5' => $md5])->find()) && !empty($img['media_url'])) { - return $img['media_url']; - } - # 下载临时文件到本地 - $content = file_get_contents($local_url); - $filename = 'wechat/image/' . join('/', str_split($md5, 16)) . '.' . strtolower(pathinfo($local_url, 4)); - # 上传图片到微信服务器 - if (($result = FileService::local($filename, $content)) && isset($result['file'])) { - $wechat = load_wechat('media'); - $info = $wechat->uploadImg(['media' => base64_encode($content)]); - if (!empty($info)) { - $data = ['local_url' => $local_url, 'media_url' => $info['url'], 'md5' => $md5]; - Db::name('WechatNewsImage')->insert($data); - return $info['url']; - } - Log::error("图片上传失败,请稍后再试!{$wechat->errMsg}[{$wechat->errCode}]"); - } - return null; - } - - /** - * 上传图片永久素材 - * @param string $local_url 文件URL地址 - * @param string $type 文件类型 - * @param bool $is_video 是否为视频文件 - * @param array $video_info 视频信息 - * @return string|null - */ - public static function uploadForeverMedia($local_url = '', $type = 'image', $is_video = false, $video_info = []) - { - # 检测文件上否已经上传过了 - $wechat = load_wechat('media'); - $map = ['md5' => md5($local_url), 'appid' => $wechat->getAppid()]; - if (($img = Db::name('WechatNewsMedia')->where($map)->find()) && !empty($img['media_id'])) { - return $img['media_id']; - } - # 下载临时文件到本地 - $content = file_get_contents($local_url); - $filename = 'wechat/image/' . join('/', str_split(md5($local_url), 16)) . '.' . strtolower(pathinfo($local_url, 4)); - if (($upload = FileService::local($filename, $content)) && isset($upload['file']) && file_exists($upload['file'])) { - # 上传图片到微信服务器 - if (false !== ($result = $wechat->uploadForeverMedia(['media' => base64_encode($content)], $type, $is_video, $video_info))) { - $data = ['md5' => $map['md5'], 'type' => $type, 'appid' => $wechat->getAppid(), 'media_id' => $result['media_id'], 'local_url' => $local_url]; - isset($result['url']) && $data['media_url'] = $result['url']; - Db::name('WechatNewsMedia')->insert($data); - return $data['media_id']; - } - } - Log::error("素材上传失败, 请稍后再试! {$wechat->errMsg}[{$wechat->errCode}]"); - return null; - } - - /** - * 从微信服务器获取所有标签 - * @return bool - */ - public static function syncFansTags() - { - $wechat = load_wechat("User"); - if (($result = $wechat->getTags()) !== false) { - Db::name('WechatFansTags')->where('appid', $wechat->getAppid())->delete(); - foreach (array_chunk($result['tags'], 100) as $list) { - Db::name('WechatFansTags')->insertAll($list); - } - } - return true; - } - - /** - * 同步粉丝的标签 - * @param string $openid - * @return bool - */ - public static function syncFansTagsByOpenid($openid) - { - $wechat = load_wechat('User'); - $tagsid = $wechat->getUserTags($openid); - if ($tagsid === false || !is_array($tagsid)) { - return false; - } - $data = ['openid' => $openid, 'tagid_list' => join(',', $tagsid)]; - return DataService::save('wechat_fans', $data, 'openid', ['appid' => $wechat->getAppid()]); - } - - /** - * 保存/更新粉丝信息 - * @param array $user - * @param string $appid - * @return bool - */ - public static function setFansInfo($user, $appid = '') - { - if (!empty($user['subscribe_time'])) { - $user['subscribe_at'] = date('Y-m-d H:i:s', $user['subscribe_time']); - } - if (!empty($user['tagid_list']) && is_array($user['tagid_list'])) { - $user['tagid_list'] = join(',', $user['tagid_list']); - }else{ - unset($user['tagid_list']); - } - foreach (['country', 'province', 'city', 'nickname', 'remark'] as $k) { - isset($user[$k]) && $user[$k] = ToolsService::emojiEncode($user[$k]); - } - $user['appid'] = $appid; - return DataService::save('WechatFans', $user, 'openid'); - } - - /** - * 读取粉丝信息 - * @param string $openid 微信用户openid - * @param string $appid 公众号appid - * @return array|false - */ - public static function getFansInfo($openid, $appid = null) - { - $map = ['openid' => $openid]; - is_string($appid) && $map['appid'] = $appid; - $user = Db::name('WechatFans')->where($map)->find(); - foreach (['country', 'province', 'city', 'nickname', 'remark'] as $k) { - isset($user[$k]) && $user[$k] = ToolsService::emojiDecode($user[$k]); - } - return $user; - } - - /** - * 同步获取粉丝列表 - * @param string $next_openid - * @return bool - */ - public static function syncAllFans($next_openid = '') - { - $wechat = load_wechat('User'); - $appid = $wechat->getAppid(); - if (false === ($result = $wechat->getUserList($next_openid)) || empty($result['data']['openid'])) { - Log::error("获取粉丝列表失败, {$wechat->errMsg} [{$wechat->errCode}]"); - return false; - } - foreach (array_chunk($result['data']['openid'], 100) as $openids) { - if (false === ($info = $wechat->getUserBatchInfo($openids)) || !is_array($info)) { - Log::error("获取用户信息失败, {$wechat->errMsg} [{$wechat->errCode}]"); - return false; - } - foreach ($info as $user) { - if (false === self::setFansInfo($user, $appid)) { - Log::error('更新粉丝信息更新失败!'); - return false; - } - if ($result['next_openid'] === $user['openid']) { - unset($result['next_openid']); - } - } - } - return empty($result['next_openid']) ? true : self::syncAllFans($result['next_openid']); - } - - /** - * 同步获取黑名单信息 - * @param string $next_openid - * @return bool - */ - public static function syncBlackFans($next_openid = '') - { - $wechat = load_wechat('User'); - $result = $wechat->getBacklist($next_openid); - if ($result === false || empty($result['data']['openid'])) { - if (empty($result['total'])) { - return true; - } - Log::error("获取粉丝黑名单列表失败,{$wechat->errMsg} [{$wechat->errCode}]"); - return false; - } - foreach ($result['data']['openid'] as $openid) { - if (false === ($user = $wechat->getUserInfo($openid))) { - Log::error("获取用户[{$openid}]信息失败,$wechat->errMsg"); - return false; - } - $user['is_back'] = '1'; - if (false === self::setFansInfo($user)) { - Log::error('更新粉丝信息更新失败!'); - return false; - } - if ($result['next_openid'] === $openid) { - unset($result['next_openid']); - } - } - return empty($result['next_openid']) ? true : self::syncBlackFans($result['next_openid']); + return self::instance($name); } } diff --git a/index.php b/index.php index c677a192a..07c0a05ed 100644 --- a/index.php +++ b/index.php @@ -12,14 +12,10 @@ // | github开源项目:https://github.com/zoujingli/ThinkAdmin // +---------------------------------------------------------------------- -/* SESSION会话名称 */ -session_name('s' . substr(md5(__FILE__), 0, 8)); +namespace think; -/* 定义应用目录 */ -define('APP_PATH', __DIR__ . '/application/'); +// 加载基础文件 +require __DIR__ . '/thinkphp/base.php'; -/* 定义Runtime运行目录 */ -define('RUNTIME_PATH', __DIR__ . '/runtime/'); - -/* 加载框架引导文件 */ -require __DIR__ . '/thinkphp/start.php'; +// 执行应用并响应 +Container::get('app', [__DIR__ . '/application/'])->run()->send(); diff --git a/route/route.php b/route/route.php new file mode 100644 index 000000000..ba8649f28 --- /dev/null +++ b/route/route.php @@ -0,0 +1,57 @@ + 0, 'msg' => '测试环境禁修改用户密码!']); +}); +Route::post('admin/config/index', function () { + return json(['code' => 0, 'msg' => '测试环境禁修改系统配置操作!']); +}); +Route::post('admin/config/file', function () { + return json(['code' => 0, 'msg' => '测试环境禁修改文件配置操作!']); +}); +Route::post('admin/menu/index', function () { + return json(['code' => 0, 'msg' => '测试环境禁排序菜单操作!']); +}); +Route::post('admin/menu/add', function () { + return json(['code' => 0, 'msg' => '测试环境禁添加菜单操作!']); +}); +Route::post('admin/menu/edit', function () { + return json(['code' => 0, 'msg' => '测试环境禁编辑菜单操作!']); +}); +Route::post('admin/menu/forbid', function () { + return json(['code' => 0, 'msg' => '测试环境禁止禁用菜单操作!']); +}); +Route::post('admin/menu/del', function () { + return json(['code' => 0, 'msg' => '测试环境禁止删除菜单操作!']); +}); +Route::post('wechat/config/index', function () { + return json(['code' => 0, 'msg' => '测试环境禁止修改微信配置操作!']); +}); +Route::post('admin/node/save', function () { + return json(['code' => 0, 'msg' => '测试环境禁止修改节点数据操作!']); +}); +Route::post('wechat/menu/edit', function () { + return json(['code' => 0, 'msg' => '测试环境禁止修改微信菜单操作!']); +}); +Route::get( + 'wechat/menu/cancel', function () { + return json(['code' => 0, 'msg' => '测试环境禁止删除微信菜单操作!']); +}); diff --git a/static/admin.js b/static/admin.js new file mode 100644 index 000000000..ccce6efdb --- /dev/null +++ b/static/admin.js @@ -0,0 +1,664 @@ +// +---------------------------------------------------------------------- +// | ThinkAdmin +// +---------------------------------------------------------------------- +// | 版权所有 2014~2018 广州楚才信息科技有限公司 [ http://www.cuci.cc ] +// +---------------------------------------------------------------------- +// | 官方网站: http://think.ctolog.com +// +---------------------------------------------------------------------- +// | 开源协议 ( https://mit-license.org ) +// +---------------------------------------------------------------------- +// | github开源项目:https://github.com/zoujingli/ThinkAdmin +// +---------------------------------------------------------------------- + +/** + * jQuery后台初始化 + * @version 1.0 + * @date 2018/02/03 + * @author Anyon + */ +if (typeof layui !== 'undefined') { + var form = layui.form, + layer = layui.layer, + laydate = layui.laydate; + if (typeof jQuery === 'undefined') { + var $ = jQuery = layui.$; + } +} + +$(function () { + + // 当前页面Bogy对象 + var $body = $('body'); + + // jQuery placeholder, fix for IE6,7,8,9 + var JPlaceHolder = new function () { + this.init = function () { + if (!('placeholder' in document.createElement('input'))) { + $(':input[placeholder]').map(function () { + var self = $(this), txt = self.attr('placeholder'); + self.wrap($('
    ').css({zoom: '1', margin: 'none', border: 'none', padding: 'none', background: 'none', position: 'relative'})); + var pos = self.position(), h = self.outerHeight(true), paddingleft = self.css('padding-left'); + var holder = $('').text(txt).css({position: 'absolute', left: pos.left, top: pos.top, height: h, lineHeight: h + 'px', paddingLeft: paddingleft, color: '#aaa'}).appendTo(self.parent()); + self.on('focusin focusout change keyup', function () { + self.val() ? holder.hide() : holder.show(); + }); + holder.click(function () { + self.get(0).focus(); + }); + self.val() && holder.hide(); + }); + } + }; + this.init(); + }; + + /*! 消息组件实例 */ + $.msg = new function () { + var self = this; + this.shade = [0.02, '#000']; + this.dialogIndexs = []; + // 关闭消息框 + this.close = function (index) { + return layer.close(index); + }; + // 弹出警告消息框 + this.alert = function (msg, callback) { + var index = layer.alert(msg, {end: callback, scrollbar: false}); + return this.dialogIndexs.push(index), index; + }; + // 确认对话框 + this.confirm = function (msg, ok, no) { + var index = layer.confirm(msg, {title: '操作确认', btn: ['确认', '取消']}, function () { + typeof ok === 'function' && ok.call(this); + }, function () { + typeof no === 'function' && no.call(this); + self.close(index); + }); + return index; + }; + // 显示成功类型的消息 + this.success = function (msg, time, callback) { + var index = layer.msg(msg, {icon: 1, shade: this.shade, scrollbar: false, end: callback, time: (time || 2) * 1000, shadeClose: true}); + return this.dialogIndexs.push(index), index; + }; + // 显示失败类型的消息 + this.error = function (msg, time, callback) { + var index = layer.msg(msg, {icon: 2, shade: this.shade, scrollbar: false, time: (time || 3) * 1000, end: callback, shadeClose: true}); + return this.dialogIndexs.push(index), index; + }; + // 状态消息提示 + this.tips = function (msg, time, callback) { + var index = layer.msg(msg, {time: (time || 3) * 1000, shade: this.shade, end: callback, shadeClose: true}); + return this.dialogIndexs.push(index), index; + }; + // 显示正在加载中的提示 + this.loading = function (msg, callback) { + var index = msg ? layer.msg(msg, {icon: 16, scrollbar: false, shade: this.shade, time: 0, end: callback}) : layer.load(2, {time: 0, scrollbar: false, shade: this.shade, end: callback}); + return this.dialogIndexs.push(index), index; + }; + // 自动处理显示Think返回的Json数据 + this.auto = function (data, time) { + return (parseInt(data.code) === 1) ? self.success(data.msg, time, function () { + !!data.url ? (window.location.href = data.url) : $.form.reload(); + for (var i in self.dialogIndexs) { + layer.close(self.dialogIndexs[i]); + } + self.dialogIndexs = []; + }) : self.error(data.msg, 3, function () { + !!data.url && (window.location.href = data.url); + }); + }; + }; + + /*! 表单自动化组件 */ + $.form = new function () { + var self = this; + // 默认异常提示消息 + this.errMsg = '{status}服务器繁忙,请稍候再试!'; + // 内容区域动态加载后初始化 + this.reInit = function ($container) { + $.validate.listen.call(this), JPlaceHolder.init(); + $container.find('[required]').parent().prevAll('label').addClass('label-required'); + }; + // 在内容区显示视图 + this.show = function (html) { + var $container = $('.framework-body').html(html); + reinit.call(this), setTimeout(reinit, 500), setTimeout(reinit, 1000); + + function reinit() { + $.form.reInit($container); + } + }; + // 以hash打开网页 + this.href = function (url, obj) { + if (url !== '#') { + window.location.href = '#' + $.menu.parseUri(url, obj); + } else if (obj && obj.getAttribute('data-menu-node')) { + var node = obj.getAttribute('data-menu-node'); + $('[data-menu-node^="' + node + '-"][data-open!="#"]:first').trigger('click'); + } + }; + // 刷新当前页面 + this.reload = function () { + window.onhashchange.call(this); + }; + // 异步加载的数据 + this.load = function (url, data, type, callback, loading, tips, time) { + var self = this, dialogIndex = 0; + (loading !== false) && (dialogIndex = $.msg.loading(tips)); + (typeof Pace === 'object') && Pace.restart(); + $.ajax({ + type: type || 'GET', url: $.menu.parseUri(url), data: data || {}, + statusCode: { + 404: function () { + $.msg.close(dialogIndex); + $.msg.tips(self.errMsg.replace('{status}', 'E404 - ')); + }, + 500: function () { + $.msg.close(dialogIndex); + $.msg.tips(self.errMsg.replace('{status}', 'E500 - ')); + } + }, + error: function (XMLHttpRequest, textStatus, errorThrown) { + $.msg.close(dialogIndex); + $.msg.tips(self.errMsg.replace('{status}', 'E' + textStatus + ' - ')); + }, + success: function (res) { + $.msg.close(dialogIndex); + if (typeof callback === 'function' && callback.call(self, res) === false) { + return false; + } + if (typeof (res) === 'object') { + return $.msg.auto(res, time); + } + self.show(res); + } + }); + }; + // 加载HTML到目标位置 + this.open = function (url, data, callback, loading, tips) { + this.load(url, data, 'get', function (res) { + if (typeof (res) === 'object') { + return $.msg.auto(res); + } + self.show(res); + }, loading, tips); + }; + // 打开一个iframe窗口 + this.iframe = function (url, title) { + return layer.open({title: title || '窗口', type: 2, area: ['800px', '530px'], fix: true, maxmin: false, content: url}); + }; + // 加载HTML到弹出层 + this.modal = function (url, data, title, callback, loading, tips) { + this.load(url, data, 'GET', function (res) { + if (typeof (res) === 'object') { + return $.msg.auto(res); + } + var layerIndex = layer.open({ + type: 1, btn: false, area: "800px", content: res, title: title || '', success: function (dom, index) { + $(dom).find('[data-close]').off('click').on('click', function () { + if ($(this).attr('data-confirm')) { + var confirmIndex = $.msg.confirm($(this).attr('data-confirm'), function () { + layer.close(confirmIndex), layer.close(index); + }); + return false; + } + layer.close(index); + }); + $.form.reInit($(dom)); + } + }); + $.msg.dialogIndexs.push(layerIndex); + return (typeof callback === 'function') && callback.call(this); + }, loading, tips); + }; + }; + + // 上传单个图片 + $.fn.uploadOneImage = function () { + var name = $(this).attr('name') || 'image'; + var type = $(this).data('type') || 'png,jpg'; + var $tpl = $(''); + $(this).hide().attr('name', name).after($tpl).on('change', function () { + $tpl.get(0).style = this.value ? 'background-image:url(' + this.value + ')' : ''; + }).trigger('change'); + }; + + // 上传多个图片 + $.fn.uploadMultipleImage = function () { + var type = $(this).data('type') || 'png,jpg'; + var name = $(this).attr('name') || 'umt-image'; + var $tpl = $(''); + $(this).hide().attr('name', name).after($tpl).on('change', function () { + var input = this, values = [], srcs = this.value.split('|'); + $(this).prevAll('.uploadimage').map(function () { + values.push($(this).attr('data-tips-image')); + }), $(this).prevAll('.uploadimage').remove(), values.reverse(); + for (var i in srcs) { + srcs[i] && values.push(srcs[i]); + } + this.value = values.join('|'); + for (var i in values) { + var tpl = ''; + var $tpl = $(tpl).attr('data-tips-image', values[i]).css('backgroundImage', 'url(' + values[i] + ')'); + $tpl.data('input', input).data('srcs', values).data('index', i); + $tpl.on('click', 'a', function (e) { + e.stopPropagation(); + var $cur = $(this).parent(); + var dialogIndex = $.msg.confirm('确定要移除这张图片吗?', function () { + var data = $cur.data('srcs'), tmp = []; + for (var i in data) { + i !== $cur.data('index') && tmp.push(data[i]); + } + $cur.data('input').value = tmp.join('|'); + $cur.remove(), $.msg.close(dialogIndex); + }); + }) + $(this).before($tpl); + } + }).trigger('change'); + }; + + // 注册对象到JqFn + $.fn.validate = function (callback, options) { + return (new validate()).check(this, callback, options); + }; + + // 注册对象到Jq + $.validate = function (form, callback, options) { + return (new validate()).check(form, callback, options); + }; + + // 自动监听规则内表单 + $.validate.listen = function () { + $('form[data-auto]').map(function () { + if ($(this).attr('data-listen') !== 'true') { + var callbackname = $(this).attr('data-callback'); + $(this).attr('data-listen', 'true').validate(function (data) { + var method = this.getAttribute('method') || 'POST'; + var tips = this.getAttribute('data-tips') || undefined; + var url = this.getAttribute('action') || window.location.href; + var callback = window[callbackname || '_default_callback'] || undefined; + var time = this.getAttribute('data-time') || undefined; + $.form.load(url, data, method, callback, true, tips, time); + }); + $(this).find('[data-form-loaded]').map(function () { + $(this).html(this.getAttribute('data-form-loaded') || this.innerHTML); + $(this).removeAttr('data-form-loaded').removeClass('layui-disabled'); + }); + } + }); + }; + + // 表单验证 + function validate() { + var self = this; + // 表单元素 + this.tags = 'input,textarea,select'; + // 检测元素事件 + this.checkEvent = {change: true, blur: true, keyup: false}; + // 去除字符串两头的空格 + this.trim = function (str) { + return str.replace(/(^\s*)|(\s*$)/g, ''); + }; + // 标签元素是否可见 + this.isVisible = function (ele) { + return $(ele).is(':visible'); + }; + // 检测属性是否有定义 + this.hasProp = function (ele, prop) { + if (typeof prop !== "string") { + return false; + } + var attrProp = ele.getAttribute(prop); + return (typeof attrProp !== 'undefined' && attrProp !== null && attrProp !== false); + }; + // 判断表单元素是否为空 + this.isEmpty = function (ele, value) { + var trimValue = this.trim(ele.value); + value = value || ele.getAttribute('placeholder'); + return (trimValue === "" || trimValue === value); + }; + // 正则验证表单元素 + this.isRegex = function (ele, regex, params) { + var inputValue = ele.value, dealValue = this.trim(inputValue); + regex = regex || ele.getAttribute('pattern'); + if (dealValue === "" || !regex) { + return true; + } + if (dealValue !== inputValue) { + (ele.tagName.toLowerCase() !== "textarea") ? (ele.value = dealValue) : (ele.innerHTML = dealValue); + } + return new RegExp(regex, params || 'i').test(dealValue); + }; + // 检侧所的表单元素 + this.isAllpass = function (elements, options) { + if (!elements) { + return true; + } + var allpass = true, self = this, params = options || {}; + if (elements.size && elements.size() === 1 && elements.get(0).tagName.toLowerCase() === "form") { + elements = $(elements).find(self.tags); + } else if (elements.tagName && elements.tagName.toLowerCase() === "form") { + elements = $(elements).find(self.tags); + } + elements.each(function () { + if (self.checkInput(this, params) === false) { + return $(this).focus(), (allpass = false); + } + }); + return allpass; + }; + // 验证标志 + this.remind = function (input) { + return this.isVisible(input) ? this.showError(input, input.getAttribute('title') || '') : false; + }; + // 检测表单单元 + this.checkInput = function (input) { + var type = (input.getAttribute("type") + "").replace(/\W+$/, "").toLowerCase(); + var tag = input.tagName.toLowerCase(), isRequired = this.hasProp(input, "required"); + if (this.hasProp(input, 'data-auto-none') || input.disabled || type === 'submit' || type === 'reset' || type === 'file' || type === 'image' || !this.isVisible(input)) { + return; + } + var allpass = true; + if (type === "radio" && isRequired) { + var radiopass = false, eleRadios = input.name ? $("input[type='radio'][name='" + input.name + "']") : $(input); + eleRadios.each(function () { + (radiopass === false && $(this).is("[checked]")) && (radiopass = true); + }); + if (radiopass === false) { + allpass = this.remind(eleRadios.get(0), type, tag); + } else { + this.hideError(input); + } + } else if (type === "checkbox" && isRequired && !$(input).is("[checked]")) { + allpass = this.remind(input, type, tag); + } else if (tag === "select" && isRequired && !input.value) { + allpass = this.remind(input, type, tag); + } else if ((isRequired && this.isEmpty(input)) || !(allpass = this.isRegex(input))) { + allpass ? this.remind(input, type, "empty") : this.remind(input, type, tag); + allpass = false; + } else { + this.hideError(input); + } + return allpass; + }; + // 错误消息显示 + this.showError = function (ele, content) { + $(ele).addClass('validate-error'), this.insertError(ele); + $($(ele).data('input-info')).addClass('fadeInRight animated').css({width: 'auto'}).html(content); + }; + // 错误消息消除 + this.hideError = function (ele) { + $(ele).removeClass('validate-error'), this.insertError(ele); + $($(ele).data('input-info')).removeClass('fadeInRight').css({width: '30px'}).html(''); + }; + // 错误消息标签插入 + this.insertError = function (ele) { + var $html = $(''); + $html.css({top: $(ele).position().top + 'px', paddingBottom: $(ele).css('paddingBottom'), lineHeight: $(ele).css('height')}); + $(ele).data('input-info') || $(ele).data('input-info', $html.insertAfter(ele)); + }; + // 表单验证入口 + this.check = function (form, callback, options) { + var params = $.extend({}, options || {}); + $(form).attr("novalidate", "novalidate"); + $(form).find(self.tags).map(function () { + for (var i in self.checkEvent) { + if (self.checkEvent[i] === true) { + $(this).off(i, func).on(i, func); + } + } + + function func() { + self.checkInput(this); + } + }); + $(form).bind("submit", function (event) { + if (self.isAllpass($(this).find(self.tags), params) && typeof callback === 'function') { + if (typeof CKEDITOR === 'object' && typeof CKEDITOR.instances === 'object') { + for (var instance in CKEDITOR.instances) { + CKEDITOR.instances[instance].updateElement(); + } + } + callback.call(this, $(form).serialize()); + } + return event.preventDefault(), false; + }); + return $(form).data('validate', this); + }; + } + + /*! 后台菜单辅助插件 */ + $.menu = new function () { + // 计算URL地址中有效的URI + this.getUri = function (uri) { + uri = uri || window.location.href; + uri = (uri.indexOf(window.location.host) > -1 ? uri.split(window.location.host)[1] : uri).split('?')[0]; + return (uri.indexOf('#') !== -1 ? uri.split('#')[1] : uri); + }; + // 通过URI查询最有可能的菜单NODE + this.queryNode = function (url) { + var node = location.href.replace(/.*spm=([\d\-m]+).*/ig, '$1'); + if (!/^m\-/.test(node)) { + var $menu = $('[data-menu-node][data-open*="' + url.replace(/\.html$/ig, '') + '"]'); + return $menu.size() ? $menu.get(0).getAttribute('data-menu-node') : ''; + } + return node; + }; + // URL转URI + this.parseUri = function (uri, obj) { + var params = {}; + if (uri.indexOf('?') > -1) { + var serach = uri.split('?')[1].split('&'); + for (var i in serach) { + if (serach[i].indexOf('=') > -1) { + var arr = serach[i].split('='); + try { + params[arr[0]] = window.decodeURIComponent(window.decodeURIComponent(arr[1].replace(/%2B/ig, ' '))); + } catch (e) { + console.log([e, uri, serach, arr]); + } + } + } + } + uri = this.getUri(uri); + params.spm = obj && obj.getAttribute('data-menu-node') || this.queryNode(uri); + delete params[""]; + var query = '?' + $.param(params); + return uri + (query !== '?' ? query : ''); + }; + // 后台菜单动作初始化 + this.listen = function () { + var self = this; + // 左则二级菜单展示 + $('[data-submenu-layout]>a').on('click', function () { + $(this).parent().toggleClass('open'); + self.syncOpenStatus(1); + }); + // 同步二级菜单展示状态 + this.syncOpenStatus = function (mode) { + $('[data-submenu-layout]').map(function () { + var node = $(this).attr('data-submenu-layout'); + if (mode === 1) { + var type = (this.className || '').indexOf('open') > -1 ? 2 : 1; + layui.data('menu', {key: node, value: type}); + } else { + var type = layui.data('menu')[node] || 2; + (type === 2) && $(this).addClass('open'); + } + }); + }; + window.onhashchange = function () { + var hash = window.location.hash || ''; + if (hash.length < 1) { + return $('[data-menu-node][data-open!="#"]:first').trigger('click'); + } + $.form.load(hash); + self.syncOpenStatus(2); + // 菜单选择切换 + var node = self.queryNode(self.getUri()); + if (/^m\-/.test(node)) { + var $all = $('a[data-menu-node]'), tmp = node.split('-'), tmpNode = tmp.shift(); + while (tmp.length > 0) { + tmpNode = tmpNode + '-' + tmp.shift(); + $all = $all.not($('a[data-menu-node="' + tmpNode + '"]').addClass('active')); + } + $all.removeClass('active'); + // 菜单模式切换 + if (node.split('-').length > 2) { + var _tmp = node.split('-'), _node = _tmp.shift() + '-' + _tmp.shift(); + $('[data-menu-layout]').not($('[data-menu-layout="' + _node + '"]').removeClass('hide')).addClass('hide'); + $('[data-menu-node="' + node + '"]').parent('div').parent('div').addClass('open'); + $('body.framework').removeClass('mini'); + } else { + $('body.framework').addClass('mini'); + } + self.syncOpenStatus(1); + } + }; + // URI初始化动作 + window.onhashchange.call(this); + }; + }; + + /*! 注册 data-load 事件行为 */ + $body.on('click', '[data-load]', function () { + var url = $(this).attr('data-load'), tips = $(this).attr('data-tips'); + if ($(this).attr('data-confirm')) { + return $.msg.confirm($(this).attr('data-confirm'), _goLoad); + } + return _goLoad.call(this); + + function _goLoad() { + $.form.load(url, {}, 'get', null, true, tips); + } + }); + + /*! 注册 data-serach 表单搜索行为 */ + $body.on('submit', 'form.form-search', function () { + var url = $(this).attr('action').replace(/\&?page\=\d+/g, ''), split = url.indexOf('?') === -1 ? '?' : '&'; + if ((this.method || 'get').toLowerCase() === 'get') { + return window.location.href = '#' + $.menu.parseUri(url + split + $(this).serialize()); + } + $.form.load(url, this, 'post'); + }); + + /*! 注册 data-modal 事件行为 */ + $body.on('click', '[data-modal]', function () { + return $.form.modal($(this).attr('data-modal'), 'open_type=modal', $(this).attr('data-title') || '编辑'); + }); + + /*! 注册 data-open 事件行为 */ + $body.on('click', '[data-open]', function () { + $.form.href($(this).attr('data-open'), this); + }); + + /*! 注册 data-reload 事件行为 */ + $body.on('click', '[data-reload]', function () { + $.form.reload(); + }); + + /*! 注册 data-check 事件行为 */ + $body.on('click', '[data-check-target]', function () { + var checked = !!this.checked; + $($(this).attr('data-check-target')).map(function () { + this.checked = checked; + }); + }); + + /*! 注册 data-update 事件行为 */ + $body.on('click', '[data-update]', function () { + var id = $(this).attr('data-update') || (function () { + var data = []; + return $($(this).attr('data-list-target') || 'input.list-check-box').map(function () { + (this.checked) && data.push(this.value); + }), data.join(','); + }).call(this); + if (id.length < 1) { + return $.msg.tips('请选择需要操作的数据!'); + } + var action = $(this).attr('data-action') || $(this).parents('[data-location]').attr('data-location'); + var value = $(this).attr('data-value') || 0, field = $(this).attr('data-field') || 'status'; + $.msg.confirm('确定要操作这些数据吗?', function () { + $.form.load(action, {field: field, value: value, id: id}, 'post'); + }); + }); + + /*! 注册 data-href 事件行为 */ + $body.on('click', '[data-href]', function () { + var href = $(this).attr('data-href'); + (href && href.indexOf('#') !== 0) && (window.location.href = href); + }); + + /*! 注册 data-page-href 事件行为 */ + $body.on('click', 'a[data-page-href]', function () { + window.location.href = '#' + $.menu.parseUri(this.href, this); + }); + + /*! 注册 data-file 事件行为 */ + $body.on('click', '[data-file]', function () { + var method = $(this).attr('data-file') === 'one' ? 'one' : 'mtl'; + var type = $(this).attr('data-type') || 'jpg,png', field = $(this).attr('data-field') || 'file'; + var title = $(this).attr('data-title') || '文件上传', uptype = $(this).attr('data-uptype') || ''; + var url = window.ROOT_URL + '/index.php/admin/plugs/upfile.html?mode=' + method + '&uptype=' + uptype + '&type=' + type + '&field=' + field; + $.form.iframe(url, title || '文件管理'); + }); + + /*! 注册 data-iframe 事件行为 */ + $body.on('click', '[data-iframe]', function () { + $.form.iframe($(this).attr('data-iframe'), $(this).attr('data-title') || '窗口'); + }); + + /*! 注册 data-icon 事件行为 */ + $body.on('click', '[data-icon]', function () { + var field = $(this).attr('data-icon') || $(this).attr('data-field') || 'icon'; + var url = window.ROOT_URL + '/index.php/admin/plugs/icon.html?field=' + field; + $.form.iframe(url, '图标选择'); + }); + + /*! 注册 data-tips-image 事件行为 */ + $body.on('click', '[data-tips-image]', function () { + var img = new Image(), src = this.getAttribute('data-tips-image') || this.src; + var imgWidth = this.getAttribute('data-width') || '480px'; + img.onload = function () { + var $content = $(img).appendTo('body').css({background: '#fff', width: imgWidth, height: 'auto'}); + layer.open({ + type: 1, area: imgWidth, title: false, closeBtn: 1, + skin: 'layui-layer-nobg', shadeClose: true, content: $content, + end: function () { + $(img).remove(); + } + }); + }; + img.src = src; + }); + + /*! 注册 data-tips-text 事件行为 */ + $body.on('mouseenter', '[data-tips-text]', function () { + var text = $(this).attr('data-tips-text'), placement = $(this).attr('data-tips-placement') || 'auto'; + $(this).tooltip({title: text, placement: placement}).tooltip('show'); + }); + + /*! 注册 data-phone-view 事件行为 */ + $body.on('click', '[data-phone-view]', function () { + var $container = $('
    公众号
    ').appendTo('body'); + $container.find('iframe').attr('src', this.getAttribute('data-phone-view') || this.href); + layer.style(layer.open({ + type: 1, scrollbar: !1, area: ['335px', '600px'], title: !1, + closeBtn: 1, skin: 'layui-layer-nobg', shadeClose: !1, content: $container, + end: function () { + $container.remove(); + } + }), {boxShadow: 'none'}); + }); + + /*! 分页事件切换处理 */ + $body.on('change', '.pagination-trigger select', function () { + window.location.href = this.options[this.selectedIndex].getAttribute('data-url'); + }); + + /*! 初始化 */ + $.menu.listen(); + $.validate.listen(this); +}); \ No newline at end of file diff --git a/static/admin/app.js b/static/admin/app.js deleted file mode 100644 index 4d7eab4db..000000000 --- a/static/admin/app.js +++ /dev/null @@ -1,75 +0,0 @@ - -// +---------------------------------------------------------------------- -// | ThinkAdmin -// +---------------------------------------------------------------------- -// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] -// +---------------------------------------------------------------------- -// | 官方网站: http://think.ctolog.com -// +---------------------------------------------------------------------- -// | 开源协议 ( https://mit-license.org ) -// +---------------------------------------------------------------------- -// | github开源项目:https://github.com/zoujingli/ThinkAdmin -// +---------------------------------------------------------------------- - -// 当前资源URL目录 -var _root = (function () { - var scripts = document.scripts, src = scripts[scripts.length - 1].src; - return src.substring(0, src.lastIndexOf("/") + 1); -})(); - -// RequireJs 配置参数 -require.config({ - waitSeconds: 0, - baseUrl: _root, - map: {'*': {css: _root + '../plugs/require/require.css.js'}}, - paths: { - // 自定义插件(源码自创建或已修改源码) - 'admin.plugs': ['plugs'], - 'admin.listen': ['listen'], - 'template': ['../plugs/template/template'], - 'pcasunzips': ['../plugs/jquery/pcasunzips'], - // 开源插件(未修改源码) - 'pace': ['../plugs/jquery/pace.min'], - 'json': ['../plugs/jquery/json2.min'], - 'layui': ['../plugs/layui/layui'], - 'jquery': ['../plugs/jquery/jquery.min'], - 'base64': ['../plugs/jquery/base64.min'], - 'angular': ['../plugs/angular/angular.min'], - 'ckeditor': ['../plugs/ckeditor/ckeditor'], - 'websocket': ['../plugs/socket/websocket'], - 'bootstrap': ['../plugs/bootstrap/js/bootstrap.min'], - 'bootstrap.typeahead': ['../plugs/bootstrap/js/bootstrap3-typeahead.min'], - 'jquery.ztree': ['../plugs/ztree/jquery.ztree.all.min'], - 'jquery.masonry': ['../plugs/jquery/masonry.min'], - 'jquery.cookies': ['../plugs/jquery/jquery.cookie'], - }, - shim: { - 'layui': {deps: ['jquery']}, - 'ckeditor': {deps: ['jquery']}, - 'websocket': {deps: [_root + '../plugs/socket/swfobject.min.js']}, - 'pcasunzips': {deps: ['jquery']}, - 'admin.plugs': {deps: ['jquery', 'layui']}, - 'admin.listen': {deps: ['jquery', 'jquery.cookies', 'admin.plugs']}, - 'bootstrap': {deps: ['jquery']}, - 'bootstrap.typeahead': {deps: ['bootstrap']}, - 'jquery.ztree': {deps: ['jquery', 'css!' + _root + '../plugs/ztree/zTreeStyle/zTreeStyle.css']}, - 'jquery.cookies': {deps: ['jquery']}, - 'jquery.masonry': {deps: ['jquery']}, - }, - // deps: [], - // 开启debug模式,不缓存资源 - // urlArgs: "ver=" + (new Date()).getTime() -}); - -// UI框架初始化 -PageLayout.call(this); -function PageLayout(callback, custom, basic) { - window.WEB_SOCKET_SWF_LOCATION = _root + "../plugs/socket/WebSocketMain.swf"; - require(basic || ['pace', 'jquery', 'layui', 'bootstrap'], function () { - layui.config({dir: _root + '../plugs/layui/'}); - layui.use(['layer', 'form', 'laydate'], function () { - window.layer = layui.layer, window.form = layui.form, window.laydate = layui.laydate; - require(custom || ['admin.listen', 'ckeditor'], callback || false); - }); - }); -} diff --git a/static/admin/listen.js b/static/admin/listen.js deleted file mode 100644 index 59475d76c..000000000 --- a/static/admin/listen.js +++ /dev/null @@ -1,149 +0,0 @@ - -// +---------------------------------------------------------------------- -// | ThinkAdmin -// +---------------------------------------------------------------------- -// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] -// +---------------------------------------------------------------------- -// | 官方网站: http://think.ctolog.com -// +---------------------------------------------------------------------- -// | 开源协议 ( https://mit-license.org ) -// +---------------------------------------------------------------------- -// | github开源项目:https://github.com/zoujingli/ThinkAdmin -// +---------------------------------------------------------------------- - -define(['jquery', 'admin.plugs'], function () { - - /*! 定义当前body对象 */ - this.$body = $('body'); - - /*! 注册 data-load 事件行为 */ - this.$body.on('click', '[data-load]', function () { - var url = $(this).attr('data-load'), tips = $(this).attr('data-tips'); - if ($(this).attr('data-confirm')) { - return $.msg.confirm($(this).attr('data-confirm'), _goLoad); - } - return _goLoad.call(this); - function _goLoad() { - $.form.load(url, {}, 'get', null, true, tips); - } - }); - - /*! 注册 data-serach 表单搜索行为 */ - this.$body.on('submit', 'form.form-search', function () { - var url = $(this).attr('action').replace(/\&?page\=\d+/g, ''), split = url.indexOf('?') === -1 ? '?' : '&'; - if ((this.method || 'get').toLowerCase() === 'get') { - return window.location.href = '#' + $.menu.parseUri(url + split + $(this).serialize()); - } - $.form.load(url, this, 'post'); - }); - - /*! 注册 data-modal 事件行为 */ - this.$body.on('click', '[data-modal]', function () { - return $.form.modal($(this).attr('data-modal'), 'open_type=modal', $(this).attr('data-title') || '编辑'); - }); - - /*! 注册 data-open 事件行为 */ - this.$body.on('click', '[data-open]', function () { - $.form.href($(this).attr('data-open'), this); - }); - - /*! 注册 data-reload 事件行为 */ - this.$body.on('click', '[data-reload]', function () { - $.form.reload(); - }); - - /*! 注册 data-check 事件行为 */ - this.$body.on('click', '[data-check-target]', function () { - var checked = !!this.checked; - $($(this).attr('data-check-target')).map(function () { - this.checked = checked; - }); - }); - - /*! 注册 data-update 事件行为 */ - this.$body.on('click', '[data-update]', function () { - var id = $(this).attr('data-update') || (function () { - var data = []; - return $($(this).attr('data-list-target') || 'input.list-check-box').map(function () { - (this.checked) && data.push(this.value); - }), data.join(','); - }).call(this); - if (id.length < 1) { - return $.msg.tips('请选择需要操作的数据!'); - } - var action = $(this).attr('data-action') || $(this).parents('[data-location]').attr('data-location'); - var value = $(this).attr('data-value') || 0, field = $(this).attr('data-field') || 'status'; - $.msg.confirm('确定要操作这些数据吗?', function () { - $.form.load(action, {field: field, value: value, id: id}, 'POST'); - }); - }); - - /*! 注册 data-href 事件行为 */ - this.$body.on('click', '[data-href]', function () { - var href = $(this).attr('data-href'); - (href && href.indexOf('#') !== 0) && (window.location.href = href); - }); - - /*! 注册 data-page-href 事件行为 */ - this.$body.on('click', 'a[data-page-href]', function () { - window.location.href = '#' + $.menu.parseUri(this.href, this); - }); - - /*! 注册 data-file 事件行为 */ - this.$body.on('click', '[data-file]', function () { - var method = $(this).attr('data-file') === 'one' ? 'one' : 'mtl'; - var type = $(this).attr('data-type') || 'jpg,png', field = $(this).attr('data-field') || 'file'; - var title = $(this).attr('data-title') || '文件上传', uptype = $(this).attr('data-uptype') || ''; - var url = window.ROOT_URL + '/index.php/admin/plugs/upfile.html?mode=' + method + '&uptype=' + uptype + '&type=' + type + '&field=' + field; - $.form.iframe(url, title || '文件管理'); - }); - - /*! 注册 data-iframe 事件行为 */ - this.$body.on('click', '[data-iframe]', function () { - $.form.iframe($(this).attr('data-iframe'), $(this).attr('data-title') || '窗口'); - }); - - /*! 注册 data-icon 事件行为 */ - this.$body.on('click', '[data-icon]', function () { - var field = $(this).attr('data-icon') || $(this).attr('data-field') || 'icon'; - var url = window.ROOT_URL + '/index.php/admin/plugs/icon.html?field=' + field; - $.form.iframe(url, '图标选择'); - }); - - /*! 注册 data-tips-image 事件行为 */ - this.$body.on('click', '[data-tips-image]', function () { - var img = new Image(), src = this.getAttribute('data-tips-image') || this.src; - var imgWidth = this.getAttribute('data-width') || '480px'; - img.onload = function () { - var $content = $(img).appendTo('body').css({background: '#fff', width: imgWidth, height: 'auto'}); - layer.open({type: 1, area: imgWidth, title: false, closeBtn: 1, skin: 'layui-layer-nobg', shadeClose: true, content: $content, end: function () { - $(img).remove(); - } - }); - }; - img.src = src; - }); - - /*! 注册 data-tips-text 事件行为 */ - this.$body.on('mouseenter', '[data-tips-text]', function () { - var text = $(this).attr('data-tips-text'), placement = $(this).attr('data-tips-placement') || 'auto'; - $(this).tooltip({title: text, placement: placement}).tooltip('show'); - }); - - /*! 注册 data-phone-view 事件行为 */ - this.$body.on('click', '[data-phone-view]', function () { - var $container = $('
    公众号
    ').appendTo('body'); - $container.find('iframe').attr('src', this.getAttribute('data-phone-view') || this.href); - layer.style(layer.open({type: 1, scrollbar: !1, area: ['335px', '600px'], title: !1, closeBtn: 1, skin: 'layui-layer-nobg', shadeClose: !1, content: $container, end: function () { - $container.remove(); - } - }), {boxShadow: 'none'}); - }); - - /*! 后台菜单控制初始化 */ - $.menu.listen(); - - /*! 表单监听初始化 */ - $.validate.listen(this); - -}); \ No newline at end of file diff --git a/static/admin/plugs.js b/static/admin/plugs.js deleted file mode 100644 index 61d0397ad..000000000 --- a/static/admin/plugs.js +++ /dev/null @@ -1,484 +0,0 @@ - -// +---------------------------------------------------------------------- -// | ThinkAdmin -// +---------------------------------------------------------------------- -// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] -// +---------------------------------------------------------------------- -// | 官方网站: http://think.ctolog.com -// +---------------------------------------------------------------------- -// | 开源协议 ( https://mit-license.org ) -// +---------------------------------------------------------------------- -// | github开源项目:https://github.com/zoujingli/ThinkAdmin -// +---------------------------------------------------------------------- - -define(['jquery'], function () { - - // jQuery placeholder, fix for IE6,7,8,9 - var JPlaceHolder = { - _check: function () { - return 'placeholder' in document.createElement('input'); - }, - init: function () { - !this._check() && this.fix(); - }, - fix: function () { - $(':input[placeholder]').map(function () { - var self = $(this), txt = self.attr('placeholder'); - self.wrap($('
    ').css({zoom: '1', margin: 'none', border: 'none', padding: 'none', background: 'none', position: 'relative'})); - var pos = self.position(), h = self.outerHeight(true), paddingleft = self.css('padding-left'); - var holder = $('').text(txt).css({position: 'absolute', left: pos.left, top: pos.top, height: h, lineHeight: h + 'px', paddingLeft: paddingleft, color: '#aaa'}).appendTo(self.parent()); - self.on('focusin focusout change keyup', function () { - self.val() ? holder.hide() : holder.show(); - }); - holder.click(function () { - self.get(0).focus(); - }); - self.val() && holder.hide(); - }); - } - }; - JPlaceHolder.init(); - - // 消息处理 - $.msg = new msg(); - function msg() { - var self = this; - this.shade = [0.02, '#000']; - // 关闭消息框 - this.close = function () { - return layer.close(this.index); - }; - // 弹出警告消息框 - this.alert = function (msg, callback) { - return this.index = layer.alert(msg, {end: callback, scrollbar: false}); - }; - // 确认对话框 - this.confirm = function (msg, ok, no) { - var self = this; - return this.index = layer.confirm(msg, {btn: ['确认', '取消']}, function () { - typeof ok === 'function' && ok.call(this); - self.close(); - }, function () { - typeof no === 'function' && no.call(this); - self.close(); - }); - }; - // 显示成功类型的消息 - this.success = function (msg, time, callback) { - return this.index = layer.msg(msg, {icon: 1, shade: this.shade, scrollbar: false, end: callback, time: (time || 2) * 1000, shadeClose: true}); - }; - // 显示失败类型的消息 - this.error = function (msg, time, callback) { - return this.index = layer.msg(msg, {icon: 2, shade: this.shade, scrollbar: false, time: (time || 3) * 1000, end: callback, shadeClose: true}); - }; - // 状态消息提示 - this.tips = function (msg, time, callback) { - return this.index = layer.msg(msg, {time: (time || 3) * 1000, shade: this.shade, end: callback, shadeClose: true}); - }; - // 显示正在加载中的提示 - this.loading = function (msg, callback) { - return this.index = msg ? layer.msg(msg, {icon: 16, scrollbar: false, shade: this.shade, time: 0, end: callback}) : layer.load(2, {time: 0, scrollbar: false, shade: this.shade, end: callback}); - }; - this.successNeedCloseLayerIndex = []; - // 自动处理显示Think返回的Json数据 - this.auto = function (data, time) { - if (parseInt(data.code) === 1) { - return self.success(data.msg, time, function () { - !!data.url ? (window.location.href = data.url) : $.form.reload(); - self.close(); - for (var i in self.successNeedCloseLayerIndex) { - layer.close(self.successNeedCloseLayerIndex[i]); - } - self.successNeedCloseLayerIndex = []; - }); - } - return self.error(data.msg, 3, function () { - !!data.url && (window.location.href = data.url); - }); - }; - } - - // 表单构造函数 - $.form = new form(); - function form() { - this.errMsg = '{status}服务器繁忙,请稍候再试!'; - // 内容区域动态加载后初始化 - this.reInit = function ($container) { - $.validate.listen.call(this), JPlaceHolder.init(); - $container.find('[required]').parent().prevAll('label').addClass('label-required'); - }; - // 以hash打开网页 - this.href = function (url, obj) { - window.location.href = '#' + $.menu.parseUri(url, obj); - }; - // 在内容区显示视图 - this.show = function (html) { - var $container = $('.framework-container').html(html); - reinit.call(this), setTimeout(reinit, 500), setTimeout(reinit, 1000); - function reinit() { - $.form.reInit($container); - } - }; - // 刷新当前页面 - this.reload = function () { - window.onhashchange.call(this); - }; - // 异步加载的数据 - this.load = function (url, data, type, callback, loading, tips, time) { - var self = this, dialogIndex = 0; - (loading !== false) && (dialogIndex = $.msg.loading(tips)); - (typeof Pace === 'object') && Pace.restart(); - $.ajax({ - type: type || 'GET', url: $.menu.parseUri(url), data: data || {}, - statusCode: { - 404: function () { - $.msg.close(dialogIndex); - $.msg.tips(self.errMsg.replace('{status}', 'E404 - ')); - }, - 500: function () { - $.msg.close(dialogIndex); - $.msg.tips(self.errMsg.replace('{status}', 'E500 - ')); - } - }, - error: function (XMLHttpRequest, textStatus, errorThrown) { - $.msg.close(dialogIndex); - $.msg.tips(self.errMsg.replace('{status}', 'E' + textStatus + ' - ')); - }, - success: function (res) { - $.msg.close(dialogIndex); - if (typeof callback === 'function' && callback.call(self, res) === false) { - return false; - } - if (typeof (res) === 'object') { - return $.msg.auto(res, time); - } - self.show(res); - } - }); - }; - // 加载HTML到目标位置 - this.open = function (url, data, callback, loading, tips) { - this.load(url, data, 'get', function (res) { - if (typeof (res) === 'object') { - return $.msg.auto(res); - } - var $container = $('.framework-container').html(res); - reinit.call(this), setTimeout(reinit, 500), setTimeout(reinit, 1000); - return (typeof callback === 'function') && callback.call(this); - function reinit() { - $.form.reInit($container); - } - }, loading, tips); - }; - // 打开一个iframe窗口 - this.iframe = function (url, title) { - return layer.open({title: title || '窗口', type: 2, area: ['800px', '530px'], fix: true, maxmin: false, content: url}); - }; - // 加载HTML到弹出层 - this.modal = function (url, data, title, callback, loading, tips) { - this.load(url, data, 'GET', function (res) { - if (typeof (res) === 'object') { - return $.msg.auto(res); - } - var layerIndex = layer.open({type: 1, btn: false, area: "800px", content: res, title: title || '', success: function (dom, index) { - $(dom).find('[data-close]').off('click').on('click', function () { - if ($(this).attr('data-confirm')) { - return $.msg.confirm($(this).attr('data-confirm'), function () { - layer.close(index); - }); - } - layer.close(index); - }); - $.form.reInit($(dom)); - } - }); - $.msg.successNeedCloseLayerIndex.push(layerIndex); - return (typeof callback === 'function') && callback.call(this); - }, loading, tips); - }; - } - - // 注册对象到JqFn - $.fn.validate = function (callback, options) { - return (new validate()).check(this, callback, options); - }; - - // 注册对象到Jq - $.validate = function (form, callback, options) { - return (new validate()).check(form, callback, options); - }; - - // 自动监听规则内表单 - $.validate.listen = function () { - $('form[data-auto]').map(function () { - if ($(this).attr('data-listen') !== 'true') { - var callbackname = $(this).attr('data-callback'); - $(this).attr('data-listen', 'true').validate(function (data) { - var method = this.getAttribute('method') || 'POST'; - var tips = this.getAttribute('data-tips') || undefined; - var url = this.getAttribute('action') || window.location.href; - var callback = window[callbackname || '_default_callback'] || undefined; - var time = this.getAttribute('data-time') || undefined; - $.form.load(url, data, method, callback, true, tips, time); - }); - $(this).find('[data-form-loaded]').map(function () { - $(this).html(this.getAttribute('data-form-loaded') || this.innerHTML); - $(this).removeAttr('data-form-loaded').removeClass('layui-disabled'); - }); - } - }); - }; - - // 表单验证 - function validate() { - var self = this; - // 表单元素 - this.tags = 'input,textarea,select'; - // 检测元素事件 - this.checkEvent = {change: true, blur: true, keyup: false}; - // 去除字符串两头的空格 - this.trim = function (str) { - return str.replace(/(^\s*)|(\s*$)/g, ''); - }; - // 标签元素是否可见 - this.isVisible = function (ele) { - return $(ele).is(':visible'); - }; - // 检测属性是否有定义 - this.hasProp = function (ele, prop) { - if (typeof prop !== "string") { - return false; - } - var attrProp = ele.getAttribute(prop); - return (typeof attrProp !== 'undefined' && attrProp !== null && attrProp !== false); - }; - // 判断表单元素是否为空 - this.isEmpty = function (ele, value) { - var trimValue = this.trim(ele.value); - value = value || ele.getAttribute('placeholder'); - return (trimValue === "" || trimValue === value); - }; - // 正则验证表单元素 - this.isRegex = function (ele, regex, params) { - var inputValue = ele.value, dealValue = this.trim(inputValue); - regex = regex || ele.getAttribute('pattern'); - if (dealValue === "" || !regex) { - return true; - } - if (dealValue !== inputValue) { - (ele.tagName.toLowerCase() !== "textarea") ? (ele.value = dealValue) : (ele.innerHTML = dealValue); - } - return new RegExp(regex, params || 'i').test(dealValue); - }; - // 检侧所的表单元素 - this.isAllpass = function (elements, options) { - if (!elements) { - return true; - } - var allpass = true, self = this, params = options || {}; - if (elements.size && elements.size() === 1 && elements.get(0).tagName.toLowerCase() === "form") { - elements = $(elements).find(self.tags); - } else if (elements.tagName && elements.tagName.toLowerCase() === "form") { - elements = $(elements).find(self.tags); - } - elements.each(function () { - if (self.checkInput(this, params) === false) { - return $(this).focus(), (allpass = false); - } - }); - return allpass; - }; - // 验证标志 - this.remind = function (input) { - return this.isVisible(input) ? this.showError(input, input.getAttribute('title') || '') : false; - }; - // 检测表单单元 - this.checkInput = function (input) { - var type = (input.getAttribute("type") + "").replace(/\W+$/, "").toLowerCase(); - var tag = input.tagName.toLowerCase(), isRequired = this.hasProp(input, "required"); - if (this.hasProp(input, 'data-auto-none') || input.disabled || type === 'submit' || type === 'reset' || type === 'file' || type === 'image' || !this.isVisible(input)) { - return; - } - var allpass = true; - if (type === "radio" && isRequired) { - var radiopass = false, eleRadios = input.name ? $("input[type='radio'][name='" + input.name + "']") : $(input); - eleRadios.each(function () { - (radiopass === false && $(this).is("[checked]")) && (radiopass = true); - }); - if (radiopass === false) { - allpass = this.remind(eleRadios.get(0), type, tag); - } else { - this.hideError(input); - } - } else if (type === "checkbox" && isRequired && !$(input).is("[checked]")) { - allpass = this.remind(input, type, tag); - } else if (tag === "select" && isRequired && !input.value) { - allpass = this.remind(input, type, tag); - } else if ((isRequired && this.isEmpty(input)) || !(allpass = this.isRegex(input))) { - allpass ? this.remind(input, type, "empty") : this.remind(input, type, tag); - allpass = false; - } else { - this.hideError(input); - } - return allpass; - }; - // 错误消息显示 - this.showError = function (ele, content) { - $(ele).addClass('validate-error'), this.insertError(ele); - $($(ele).data('input-info')).addClass('fadeInRight animated').css({width: 'auto'}).html(content); - }; - // 错误消息消除 - this.hideError = function (ele) { - $(ele).removeClass('validate-error'), this.insertError(ele); - $($(ele).data('input-info')).removeClass('fadeInRight').css({width: '30px'}).html(''); - }; - // 错误消息标签插入 - this.insertError = function (ele) { - var $html = $(''); - $html.css({top: $(ele).position().top + 'px', paddingBottom: $(ele).css('paddingBottom'), lineHeight: $(ele).css('height')}); - $(ele).data('input-info') || $(ele).data('input-info', $html.insertAfter(ele)); - }; - // 表单验证入口 - this.check = function (form, callback, options) { - var params = $.extend({}, options || {}); - $(form).attr("novalidate", "novalidate"); - $(form).find(self.tags).map(function () { - for (var i in self.checkEvent) { - if (self.checkEvent[i] === true) { - $(this).off(i, func).on(i, func); - } - } - function func() { - self.checkInput(this); - } - }); - $(form).bind("submit", function (event) { - if (self.isAllpass($(this).find(self.tags), params) && typeof callback === 'function') { - if (typeof CKEDITOR === 'object' && typeof CKEDITOR.instances === 'object') { - for (var instance in CKEDITOR.instances) { - CKEDITOR.instances[instance].updateElement(); - } - } - callback.call(this, $(form).serialize()); - } - return event.preventDefault(), false; - }); - return $(form).data('validate', this); - }; - } - - // 后台菜单辅助插件 - $.menu = new menu(); - function menu() { - // 计算URL地址中有效的URI - this.getUri = function (uri) { - uri = uri || window.location.href; - uri = (uri.indexOf(window.location.host) !== -1 ? uri.split(window.location.host)[1] : uri).split('?')[0]; - return (uri.indexOf('#') !== -1 ? uri.split('#')[1] : uri); - }; - // 通过URI查询最有可能的菜单NODE - this.queryNode = function (url) { - var $menu = $('[data-menu-node][data-open*="_URL_"]'.replace('_URL_', url.replace(/\.html$/ig, ''))); - if ($menu.size()) { - return $menu.get(0).getAttribute('data-menu-node'); - } - return /^m\-/.test(node = location.href.replace(/.*spm=([\d\-m]+).*/ig, '$1')) ? node : ''; - }; - // URL转URI - this.parseUri = function (uri, obj) { - var params = {}; - if (uri.indexOf('?') !== -1) { - var queryParams = uri.split('?')[1].split('&'); - for (var i in queryParams) { - if (queryParams[i].indexOf('=') !== -1) { - var hash = queryParams[i].split('='); - try { - params[hash[0]] = window.decodeURIComponent(window.decodeURIComponent(hash[1].replace(/%2B/ig, ' '))); - } catch (e) { - console.log([e, uri, queryParams, hash]); - } - } - } - } - uri = this.getUri(uri); - params.spm = obj && obj.getAttribute('data-menu-node') || this.queryNode(uri); - if (!params.token) { - var token = window.location.href.replace(/.*token=(\w+).*/ig, '$1'); - (/^\w{16}$/.test(token)) && (params.token = token); - } - delete params[""]; - var query = '?' + $.param(params); - return uri + (query !== '?' ? query : ''); - }; - // 后台菜单动作初始化 - this.listen = function () { - /*! 左侧菜单状态切换 */ - $('ul.sidebar-trans .nav-item a').on('click', function () { - $(this).parents('.sidebar-nav.main-nav').addClass('open').find('ul.sidebar-trans').show(); - $('.sidebar-trans .nav-item').not($(this).parent().addClass('active')).removeClass('active'); - }); - $('body').on('click', '.framework-sidebar-full .sidebar-title', function () { - var $trans = $(this).next('ul.sidebar-trans'), node = $trans.attr('data-menu-node') || false; - node && $.cookie(node, $(this).parent().toggleClass('open').hasClass('open') ? 2 : 1); - $(this).parent().hasClass('open') ? $trans.show() : $trans.hide(); - }); - $('ul.sidebar-trans').map(function () { - var node = this.getAttribute('data-menu-node') || false; - node && (parseInt($.cookie(node) || 2) === 2) && $(this).show().parent().addClass('open'); - }); - /*! Mini 菜单模式 Tips 显示*/ - $('body').on('mouseenter mouseleave', '.framework-sidebar-mini .sidebar-trans .nav-item,.framework-sidebar-mini .sidebar-title', function (e) { - $(this).tooltip({ - template: '', - title: $(this).text(), placement: 'right', container: 'body' - }).tooltip('show'), (e.type === 'mouseleave') && $(this).tooltip('destroy'); - }); - /*! 切换左侧菜单 */ - var $menutarget = $('[data-menu-target]').on('click', function () { - $menutarget.not($(this).addClass('active')).removeClass('active'); - var menuNode = $(this).attr('data-menu-target'), $left = $('[data-menu-box=' + menuNode + ']').removeClass('hide'); - $("[data-menu-box]").not($left).addClass('hide'), $left.find('[data-open]:first').trigger('click') - }); - /*! 左侧菜单样式切换 */ - var $targetmenu = $('.sidebar-fold').on('click', function () { - var $body = $('.framework-body').toggleClass('framework-sidebar-mini framework-sidebar-full'); - $.cookie('menu-style', $body.hasClass('framework-sidebar-mini') ? 'mini' : 'full'); - }); - ($.cookie('menu-style') !== 'mini') && $targetmenu.trigger('click'); - /*! URI路由处理 */ - window.onhashchange = function () { - var hash = (window.location.hash || '').substring(1), node = hash.replace(/.*spm=([\d\-m]+).*/ig, "$1"); - if (!/^m\-[\d\-]+$/.test(node)) { - node = $.menu.queryNode($.menu.getUri()) || ''; - } - if (hash.length < 1 || node.length < 1) { - return $('.topbar-home-link:first').trigger('click'); - } - /* 顶部菜单选中处理 */ - var pNode = [node.split('-')[0], node.split('-')[1]].join('-'); - $('.topbar-home-link').not($('[data-menu-target="' + pNode + '"]').addClass('active')).removeClass('active'); - /* 左则菜单处理 */ - var $menu = $('[data-menu-node="' + node + '"]').eq(0); - if ($menu.size() > 0) { - $menu.parents('.main-nav').addClass('open'), $menu.parents('.sidebar-trans').removeClass('hide').show(); - var $li = $menu.parent('li').addClass('active'); - $li.parents('.framework-sidebar').find('li.active').not($li).removeClass('active'); - $menu.parents('[data-menu-box]').removeClass('hide').siblings('[data-menu-box]').addClass('hide'); - if (/^m\-\d+$/i.test(node)) { - $('.framework-sidebar').addClass('hide'), $menu.addClass('active'); - $('.framework-container').css('left', 0).addClass('framework-sidebar-full'); - } else { - $('.framework-sidebar').removeClass('hide'); - $('.framework-container').removeAttr('style').addClass('framework-sidebar-full'); - } - } else { - $('.framework-sidebar').hide(); - $('.framework-container').removeClass('framework-sidebar-full'); - } - $.form.open(hash); - }; - // URI初始化动作 - window.onhashchange.call(this); - }; - } -}); \ No newline at end of file diff --git a/static/app.js b/static/app.js new file mode 100644 index 000000000..c7219bc82 --- /dev/null +++ b/static/app.js @@ -0,0 +1,72 @@ +// +---------------------------------------------------------------------- +// | ThinkAdmin +// +---------------------------------------------------------------------- +// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] +// +---------------------------------------------------------------------- +// | 官方网站: http://think.ctolog.com +// +---------------------------------------------------------------------- +// | 开源协议 ( https://mit-license.org ) +// +---------------------------------------------------------------------- +// | github开源项目:https://github.com/zoujingli/ThinkAdmin +// +---------------------------------------------------------------------- + +// 当前资源URL目录 +var baseRoot = (function () { + var scripts = document.scripts, src = scripts[scripts.length - 1].src; + return src.substring(0, src.lastIndexOf("/") + 1); +})(); + +// 配置参数 +require.config({ + waitSeconds: 60, + baseUrl: baseRoot, + map: {'*': {css: baseRoot + 'plugs/require/require.css.js'}}, + paths: { + 'template': ['plugs/template/template'], + 'pcasunzips': ['plugs/jquery/pcasunzips'], + // openSource + 'json': ['plugs/jquery/json2.min'], + 'layui': ['plugs/layui/layui'], + 'base64': ['plugs/jquery/base64.min'], + 'angular': ['plugs/angular/angular.min'], + 'ckeditor': ['plugs/ckeditor/ckeditor'], + 'websocket': ['plugs/socket/websocket'], + // jQuery + 'jquery.ztree': ['plugs/ztree/jquery.ztree.all.min'], + 'jquery.masonry': ['plugs/jquery/masonry.min'], + 'jquery.cookies': ['plugs/jquery/jquery.cookie'], + // bootstrap + 'bootstrap': ['plugs/bootstrap/js/bootstrap.min'], + 'bootstrap.typeahead': ['plugs/bootstrap/js/bootstrap3-typeahead.min'], + 'bootstrap.multiselect': ['plugs/bootstrap-multiselect/bootstrap-multiselect'], + // distpicker + 'distpicker': ['plugs/distpicker/distpicker'], + }, + shim: { + // open-source + 'websocket': {deps: [baseRoot + 'plugs/socket/swfobject.min.js']}, + // jquery + 'jquery.ztree': {deps: ['css!' + baseRoot + 'plugs/ztree/zTreeStyle/zTreeStyle.css']}, + // bootstrap + 'bootstrap.typeahead': {deps: ['bootstrap']}, + 'bootstrap.multiselect': {deps: ['bootstrap', 'css!' + baseRoot + 'plugs/bootstrap-multiselect/bootstrap-multiselect.css']}, + 'distpicker': {deps: [baseRoot + 'plugs/distpicker/distpicker.data.js']}, + }, + deps: ['json', 'bootstrap'], + // 开启debug模式,不缓存资源 + // urlArgs: "ver=" + (new Date()).getTime() +}); + +// 注册jquery到require模块 +define('jquery', function () { + return layui.$; +}); + +// UI框架初始化 +PageLayout.call(this); + +// UI框架布局函数 +function PageLayout(callback, custom) { + window.WEB_SOCKET_SWF_LOCATION = baseRoot + "plugs/socket/WebSocketMain.swf"; + require(custom || [], callback || false); +} \ No newline at end of file diff --git a/static/plugs/angular/angular.min.js b/static/plugs/angular/angular.min.js index 45fd62648..716c24dc8 100644 --- a/static/plugs/angular/angular.min.js +++ b/static/plugs/angular/angular.min.js @@ -1,335 +1,337 @@ /* - AngularJS v1.6.6 + AngularJS v1.6.8 (c) 2010-2017 Google, Inc. http://angularjs.org License: MIT */ -(function(u){'use strict';function oe(a){if(E(a))t(a.objectMaxDepth)&&(Lc.objectMaxDepth=Ub(a.objectMaxDepth)?a.objectMaxDepth:NaN);else return Lc}function Ub(a){return Y(a)&&0c)return"...";var d=b.$$hashKey,g;if(I(a)){g=0;for(var f=a.length;g").append(a).html();try{return a[0].nodeType===Oa?N(b):b.match(/^(<[^>]+>)/)[1].replace(/^<([\w-]+)/,function(a,b){return"<"+N(b)})}catch(d){return N(b)}}function Sc(a){try{return decodeURIComponent(a)}catch(b){}}function Tc(a){var b={};p((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g, -"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=Sc(e),t(e)&&(f=t(f)?Sc(f):!0,ra.call(b,e)?I(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function cc(a){var b=[];p(a,function(a,c){I(a)?p(a,function(a){b.push(ia(c,!0)+(!0===a?"":"="+ia(a,!0)))}):b.push(ia(c,!0)+(!0===a?"":"="+ia(a,!0)))});return b.length?b.join("&"):""}function gb(a){return ia(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ia(a,b){return encodeURIComponent(a).replace(/%40/gi, -"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ve(a,b){var d,c,e=Ha.length;for(c=0;c protocol indicates an extension, document.location.href does not match."))}function Uc(a,b,d){E(d)||(d={});d=P({strictDi:!1},d);var c=function(){a=B(a);if(a.injector()){var c=a[0]===u.document?"document":Aa(a);throw qa("btstrpd",c.replace(//,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]); -b.unshift("ng");c=hb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;u&&e.test(u.name)&&(d.debugInfoEnabled=!0,u.name=u.name.replace(e,""));if(u&&!f.test(u.name))return c();u.name=u.name.replace(f,"");$.resumeBootstrap=function(a){p(a,function(a){b.push(a)});return c()};A($.resumeDeferredBootstrap)&&$.resumeDeferredBootstrap()}function ye(){u.name= -"NG_ENABLE_DEBUG_INFO!"+u.name;u.location.reload()}function ze(a){a=$.element(a).injector();if(!a)throw qa("test");return a.get("$$testability")}function Vc(a,b){b=b||"_";return a.replace(Ae,function(a,c){return(c?b:"")+a.toLowerCase()})}function Be(){var a;if(!Wc){var b=tb();(la=w(b)?u.jQuery:b?u[b]:void 0)&&la.fn.on?(B=la,P(la.fn,{scope:Sa.scope,isolateScope:Sa.isolateScope,controller:Sa.controller,injector:Sa.injector,inheritedData:Sa.inheritedData}),a=la.cleanData,la.cleanData=function(b){for(var c, -e=0,f;null!=(f=b[e]);e++)(c=la._data(f,"events"))&&c.$destroy&&la(f).triggerHandler("$destroy");a(b)}):B=U;$.element=B;Wc=!0}}function ib(a,b,d){if(!a)throw qa("areq",b||"?",d||"required");return a}function ub(a,b,d){d&&I(a)&&(a=a[a.length-1]);ib(A(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ia(a,b){if("hasOwnProperty"===a)throw qa("badname",b);}function Xc(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;gc)return"...";var d=b.$$hashKey,g;if(I(a)){g=0;for(var f=a.length;g").append(a).html();try{return a[0].nodeType===Oa?L(b):b.match(/^(<[^>]+>)/)[1].replace(/^<([\w-]+)/,function(a,b){return"<"+L(b)})}catch(d){return L(b)}}function Tc(a){try{return decodeURIComponent(a)}catch(b){}}function ec(a){var b={};r((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g, +"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=Tc(e),u(e)&&(f=u(f)?Tc(f):!0,ra.call(b,e)?I(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function fc(a){var b=[];r(a,function(a,c){I(a)?r(a,function(a){b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))}):b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))});return b.length?b.join("&"):""}function fb(a){return ja(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ja(a,b){return encodeURIComponent(a).replace(/%40/gi, +"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ve(a,b){var d,c,e=Ha.length;for(c=0;c protocol indicates an extension, document.location.href does not match."))}function Uc(a,b,d){B(d)||(d={});d=O({strictDi:!1},d);var c=function(){a=z(a);if(a.injector()){var c=a[0]===w.document?"document":za(a);throw qa("btstrpd",c.replace(//,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]); +b.unshift("ng");c=gb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;w&&e.test(w.name)&&(d.debugInfoEnabled=!0,w.name=w.name.replace(e,""));if(w&&!f.test(w.name))return c();w.name=w.name.replace(f,"");$.resumeBootstrap=function(a){r(a,function(a){b.push(a)});return c()};C($.resumeDeferredBootstrap)&&$.resumeDeferredBootstrap()}function ye(){w.name= +"NG_ENABLE_DEBUG_INFO!"+w.name;w.location.reload()}function ze(a){a=$.element(a).injector();if(!a)throw qa("test");return a.get("$$testability")}function Vc(a,b){b=b||"_";return a.replace(Ae,function(a,c){return(c?b:"")+a.toLowerCase()})}function Be(){var a;if(!Wc){var b=rb();(ma=x(b)?w.jQuery:b?w[b]:void 0)&&ma.fn.on?(z=ma,O(ma.fn,{scope:Sa.scope,isolateScope:Sa.isolateScope,controller:Sa.controller,injector:Sa.injector,inheritedData:Sa.inheritedData}),a=ma.cleanData,ma.cleanData=function(b){for(var c, +e=0,f;null!=(f=b[e]);e++)(c=ma._data(f,"events"))&&c.$destroy&&ma(f).triggerHandler("$destroy");a(b)}):z=V;$.element=z;Wc=!0}}function hb(a,b,d){if(!a)throw qa("areq",b||"?",d||"required");return a}function sb(a,b,d){d&&I(a)&&(a=a[a.length-1]);hb(C(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ia(a,b){if("hasOwnProperty"===a)throw qa("badname",b);}function Xc(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g")+c[2];for(c=c[0];c--;)d=d.lastChild;f=eb(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";p(f,function(a){e.appendChild(a)});return e}function U(a){if(a instanceof U)return a;var b;D(a)&&(a=Q(a),b=!0);if(!(this instanceof U)){if(b&&"<"!==a.charAt(0))throw hc("nosel");return new U(a)}if(b){b=u.document;var d;a=(d=fg.exec(a))?[b.createElement(d[1])]:(d=fd(a,b))?d.childNodes: -[];ic(this,a)}else A(a)?gd(a):ic(this,a)}function jc(a){return a.cloneNode(!0)}function zb(a,b){!b&&fc(a)&&B.cleanData([a]);a.querySelectorAll&&B.cleanData(a.querySelectorAll("*"))}function hd(a,b,d,c){if(t(c))throw hc("offargs");var e=(c=Ab(a))&&c.events,f=c&&c.handle;if(f)if(b){var g=function(b){var c=e[b];t(d)&&db(c||[],d);t(d)&&c&&0l&&this.remove(q.key);return b}},get:function(a){if(l";b=Ba.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function na(a,b){try{a.addClass(b)}catch(c){}}function ca(a,b,c,d,e){a instanceof B||(a=B(a));var g=Va(a,b,a,c,d,e);ca.$$addScopeClass(a); -var f=null;return function(b,c,d){if(!a)throw ba("multilink");ib(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var h=d.parentBoundTranscludeFn,k=d.transcludeControllers;d=d.futureParentElement;h&&h.$$boundTransclude&&(h=h.$$boundTransclude);f||(f=(d=d&&d[0])?"foreignobject"!==za(d)&&ha.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==f?B(ja(f,B("
    ").append(a).html())):c?Sa.clone.call(a):a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);ca.$$addScopeInfo(d,b);c&& -c(d,b);g&&g(b,d,d,h);c||(a=g=null);return d}}function Va(a,b,c,d,e,g){function f(a,c,d,e){var g,k,l,m,q,n,H;if(s)for(H=Array(c.length),m=0;mx.priority)break;if(u=x.scope)x.templateUrl||(E(u)?(aa("new/isolated scope",K||s,x,F),K=x):aa("new/isolated scope",K,x,F)),s=s||x;W=x.name;if(!t&&(x.replace&&(x.templateUrl||x.template)||x.transclude&&!x.$$tlb)){for(u=Da+1;t=a[u++];)if(t.transclude&&!t.$$tlb||t.replace&&(t.templateUrl||t.template)){Jb=!0;break}t=!0}!x.templateUrl&&x.controller&&(J=J||S(),aa("'"+W+"' controller",J[W],x,F),J[W]=x);if(u=x.transclude)if(R=!0, -x.$$tlb||(aa("transclusion",y,x,F),y=x),"element"===u)ta=!0,n=x.priority,z=F,F=d.$$element=B(ca.$$createComment(W,d[W])),b=F[0],la(g,ya.call(z,0),b),z[0].$$parentNode=z[0].parentNode,r=V(Jb,z,e,n,f&&f.name,{nonTlbTranscludeDirective:y});else{var na=S();if(E(u)){z=[];var Va=S(),Ma=S();p(u,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Va[a]=b;na[b]=null;Ma[b]=c});p(F.contents(),function(a){var b=Va[Ea(za(a))];b?(Ma[b]=!0,na[b]=na[b]||[],na[b].push(a)):z.push(a)});p(Ma,function(a,b){if(!a)throw ba("reqslot", -b);});for(var N in na)na[N]&&(na[N]=V(Jb,na[N],e))}else z=B(jc(b)).contents();F.empty();r=V(Jb,z,e,void 0,void 0,{needsNewScope:x.$$isolateScope||x.$$newScope});r.$$slots=na}if(x.template)if(L=!0,aa("template",v,x,F),v=x,u=A(x.template)?x.template(F,d):x.template,u=Ia(u),x.replace){f=x;z=gc.test(u)?qd(ja(x.templateNamespace,Q(u))):[];b=z[0];if(1!==z.length||1!==b.nodeType)throw ba("tplrt",W,"");la(g,F,b);C={$attr:{}};u=M(b,[],C);var nc=a.splice(Da+1,a.length-(Da+1));(K||s)&&Z(u,K,s);a=a.concat(u).concat(nc); -ea(d,C);C=a.length}else F.html(u);if(x.templateUrl)L=!0,aa("template",v,x,F),v=x,x.replace&&(f=x),q=ia(a.splice(Da,a.length-Da),F,d,g,R&&r,h,k,{controllerDirectives:J,newScopeDirective:s!==x&&s,newIsolateScopeDirective:K,templateDirective:v,nonTlbTranscludeDirective:y}),C=a.length;else if(x.compile)try{O=x.compile(F,d,r);var T=x.$$originalDirective||x;A(O)?m(null,Ra(T,O),Ua,D):O&&m(Ra(T,O.pre),Ra(T,O.post),Ua,D)}catch($){c($,Aa(F))}x.terminal&&(q.terminal=!0,n=Math.max(n,x.priority))}q.scope=s&&!0=== -s.scope;q.transcludeOnThisElement=R;q.templateOnThisElement=L;q.transclude=r;l.hasElementTranscludeDirective=ta;return q}function X(a,b,c,d){var e;if(D(b)){var g=b.match(l);b=b.substring(g[0].length);var f=g[1]||g[3],g="?"===g[2];"^^"===f?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e=f?c.inheritedData(h):c.data(h)}if(!e&&!g)throw ba("ctreq",b,a);}else if(I(b))for(e=[],f=0,g=b.length;fc.priority)&&-1!==c.restrict.indexOf(e)){k&&(c=Yb(c,{$$start:k,$$end:l}));if(!c.$$bindings){var s=m=c,H=c.name,J={isolateScope:null,bindToController:null};E(s.scope)&&(!0===s.bindToController?(J.bindToController=d(s.scope,H,!0),J.isolateScope={}):J.isolateScope=d(s.scope,H,!1));E(s.bindToController)&&(J.bindToController=d(s.bindToController,H,!0));if(J.bindToController&&!s.controller)throw ba("noctrl",H);m=m.$$bindings=J;E(m.isolateScope)&&(c.$$isolateBindings=m.isolateScope)}b.push(c); -m=c}}return m}function $(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function va(a,b){if("srcdoc"===b)return x.HTML;var c=za(a);if("src"===b||"ngSrc"===b){if(-1===["img","video","audio","source","track"].indexOf(c))return x.RESOURCE_URL}else if("xlinkHref"===b||"form"===c&&"action"===b||"link"===c&&"href"===b)return x.RESOURCE_URL}function xa(a,c,d,e,g){var f=va(a,e),k=h[e]||g,l=b(d,!g,f,k);if(l){if("multiple"===e&&"select"===za(a))throw ba("selmulti", -Aa(a));if(m.test(e))throw ba("nodomevents");c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers=S());var h=g[e];h!==d&&(l=h&&b(h,!0,f,k),d=h);l&&(g[e]=l(a),(c[e]||(c[e]=[])).$$inter=!0,(g.$$observers&&g.$$observers[e].$$scope||a).$watch(l,function(a,b){"class"===e&&a!==b?g.$updateClass(a,b):g.$set(e,a)}))}}}})}}function la(a,b,c){var d=b[0],e=b.length,g=d.parentNode,f,h;if(a)for(f=0,h=a.length;f=b)return a;for(;b--;){var d=a[b];(8===d.nodeType||d.nodeType===Oa&&""===d.nodeValue.trim())&&ug.call(a,b,1)}return a}function sg(a,b){if(b&&D(b))return b;if(D(a)){var d=td.exec(a);if(d)return d[3]}}function yf(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Ia(b,"controller");E(b)? -P(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!E(a.$scope))throw M("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,k,h){var l,m,n;k=!0===k;h&&D(h)&&(n=h);if(D(f)){h=f.match(td);if(!h)throw ud("ctrlfmt",f);m=h[1];n=n||h[3];f=a.hasOwnProperty(m)?a[m]:Xc(g.$scope,m,!0)||(b?Xc(c,m,!0):void 0);if(!f)throw ud("ctrlreg",m);ub(f,m,!0)}if(k)return k=(I(f)?f[f.length-1]:f).prototype,l=Object.create(k||null),n&&e(g,n, -l,m||f.name),P(function(){var a=d.invoke(f,l,g,m);a!==l&&(E(a)||A(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function zf(){this.$get=["$window",function(a){return B(a.document)}]}function Af(){this.$get=["$document","$rootScope",function(a,b){function d(){e=c.hidden}var c=a[0],e=c&&c.hidden;a.on("visibilitychange",d);b.$on("$destroy",function(){a.off("visibilitychange",d)});return function(){return e}}]}function Bf(){this.$get= -["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function pc(a){return E(a)?ea(a)?a.toISOString():fb(a):a}function Gf(){this.$get=function(){return function(a){if(!a)return"";var b=[];Nc(a,function(a,c){null===a||w(a)||A(a)||(I(a)?p(a,function(a){b.push(ia(c)+"="+ia(pc(a)))}):b.push(ia(c)+"="+ia(pc(a))))});return b.join("&")}}}function Hf(){this.$get=function(){return function(a){function b(a,e,f){null===a||w(a)||(I(a)?p(a,function(a,c){b(a,e+"["+(E(a)?c:"")+"]")}):E(a)&&!ea(a)? -Nc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ia(e)+"="+ia(pc(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function qc(a,b){if(D(a)){var d=a.replace(vg,"").trim();if(d){var c=b("Content-Type"),c=c&&0===c.indexOf(vd),e;(e=c)||(e=(e=d.match(wg))&&xg[e[0]].test(d));if(e)try{a=Qc(d)}catch(f){if(!c)return a;throw rc("baddata",a,f);}}}return a}function wd(a){var b=S(),d;D(a)?p(a.split("\n"),function(a){d=a.indexOf(":");var e=N(Q(a.substr(0,d)));a=Q(a.substr(d+1));e&&(b[e]= -b[e]?b[e]+", "+a:a)}):E(a)&&p(a,function(a,d){var f=N(d),g=Q(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function xd(a){var b;return function(d){b||(b=wd(a));return d?(d=b[N(d)],void 0===d&&(d=null),d):b}}function yd(a,b,d,c){if(A(c))return c(a,b,d);p(c,function(c){a=c(a,b,d)});return a}function Ff(){var a=this.defaults={transformResponse:[qc],transformRequest:[function(a){return E(a)&&"[object File]"!==ha.call(a)&&"[object Blob]"!==ha.call(a)&&"[object FormData]"!==ha.call(a)?fb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"}, -post:ja(sc),put:ja(sc),patch:ja(sc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer",jsonpCallbackParam:"callback"},b=!1;this.useApplyAsync=function(a){return t(a)?(b=!!a,this):b};var d=this.interceptors=[];this.$get=["$browser","$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector","$sce",function(c,e,f,g,k,h,l,m){function n(b){function d(a,b){for(var c=0,e=b.length;ca?b:h.reject(b)}if(!E(b))throw M("$http")("badreq",b);if(!D(m.valueOf(b.url)))throw M("$http")("badreq",b.url);var f=P({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer,jsonpCallbackParam:a.jsonpCallbackParam},b);f.headers= -function(b){var c=a.headers,d=P({},b.headers),g,f,h,c=P({},c.common,c[N(b.method)]);a:for(g in c){f=N(g);for(h in d)if(N(h)===f)continue a;d[g]=c[g]}return e(d,ja(b))}(b);f.method=wb(f.method);f.paramSerializer=D(f.paramSerializer)?l.get(f.paramSerializer):f.paramSerializer;c.$$incOutstandingRequestCount();var k=[],n=[];b=h.resolve(f);p(v,function(a){(a.request||a.requestError)&&k.unshift(a.request,a.requestError);(a.response||a.responseError)&&n.push(a.response,a.responseError)});b=d(b,k);b=b.then(function(b){var c= -b.headers,d=yd(b.data,xd(c),void 0,b.transformRequest);w(d)&&p(c,function(a,b){"content-type"===N(b)&&delete c[b]});w(b.withCredentials)&&!w(a.withCredentials)&&(b.withCredentials=a.withCredentials);return q(b,d).then(g,g)});b=d(b,n);return b=b.finally(function(){c.$$completeOutstandingRequest(C)})}function q(c,d){function g(a){if(a){var c={};p(a,function(a,d){c[d]=function(c){function d(){a(c)}b?k.$applyAsync(d):k.$$phase?d():k.$apply(d)}});return c}}function l(a,c,d,e,g){function f(){q(c,a,d,e, -g)}R&&(200<=a&&300>a?R.put(O,[a,c,wd(d),e,g]):R.remove(O));b?k.$applyAsync(f):(f(),k.$$phase||k.$apply())}function q(a,b,d,e,g){b=-1<=b?b:0;(200<=b&&300>b?K.resolve:K.reject)({data:a,status:b,headers:xd(d),config:c,statusText:e,xhrStatus:g})}function H(a){q(a.data,a.status,ja(a.headers()),a.statusText,a.xhrStatus)}function v(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var K=h.defer(),F=K.promise,R,x,W=c.headers,r="jsonp"===N(c.method),O=c.url;r?O=m.getTrustedResourceUrl(O): -D(O)||(O=m.valueOf(O));O=G(O,c.paramSerializer(c.params));r&&(O=L(O,c.jsonpCallbackParam));n.pendingRequests.push(c);F.then(v,v);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(R=E(c.cache)?c.cache:E(a.cache)?a.cache:z);R&&(x=R.get(O),t(x)?x&&A(x.then)?x.then(H,H):I(x)?q(x[1],x[0],ja(x[2]),x[3],x[4]):q(x,200,{},"OK","complete"):R.put(O,F));w(x)&&((x=zd(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:void 0)&&(W[c.xsrfHeaderName||a.xsrfHeaderName]=x),e(c.method,O,d,l,W,c.timeout, -c.withCredentials,c.responseType,g(c.eventHandlers),g(c.uploadEventHandlers)));return F}function G(a,b){0=l&&(r.resolve(v),p(ma.$$intervalId),delete g[ma.$$intervalId]);s||a.$apply()},h);g[ma.$$intervalId]=r;return ma}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].promise.$$state.pur= -!0,g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),delete g[a.$$intervalId],!0):!1};return f}]}function tc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=gb(a[b]);return a.join("/")}function Ad(a,b){var d=ua(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||zg[d.protocol]||null}function Bd(a,b){if(Ag.test(a))throw mb("badpath",a);var d="/"!==a.charAt(0);d&&(a="/"+a);var c=ua(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1): -c.pathname);b.$$search=Tc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!==b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function uc(a,b){return a.slice(0,b.length)===b}function va(a,b){if(uc(b,a))return b.substr(a.length)}function La(a){var b=a.indexOf("#");return-1===b?a:a.substr(0,b)}function nb(a){return a.replace(/(#.+)|#$/,"$1")}function vc(a,b,d){this.$$html5=!0;d=d||"";Ad(a,this);this.$$parse=function(a){var d=va(b,a);if(!D(d))throw mb("ipthprfx",a,b);Bd(d,this);this.$$path||(this.$$path= -"/");this.$$compose()};this.$$compose=function(){var a=cc(this.$$search),d=this.$$hash?"#"+gb(this.$$hash):"";this.$$url=tc(this.$$path)+(a?"?"+a:"")+d;this.$$absUrl=b+this.$$url.substr(1);this.$$urlUpdatedByLocation=!0};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;t(f=va(a,c))?(g=f,g=d&&t(f=va(d,f))?b+(va("/",f)||f):a+g):t(f=va(b,c))?g=b+f:b===c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function wc(a,b,d){Ad(a,this);this.$$parse=function(c){var e=va(a, -c)||va(b,c),f;w(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",w(e)&&(a=c,this.replace())):(f=va(d,e),w(f)&&(f=e));Bd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;uc(f,e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=cc(this.$$search),e=this.$$hash?"#"+gb(this.$$hash):"";this.$$url=tc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"");this.$$urlUpdatedByLocation=!0};this.$$parseLinkUrl=function(b,d){return La(a)=== -La(b)?(this.$$parse(b),!0):!1}}function Cd(a,b,d){this.$$html5=!0;wc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a===La(c)?f=c:(g=va(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=cc(this.$$search),e=this.$$hash?"#"+gb(this.$$hash):"";this.$$url=tc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url;this.$$urlUpdatedByLocation=!0}}function Lb(a){return function(){return this[a]}} -function Dd(a,b){return function(d){if(w(d))return this[a];this[a]=b(d);this.$$compose();return this}}function Lf(){var a="!",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return t(b)?(a=b,this):a};this.html5Mode=function(a){if(Na(a))return b.enabled=a,this;if(E(a)){Na(a.enabled)&&(b.enabled=a.enabled);Na(a.requireBase)&&(b.requireBase=a.requireBase);if(Na(a.rewriteLinks)||D(a.rewriteLinks))b.rewriteLinks=a.rewriteLinks;return this}return b};this.$get=["$rootScope","$browser", -"$sniffer","$rootElement","$window",function(d,c,e,f,g){function k(a,b,d){var e=l.url(),g=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(f){throw l.url(e),l.$$state=g,f;}}function h(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,b)}var l,m;m=c.baseHref();var n=c.url(),q;if(b.enabled){if(!m&&b.requireBase)throw mb("nobase");q=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?vc:Cd}else q=La(n),m=wc;var G=q.substr(0,La(q).lastIndexOf("/")+1);l=new m(q,G,"#"+ -a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var p=/^\s*(javascript|mailto):/i;f.on("click",function(a){var e=b.rewriteLinks;if(e&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!==a.which&&2!==a.button){for(var h=B(a.target);"a"!==za(h[0]);)if(h[0]===f[0]||!(h=h.parent())[0])return;if(!D(e)||!w(h.attr(e))){var e=h.prop("href"),k=h.attr("href")||h.attr("xlink:href");E(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=ua(e.animVal).href);p.test(e)||!e||h.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(e, -k)||(a.preventDefault(),l.absUrl()!==c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}}});nb(l.absUrl())!==nb(n)&&c.url(l.absUrl(),!0);var z=!0;c.onUrlChange(function(a,b){uc(a,G)?(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,g;a=nb(a);l.$$parse(a);l.$$state=b;g=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(g?(l.$$parse(c),l.$$state=e,k(c,!1,e)):(z=!1,h(c,e)))}),d.$$phase||d.$digest()):g.location.href=a});d.$watch(function(){if(z||l.$$urlUpdatedByLocation){l.$$urlUpdatedByLocation= -!1;var a=nb(c.url()),b=nb(l.absUrl()),g=c.state(),f=l.$$replace,m=a!==b||l.$$html5&&e.history&&g!==l.$$state;if(z||m)z=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,g).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=g):(m&&k(b,f,g===l.$$state?null:l.$$state),h(a,g)))})}l.$$replace=!1});return l}]}function Mf(){var a=!0,b=this;this.debugEnabled=function(b){return t(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){$b(a)&&(a.stack&& -f?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||C;return function(){var a=[];p(arguments,function(b){a.push(c(b))});return Function.prototype.apply.call(e,b,a)}}var f=Ca||/\bEdge\//.test(d.navigator&&d.navigator.userAgent);return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b, -arguments)}}()}}]}function Bg(a){return a+""}function Cg(a,b){return"undefined"!==typeof a?a:b}function Ed(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function Dg(a,b){switch(a.type){case r.MemberExpression:if(a.computed)return!1;break;case r.UnaryExpression:return 1;case r.BinaryExpression:return"+"!==a.operator?1:!1;case r.CallExpression:return!1}return void 0===b?Fd:b}function V(a,b,d){var c,e,f=a.isPure=Dg(a,d);switch(a.type){case r.Program:c=!0;p(a.body,function(a){V(a.expression, -b,f);c=c&&a.expression.constant});a.constant=c;break;case r.Literal:a.constant=!0;a.toWatch=[];break;case r.UnaryExpression:V(a.argument,b,f);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case r.BinaryExpression:V(a.left,b,f);V(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case r.LogicalExpression:V(a.left,b,f);V(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case r.ConditionalExpression:V(a.test, -b,f);V(a.alternate,b,f);V(a.consequent,b,f);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case r.Identifier:a.constant=!1;a.toWatch=[a];break;case r.MemberExpression:V(a.object,b,f);a.computed&&V(a.property,b,f);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=a.constant?[]:[a];break;case r.CallExpression:c=d=a.filter?!b(a.callee.name).$stateful:!1;e=[];p(a.arguments,function(a){V(a,b,f);c=c&&a.constant;e.push.apply(e, -a.toWatch)});a.constant=c;a.toWatch=d?e:[a];break;case r.AssignmentExpression:V(a.left,b,f);V(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case r.ArrayExpression:c=!0;e=[];p(a.elements,function(a){V(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c;a.toWatch=e;break;case r.ObjectExpression:c=!0;e=[];p(a.properties,function(a){V(a.value,b,f);c=c&&a.value.constant;e.push.apply(e,a.value.toWatch);a.computed&&(V(a.key,b,!1),c=c&&a.key.constant,e.push.apply(e, -a.key.toWatch))});a.constant=c;a.toWatch=e;break;case r.ThisExpression:a.constant=!1;a.toWatch=[];break;case r.LocalsExpression:a.constant=!1,a.toWatch=[]}}function Gd(a){if(1===a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function Hd(a){return a.type===r.Identifier||a.type===r.MemberExpression}function Id(a){if(1===a.body.length&&Hd(a.body[0].expression))return{type:r.AssignmentExpression,left:a.body[0].expression,right:{type:r.NGValueParameter},operator:"="}} -function Jd(a){this.$filter=a}function Kd(a){this.$filter=a}function xc(a,b,d){this.ast=new r(a,d);this.astCompiler=d.csp?new Kd(b):new Jd(b)}function yc(a){return A(a.valueOf)?a.valueOf():Eg.call(a)}function Nf(){var a=S(),b={"true":!0,"false":!1,"null":null,undefined:void 0},d,c;this.addLiteral=function(a,c){b[a]=c};this.setIdentifierFns=function(a,b){d=a;c=b;return this};this.$get=["$filter",function(e){function f(a,b,c){return null==a||null==b?a===b:"object"!==typeof a||(a=yc(a),"object"!==typeof a|| -c)?a===b||a!==a&&b!==b:!1}function g(a,b,c,d,e){var g=d.inputs,h;if(1===g.length){var k=f,g=g[0];return a.$watch(function(a){var b=g(a);f(b,k,g.isPure)||(h=d(a,void 0,void 0,[b]),k=b&&yc(b));return h},b,c,e)}for(var l=[],m=[],n=0,p=g.length;n= -c.$$state.status&&e&&e.length&&a(function(){for(var a,c,g=0,f=e.length;ga)for(b in l++,f)ra.call(e,b)||(p--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1G&&(w=4-G,v[w]||(v[w]=[]),v[w].push({msg:A(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:h}));else if(a===c){p=!1;break a}}catch(E){f(E)}if(!(q=y.$$watchersCount&&y.$$childHead||y!==this&&y.$$nextSibling))for(;y!==this&&!(q=y.$$nextSibling);)y=y.$parent}while(y=q);if((p||t.length)&&!G--)throw s.$$phase= -null,d("infdig",b,v);}while(p||t.length);for(s.$$phase=null;JCa)throw wa("iequirks");var c=ja(oa);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=bb);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs, -f=c.getTrusted,g=c.trustAs;p(oa,function(a,b){var d=N(b);c[("parse_as_"+d).replace(Ac,jb)]=function(b){return e(a,b)};c[("get_trusted_"+d).replace(Ac,jb)]=function(b){return f(a,b)};c[("trust_as_"+d).replace(Ac,jb)]=function(b){return g(a,b)}});return c}]}function Tf(){this.$get=["$window","$document",function(a,b){var d={},c=!((!a.nw||!a.nw.process)&&a.chrome&&(a.chrome.app&&a.chrome.app.runtime||!a.chrome.app&&a.chrome.runtime&&a.chrome.runtime.id))&&a.history&&a.history.pushState,e=Z((/android (\d+)/.exec(N((a.navigator|| -{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},k=g.body&&g.body.style,h=!1,l=!1;k&&(h=!!("transition"in k||"webkitTransition"in k),l=!!("animation"in k||"webkitAnimation"in k));return{history:!(!c||4>e||f),hasEvent:function(a){if("input"===a&&Ca)return!1;if(w(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ja(),transitions:h,animations:l,android:e}}]}function Vf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$exceptionHandler", -"$templateCache","$http","$q","$sce",function(b,d,c,e,f){function g(k,h){g.totalPendingRequests++;if(!D(k)||w(d.get(k)))k=f.getTrustedResourceUrl(k);var l=c.defaults&&c.defaults.transformResponse;I(l)?l=l.filter(function(a){return a!==qc}):l===qc&&(l=null);return c.get(k,P({cache:d,transformResponse:l},a)).finally(function(){g.totalPendingRequests--}).then(function(a){d.put(k,a.data);return a.data},function(a){h||(a=Gg("tpload",k,a.status,a.statusText),b(a));return e.reject(a)})}g.totalPendingRequests= -0;return g}]}function Wf(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];p(a,function(a){var c=$.element(a).data("$binding");c&&p(c,function(c){d?(new RegExp("(^|\\s)"+Md(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!==c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],k=0;kc&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)===Cc;e++); -if(e===(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)===Cc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Wd&&(d=d.splice(0,Wd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function Og(a,b,d,c){var e=a.d,f=e.length-a.i;b=w(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d- -1]++;for(;fk;)h.unshift(0),k++;0=b.lgSize&&k.unshift(h.splice(-b.lgSize,h.length).join(""));h.length> -b.gSize;)k.unshift(h.splice(-b.gSize,h.length).join(""));h.length&&k.unshift(h.join(""));h=k.join(d);f.length&&(h+=c+f.join(""));e&&(h+="e+"+e)}return 0>a&&!g?b.negPre+h+b.negSuf:b.posPre+h+b.posSuf}function Mb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length-d)f+=d;0===f&&-12===d&&(f=12);return Mb(f,b,c,e)}}function ob(a,b,d){return function(c,e){var f= -c["get"+a](),g=wb((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Xd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Yd(a){return function(b){var d=Xd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Mb(b,a)}}function Dc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Rd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,k=b[8]?a.setUTCFullYear:a.setFullYear, -h=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Z(b[9]+b[10]),g=Z(b[9]+b[11]));k.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));f=Z(b[4]||0)-f;g=Z(b[5]||0)-g;k=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));h.call(a,f,g,k,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",k=[],h,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;D(c)&&(c=Pg.test(c)?Z(c):b(c));Y(c)&&(c=new Date(c));if(!ea(c)||!isFinite(c.getTime()))return c; -for(;d;)(l=Qg.exec(d))?(k=eb(k,l,1),d=k.pop()):(k.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=Rc(f,m),c=bc(c,f,!0));p(k,function(b){h=Rg[b];g+=h?h(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ig(){return function(a,b){w(b)&&(b=2);return fb(a,b)}}function Jg(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(T(b))return a;Y(a)&&(a=a.toString());if(!xa(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+ -d):d;return 0<=b?Ec(a,d,d+b):0===d?Ec(a,b,a.length):Ec(a,Math.max(0,d+b),d)}}function Ec(a,b,d){return D(a)?a.slice(b,d):ya.call(a,b,d)}function Td(a){function b(b){return b.map(function(b){var c=1,d=bb;if(A(b))d=b;else if(D(b)){if("+"===b.charAt(0)||"-"===b.charAt(0))c="-"===b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}} -function c(a,b){var c=0,d=a.type,h=b.type;if(d===h){var h=a.value,l=b.value;"string"===d?(h=h.toLowerCase(),l=l.toLowerCase()):"object"===d&&(E(h)&&(h=a.index),E(l)&&(l=b.index));h!==l&&(c=hb||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(ce[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!h){var b=this.validity,c=b.badInput,d=b.typeMismatch;h=f.defer(function(){h=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Pb(a,b){return function(d,c){var e,f;if(ea(d))return d; -if(D(d)){'"'===d.charAt(0)&&'"'===d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(Sg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},p(e,function(a,c){c=z};g.$observe("min",function(a){z=q(a);k.$validate()})}if(t(g.max)||g.ngMax){var v;k.$validators.max=function(a){return!n(a)||w(v)||d(a)<=v};g.$observe("max",function(a){v=q(a);k.$validate()})}}}function Gc(a,b,d,c){(c.$$hasNativeValidators=E(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?void 0:a})}function de(a){a.$$parserName="number";a.$parsers.push(function(b){if(a.$isEmpty(b))return null;if(Tg.test(b))return parseFloat(b)}); -a.$formatters.push(function(b){if(!a.$isEmpty(b)){if(!Y(b))throw rb("numfmt",b);b=b.toString()}return b})}function Xa(a){t(a)&&!Y(a)&&(a=parseFloat(a));return T(a)?void 0:a}function Hc(a){var b=a.toString(),d=b.indexOf(".");return-1===d?-1a&&(a=/e-(\d+)$/.exec(b))?Number(a[1]):0:b.length-d-1}function ee(a,b,d){a=Number(a);var c=(a|0)!==a,e=(b|0)!==b,f=(d|0)!==d;if(c||e||f){var g=c?Hc(a):0,k=e?Hc(b):0,h=f?Hc(d):0,g=Math.max(g,k,h),g=Math.pow(10,g);a*=g;b*=g;d*=g;c&&(a=Math.round(a));e&&(b=Math.round(b)); -f&&(d=Math.round(d))}return 0===(a-b)%d}function fe(a,b,d,c,e){if(t(c)){a=a(c);if(!a.constant)throw rb("constexpr",d,c);return a(b)}return e}function Ic(a,b){function d(a,b){if(!a||!a.length)return[];if(!b||!b.length)return a;var c=[],d=0;a:for(;d(?:<\/\1>|)$/,gc=/<|&#?\w+;/,dg=/<([\w:-]+)/,eg=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,aa={option:[1,'"],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};aa.optgroup=aa.option;aa.tbody=aa.tfoot=aa.colgroup=aa.caption=aa.thead;aa.th=aa.td;var lg=u.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Sa=U.prototype={ready:gd,toString:function(){var a=[];p(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"}, -eq:function(a){return 0<=a?B(this[a]):B(this[this.length+a])},length:0,push:Wg,sort:[].sort,splice:[].splice},Hb={};p("multiple selected checked disabled readOnly required open".split(" "),function(a){Hb[N(a)]=a});var ld={};p("input select option textarea button form details".split(" "),function(a){ld[a]=!0});var sd={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern",ngStep:"step"};p({data:lc,removeData:kc,hasData:function(a){for(var b in kb[a.ng339])return!0; -return!1},cleanData:function(a){for(var b=0,d=a.length;b/,og=/^[^(]*\(\s*([^)]*)\)/m, -Zg=/,/,$g=/^\s*(_?)(\S+?)\1\s*$/,mg=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ba=M("$injector");hb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw D(d)&&d||(d=a.name||pg(a)),Ba("strictdi",d);b=nd(a);p(b[1].split(Zg),function(a){a.replace($g,function(a,b,d){c.push(d)})})}a.$inject=c}}else I(a)?(b=a.length-1,ub(a[b],"fn"),c=a.slice(0,b)):ub(a,"fn",!0);return c};var he=M("$animate"),sf=function(){this.$get=C},tf=function(){var a=new Ib,b=[];this.$get= -["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=D(b)?b.split(" "):I(b)?b:[],p(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){p(b,function(b){var c=a.get(b);if(c){var d=qg(b.attr("class")),e="",f="";p(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});p(b,function(a){e&&Eb(a,e);f&&Db(a,f)});a.delete(b)}});b.length=0}return{enabled:C,on:C,off:C,pin:C,push:function(g,k,h,l){l&&l();h=h||{};h.from&&g.css(h.from);h.to&&g.css(h.to);if(h.addClass|| -h.removeClass)if(k=h.addClass,l=h.removeClass,h=a.get(g)||{},k=e(h,k,!0),l=e(h,l,!1),k||l)a.set(g,h),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},qf=["$provide",function(a){var b=this,d=null,c=null;this.$$registeredAnimations=Object.create(null);this.register=function(c,d){if(c&&"."!==c.charAt(0))throw he("notcsel",c);var g=c+"-animation";b.$$registeredAnimations[c.substr(1)]=g;a.factory(g,d)};this.customFilter=function(a){1===arguments.length&&(c=A(a)?a:null);return c}; +ngModelOptions:nf}).directive({ngInclude:of}).directive(vb).directive(dd);a.provider({$anchorScroll:pf,$animate:qf,$animateCss:rf,$$animateJs:sf,$$animateQueue:tf,$$AnimateRunner:uf,$$animateAsyncRun:vf,$browser:wf,$cacheFactory:xf,$controller:yf,$document:zf,$$isDocumentHidden:Af,$exceptionHandler:Bf,$filter:ed,$$forceReflow:Cf,$interpolate:Df,$interval:Ef,$http:Ff,$httpParamSerializer:Gf,$httpParamSerializerJQLike:Hf,$httpBackend:If,$xhrFactory:Jf,$jsonpCallbacks:Kf,$location:Lf,$log:Mf,$parse:Nf, +$rootScope:Of,$q:Pf,$$q:Qf,$sce:Rf,$sceDelegate:Sf,$sniffer:Tf,$templateCache:Uf,$templateRequest:Vf,$$testability:Wf,$timeout:Xf,$window:Yf,$$rAF:Zf,$$jqLite:$f,$$Map:ag,$$cookieReader:bg})}]).info({angularVersion:"1.6.8"})}function wb(a,b){return b.toUpperCase()}function xb(a){return a.replace(cg,wb)}function jc(a){a=a.nodeType;return 1===a||!a||9===a}function fd(a,b){var d,c,e=b.createDocumentFragment(),f=[];if(kc.test(a)){d=e.appendChild(b.createElement("div"));c=(dg.exec(a)||["",""])[1].toLowerCase(); +c=aa[c]||aa._default;d.innerHTML=c[1]+a.replace(eg,"<$1>")+c[2];for(c=c[0];c--;)d=d.lastChild;f=db(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";r(f,function(a){e.appendChild(a)});return e}function V(a){if(a instanceof V)return a;var b;E(a)&&(a=Q(a),b=!0);if(!(this instanceof V)){if(b&&"<"!==a.charAt(0))throw lc("nosel");return new V(a)}if(b){b=w.document;var d;a=(d=fg.exec(a))?[b.createElement(d[1])]:(d=fd(a,b))?d.childNodes: +[];mc(this,a)}else C(a)?gd(a):mc(this,a)}function nc(a){return a.cloneNode(!0)}function yb(a,b){!b&&jc(a)&&z.cleanData([a]);a.querySelectorAll&&z.cleanData(a.querySelectorAll("*"))}function hd(a,b,d,c){if(u(c))throw lc("offargs");var e=(c=zb(a))&&c.events,f=c&&c.handle;if(f)if(b){var g=function(b){var c=e[b];u(d)&&cb(c||[],d);u(d)&&c&&0l&&this.remove(n.key);return b}},get:function(a){if(l";b=Ba.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function na(a,b){try{a.addClass(b)}catch(c){}}function da(a,b,c,d,e){a instanceof z||(a=z(a));var g=Ua(a,b,a,c,d,e);da.$$addScopeClass(a); +var f=null;return function(b,c,d){if(!a)throw ca("multilink");hb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var h=d.parentBoundTranscludeFn,k=d.transcludeControllers;d=d.futureParentElement;h&&h.$$boundTransclude&&(h=h.$$boundTransclude);f||(f=(d=d&&d[0])?"foreignobject"!==ya(d)&&ia.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==f?z(ka(f,z("
    ").append(a).html())):c?Sa.clone.call(a):a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);da.$$addScopeInfo(d,b);c&& +c(d,b);g&&g(b,d,d,h);c||(a=g=null);return d}}function Ua(a,b,c,d,e,g){function f(a,c,d,e){var g,k,l,m,p,n,G;if(t)for(G=Array(c.length),m=0;ms.priority)break;if(w=s.scope)s.templateUrl||(B(w)?(aa("new/isolated scope",J||t,s,v),J=s):aa("new/isolated scope",J,s,v)),t=t||s;R=s.name;if(!u&&(s.replace&&(s.templateUrl||s.template)||s.transclude&&!s.$$tlb)){for(w=Da+1;u=a[w++];)if(u.transclude&&!u.$$tlb||u.replace&&(u.templateUrl||u.template)){Ib=!0;break}u=!0}!s.templateUrl&&s.controller&&(H=H||S(),aa("'"+R+"' controller",H[R],s,v),H[R]=s);if(w=s.transclude)if(ba=!0, +s.$$tlb||(aa("transclusion",y,s,v),y=s),"element"===w)T=!0,n=s.priority,P=v,v=d.$$element=z(da.$$createComment(R,d[R])),b=v[0],ma(g,xa.call(P,0),b),P[0].$$parentNode=P[0].parentNode,q=W(Ib,P,e,n,f&&f.name,{nonTlbTranscludeDirective:y});else{var na=S();if(B(w)){P=[];var Ua=S(),Ma=S();r(w,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Ua[a]=b;na[b]=null;Ma[b]=c});r(v.contents(),function(a){var b=Ua[Ea(ya(a))];b?(Ma[b]=!0,na[b]=na[b]||[],na[b].push(a)):P.push(a)});r(Ma,function(a,b){if(!a)throw ca("reqslot", +b);});for(var L in na)na[L]&&(na[L]=W(Ib,na[L],e))}else P=z(nc(b)).contents();v.empty();q=W(Ib,P,e,void 0,void 0,{needsNewScope:s.$$isolateScope||s.$$newScope});q.$$slots=na}if(s.template)if(M=!0,aa("template",A,s,v),A=s,w=C(s.template)?s.template(v,d):s.template,w=Ia(w),s.replace){f=s;P=kc.test(w)?qd(ka(s.templateNamespace,Q(w))):[];b=P[0];if(1!==P.length||1!==b.nodeType)throw ca("tplrt",R,"");ma(g,v,b);D={$attr:{}};w=K(b,[],D);var rc=a.splice(Da+1,a.length-(Da+1));(J||t)&&Z(w,J,t);a=a.concat(w).concat(rc); +fa(d,D);D=a.length}else v.html(w);if(s.templateUrl)M=!0,aa("template",A,s,v),A=s,s.replace&&(f=s),p=ja(a.splice(Da,a.length-Da),v,d,g,ba&&q,h,k,{controllerDirectives:H,newScopeDirective:t!==s&&t,newIsolateScopeDirective:J,templateDirective:A,nonTlbTranscludeDirective:y}),D=a.length;else if(s.compile)try{N=s.compile(v,d,q);var U=s.$$originalDirective||s;C(N)?m(null,Ra(U,N),Ta,E):N&&m(Ra(U,N.pre),Ra(U,N.post),Ta,E)}catch($){c($,za(v))}s.terminal&&(p.terminal=!0,n=Math.max(n,s.priority))}p.scope=t&& +!0===t.scope;p.transcludeOnThisElement=ba;p.templateOnThisElement=M;p.transclude=q;l.hasElementTranscludeDirective=T;return p}function X(a,b,c,d){var e;if(E(b)){var g=b.match(l);b=b.substring(g[0].length);var f=g[1]||g[3],g="?"===g[2];"^^"===f?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e=f?c.inheritedData(h):c.data(h)}if(!e&&!g)throw ca("ctreq",b,a);}else if(I(b))for(e=[],f=0,g=b.length;fc.priority)&&-1!==c.restrict.indexOf(e)){k&&(c=$b(c,{$$start:k,$$end:l}));if(!c.$$bindings){var t=m=c,G=c.name,H={isolateScope:null,bindToController:null};B(t.scope)&&(!0===t.bindToController?(H.bindToController=d(t.scope,G,!0),H.isolateScope={}):H.isolateScope=d(t.scope,G,!1));B(t.bindToController)&&(H.bindToController=d(t.bindToController,G,!0));if(H.bindToController&&!t.controller)throw ca("noctrl",G);m=m.$$bindings=H;B(m.isolateScope)&&(c.$$isolateBindings=m.isolateScope)}b.push(c); +m=c}}return m}function $(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function ua(a,b){if("srcdoc"===b)return T.HTML;var c=ya(a);if("src"===b||"ngSrc"===b){if(-1===["img","video","audio","source","track"].indexOf(c))return T.RESOURCE_URL}else if("xlinkHref"===b||"form"===c&&"action"===b||"link"===c&&"href"===b)return T.RESOURCE_URL}function wa(a,c,d,e,g){var f=ua(a,e),h=k[e]||g,l=b(d,!g,f,h);if(l){if("multiple"===e&&"select"===ya(a))throw ca("selmulti", +za(a));if(m.test(e))throw ca("nodomevents");c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers=S());var k=g[e];k!==d&&(l=k&&b(k,!0,f,h),d=k);l&&(g[e]=l(a),(c[e]||(c[e]=[])).$$inter=!0,(g.$$observers&&g.$$observers[e].$$scope||a).$watch(l,function(a,b){"class"===e&&a!==b?g.$updateClass(a,b):g.$set(e,a)}))}}}})}}function ma(a,b,c){var d=b[0],e=b.length,g=d.parentNode,f,h;if(a)for(f=0,h=a.length;f=b)return a;for(;b--;){var d=a[b];(8===d.nodeType||d.nodeType===Oa&&""===d.nodeValue.trim())&&ug.call(a,b,1)}return a}function sg(a,b){if(b&&E(b))return b;if(E(a)){var d=td.exec(a);if(d)return d[3]}}function yf(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b, +c){Ia(b,"controller");B(b)?O(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!B(a.$scope))throw K("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,p;h=!0===h;k&&E(k)&&(p=k);if(E(f)){k=f.match(td);if(!k)throw ud("ctrlfmt",f);m=k[1];p=p||k[3];f=a.hasOwnProperty(m)?a[m]:Xc(g.$scope,m,!0)||(b?Xc(c,m,!0):void 0);if(!f)throw ud("ctrlreg",m);sb(f,m,!0)}if(h)return h=(I(f)?f[f.length-1]:f).prototype,l=Object.create(h|| +null),p&&e(g,p,l,m||f.name),O(function(){var a=d.invoke(f,l,g,m);a!==l&&(B(a)||C(a))&&(l=a,p&&e(g,p,l,m||f.name));return l},{instance:l,identifier:p});l=d.instantiate(f,g,m);p&&e(g,p,l,m||f.name);return l}}]}function zf(){this.$get=["$window",function(a){return z(a.document)}]}function Af(){this.$get=["$document","$rootScope",function(a,b){function d(){e=c.hidden}var c=a[0],e=c&&c.hidden;a.on("visibilitychange",d);b.$on("$destroy",function(){a.off("visibilitychange",d)});return function(){return e}}]} +function Bf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function tc(a){return B(a)?fa(a)?a.toISOString():eb(a):a}function Gf(){this.$get=function(){return function(a){if(!a)return"";var b=[];Oc(a,function(a,c){null===a||x(a)||C(a)||(I(a)?r(a,function(a){b.push(ja(c)+"="+ja(tc(a)))}):b.push(ja(c)+"="+ja(tc(a))))});return b.join("&")}}}function Hf(){this.$get=function(){return function(a){function b(a,e,f){null===a||x(a)||(I(a)?r(a,function(a,c){b(a,e+"["+(B(a)? +c:"")+"]")}):B(a)&&!fa(a)?Oc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ja(e)+"="+ja(tc(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function uc(a,b){if(E(a)){var d=a.replace(vg,"").trim();if(d){var c=b("Content-Type"),c=c&&0===c.indexOf(vd),e;(e=c)||(e=(e=d.match(wg))&&xg[e[0]].test(d));if(e)try{a=Rc(d)}catch(f){if(!c)return a;throw Kb("baddata",a,f);}}}return a}function wd(a){var b=S(),d;E(a)?r(a.split("\n"),function(a){d=a.indexOf(":");var e=L(Q(a.substr(0,d)));a= +Q(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):B(a)&&r(a,function(a,d){var f=L(d),g=Q(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function xd(a){var b;return function(d){b||(b=wd(a));return d?(d=b[L(d)],void 0===d&&(d=null),d):b}}function yd(a,b,d,c){if(C(c))return c(a,b,d);r(c,function(c){a=c(a,b,d)});return a}function Ff(){var a=this.defaults={transformResponse:[uc],transformRequest:[function(a){return B(a)&&"[object File]"!==ia.call(a)&&"[object Blob]"!==ia.call(a)&&"[object FormData]"!==ia.call(a)? +eb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ka(vc),put:ka(vc),patch:ka(vc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer",jsonpCallbackParam:"callback"},b=!1;this.useApplyAsync=function(a){return u(a)?(b=!!a,this):b};var d=this.interceptors=[];this.$get=["$browser","$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector","$sce",function(c,e,f,g,h,k,l,m){function p(b){function d(a,b){for(var c=0, +e=b.length;ca?b:k.reject(b)}if(!B(b))throw K("$http")("badreq",b);if(!E(m.valueOf(b.url)))throw K("$http")("badreq",b.url);var f=O({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer, +jsonpCallbackParam:a.jsonpCallbackParam},b);f.headers=function(b){var c=a.headers,d=O({},b.headers),g,f,h,c=O({},c.common,c[L(b.method)]);a:for(g in c){f=L(g);for(h in d)if(L(h)===f)continue a;d[g]=c[g]}return e(d,ka(b))}(b);f.method=ub(f.method);f.paramSerializer=E(f.paramSerializer)?l.get(f.paramSerializer):f.paramSerializer;c.$$incOutstandingRequestCount();var h=[],p=[];b=k.resolve(f);r(y,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&& +p.push(a.response,a.responseError)});b=d(b,h);b=b.then(function(b){var c=b.headers,d=yd(b.data,xd(c),void 0,b.transformRequest);x(d)&&r(c,function(a,b){"content-type"===L(b)&&delete c[b]});x(b.withCredentials)&&!x(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,d).then(g,g)});b=d(b,p);return b=b.finally(function(){c.$$completeOutstandingRequest(D)})}function n(c,d){function g(a){if(a){var c={};r(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d(): +h.$apply(d)}});return c}}function l(a,c,d,e,g){function f(){n(c,a,d,e,g)}M&&(200<=a&&300>a?M.put(N,[a,c,wd(d),e,g]):M.remove(N));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function n(a,b,d,e,g){b=-1<=b?b:0;(200<=b&&300>b?J.resolve:J.reject)({data:a,status:b,headers:xd(d),config:c,statusText:e,xhrStatus:g})}function G(a){n(a.data,a.status,ka(a.headers()),a.statusText,a.xhrStatus)}function y(){var a=p.pendingRequests.indexOf(c);-1!==a&&p.pendingRequests.splice(a,1)}var J=k.defer(),R=J.promise,M, +T,P=c.headers,q="jsonp"===L(c.method),N=c.url;q?N=m.getTrustedResourceUrl(N):E(N)||(N=m.valueOf(N));N=F(N,c.paramSerializer(c.params));q&&(N=s(N,c.jsonpCallbackParam));p.pendingRequests.push(c);R.then(y,y);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(M=B(c.cache)?c.cache:B(a.cache)?a.cache:v);M&&(T=M.get(N),u(T)?T&&C(T.then)?T.then(G,G):I(T)?n(T[1],T[0],ka(T[2]),T[3],T[4]):n(T,200,{},"OK","complete"):M.put(N,R));x(T)&&((T=zd(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]: +void 0)&&(P[c.xsrfHeaderName||a.xsrfHeaderName]=T),e(c.method,N,d,l,P,c.timeout,c.withCredentials,c.responseType,g(c.eventHandlers),g(c.uploadEventHandlers)));return R}function F(a,b){0=l&&(r.resolve(y),v(q.$$intervalId),delete g[q.$$intervalId]);t||a.$apply()},k);g[q.$$intervalId]=r;return q} +var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].promise.$$state.pur=!0,g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),delete g[a.$$intervalId],!0):!1};return f}]}function wc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=fb(a[b].replace(/%2F/g,"/"));return a.join("/")}function Ad(a,b){var d=ta(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||zg[d.protocol]||null}function Bd(a,b,d){if(Ag.test(a))throw kb("badpath",a);var c="/"!== +a.charAt(0);c&&(a="/"+a);a=ta(a);for(var c=(c&&"/"===a.pathname.charAt(0)?a.pathname.substring(1):a.pathname).split("/"),e=c.length;e--;)c[e]=decodeURIComponent(c[e]),d&&(c[e]=c[e].replace(/\//g,"%2F"));d=c.join("/");b.$$path=d;b.$$search=ec(a.search);b.$$hash=decodeURIComponent(a.hash);b.$$path&&"/"!==b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function xc(a,b){return a.slice(0,b.length)===b}function ua(a,b){if(xc(b,a))return b.substr(a.length)}function La(a){var b=a.indexOf("#");return-1===b?a: +a.substr(0,b)}function lb(a){return a.replace(/(#.+)|#$/,"$1")}function yc(a,b,d){this.$$html5=!0;d=d||"";Ad(a,this);this.$$parse=function(a){var d=ua(b,a);if(!E(d))throw kb("ipthprfx",a,b);Bd(d,this,!0);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=fc(this.$$search),d=this.$$hash?"#"+fb(this.$$hash):"";this.$$url=wc(this.$$path)+(a?"?"+a:"")+d;this.$$absUrl=b+this.$$url.substr(1);this.$$urlUpdatedByLocation=!0};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)), +!0;var f,g;u(f=ua(a,c))?(g=f,g=d&&u(f=ua(d,f))?b+(ua("/",f)||f):a+g):u(f=ua(b,c))?g=b+f:b===c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function zc(a,b,d){Ad(a,this);this.$$parse=function(c){var e=ua(a,c)||ua(b,c),f;x(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",x(e)&&(a=c,this.replace())):(f=ua(d,e),x(f)&&(f=e));Bd(f,this,!1);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;xc(f,e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=fc(this.$$search), +e=this.$$hash?"#"+fb(this.$$hash):"";this.$$url=wc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"");this.$$urlUpdatedByLocation=!0};this.$$parseLinkUrl=function(b,d){return La(a)===La(b)?(this.$$parse(b),!0):!1}}function Cd(a,b,d){this.$$html5=!0;zc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a===La(c)?f=c:(g=ua(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=fc(this.$$search), +e=this.$$hash?"#"+fb(this.$$hash):"";this.$$url=wc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url;this.$$urlUpdatedByLocation=!0}}function Lb(a){return function(){return this[a]}}function Dd(a,b){return function(d){if(x(d))return this[a];this[a]=b(d);this.$$compose();return this}}function Lf(){var a="!",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return u(b)?(a=b,this):a};this.html5Mode=function(a){if(Na(a))return b.enabled=a,this;if(B(a)){Na(a.enabled)&&(b.enabled= +a.enabled);Na(a.requireBase)&&(b.requireBase=a.requireBase);if(Na(a.rewriteLinks)||E(a.rewriteLinks))b.rewriteLinks=a.rewriteLinks;return this}return b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),g=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(f){throw l.url(e),l.$$state=g,f;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,b)}var l,m;m=c.baseHref();var p=c.url(),n;if(b.enabled){if(!m&& +b.requireBase)throw kb("nobase");n=p.substring(0,p.indexOf("/",p.indexOf("//")+2))+(m||"/");m=e.history?yc:Cd}else n=La(p),m=zc;var F=n.substr(0,La(n).lastIndexOf("/")+1);l=new m(n,F,"#"+a);l.$$parseLinkUrl(p,p);l.$$state=c.state();var s=/^\s*(javascript|mailto):/i;f.on("click",function(a){var e=b.rewriteLinks;if(e&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!==a.which&&2!==a.button){for(var h=z(a.target);"a"!==ya(h[0]);)if(h[0]===f[0]||!(h=h.parent())[0])return;if(!E(e)||!x(h.attr(e))){var e=h.prop("href"), +k=h.attr("href")||h.attr("xlink:href");B(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=ta(e.animVal).href);s.test(e)||!e||h.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(e,k)||(a.preventDefault(),l.absUrl()!==c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}}});lb(l.absUrl())!==lb(p)&&c.url(l.absUrl(),!0);var v=!0;c.onUrlChange(function(a,b){xc(a,F)?(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,g;a=lb(a);l.$$parse(a);l.$$state=b;g=d.$broadcast("$locationChangeStart", +a,c,b,e).defaultPrevented;l.absUrl()===a&&(g?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(v=!1,k(c,e)))}),d.$$phase||d.$digest()):g.location.href=a});d.$watch(function(){if(v||l.$$urlUpdatedByLocation){l.$$urlUpdatedByLocation=!1;var a=lb(c.url()),b=lb(l.absUrl()),g=c.state(),f=l.$$replace,m=a!==b||l.$$html5&&e.history&&g!==l.$$state;if(v||m)v=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,g).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=g): +(m&&h(b,f,g===l.$$state?null:l.$$state),k(a,g)))})}l.$$replace=!1});return l}]}function Mf(){var a=!0,b=this;this.debugEnabled=function(b){return u(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){bc(a)&&(a.stack&&f?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||D;return function(){var a=[];r(arguments,function(b){a.push(c(b))});return Function.prototype.apply.call(e, +b,a)}}var f=Ca||/\bEdge\//.test(d.navigator&&d.navigator.userAgent);return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Bg(a){return a+""}function Cg(a,b){return"undefined"!==typeof a?a:b}function Ed(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function Dg(a,b){switch(a.type){case q.MemberExpression:if(a.computed)return!1;break;case q.UnaryExpression:return 1;case q.BinaryExpression:return"+"!== +a.operator?1:!1;case q.CallExpression:return!1}return void 0===b?Fd:b}function W(a,b,d){var c,e,f=a.isPure=Dg(a,d);switch(a.type){case q.Program:c=!0;r(a.body,function(a){W(a.expression,b,f);c=c&&a.expression.constant});a.constant=c;break;case q.Literal:a.constant=!0;a.toWatch=[];break;case q.UnaryExpression:W(a.argument,b,f);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case q.BinaryExpression:W(a.left,b,f);W(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch= +a.left.toWatch.concat(a.right.toWatch);break;case q.LogicalExpression:W(a.left,b,f);W(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case q.ConditionalExpression:W(a.test,b,f);W(a.alternate,b,f);W(a.consequent,b,f);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case q.Identifier:a.constant=!1;a.toWatch=[a];break;case q.MemberExpression:W(a.object,b,f);a.computed&&W(a.property,b,f);a.constant=a.object.constant&& +(!a.computed||a.property.constant);a.toWatch=a.constant?[]:[a];break;case q.CallExpression:c=d=a.filter?!b(a.callee.name).$stateful:!1;e=[];r(a.arguments,function(a){W(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c;a.toWatch=d?e:[a];break;case q.AssignmentExpression:W(a.left,b,f);W(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case q.ArrayExpression:c=!0;e=[];r(a.elements,function(a){W(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c; +a.toWatch=e;break;case q.ObjectExpression:c=!0;e=[];r(a.properties,function(a){W(a.value,b,f);c=c&&a.value.constant;e.push.apply(e,a.value.toWatch);a.computed&&(W(a.key,b,!1),c=c&&a.key.constant,e.push.apply(e,a.key.toWatch))});a.constant=c;a.toWatch=e;break;case q.ThisExpression:a.constant=!1;a.toWatch=[];break;case q.LocalsExpression:a.constant=!1,a.toWatch=[]}}function Gd(a){if(1===a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function Hd(a){return a.type=== +q.Identifier||a.type===q.MemberExpression}function Id(a){if(1===a.body.length&&Hd(a.body[0].expression))return{type:q.AssignmentExpression,left:a.body[0].expression,right:{type:q.NGValueParameter},operator:"="}}function Jd(a){this.$filter=a}function Kd(a){this.$filter=a}function Mb(a,b,d){this.ast=new q(a,d);this.astCompiler=d.csp?new Kd(b):new Jd(b)}function Ac(a){return C(a.valueOf)?a.valueOf():Eg.call(a)}function Nf(){var a=S(),b={"true":!0,"false":!1,"null":null,undefined:void 0},d,c;this.addLiteral= +function(a,c){b[a]=c};this.setIdentifierFns=function(a,b){d=a;c=b;return this};this.$get=["$filter",function(e){function f(b,c){var d,g;switch(typeof b){case "string":return g=b=b.trim(),d=a[g],d||(d=new Nb(n),d=(new Mb(d,e,n)).parse(b),d.constant?d.$$watchDelegate=m:d.oneTime?d.$$watchDelegate=d.literal?l:k:d.inputs&&(d.$$watchDelegate=h),a[g]=d),p(d,c);case "function":return p(b,c);default:return p(D,c)}}function g(a,b,c){return null==a||null==b?a===b:"object"!==typeof a||(a=Ac(a),"object"!==typeof a|| +c)?a===b||a!==a&&b!==b:!1}function h(a,b,c,d,e){var f=d.inputs,h;if(1===f.length){var k=g,f=f[0];return a.$watch(function(a){var b=f(a);g(b,k,f.isPure)||(h=d(a,void 0,void 0,[b]),k=b&&Ac(b));return h},b,c,e)}for(var l=[],m=[],p=0,n=f.length;p=c.$$state.status&&e&&e.length&&a(function(){for(var a,c,g=0,f=e.length;ga)for(b in l++,f)ra.call(e,b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1F&&(y=4-F,A[y]||(A[y]=[]),A[y].push({msg:C(a.exp)?"fn: "+(a.exp.name||a.exp.toString()): +a.exp,newVal:g,oldVal:k}));else if(a===c){r=!1;break a}}catch(D){f(D)}if(!(n=q.$$watchersCount&&q.$$childHead||q!==this&&q.$$nextSibling))for(;q!==this&&!(n=q.$$nextSibling);)q=q.$parent}while(q=n);if((r||u.length)&&!F--)throw t.$$phase=null,d("infdig",b,A);}while(r||u.length);for(t.$$phase=null;HCa)throw va("iequirks");var c=ka(oa);c.isEnabled=function(){return a};c.trustAs= +d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=ab);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;r(oa,function(a,b){var d=L(b);c[("parse_as_"+d).replace(Bc,wb)]=function(b){return e(a,b)};c[("get_trusted_"+d).replace(Bc,wb)]=function(b){return f(a,b)};c[("trust_as_"+d).replace(Bc,wb)]=function(b){return g(a,b)}});return c}]} +function Tf(){this.$get=["$window","$document",function(a,b){var d={},c=!((!a.nw||!a.nw.process)&&a.chrome&&(a.chrome.app&&a.chrome.app.runtime||!a.chrome.app&&a.chrome.runtime&&a.chrome.runtime.id))&&a.history&&a.history.pushState,e=Z((/android (\d+)/.exec(L((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h=g.body&&g.body.style,k=!1,l=!1;h&&(k=!!("transition"in h||"webkitTransition"in h),l=!!("animation"in h||"webkitAnimation"in h));return{history:!(!c|| +4>e||f),hasEvent:function(a){if("input"===a&&Ca)return!1;if(x(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ja(),transitions:k,animations:l,android:e}}]}function Vf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$exceptionHandler","$templateCache","$http","$q","$sce",function(b,d,c,e,f){function g(h,k){g.totalPendingRequests++;if(!E(h)||x(d.get(h)))h=f.getTrustedResourceUrl(h);var l=c.defaults&&c.defaults.transformResponse;I(l)?l=l.filter(function(a){return a!== +uc}):l===uc&&(l=null);return c.get(h,O({cache:d,transformResponse:l},a)).finally(function(){g.totalPendingRequests--}).then(function(a){d.put(h,a.data);return a.data},function(a){k||(a=Gg("tpload",h,a.status,a.statusText),b(a));return e.reject(a)})}g.totalPendingRequests=0;return g}]}function Wf(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var c=$.element(a).data("$binding");c&& +r(c,function(c){d?(new RegExp("(^|\\s)"+Md(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!==c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;hc&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)===Dc;e++);if(e===(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)===Dc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Wd&&(d=d.splice(0,Wd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function Og(a,b,d,c){var e=a.d,f=e.length-a.i;b=x(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;fh;)k.unshift(0),h++;0=b.lgSize&&h.unshift(k.splice(-b.lgSize,k.length).join(""));k.length>b.gSize;)h.unshift(k.splice(-b.gSize,k.length).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Ob(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length< +b;)a=Dc+a;d&&(a=a.substr(a.length-b));return e+a}function ea(a,b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0-d)f+=d;0===f&&-12===d&&(f=12);return Ob(f,b,c,e)}}function mb(a,b,d){return function(c,e){var f=c["get"+a](),g=ub((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Xd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Yd(a){return function(b){var d=Xd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))- ++d;b=1+Math.round(b/6048E5);return Ob(b,a)}}function Ec(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Rd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Z(b[9]+b[10]),g=Z(b[9]+b[11]));h.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));f=Z(b[4]||0)-f;g=Z(b[5]||0)-g;h=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; +return function(c,d,f){var g="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;E(c)&&(c=Pg.test(c)?Z(c):b(c));Y(c)&&(c=new Date(c));if(!fa(c)||!isFinite(c.getTime()))return c;for(;d;)(l=Qg.exec(d))?(h=db(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=Sc(f,m),c=dc(c,f,!0));r(h,function(b){k=Rg[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ig(){return function(a,b){x(b)&&(b=2);return eb(a,b)}}function Jg(){return function(a, +b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(U(b))return a;Y(a)&&(a=a.toString());if(!wa(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?Fc(a,d,d+b):0===d?Fc(a,b,a.length):Fc(a,Math.max(0,d+b),d)}}function Fc(a,b,d){return E(a)?a.slice(b,d):xa.call(a,b,d)}function Td(a){function b(b){return b.map(function(b){var c=1,d=ab;if(C(b))d=b;else if(E(b)){if("+"===b.charAt(0)||"-"===b.charAt(0))c="-"===b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e= +d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}function c(a,b){var c=0,d=a.type,k=b.type;if(d===k){var k=a.value,l=b.value;"string"===d?(k=k.toLowerCase(),l=l.toLowerCase()):"object"===d&&(B(k)&&(k=a.index),B(l)&&(l=b.index));k!==l&&(c=kb||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(ce[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch=== +d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Rb(a,b){return function(d,c){var e,f;if(fa(d))return d;if(E(d)){'"'===d.charAt(0)&&'"'===d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(Sg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0, +ss:0,sss:0},r(e,function(a,c){c=q};g.$observe("min",function(a){q=n(a);h.$validate()})}if(u(g.max)||g.ngMax){var y;h.$validators.max=function(a){return!p(a)||x(y)||d(a)<=y};g.$observe("max",function(a){y=n(a);h.$validate()})}}}function Hc(a,b,d,c){(c.$$hasNativeValidators=B(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{}; +return c.badInput||c.typeMismatch?void 0:a})}function de(a){a.$$parserName="number";a.$parsers.push(function(b){if(a.$isEmpty(b))return null;if(Tg.test(b))return parseFloat(b)});a.$formatters.push(function(b){if(!a.$isEmpty(b)){if(!Y(b))throw pb("numfmt",b);b=b.toString()}return b})}function Wa(a){u(a)&&!Y(a)&&(a=parseFloat(a));return U(a)?void 0:a}function Ic(a){var b=a.toString(),d=b.indexOf(".");return-1===d?-1a&&(a=/e-(\d+)$/.exec(b))?Number(a[1]):0:b.length-d-1}function ee(a,b,d){a=Number(a); +var c=(a|0)!==a,e=(b|0)!==b,f=(d|0)!==d;if(c||e||f){var g=c?Ic(a):0,h=e?Ic(b):0,k=f?Ic(d):0,g=Math.max(g,h,k),g=Math.pow(10,g);a*=g;b*=g;d*=g;c&&(a=Math.round(a));e&&(b=Math.round(b));f&&(d=Math.round(d))}return 0===(a-b)%d}function fe(a,b,d,c,e){if(u(c)){a=a(c);if(!a.constant)throw pb("constexpr",d,c);return a(b)}return e}function Jc(a,b){function d(a,b){if(!a||!a.length)return[];if(!b||!b.length)return a;var c=[],d=0;a:for(;d(?:<\/\1>|)$/,kc=/<|&#?\w+;/,dg=/<([\w:-]+)/,eg=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,aa={option:[1,'"],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};aa.optgroup=aa.option;aa.tbody=aa.tfoot=aa.colgroup=aa.caption=aa.thead;aa.th=aa.td;var lg=w.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Sa=V.prototype={ready:gd,toString:function(){var a=[];r(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"}, +eq:function(a){return 0<=a?z(this[a]):z(this[this.length+a])},length:0,push:Wg,sort:[].sort,splice:[].splice},Gb={};r("multiple selected checked disabled readOnly required open".split(" "),function(a){Gb[L(a)]=a});var ld={};r("input select option textarea button form details".split(" "),function(a){ld[a]=!0});var sd={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern",ngStep:"step"};r({data:pc,removeData:oc,hasData:function(a){for(var b in ib[a.ng339])return!0; +return!1},cleanData:function(a){for(var b=0,d=a.length;b/,og=/^[^(]*\(\s*([^)]*)\)/m, +Zg=/,/,$g=/^\s*(_?)(\S+?)\1\s*$/,mg=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ba=K("$injector");gb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw E(d)&&d||(d=a.name||pg(a)),Ba("strictdi",d);b=nd(a);r(b[1].split(Zg),function(a){a.replace($g,function(a,b,d){c.push(d)})})}a.$inject=c}}else I(a)?(b=a.length-1,sb(a[b],"fn"),c=a.slice(0,b)):sb(a,"fn",!0);return c};var he=K("$animate"),sf=function(){this.$get=D},tf=function(){var a=new Hb,b=[];this.$get= +["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=E(b)?b.split(" "):I(b)?b:[],r(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){r(b,function(b){var c=a.get(b);if(c){var d=qg(b.attr("class")),e="",f="";r(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});r(b,function(a){e&&Db(a,e);f&&Cb(a,f)});a.delete(b)}});b.length=0}return{enabled:D,on:D,off:D,pin:D,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass|| +k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.set(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},qf=["$provide",function(a){var b=this,d=null,c=null;this.$$registeredAnimations=Object.create(null);this.register=function(c,d){if(c&&"."!==c.charAt(0))throw he("notcsel",c);var g=c+"-animation";b.$$registeredAnimations[c.substr(1)]=g;a.factory(g,d)};this.customFilter=function(a){1===arguments.length&&(c=C(a)?a:null);return c}; this.classNameFilter=function(a){if(1===arguments.length&&(d=a instanceof RegExp?a:null)&&/[(\s|\/)]ng-animate[(\s|\/)]/.test(d.toString()))throw d=null,he("nongcls","ng-animate");return d};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var e;a:{for(e=0;e <= >= && || ! = |".split(" "),function(a){Sb[a]=!0});var ch={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},zc=function(a){this.options=a};zc.prototype={constructor:zc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index <= >= && || ! = |".split(" "),function(a){Ub[a]=!0});var ch={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Nb=function(a){this.options=a};Nb.prototype={constructor:Nb,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue? this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"=== -a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=t(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw Ya("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index","<=",">=");)a={type:r.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:r.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:r.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a}, -unary:function(){var a;return(a=this.expect("+","-","!"))?{type:r.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:r.Literal,value:this.options.literals[this.consume().text]}: -this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:r.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:r.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:r.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE"); -return a},filter:function(a){a=[a];for(var b={type:r.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:r.Identifier,name:a.text}},constant:function(){return{type:r.Literal,value:this.consume().value}}, -arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:r.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:r.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")? -(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");return{type:r.ObjectExpression,properties:a}},throwError:function(a,b){throw Ya("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw Ya("ueoe", -this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw Ya("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:r.ThisExpression}, -$locals:{type:r.LocalsExpression}}};var Fd=2;Jd.prototype={compile:function(a){var b=this;this.state={nextId:0,filters:{},fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};V(a,b.$filter);var d="",c;this.stage="assign";if(c=Id(a))this.state.computing="assign",d=this.nextId(),this.recurse(c,d),this.return_(d),d="fn.assign="+this.generateFunction("assign","s,v,l");c=Gd(a.body);b.stage="inputs";p(c,function(a,c){var d="fn"+c;b.state[d]={vars:[],body:[],own:{}};b.state.computing=d; -var k=b.nextId();b.recurse(a,k);b.return_(k);b.state.inputs.push({name:d,isPure:a.isPure});a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(a);a='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+d+this.watchFns()+"return fn;";a=(new Function("$filter","getStringValue","ifDefined","plus",a))(this.$filter,Bg,Cg,Ed);this.state=this.stage=void 0;return a},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs, -d=this;p(b,function(b){a.push("var "+b.name+"="+d.generateFunction(b.name,"s"));b.isPure&&a.push(b.name,".isPure="+JSON.stringify(b.isPure)+";")});b.length&&a.push("fn.inputs=["+b.map(function(a){return a.name}).join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;p(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length? -"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,k,h=this,l,m,n;c=c||C;if(!f&&t(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case r.Program:p(a.body,function(b,c){h.recurse(b.expression,void 0,void 0,function(a){k=a});c!==a.body.length-1?h.current().body.push(k,";"):h.return_(k)});break;case r.Literal:m=this.escape(a.value); -this.assign(b,m);c(b||m);break;case r.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){k=a});m=a.operator+"("+this.ifDefined(k,0)+")";this.assign(b,m);c(m);break;case r.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){k=a});m="+"===a.operator?this.plus(g,k):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(k,0):"("+g+")"+a.operator+"("+k+")";this.assign(b,m);c(m);break;case r.LogicalExpression:b=b||this.nextId(); -h.recurse(a.left,b);h.if_("&&"===a.operator?b:h.not(b),h.lazyRecurse(a.right,b));c(b);break;case r.ConditionalExpression:b=b||this.nextId();h.recurse(a.test,b);h.if_(b,h.lazyRecurse(a.alternate,b),h.lazyRecurse(a.consequent,b));c(b);break;case r.Identifier:b=b||this.nextId();d&&(d.context="inputs"===h.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);h.if_("inputs"===h.stage||h.not(h.getHasOwnProperty("l",a.name)),function(){h.if_("inputs"=== -h.stage||"s",function(){e&&1!==e&&h.if_(h.isNull(h.nonComputedMember("s",a.name)),h.lazyAssign(h.nonComputedMember("s",a.name),"{}"));h.assign(b,h.nonComputedMember("s",a.name))})},b&&h.lazyAssign(b,h.nonComputedMember("l",a.name)));c(b);break;case r.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();h.recurse(a.object,g,void 0,function(){h.if_(h.notNull(g),function(){a.computed?(k=h.nextId(),h.recurse(a.property,k),h.getStringValue(k),e&&1!==e&&h.if_(h.not(h.computedMember(g, -k)),h.lazyAssign(h.computedMember(g,k),"{}")),m=h.computedMember(g,k),h.assign(b,m),d&&(d.computed=!0,d.name=k)):(e&&1!==e&&h.if_(h.isNull(h.nonComputedMember(g,a.property.name)),h.lazyAssign(h.nonComputedMember(g,a.property.name),"{}")),m=h.nonComputedMember(g,a.property.name),h.assign(b,m),d&&(d.computed=!1,d.name=a.property.name))},function(){h.assign(b,"undefined")});c(b)},!!e);break;case r.CallExpression:b=b||this.nextId();a.filter?(k=h.filter(a.callee.name),l=[],p(a.arguments,function(a){var b= -h.nextId();h.recurse(a,b);l.push(b)}),m=k+"("+l.join(",")+")",h.assign(b,m),c(b)):(k=h.nextId(),g={},l=[],h.recurse(a.callee,k,g,function(){h.if_(h.notNull(k),function(){p(a.arguments,function(b){h.recurse(b,a.constant?void 0:h.nextId(),void 0,function(a){l.push(a)})});m=g.name?h.member(g.context,g.name,g.computed)+"("+l.join(",")+")":k+"("+l.join(",")+")";h.assign(b,m)},function(){h.assign(b,"undefined")});c(b)}));break;case r.AssignmentExpression:k=this.nextId();g={};this.recurse(a.left,void 0, -g,function(){h.if_(h.notNull(g.context),function(){h.recurse(a.right,k);m=h.member(g.context,g.name,g.computed)+a.operator+k;h.assign(b,m);c(b||m)})},1);break;case r.ArrayExpression:l=[];p(a.elements,function(b){h.recurse(b,a.constant?void 0:h.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(b||m);break;case r.ObjectExpression:l=[];n=!1;p(a.properties,function(a){a.computed&&(n=!0)});n?(b=b||this.nextId(),this.assign(b,"{}"),p(a.properties,function(a){a.computed? -(g=h.nextId(),h.recurse(a.key,g)):g=a.key.type===r.Identifier?a.key.name:""+a.key.value;k=h.nextId();h.recurse(a.value,k);h.assign(h.member(b,g,a.computed),k)})):(p(a.properties,function(b){h.recurse(b.value,a.constant?void 0:h.nextId(),void 0,function(a){l.push(h.escape(b.key.type===r.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case r.ThisExpression:this.assign(b,"s");c(b||"s");break;case r.LocalsExpression:this.assign(b,"l");c(b||"l");break; -case r.NGValueParameter:this.assign(b,"v"),c(b||"v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a, +for(var d="",c=a,e=!1;this.index","<=",">=");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a}, +unary:function(){var a;return(a=this.expect("+","-","!"))?{type:q.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:q.Literal,value:this.options.literals[this.consume().text]}: +this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:q.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:q.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:q.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE"); +return a},filter:function(a){a=[a];for(var b={type:q.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:q.Identifier,name:a.text}},constant:function(){return{type:q.Literal,value:this.consume().value}}, +arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:q.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:q.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")? +(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");return{type:q.ObjectExpression,properties:a}},throwError:function(a,b){throw Xa("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw Xa("ueoe", +this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw Xa("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:q.ThisExpression}, +$locals:{type:q.LocalsExpression}}};var Fd=2;Jd.prototype={compile:function(a){var b=this;this.state={nextId:0,filters:{},fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};W(a,b.$filter);var d="",c;this.stage="assign";if(c=Id(a))this.state.computing="assign",d=this.nextId(),this.recurse(c,d),this.return_(d),d="fn.assign="+this.generateFunction("assign","s,v,l");c=Gd(a.body);b.stage="inputs";r(c,function(a,c){var d="fn"+c;b.state[d]={vars:[],body:[],own:{}};b.state.computing=d; +var h=b.nextId();b.recurse(a,h);b.return_(h);b.state.inputs.push({name:d,isPure:a.isPure});a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(a);a='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+d+this.watchFns()+"return fn;";a=(new Function("$filter","getStringValue","ifDefined","plus",a))(this.$filter,Bg,Cg,Ed);this.state=this.stage=void 0;return a},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs, +d=this;r(b,function(b){a.push("var "+b.name+"="+d.generateFunction(b.name,"s"));b.isPure&&a.push(b.name,".isPure="+JSON.stringify(b.isPure)+";")});b.length&&a.push("fn.inputs=["+b.map(function(a){return a.name}).join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;r(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length? +"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m,p;c=c||D;if(!f&&u(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case q.Program:r(a.body,function(b,c){k.recurse(b.expression,void 0,void 0,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case q.Literal:m=this.escape(a.value); +this.assign(b,m);c(b||m);break;case q.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);c(m);break;case q.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case q.LogicalExpression:b=b||this.nextId(); +k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case q.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case q.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"=== +k.stage||"s",function(){e&&1!==e&&k.if_(k.isNull(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));c(b);break;case q.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,void 0,function(){k.if_(k.notNull(g),function(){a.computed?(h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),e&&1!==e&&k.if_(k.not(k.computedMember(g, +h)),k.lazyAssign(k.computedMember(g,h),"{}")),m=k.computedMember(g,h),k.assign(b,m),d&&(d.computed=!0,d.name=h)):(e&&1!==e&&k.if_(k.isNull(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}")),m=k.nonComputedMember(g,a.property.name),k.assign(b,m),d&&(d.computed=!1,d.name=a.property.name))},function(){k.assign(b,"undefined")});c(b)},!!e);break;case q.CallExpression:b=b||this.nextId();a.filter?(h=k.filter(a.callee.name),l=[],r(a.arguments,function(a){var b= +k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){r(a.arguments,function(b){k.recurse(b,a.constant?void 0:k.nextId(),void 0,function(a){l.push(a)})});m=g.name?k.member(g.context,g.name,g.computed)+"("+l.join(",")+")":h+"("+l.join(",")+")";k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case q.AssignmentExpression:h=this.nextId();g={};this.recurse(a.left,void 0, +g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case q.ArrayExpression:l=[];r(a.elements,function(b){k.recurse(b,a.constant?void 0:k.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(b||m);break;case q.ObjectExpression:l=[];p=!1;r(a.properties,function(a){a.computed&&(p=!0)});p?(b=b||this.nextId(),this.assign(b,"{}"),r(a.properties,function(a){a.computed? +(g=k.nextId(),k.recurse(a.key,g)):g=a.key.type===q.Identifier?a.key.name:""+a.key.value;h=k.nextId();k.recurse(a.value,h);k.assign(k.member(b,g,a.computed),h)})):(r(a.properties,function(b){k.recurse(b.value,a.constant?void 0:k.nextId(),void 0,function(a){l.push(k.escape(b.key.type===q.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case q.ThisExpression:this.assign(b,"s");c(b||"s");break;case q.LocalsExpression:this.assign(b,"l");c(b||"l");break; +case q.NGValueParameter:this.assign(b,"v"),c(b||"v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a, b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},isNull:function(a){return a+"==null"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a, -b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(D(a))return"'"+a.replace(this.stringEscapeRegex, -this.stringEscapeFn)+"'";if(Y(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw Ya("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};Kd.prototype={compile:function(a){var b=this;V(a,b.$filter);var d,c;if(d=Id(a))c=this.recurse(d);d=Gd(a.body);var e;d&&(e=[],p(d,function(a,c){var d= -b.recurse(a);d.isPure=a.isPure;a.input=d;e.push(d);a.watchId=c}));var f=[];p(a.body,function(a){f.push(b.recurse(a.expression))});a=0===a.body.length?C:1===a.body.length?f[0]:function(a,b){var c;p(f,function(d){c=d(a,b)});return c};c&&(a.assign=function(a,b,d){return c(a,d,b)});e&&(a.inputs=e);return a},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case r.Literal:return this.value(a.value,b);case r.UnaryExpression:return e=this.recurse(a.argument), -this["unary"+a.operator](e,b);case r.BinaryExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case r.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case r.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case r.Identifier:return f.identifier(a.name,b,d);case r.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed|| -(e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d):this.nonComputedMember(c,e,b,d);case r.CallExpression:return g=[],p(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],q=0;q":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f, -g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k)?b(e,f,g,k):d(e,f,g,k);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0, -name:void 0,value:a}:a}},identifier:function(a,b,d){return function(c,e,f,g){c=e&&a in e?e:c;d&&1!==d&&c&&null==c[a]&&(c[a]={});e=c?c[a]:void 0;return b?{context:c,name:a,value:e}:e}},computedMember:function(a,b,d,c){return function(e,f,g,k){var h=a(e,f,g,k),l,m;null!=h&&(l=b(e,f,g,k),l+="",c&&1!==c&&h&&!h[l]&&(h[l]={}),m=h[l]);return d?{context:h,name:l,value:m}:m}},nonComputedMember:function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k);c&&1!==c&&e&&null==e[b]&&(e[b]={});f=null!=e?e[b]:void 0; -return d?{context:e,name:b,value:f}:f}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};xc.prototype={constructor:xc,parse:function(a){a=this.ast.ast(a);var b=this.astCompiler.compile(a);b.literal=0===a.body.length||1===a.body.length&&(a.body[0].expression.type===r.Literal||a.body[0].expression.type===r.ArrayExpression||a.body[0].expression.type===r.ObjectExpression);b.constant=a.constant;return b}};var wa=M("$sce"),oa={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl", -JS:"js"},Ac=/_([a-z])/g,Gg=M("$compile"),X=u.document.createElement("a"),Od=ua(u.location.href);Pd.$inject=["$document"];ed.$inject=["$provide"];var Wd=22,Vd=".",Cc="0";Qd.$inject=["$locale"];Sd.$inject=["$locale"];var Rg={yyyy:da("FullYear",4,0,!1,!0),yy:da("FullYear",2,0,!0,!0),y:da("FullYear",1,0,!1,!0),MMMM:ob("Month"),MMM:ob("Month",!0),MM:da("Month",2,1),M:da("Month",1,1),LLLL:ob("Month",!1,!0),dd:da("Date",2),d:da("Date",1),HH:da("Hours",2),H:da("Hours",1),hh:da("Hours",2,-12),h:da("Hours", -1,-12),mm:da("Minutes",2),m:da("Minutes",1),ss:da("Seconds",2),s:da("Seconds",1),sss:da("Milliseconds",3),EEEE:ob("Day"),EEE:ob("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Mb(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},Qg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/, -Pg=/^-?\d+$/;Rd.$inject=["$locale"];var Kg=ka(N),Lg=ka(wb);Td.$inject=["$parse"];var He=ka({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ha.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),xb={};p(Hb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!==a){var c=Ea("ng-"+b),e=d;"checked"===a&&(e=function(a, -b,e){e.ngModel!==e[c]&&d(a,b,e)});xb[c]=function(){return{restrict:"A",priority:100,link:e}}}});p(sd,function(a,b){xb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"===e.ngPattern.charAt(0)&&(c=e.ngPattern.match(Vg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});p(["src","srcset","href"],function(a){var b=Ea("ng-"+a);xb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"=== -ha.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ca&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Ob={$addControl:C,$$renameControl:function(a,b){a.$name=b},$removeControl:C,$setValidity:C,$setDirty:C,$setPristine:C,$setSubmitted:C};Nb.$inject=["$element","$attrs","$scope","$animate","$interpolate"];Nb.prototype={$rollbackViewValue:function(){p(this.$$controls,function(a){a.$rollbackViewValue()})},$commitViewValue:function(){p(this.$$controls, -function(a){a.$commitViewValue()})},$addControl:function(a){Ia(a.$name,"input");this.$$controls.push(a);a.$name&&(this[a.$name]=a);a.$$parentForm=this},$$renameControl:function(a,b){var d=a.$name;this[d]===a&&delete this[d];this[b]=a;a.$name=b},$removeControl:function(a){a.$name&&this[a.$name]===a&&delete this[a.$name];p(this.$pending,function(b,d){this.$setValidity(d,null,a)},this);p(this.$error,function(b,d){this.$setValidity(d,null,a)},this);p(this.$$success,function(b,d){this.$setValidity(d,null, -a)},this);db(this.$$controls,a);a.$$parentForm=Ob},$setDirty:function(){this.$$animate.removeClass(this.$$element,Za);this.$$animate.addClass(this.$$element,Tb);this.$dirty=!0;this.$pristine=!1;this.$$parentForm.$setDirty()},$setPristine:function(){this.$$animate.setClass(this.$$element,Za,Tb+" ng-submitted");this.$dirty=!1;this.$pristine=!0;this.$submitted=!1;p(this.$$controls,function(a){a.$setPristine()})},$setUntouched:function(){p(this.$$controls,function(a){a.$setUntouched()})},$setSubmitted:function(){this.$$animate.addClass(this.$$element, -"ng-submitted");this.$submitted=!0;this.$$parentForm.$setSubmitted()}};ae({clazz:Nb,set:function(a,b,d){var c=a[b];c?-1===c.indexOf(d)&&c.push(d):a[b]=[d]},unset:function(a,b,d){var c=a[b];c&&(db(c,d),0===c.length&&delete a[b])}});var ie=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||C}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Nb,compile:function(d,f){d.addClass(Za).addClass(pb);var g=f.name?"name": -a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var q=function(b){a.$apply(function(){n.$commitViewValue();n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",q);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",q)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var p=g?c(n.$name):C;g&&(p(a,n),e.$observe(g,function(b){n.$name!==b&&(p(a,void 0),n.$$parentForm.$$renameControl(n,b),p=c(n.$name),p(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n); -p(a,void 0);P(n,Ob)})}}}}}]},Ie=ie(),Ue=ie(!0),Sg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,dh=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,eh=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,Tg=/^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,je=/^(\d{4,})-(\d{2})-(\d{2})$/, -ke=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Kc=/^(\d{4,})-W(\d\d)$/,le=/^(\d{4,})-(\d\d)$/,me=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,ce=S();p(["date","datetime-local","month","time","week"],function(a){ce[a]=!0});var ne={text:function(a,b,d,c,e,f){Wa(a,b,d,c,e,f);Fc(c)},date:qb("date",je,Pb(je,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":qb("datetimelocal",ke,Pb(ke,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:qb("time",me,Pb(me,["HH","mm", -"ss","sss"]),"HH:mm:ss.sss"),week:qb("week",Kc,function(a,b){if(ea(a))return a;if(D(a)){Kc.lastIndex=0;var d=Kc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,k=0,h=Xd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),k=b.getMilliseconds());return new Date(c,0,h.getDate()+e,d,f,g,k)}}return NaN},"yyyy-Www"),month:qb("month",le,Pb(le,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Gc(a,b,d,c);de(c);Wa(a,b,d,c,e,f);var g,k;if(t(d.min)||d.ngMin)c.$validators.min=function(a){return c.$isEmpty(a)|| -w(g)||a>=g},d.$observe("min",function(a){g=Xa(a);c.$validate()});if(t(d.max)||d.ngMax)c.$validators.max=function(a){return c.$isEmpty(a)||w(k)||a<=k},d.$observe("max",function(a){k=Xa(a);c.$validate()});if(t(d.step)||d.ngStep){var h;c.$validators.step=function(a,b){return c.$isEmpty(b)||w(h)||ee(b,g||0,h)};d.$observe("step",function(a){h=Xa(a);c.$validate()})}},url:function(a,b,d,c,e,f){Wa(a,b,d,c,e,f);Fc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||dh.test(d)}}, -email:function(a,b,d,c,e,f){Wa(a,b,d,c,e,f);Fc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||eh.test(d)}},radio:function(a,b,d,c){var e=!d.ngTrim||"false"!==Q(d.ngTrim);w(d.name)&&b.attr("name",++sb);b.on("click",function(a){var g;b[0].checked&&(g=d.value,e&&(g=Q(g)),c.$setViewValue(g,a&&a.type))});c.$render=function(){var a=d.value;e&&(a=Q(a));b[0].checked=a===c.$viewValue};d.$observe("value",c.$render)},range:function(a,b,d,c,e,f){function g(a,c){b.attr(a, -d[a]);d.$observe(a,c)}function k(a){n=Xa(a);T(c.$modelValue)||(m?(a=b.val(),n>a&&(a=n,b.val(a)),c.$setViewValue(a)):c.$validate())}function h(a){q=Xa(a);T(c.$modelValue)||(m?(a=b.val(),q=n},g("min",k));e&&(c.$validators.max=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||w(q)||b<=q},g("max",h));f&&(c.$validators.step=m?function(){return!r.stepMismatch}:function(a,b){return c.$isEmpty(b)||w(p)||ee(b,n||0,p)},g("step",l))},checkbox:function(a,b,d,c,e, -f,g,k){var h=fe(k,a,"ngTrueValue",d.ngTrueValue,!0),l=fe(k,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return sa(a,h)});c.$parsers.push(function(a){return a?h:l})},hidden:C,button:C,submit:C,reset:C,file:C},Zc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e, -f,g,k){k[0]&&(ne[N(g.type)]||ne.text)(e,f,g,k[0],b,a,d,c)}}}}],fh=/^(true|false|\d+)$/,mf=function(){function a(a,d,c){var e=t(c)?c:9===Ca?"":null;a.prop("value",e);d.$set("value",c)}return{restrict:"A",priority:100,compile:function(b,d){return fh.test(d.ngValue)?function(b,d,f){b=b.$eval(f.ngValue);a(d,f,b)}:function(b,d,f){b.$watch(f.ngValue,function(b){a(d,f,b)})}}}},Me=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c, -e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=dc(a)})}}}}],Oe=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=w(a)?"":a})}}}}],Ne=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)}); -d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],lf=ka({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Pe=Ic("",!0),Re=Ic("Odd",0),Qe=Ic("Even",1),Se=Qa({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Te=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],dd={},gh={blur:!0,focus:!0}; -p("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=Ea("ng-"+a);dd[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b]);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};gh[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var We=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600, -terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var k,h,l;d.$watch(e.ngIf,function(d){d?h||g(function(d,f){h=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);k={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),h&&(h.$destroy(),h=null),k&&(l=vb(k.clone),a.leave(l).done(function(a){!1!==a&&(l=null)}),k=null))})}}}],Xe=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:$.noop,compile:function(c, -e){var f=e.ngInclude||e.src,g=e.onload||"",k=e.autoscroll;return function(c,e,m,n,q){var p=0,r,z,v,s=function(){z&&(z.remove(),z=null);r&&(r.$destroy(),r=null);v&&(d.leave(v).done(function(a){!1!==a&&(z=null)}),z=v,v=null)};c.$watch(f,function(f){var m=function(a){!1===a||!t(k)||k&&!c.$eval(k)||b()},y=++p;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&y===p){var b=c.$new();n.template=a;a=q(b,function(a){s();d.enter(a,null,e).done(m)});r=b;v=a;r.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed|| -y!==p||(s(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(s(),n.template=null)})}}}}],of=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){ha.call(d[0]).match(/SVG/)?(d.empty(),a(fd(e.template,u.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ye=Qa({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),kf=function(){return{restrict:"A", -priority:100,require:"ngModel",link:function(a,b,d,c){var e=d.ngList||", ",f="false"!==d.ngTrim,g=f?Q(e):e;c.$parsers.push(function(a){if(!w(a)){var b=[];a&&p(a.split(g),function(a){a&&b.push(f?Q(a):a)});return b}});c.$formatters.push(function(a){if(I(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},pb="ng-valid",$d="ng-invalid",Za="ng-pristine",Tb="ng-dirty",rb=M("ngModel");Qb.$inject="$scope $exceptionHandler $attrs $element $parse $animate $timeout $q $interpolate".split(" "); -Qb.prototype={$$initGetterSetters:function(){if(this.$options.getOption("getterSetter")){var a=this.$$parse(this.$$attr.ngModel+"()"),b=this.$$parse(this.$$attr.ngModel+"($$$p)");this.$$ngModelGet=function(b){var c=this.$$parsedNgModel(b);A(c)&&(c=a(b));return c};this.$$ngModelSet=function(a,c){A(this.$$parsedNgModel(a))?b(a,{$$$p:c}):this.$$parsedNgModelAssign(a,c)}}else if(!this.$$parsedNgModel.assign)throw rb("nonassign",this.$$attr.ngModel,Aa(this.$$element));},$render:C,$isEmpty:function(a){return w(a)|| -""===a||null===a||a!==a},$$updateEmptyClasses:function(a){this.$isEmpty(a)?(this.$$animate.removeClass(this.$$element,"ng-not-empty"),this.$$animate.addClass(this.$$element,"ng-empty")):(this.$$animate.removeClass(this.$$element,"ng-empty"),this.$$animate.addClass(this.$$element,"ng-not-empty"))},$setPristine:function(){this.$dirty=!1;this.$pristine=!0;this.$$animate.removeClass(this.$$element,Tb);this.$$animate.addClass(this.$$element,Za)},$setDirty:function(){this.$dirty=!0;this.$pristine=!1;this.$$animate.removeClass(this.$$element, -Za);this.$$animate.addClass(this.$$element,Tb);this.$$parentForm.$setDirty()},$setUntouched:function(){this.$touched=!1;this.$untouched=!0;this.$$animate.setClass(this.$$element,"ng-untouched","ng-touched")},$setTouched:function(){this.$touched=!0;this.$untouched=!1;this.$$animate.setClass(this.$$element,"ng-touched","ng-untouched")},$rollbackViewValue:function(){this.$$timeout.cancel(this.$$pendingDebounce);this.$viewValue=this.$$lastCommittedViewValue;this.$render()},$validate:function(){if(!T(this.$modelValue)){var a= -this.$$lastCommittedViewValue,b=this.$$rawModelValue,d=this.$valid,c=this.$modelValue,e=this.$options.getOption("allowInvalid"),f=this;this.$$runValidators(b,a,function(a){e||d===a||(f.$modelValue=a?b:void 0,f.$modelValue!==c&&f.$$writeModelToScope())})}},$$runValidators:function(a,b,d){function c(){var c=!0;p(h.$validators,function(d,e){var g=Boolean(d(a,b));c=c&&g;f(e,g)});return c?!0:(p(h.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;p(h.$asyncValidators,function(e, -g){var h=e(a,b);if(!h||!A(h.then))throw rb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?h.$$q.all(c).then(function(){g(d)},C):g(!0)}function f(a,b){k===h.$$currentValidationRunId&&h.$setValidity(a,b)}function g(a){k===h.$$currentValidationRunId&&d(a)}this.$$currentValidationRunId++;var k=this.$$currentValidationRunId,h=this;(function(){var a=h.$$parserName||"parse";if(w(h.$$parserValid))f(a,null);else return h.$$parserValid||(p(h.$validators,function(a, -b){f(b,null)}),p(h.$asyncValidators,function(a,b){f(b,null)})),f(a,h.$$parserValid),h.$$parserValid;return!0})()?c()?e():g(!1):g(!1)},$commitViewValue:function(){var a=this.$viewValue;this.$$timeout.cancel(this.$$pendingDebounce);if(this.$$lastCommittedViewValue!==a||""===a&&this.$$hasNativeValidators)this.$$updateEmptyClasses(a),this.$$lastCommittedViewValue=a,this.$pristine&&this.$setDirty(),this.$$parseAndValidate()},$$parseAndValidate:function(){var a=this.$$lastCommittedViewValue,b=this;if(this.$$parserValid= -w(a)?void 0:!0)for(var d=0;de||c.$isEmpty(b)||b.length<=e}}}}},bd=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Z(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};u.angular.bootstrap?u.console&&console.log("WARNING: Tried to load angular more than once."): +g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0, +name:void 0,value:a}:a}},identifier:function(a,b,d){return function(c,e,f,g){c=e&&a in e?e:c;d&&1!==d&&c&&null==c[a]&&(c[a]={});e=c?c[a]:void 0;return b?{context:c,name:a,value:e}:e}},computedMember:function(a,b,d,c){return function(e,f,g,h){var k=a(e,f,g,h),l,m;null!=k&&(l=b(e,f,g,h),l+="",c&&1!==c&&k&&!k[l]&&(k[l]={}),m=k[l]);return d?{context:k,name:l,value:m}:m}},nonComputedMember:function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h);c&&1!==c&&e&&null==e[b]&&(e[b]={});f=null!=e?e[b]:void 0; +return d?{context:e,name:b,value:f}:f}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};Mb.prototype={constructor:Mb,parse:function(a){a=this.getAst(a);var b=this.astCompiler.compile(a.ast),d=a.ast;b.literal=0===d.body.length||1===d.body.length&&(d.body[0].expression.type===q.Literal||d.body[0].expression.type===q.ArrayExpression||d.body[0].expression.type===q.ObjectExpression);b.constant=a.ast.constant;b.oneTime=a.oneTime;return b},getAst:function(a){var b=!1;a=a.trim();":"=== +a.charAt(0)&&":"===a.charAt(1)&&(b=!0,a=a.substring(2));return{ast:this.ast.ast(a),oneTime:b}}};var va=K("$sce"),oa={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},Bc=/_([a-z])/g,Gg=K("$compile"),X=w.document.createElement("a"),Od=ta(w.location.href);Pd.$inject=["$document"];ed.$inject=["$provide"];var Wd=22,Vd=".",Dc="0";Qd.$inject=["$locale"];Sd.$inject=["$locale"];var Rg={yyyy:ea("FullYear",4,0,!1,!0),yy:ea("FullYear",2,0,!0,!0),y:ea("FullYear",1,0,!1,!0),MMMM:mb("Month"), +MMM:mb("Month",!0),MM:ea("Month",2,1),M:ea("Month",1,1),LLLL:mb("Month",!1,!0),dd:ea("Date",2),d:ea("Date",1),HH:ea("Hours",2),H:ea("Hours",1),hh:ea("Hours",2,-12),h:ea("Hours",1,-12),mm:ea("Minutes",2),m:ea("Minutes",1),ss:ea("Seconds",2),s:ea("Seconds",1),sss:ea("Milliseconds",3),EEEE:mb("Day"),EEE:mb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Ob(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},Qg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,Pg=/^-?\d+$/;Rd.$inject=["$locale"];var Kg=la(L),Lg=la(ub);Td.$inject=["$parse"];var He=la({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ia.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)|| +a.preventDefault()})}}}}),vb={};r(Gb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!==a){var c=Ea("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});vb[c]=function(){return{restrict:"A",priority:100,link:e}}}});r(sd,function(a,b){vb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"===e.ngPattern.charAt(0)&&(c=e.ngPattern.match(Vg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b, +a)})}}}});r(["src","srcset","href"],function(a){var b=Ea("ng-"+a);vb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"===ia.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ca&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Qb={$addControl:D,$$renameControl:function(a,b){a.$name=b},$removeControl:D,$setValidity:D,$setDirty:D,$setPristine:D,$setSubmitted:D};Pb.$inject=["$element", +"$attrs","$scope","$animate","$interpolate"];Pb.prototype={$rollbackViewValue:function(){r(this.$$controls,function(a){a.$rollbackViewValue()})},$commitViewValue:function(){r(this.$$controls,function(a){a.$commitViewValue()})},$addControl:function(a){Ia(a.$name,"input");this.$$controls.push(a);a.$name&&(this[a.$name]=a);a.$$parentForm=this},$$renameControl:function(a,b){var d=a.$name;this[d]===a&&delete this[d];this[b]=a;a.$name=b},$removeControl:function(a){a.$name&&this[a.$name]===a&&delete this[a.$name]; +r(this.$pending,function(b,d){this.$setValidity(d,null,a)},this);r(this.$error,function(b,d){this.$setValidity(d,null,a)},this);r(this.$$success,function(b,d){this.$setValidity(d,null,a)},this);cb(this.$$controls,a);a.$$parentForm=Qb},$setDirty:function(){this.$$animate.removeClass(this.$$element,Ya);this.$$animate.addClass(this.$$element,Vb);this.$dirty=!0;this.$pristine=!1;this.$$parentForm.$setDirty()},$setPristine:function(){this.$$animate.setClass(this.$$element,Ya,Vb+" ng-submitted");this.$dirty= +!1;this.$pristine=!0;this.$submitted=!1;r(this.$$controls,function(a){a.$setPristine()})},$setUntouched:function(){r(this.$$controls,function(a){a.$setUntouched()})},$setSubmitted:function(){this.$$animate.addClass(this.$$element,"ng-submitted");this.$submitted=!0;this.$$parentForm.$setSubmitted()}};ae({clazz:Pb,set:function(a,b,d){var c=a[b];c?-1===c.indexOf(d)&&c.push(d):a[b]=[d]},unset:function(a,b,d){var c=a[b];c&&(cb(c,d),0===c.length&&delete a[b])}});var ie=function(a){return["$timeout","$parse", +function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||D}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Pb,compile:function(d,f){d.addClass(Ya).addClass(nb);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var p=f[0];if(!("action"in e)){var n=function(b){a.$apply(function(){p.$commitViewValue();p.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",n);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit", +n)},0,!1)})}(f[1]||p.$$parentForm).$addControl(p);var r=g?c(p.$name):D;g&&(r(a,p),e.$observe(g,function(b){p.$name!==b&&(r(a,void 0),p.$$parentForm.$$renameControl(p,b),r=c(p.$name),r(a,p))}));d.on("$destroy",function(){p.$$parentForm.$removeControl(p);r(a,void 0);O(p,Qb)})}}}}}]},Ie=ie(),Ue=ie(!0),Sg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,dh=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i, +eh=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,Tg=/^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,je=/^(\d{4,})-(\d{2})-(\d{2})$/,ke=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Lc=/^(\d{4,})-W(\d\d)$/,le=/^(\d{4,})-(\d\d)$/,me=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,ce=S();r(["date","datetime-local","month","time","week"],function(a){ce[a]= +!0});var ne={text:function(a,b,d,c,e,f){Va(a,b,d,c,e,f);Gc(c)},date:ob("date",je,Rb(je,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":ob("datetimelocal",ke,Rb(ke,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:ob("time",me,Rb(me,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:ob("week",Lc,function(a,b){if(fa(a))return a;if(E(a)){Lc.lastIndex=0;var d=Lc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Xd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds()); +return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:ob("month",le,Rb(le,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Hc(a,b,d,c);de(c);Va(a,b,d,c,e,f);var g,h;if(u(d.min)||d.ngMin)c.$validators.min=function(a){return c.$isEmpty(a)||x(g)||a>=g},d.$observe("min",function(a){g=Wa(a);c.$validate()});if(u(d.max)||d.ngMax)c.$validators.max=function(a){return c.$isEmpty(a)||x(h)||a<=h},d.$observe("max",function(a){h=Wa(a);c.$validate()});if(u(d.step)||d.ngStep){var k;c.$validators.step= +function(a,b){return c.$isEmpty(b)||x(k)||ee(b,g||0,k)};d.$observe("step",function(a){k=Wa(a);c.$validate()})}},url:function(a,b,d,c,e,f){Va(a,b,d,c,e,f);Gc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||dh.test(d)}},email:function(a,b,d,c,e,f){Va(a,b,d,c,e,f);Gc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||eh.test(d)}},radio:function(a,b,d,c){var e=!d.ngTrim||"false"!==Q(d.ngTrim);x(d.name)&&b.attr("name",++qb); +b.on("click",function(a){var g;b[0].checked&&(g=d.value,e&&(g=Q(g)),c.$setViewValue(g,a&&a.type))});c.$render=function(){var a=d.value;e&&(a=Q(a));b[0].checked=a===c.$viewValue};d.$observe("value",c.$render)},range:function(a,b,d,c,e,f){function g(a,c){b.attr(a,d[a]);d.$observe(a,c)}function h(a){p=Wa(a);U(c.$modelValue)||(m?(a=b.val(),p>a&&(a=p,b.val(a)),c.$setViewValue(a)):c.$validate())}function k(a){n=Wa(a);U(c.$modelValue)||(m?(a=b.val(),n=p},g("min",h));e&& +(c.$validators.max=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||x(n)||b<=n},g("max",k));f&&(c.$validators.step=m?function(){return!q.stepMismatch}:function(a,b){return c.$isEmpty(b)||x(r)||ee(b,p||0,r)},g("step",l))},checkbox:function(a,b,d,c,e,f,g,h){var k=fe(h,a,"ngTrueValue",d.ngTrueValue,!0),l=fe(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1=== +a};c.$formatters.push(function(a){return sa(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:D,button:D,submit:D,reset:D,file:D},Zc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(ne[L(g.type)]||ne.text)(e,f,g,h[0],b,a,d,c)}}}}],fh=/^(true|false|\d+)$/,mf=function(){function a(a,d,c){var e=u(c)?c:9===Ca?"":null;a.prop("value",e);d.$set("value",c)}return{restrict:"A",priority:100,compile:function(b,d){return fh.test(d.ngValue)? +function(b,d,f){b=b.$eval(f.ngValue);a(d,f,b)}:function(b,d,f){b.$watch(f.ngValue,function(b){a(d,f,b)})}}}},Me=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=gc(a)})}}}}],Oe=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions); +d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=x(a)?"":a})}}}}],Ne=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],lf=la({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}), +Pe=Jc("",!0),Re=Jc("Odd",0),Qe=Jc("Even",1),Se=Qa({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Te=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],dd={},gh={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=Ea("ng-"+a);dd[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g= +d(f[b]);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};gh[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var We=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l= +tb(h.clone),a.leave(l).done(function(a){!1!==a&&(l=null)}),h=null))})}}}],Xe=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:$.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,p,n){var r=0,q,v,y,t=function(){v&&(v.remove(),v=null);q&&(q.$destroy(),q=null);y&&(d.leave(y).done(function(a){!1!==a&&(v=null)}),v=y,y=null)};c.$watch(f,function(f){var m=function(a){!1=== +a||!u(h)||h&&!c.$eval(h)||b()},v=++r;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&v===r){var b=c.$new();p.template=a;a=n(b,function(a){t();d.enter(a,null,e).done(m)});q=b;y=a;q.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed||v!==r||(t(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(t(),p.template=null)})}}}}],of=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){ia.call(d[0]).match(/SVG/)? +(d.empty(),a(fd(e.template,w.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ye=Qa({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),kf=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=d.ngList||", ",f="false"!==d.ngTrim,g=f?Q(e):e;c.$parsers.push(function(a){if(!x(a)){var b=[];a&&r(a.split(g),function(a){a&&b.push(f?Q(a):a)});return b}});c.$formatters.push(function(a){if(I(a))return a.join(e)}); +c.$isEmpty=function(a){return!a||!a.length}}}},nb="ng-valid",$d="ng-invalid",Ya="ng-pristine",Vb="ng-dirty",pb=K("ngModel");Sb.$inject="$scope $exceptionHandler $attrs $element $parse $animate $timeout $q $interpolate".split(" ");Sb.prototype={$$initGetterSetters:function(){if(this.$options.getOption("getterSetter")){var a=this.$$parse(this.$$attr.ngModel+"()"),b=this.$$parse(this.$$attr.ngModel+"($$$p)");this.$$ngModelGet=function(b){var c=this.$$parsedNgModel(b);C(c)&&(c=a(b));return c};this.$$ngModelSet= +function(a,c){C(this.$$parsedNgModel(a))?b(a,{$$$p:c}):this.$$parsedNgModelAssign(a,c)}}else if(!this.$$parsedNgModel.assign)throw pb("nonassign",this.$$attr.ngModel,za(this.$$element));},$render:D,$isEmpty:function(a){return x(a)||""===a||null===a||a!==a},$$updateEmptyClasses:function(a){this.$isEmpty(a)?(this.$$animate.removeClass(this.$$element,"ng-not-empty"),this.$$animate.addClass(this.$$element,"ng-empty")):(this.$$animate.removeClass(this.$$element,"ng-empty"),this.$$animate.addClass(this.$$element, +"ng-not-empty"))},$setPristine:function(){this.$dirty=!1;this.$pristine=!0;this.$$animate.removeClass(this.$$element,Vb);this.$$animate.addClass(this.$$element,Ya)},$setDirty:function(){this.$dirty=!0;this.$pristine=!1;this.$$animate.removeClass(this.$$element,Ya);this.$$animate.addClass(this.$$element,Vb);this.$$parentForm.$setDirty()},$setUntouched:function(){this.$touched=!1;this.$untouched=!0;this.$$animate.setClass(this.$$element,"ng-untouched","ng-touched")},$setTouched:function(){this.$touched= +!0;this.$untouched=!1;this.$$animate.setClass(this.$$element,"ng-touched","ng-untouched")},$rollbackViewValue:function(){this.$$timeout.cancel(this.$$pendingDebounce);this.$viewValue=this.$$lastCommittedViewValue;this.$render()},$validate:function(){if(!U(this.$modelValue)){var a=this.$$lastCommittedViewValue,b=this.$$rawModelValue,d=this.$valid,c=this.$modelValue,e=this.$options.getOption("allowInvalid"),f=this;this.$$runValidators(b,a,function(a){e||d===a||(f.$modelValue=a?b:void 0,f.$modelValue!== +c&&f.$$writeModelToScope())})}},$$runValidators:function(a,b,d){function c(){var c=!0;r(k.$validators,function(d,e){var g=Boolean(d(a,b));c=c&&g;f(e,g)});return c?!0:(r(k.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;r(k.$asyncValidators,function(e,g){var k=e(a,b);if(!k||!C(k.then))throw pb("nopromise",k);f(g,void 0);c.push(k.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.$$q.all(c).then(function(){g(d)},D):g(!0)}function f(a,b){h===k.$$currentValidationRunId&& +k.$setValidity(a,b)}function g(a){h===k.$$currentValidationRunId&&d(a)}this.$$currentValidationRunId++;var h=this.$$currentValidationRunId,k=this;(function(){var a=k.$$parserName||"parse";if(x(k.$$parserValid))f(a,null);else return k.$$parserValid||(r(k.$validators,function(a,b){f(b,null)}),r(k.$asyncValidators,function(a,b){f(b,null)})),f(a,k.$$parserValid),k.$$parserValid;return!0})()?c()?e():g(!1):g(!1)},$commitViewValue:function(){var a=this.$viewValue;this.$$timeout.cancel(this.$$pendingDebounce); +if(this.$$lastCommittedViewValue!==a||""===a&&this.$$hasNativeValidators)this.$$updateEmptyClasses(a),this.$$lastCommittedViewValue=a,this.$pristine&&this.$setDirty(),this.$$parseAndValidate()},$$parseAndValidate:function(){var a=this.$$lastCommittedViewValue,b=this;if(this.$$parserValid=x(a)?void 0:!0)for(var d=0;de||c.$isEmpty(b)||b.length<=e}}}}},bd=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Z(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};w.angular.bootstrap?w.console&&console.log("WARNING: Tried to load angular more than once."): (Be(),Ee($),$.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2, -minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),B(function(){we(u.document,Uc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); +minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),z(function(){we(w.document,Uc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); //# sourceMappingURL=angular.min.js.map diff --git a/static/plugs/bootstrap-multiselect/bootstrap-multiselect.css b/static/plugs/bootstrap-multiselect/bootstrap-multiselect.css new file mode 100644 index 000000000..13de57bb1 --- /dev/null +++ b/static/plugs/bootstrap-multiselect/bootstrap-multiselect.css @@ -0,0 +1 @@ +.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li.multiselect-group label{margin:0;padding:3px 20px 3px 20px;height:100%;font-weight:700}.multiselect-container>li.multiselect-group-clickable label{cursor:pointer}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.radio,.multiselect-container>li>a>label.checkbox{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.form-inline .multiselect-container label.checkbox,.form-inline .multiselect-container label.radio{padding:3px 20px 3px 40px}.form-inline .multiselect-container li a label.checkbox input[type=checkbox],.form-inline .multiselect-container li a label.radio input[type=radio]{margin-left:-20px;margin-right:0} \ No newline at end of file diff --git a/static/plugs/bootstrap-multiselect/bootstrap-multiselect.js b/static/plugs/bootstrap-multiselect/bootstrap-multiselect.js new file mode 100644 index 000000000..f8a097896 --- /dev/null +++ b/static/plugs/bootstrap-multiselect/bootstrap-multiselect.js @@ -0,0 +1,1683 @@ +/** + * Bootstrap Multiselect (https://github.com/davidstutz/bootstrap-multiselect) + * + * Apache License, Version 2.0: + * Copyright (c) 2012 - 2015 David Stutz + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * BSD 3-Clause License: + * Copyright (c) 2012 - 2015 David Stutz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of David Stutz nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +!function ($) { + "use strict";// jshint ;_; + + if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { + ko.bindingHandlers.multiselect = { + after: ['options', 'value', 'selectedOptions', 'enable', 'disable'], + + init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { + var $element = $(element); + var config = ko.toJS(valueAccessor()); + + $element.multiselect(config); + + if (allBindings.has('options')) { + var options = allBindings.get('options'); + if (ko.isObservable(options)) { + ko.computed({ + read: function() { + options(); + setTimeout(function() { + var ms = $element.data('multiselect'); + if (ms) + ms.updateOriginalOptions();//Not sure how beneficial this is. + $element.multiselect('rebuild'); + }, 1); + }, + disposeWhenNodeIsRemoved: element + }); + } + } + + //value and selectedOptions are two-way, so these will be triggered even by our own actions. + //It needs some way to tell if they are triggered because of us or because of outside change. + //It doesn't loop but it's a waste of processing. + if (allBindings.has('value')) { + var value = allBindings.get('value'); + if (ko.isObservable(value)) { + ko.computed({ + read: function() { + value(); + setTimeout(function() { + $element.multiselect('refresh'); + }, 1); + }, + disposeWhenNodeIsRemoved: element + }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); + } + } + + //Switched from arrayChange subscription to general subscription using 'refresh'. + //Not sure performance is any better using 'select' and 'deselect'. + if (allBindings.has('selectedOptions')) { + var selectedOptions = allBindings.get('selectedOptions'); + if (ko.isObservable(selectedOptions)) { + ko.computed({ + read: function() { + selectedOptions(); + setTimeout(function() { + $element.multiselect('refresh'); + }, 1); + }, + disposeWhenNodeIsRemoved: element + }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); + } + } + + var setEnabled = function (enable) { + setTimeout(function () { + if (enable) + $element.multiselect('enable'); + else + $element.multiselect('disable'); + }); + }; + + if (allBindings.has('enable')) { + var enable = allBindings.get('enable'); + if (ko.isObservable(enable)) { + ko.computed({ + read: function () { + setEnabled(enable()); + }, + disposeWhenNodeIsRemoved: element + }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); + } else { + setEnabled(enable); + } + } + + if (allBindings.has('disable')) { + var disable = allBindings.get('disable'); + if (ko.isObservable(disable)) { + ko.computed({ + read: function () { + setEnabled(!disable()); + }, + disposeWhenNodeIsRemoved: element + }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); + } else { + setEnabled(!disable); + } + } + + ko.utils.domNodeDisposal.addDisposeCallback(element, function() { + $element.multiselect('destroy'); + }); + }, + + update: function(element, valueAccessor, allBindings, viewModel, bindingContext) { + var $element = $(element); + var config = ko.toJS(valueAccessor()); + + $element.multiselect('setOptions', config); + $element.multiselect('rebuild'); + } + }; + } + + function forEach(array, callback) { + for (var index = 0; index < array.length; ++index) { + callback(array[index], index); + } + } + + /** + * Constructor to create a new multiselect using the given select. + * + * @param {jQuery} select + * @param {Object} options + * @returns {Multiselect} + */ + function Multiselect(select, options) { + + this.$select = $(select); + + // Placeholder via data attributes + if (this.$select.attr("data-placeholder")) { + options.nonSelectedText = this.$select.data("placeholder"); + } + + this.options = this.mergeOptions($.extend({}, options, this.$select.data())); + + // Initialization. + // We have to clone to create a new reference. + this.originalOptions = this.$select.clone()[0].options; + this.query = ''; + this.searchTimeout = null; + this.lastToggledInput = null; + + this.options.multiple = this.$select.attr('multiple') === "multiple"; + this.options.onChange = $.proxy(this.options.onChange, this); + this.options.onSelectAll = $.proxy(this.options.onSelectAll, this); + this.options.onDeselectAll = $.proxy(this.options.onDeselectAll, this); + this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); + this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); + this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this); + this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this); + this.options.onInitialized = $.proxy(this.options.onInitialized, this); + + // Build select all if enabled. + this.buildContainer(); + this.buildButton(); + this.buildDropdown(); + this.buildSelectAll(); + this.buildDropdownOptions(); + this.buildFilter(); + + this.updateButtonText(); + this.updateSelectAll(true); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + + if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { + this.disable(); + } + + this.$select.hide().after(this.$container); + this.options.onInitialized(this.$select, this.$container); + } + + Multiselect.prototype = { + + defaults: { + /** + * Default text function will either print 'None selected' in case no + * option is selected or a list of the selected options up to a length + * of 3 selected options. + * + * @param {jQuery} options + * @param {jQuery} select + * @returns {String} + */ + buttonText: function(options, select) { + if (this.disabledText.length > 0 + && (select.prop('disabled') || (options.length == 0 && this.disableIfEmpty))) { + + return this.disabledText; + } + else if (options.length === 0) { + return this.nonSelectedText; + } + else if (this.allSelectedText + && options.length === $('option', $(select)).length + && $('option', $(select)).length !== 1 + && this.multiple) { + + if (this.selectAllNumber) { + return this.allSelectedText + ' (' + options.length + ')'; + } + else { + return this.allSelectedText; + } + } + else if (options.length > this.numberDisplayed) { + return options.length + ' ' + this.nSelectedText; + } + else { + var selected = ''; + var delimiter = this.delimiterText; + + options.each(function() { + var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); + selected += label + delimiter; + }); + + return selected.substr(0, selected.length - this.delimiterText.length); + } + }, + /** + * Updates the title of the button similar to the buttonText function. + * + * @param {jQuery} options + * @param {jQuery} select + * @returns {@exp;selected@call;substr} + */ + buttonTitle: function(options, select) { + if (options.length === 0) { + return this.nonSelectedText; + } + else { + var selected = ''; + var delimiter = this.delimiterText; + + options.each(function () { + var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); + selected += label + delimiter; + }); + return selected.substr(0, selected.length - this.delimiterText.length); + } + }, + /** + * Create a label. + * + * @param {jQuery} element + * @returns {String} + */ + optionLabel: function(element){ + return $(element).attr('label') || $(element).text(); + }, + /** + * Create a class. + * + * @param {jQuery} element + * @returns {String} + */ + optionClass: function(element) { + return $(element).attr('class') || ''; + }, + /** + * Triggered on change of the multiselect. + * + * Not triggered when selecting/deselecting options manually. + * + * @param {jQuery} option + * @param {Boolean} checked + */ + onChange : function(option, checked) { + + }, + /** + * Triggered when the dropdown is shown. + * + * @param {jQuery} event + */ + onDropdownShow: function(event) { + + }, + /** + * Triggered when the dropdown is hidden. + * + * @param {jQuery} event + */ + onDropdownHide: function(event) { + + }, + /** + * Triggered after the dropdown is shown. + * + * @param {jQuery} event + */ + onDropdownShown: function(event) { + + }, + /** + * Triggered after the dropdown is hidden. + * + * @param {jQuery} event + */ + onDropdownHidden: function(event) { + + }, + /** + * Triggered on select all. + */ + onSelectAll: function() { + + }, + /** + * Triggered on deselect all. + */ + onDeselectAll: function() { + + }, + /** + * Triggered after initializing. + * + * @param {jQuery} $select + * @param {jQuery} $container + */ + onInitialized: function($select, $container) { + + }, + enableHTML: false, + buttonClass: 'btn btn-default', + inheritClass: false, + buttonWidth: 'auto', + buttonContainer: '
    ', + dropRight: false, + dropUp: false, + selectedClass: 'active', + // Maximum height of the dropdown menu. + // If maximum height is exceeded a scrollbar will be displayed. + maxHeight: false, + checkboxName: false, + includeSelectAllOption: false, + includeSelectAllIfMoreThan: 0, + selectAllText: ' Select all', + selectAllValue: 'multiselect-all', + selectAllName: false, + selectAllNumber: true, + selectAllJustVisible: true, + enableFiltering: false, + enableCaseInsensitiveFiltering: false, + enableFullValueFiltering: false, + enableClickableOptGroups: false, + enableCollapsibleOptGroups: false, + filterPlaceholder: 'Search', + // possible options: 'text', 'value', 'both' + filterBehavior: 'text', + includeFilterClearBtn: true, + preventInputChangeEvent: false, + nonSelectedText: 'None selected', + nSelectedText: 'selected', + allSelectedText: 'All selected', + numberDisplayed: 3, + disableIfEmpty: false, + disabledText: '', + delimiterText: ', ', + templates: { + button: '', + ul: '', + filter: '
  • ', + filterClearBtn: '', + li: '
  • ', + divider: '
  • ', + liGroup: '
  • ' + } + }, + + constructor: Multiselect, + + /** + * Builds the container of the multiselect. + */ + buildContainer: function() { + this.$container = $(this.options.buttonContainer); + this.$container.on('show.bs.dropdown', this.options.onDropdownShow); + this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); + this.$container.on('shown.bs.dropdown', this.options.onDropdownShown); + this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden); + }, + + /** + * Builds the button of the multiselect. + */ + buildButton: function() { + this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); + if (this.$select.attr('class') && this.options.inheritClass) { + this.$button.addClass(this.$select.attr('class')); + } + // Adopt active state. + if (this.$select.prop('disabled')) { + this.disable(); + } + else { + this.enable(); + } + + // Manually add button width if set. + if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { + this.$button.css({ + 'width' : '100%', //this.options.buttonWidth, + 'overflow' : 'hidden', + 'text-overflow' : 'ellipsis' + }); + this.$container.css({ + 'width': this.options.buttonWidth + }); + } + + // Keep the tab index from the select. + var tabindex = this.$select.attr('tabindex'); + if (tabindex) { + this.$button.attr('tabindex', tabindex); + } + + this.$container.prepend(this.$button); + }, + + /** + * Builds the ul representing the dropdown menu. + */ + buildDropdown: function() { + + // Build ul. + this.$ul = $(this.options.templates.ul); + + if (this.options.dropRight) { + this.$ul.addClass('pull-right'); + } + + // Set max height of dropdown menu to activate auto scrollbar. + if (this.options.maxHeight) { + // TODO: Add a class for this option to move the css declarations. + this.$ul.css({ + 'max-height': this.options.maxHeight + 'px', + 'overflow-y': 'auto', + 'overflow-x': 'hidden' + }); + } + + if (this.options.dropUp) { + + var height = Math.min(this.options.maxHeight, $('option[data-role!="divider"]', this.$select).length*26 + $('option[data-role="divider"]', this.$select).length*19 + (this.options.includeSelectAllOption ? 26 : 0) + (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering ? 44 : 0)); + var moveCalc = height + 34; + + this.$ul.css({ + 'max-height': height + 'px', + 'overflow-y': 'auto', + 'overflow-x': 'hidden', + 'margin-top': "-" + moveCalc + 'px' + }); + } + + this.$container.append(this.$ul); + }, + + /** + * Build the dropdown options and binds all necessary events. + * + * Uses createDivider and createOptionValue to create the necessary options. + */ + buildDropdownOptions: function() { + + this.$select.children().each($.proxy(function(index, element) { + + var $element = $(element); + // Support optgroups and options without a group simultaneously. + var tag = $element.prop('tagName') + .toLowerCase(); + + if ($element.prop('value') === this.options.selectAllValue) { + return; + } + + if (tag === 'optgroup') { + this.createOptgroup(element); + } + else if (tag === 'option') { + + if ($element.data('role') === 'divider') { + this.createDivider(); + } + else { + this.createOptionValue(element); + } + + } + + // Other illegal tags will be ignored. + }, this)); + + // Bind the change event on the dropdown elements. + $('li:not(.multiselect-group) input', this.$ul).on('change', $.proxy(function(event) { + var $target = $(event.target); + + var checked = $target.prop('checked') || false; + var isSelectAllOption = $target.val() === this.options.selectAllValue; + + // Apply or unapply the configured selected class. + if (this.options.selectedClass) { + if (checked) { + $target.closest('li') + .addClass(this.options.selectedClass); + } + else { + $target.closest('li') + .removeClass(this.options.selectedClass); + } + } + + // Get the corresponding option. + var value = $target.val(); + var $option = this.getOptionByValue(value); + + var $optionsNotThis = $('option', this.$select).not($option); + var $checkboxesNotThis = $('input', this.$container).not($target); + + if (isSelectAllOption) { + + if (checked) { + this.selectAll(this.options.selectAllJustVisible); + } + else { + this.deselectAll(this.options.selectAllJustVisible); + } + } + else { + if (checked) { + $option.prop('selected', true); + + if (this.options.multiple) { + // Simply select additional option. + $option.prop('selected', true); + } + else { + // Unselect all other options and corresponding checkboxes. + if (this.options.selectedClass) { + $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass); + } + + $($checkboxesNotThis).prop('checked', false); + $optionsNotThis.prop('selected', false); + + // It's a single selection, so close. + this.$button.click(); + } + + if (this.options.selectedClass === "active") { + $optionsNotThis.closest("a").css("outline", ""); + } + } + else { + // Unselect option. + $option.prop('selected', false); + } + + // To prevent select all from firing onChange: #575 + this.options.onChange($option, checked); + + // Do not update select all or optgroups on select all change! + this.updateSelectAll(); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + } + + this.$select.change(); + this.updateButtonText(); + + if(this.options.preventInputChangeEvent) { + return false; + } + }, this)); + + $('li a', this.$ul).on('mousedown', function(e) { + if (e.shiftKey) { + // Prevent selecting text by Shift+click + return false; + } + }); + + $('li a', this.$ul).on('touchstart click', $.proxy(function(event) { + event.stopPropagation(); + + var $target = $(event.target); + + if (event.shiftKey && this.options.multiple) { + if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431) + event.preventDefault(); + $target = $target.find("input"); + $target.prop("checked", !$target.prop("checked")); + } + var checked = $target.prop('checked') || false; + + if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range + var from = $target.closest("li").index(); + var to = this.lastToggledInput.closest("li").index(); + + if (from > to) { // Swap the indices + var tmp = to; + to = from; + from = tmp; + } + + // Make sure we grab all elements since slice excludes the last index + ++to; + + // Change the checkboxes and underlying options + var range = this.$ul.find("li").slice(from, to).find("input"); + + range.prop('checked', checked); + + if (this.options.selectedClass) { + range.closest('li') + .toggleClass(this.options.selectedClass, checked); + } + + for (var i = 0, j = range.length; i < j; i++) { + var $checkbox = $(range[i]); + + var $option = this.getOptionByValue($checkbox.val()); + + $option.prop('selected', checked); + } + } + + // Trigger the select "change" event + $target.trigger("change"); + } + + // Remembers last clicked option + if($target.is("input") && !$target.closest("li").is(".multiselect-item")){ + this.lastToggledInput = $target; + } + + $target.blur(); + }, this)); + + // Keyboard support. + this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) { + if ($('input[type="text"]', this.$container).is(':focus')) { + return; + } + + if (event.keyCode === 9 && this.$container.hasClass('open')) { + this.$button.click(); + } + else { + var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible"); + + if (!$items.length) { + return; + } + + var index = $items.index($items.filter(':focus')); + + // Navigation up. + if (event.keyCode === 38 && index > 0) { + index--; + } + // Navigate down. + else if (event.keyCode === 40 && index < $items.length - 1) { + index++; + } + else if (!~index) { + index = 0; + } + + var $current = $items.eq(index); + $current.focus(); + + if (event.keyCode === 32 || event.keyCode === 13) { + var $checkbox = $current.find('input'); + + $checkbox.prop("checked", !$checkbox.prop("checked")); + $checkbox.change(); + } + + event.stopPropagation(); + event.preventDefault(); + } + }, this)); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + $("li.multiselect-group input", this.$ul).on("change", $.proxy(function(event) { + event.stopPropagation(); + + var $target = $(event.target); + var checked = $target.prop('checked') || false; + + var $li = $(event.target).closest('li'); + var $group = $li.nextUntil("li.multiselect-group") + .not('.multiselect-filter-hidden') + .not('.disabled'); + + var $inputs = $group.find("input"); + + var values = []; + var $options = []; + + $.each($inputs, $.proxy(function(index, input) { + var value = $(input).val(); + var $option = this.getOptionByValue(value); + + if (checked) { + $(input).prop('checked', true); + $(input).closest('li') + .addClass(this.options.selectedClass); + + $option.prop('selected', true); + } + else { + $(input).prop('checked', false); + $(input).closest('li') + .removeClass(this.options.selectedClass); + + $option.prop('selected', false); + } + + $options.push(this.getOptionByValue(value)); + }, this)) + + // Cannot use select or deselect here because it would call updateOptGroups again. + + this.options.onChange($options, checked); + + this.updateButtonText(); + this.updateSelectAll(); + }, this)); + } + + if (this.options.enableCollapsibleOptGroups && this.options.multiple) { + $("li.multiselect-group .caret-container", this.$ul).on("click", $.proxy(function(event) { + var $li = $(event.target).closest('li'); + var $inputs = $li.nextUntil("li.multiselect-group") + .not('.multiselect-filter-hidden'); + + var visible = true; + $inputs.each(function() { + visible = visible && $(this).is(':visible'); + }); + + if (visible) { + $inputs.hide() + .addClass('multiselect-collapsible-hidden'); + } + else { + $inputs.show() + .removeClass('multiselect-collapsible-hidden'); + } + }, this)); + + $("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea'); + $("li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px'); + $("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px'); + } + }, + + /** + * Create an option using the given select option. + * + * @param {jQuery} element + */ + createOptionValue: function(element) { + var $element = $(element); + if ($element.is(':selected')) { + $element.prop('selected', true); + } + + // Support the label attribute on options. + var label = this.options.optionLabel(element); + var classes = this.options.optionClass(element); + var value = $element.val(); + var inputType = this.options.multiple ? "checkbox" : "radio"; + + var $li = $(this.options.templates.li); + var $label = $('label', $li); + $label.addClass(inputType); + $li.addClass(classes); + + if (this.options.enableHTML) { + $label.html(" " + label); + } + else { + $label.text(" " + label); + } + + var $checkbox = $('').attr('type', inputType); + + if (this.options.checkboxName) { + $checkbox.attr('name', this.options.checkboxName); + } + $label.prepend($checkbox); + + var selected = $element.prop('selected') || false; + $checkbox.val(value); + + if (value === this.options.selectAllValue) { + $li.addClass("multiselect-item multiselect-all"); + $checkbox.parent().parent() + .addClass('multiselect-all'); + } + + $label.attr('title', $element.attr('title')); + + this.$ul.append($li); + + if ($element.is(':disabled')) { + $checkbox.attr('disabled', 'disabled') + .prop('disabled', true) + .closest('a') + .attr("tabindex", "-1") + .closest('li') + .addClass('disabled'); + } + + $checkbox.prop('checked', selected); + + if (selected && this.options.selectedClass) { + $checkbox.closest('li') + .addClass(this.options.selectedClass); + } + }, + + /** + * Creates a divider using the given select option. + * + * @param {jQuery} element + */ + createDivider: function(element) { + var $divider = $(this.options.templates.divider); + this.$ul.append($divider); + }, + + /** + * Creates an optgroup. + * + * @param {jQuery} group + */ + createOptgroup: function(group) { + var label = $(group).attr("label"); + var value = $(group).attr("value"); + var $li = $('
  • '); + + var classes = this.options.optionClass(group); + $li.addClass(classes); + + if (this.options.enableHTML) { + $('label b', $li).html(" " + label); + } + else { + $('label b', $li).text(" " + label); + } + + if (this.options.enableCollapsibleOptGroups && this.options.multiple) { + $('a', $li).append(''); + } + + if (this.options.enableClickableOptGroups && this.options.multiple) { + $('a label', $li).prepend(''); + } + + if ($(group).is(':disabled')) { + $li.addClass('disabled'); + } + + this.$ul.append($li); + + $("option", group).each($.proxy(function($, group) { + this.createOptionValue(group); + }, this)) + }, + + /** + * Build the select all. + * + * Checks if a select all has already been created. + */ + buildSelectAll: function() { + if (typeof this.options.selectAllValue === 'number') { + this.options.selectAllValue = this.options.selectAllValue.toString(); + } + + var alreadyHasSelectAll = this.hasSelectAll(); + + if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple + && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) { + + // Check whether to add a divider after the select all. + if (this.options.includeSelectAllDivider) { + this.$ul.prepend($(this.options.templates.divider)); + } + + var $li = $(this.options.templates.li); + $('label', $li).addClass("checkbox"); + + if (this.options.enableHTML) { + $('label', $li).html(" " + this.options.selectAllText); + } + else { + $('label', $li).text(" " + this.options.selectAllText); + } + + if (this.options.selectAllName) { + $('label', $li).prepend(''); + } + else { + $('label', $li).prepend(''); + } + + var $checkbox = $('input', $li); + $checkbox.val(this.options.selectAllValue); + + $li.addClass("multiselect-item multiselect-all"); + $checkbox.parent().parent() + .addClass('multiselect-all'); + + this.$ul.prepend($li); + + $checkbox.prop('checked', false); + } + }, + + /** + * Builds the filter. + */ + buildFilter: function() { + + // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. + if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { + var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); + + if (this.$select.find('option').length >= enableFilterLength) { + + this.$filter = $(this.options.templates.filter); + $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); + + // Adds optional filter clear button + if(this.options.includeFilterClearBtn) { + var clearBtn = $(this.options.templates.filterClearBtn); + clearBtn.on('click', $.proxy(function(event){ + clearTimeout(this.searchTimeout); + + this.$filter.find('.multiselect-search').val(''); + $('li', this.$ul).show().removeClass('multiselect-filter-hidden'); + + this.updateSelectAll(); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + + }, this)); + this.$filter.find('.input-group').append(clearBtn); + } + + this.$ul.prepend(this.$filter); + + this.$filter.val(this.query).on('click', function(event) { + event.stopPropagation(); + }).on('input keydown', $.proxy(function(event) { + // Cancel enter key default behaviour + if (event.which === 13) { + event.preventDefault(); + } + + // This is useful to catch "keydown" events after the browser has updated the control. + clearTimeout(this.searchTimeout); + + this.searchTimeout = this.asyncFunction($.proxy(function() { + + if (this.query !== event.target.value) { + this.query = event.target.value; + + var currentGroup, currentGroupVisible; + $.each($('li', this.$ul), $.proxy(function(index, element) { + var value = $('input', element).length > 0 ? $('input', element).val() : ""; + var text = $('label', element).text(); + + var filterCandidate = ''; + if ((this.options.filterBehavior === 'text')) { + filterCandidate = text; + } + else if ((this.options.filterBehavior === 'value')) { + filterCandidate = value; + } + else if (this.options.filterBehavior === 'both') { + filterCandidate = text + '\n' + value; + } + + if (value !== this.options.selectAllValue && text) { + + // By default lets assume that element is not + // interesting for this search. + var showElement = false; + + if (this.options.enableCaseInsensitiveFiltering) { + filterCandidate = filterCandidate.toLowerCase(); + this.query = this.query.toLowerCase(); + } + + if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') { + var valueToMatch = filterCandidate.trim().substring(0, this.query.length); + if (this.query.indexOf(valueToMatch) > -1) { + showElement = true; + } + } + else if (filterCandidate.indexOf(this.query) > -1) { + showElement = true; + } + + // Toggle current element (group or group item) according to showElement boolean. + $(element).toggle(showElement) + .toggleClass('multiselect-filter-hidden', !showElement); + + // Differentiate groups and group items. + if ($(element).hasClass('multiselect-group')) { + // Remember group status. + currentGroup = element; + currentGroupVisible = showElement; + } + else { + // Show group name when at least one of its items is visible. + if (showElement) { + $(currentGroup).show() + .removeClass('multiselect-filter-hidden'); + } + + // Show all group items when group name satisfies filter. + if (!showElement && currentGroupVisible) { + $(element).show() + .removeClass('multiselect-filter-hidden'); + } + } + } + }, this)); + } + + this.updateSelectAll(); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + }, this), 300, this); + }, this)); + } + } + }, + + /** + * Unbinds the whole plugin. + */ + destroy: function() { + this.$container.remove(); + this.$select.show(); + this.$select.data('multiselect', null); + }, + + /** + * Refreshs the multiselect based on the selected options of the select. + */ + refresh: function () { + var inputs = $.map($('li input', this.$ul), $); + + $('option', this.$select).each($.proxy(function (index, element) { + var $elem = $(element); + var value = $elem.val(); + var $input; + for (var i = inputs.length; 0 < i--; /**/) { + if (value !== ($input = inputs[i]).val()) + continue; // wrong li + + if ($elem.is(':selected')) { + $input.prop('checked', true); + + if (this.options.selectedClass) { + $input.closest('li') + .addClass(this.options.selectedClass); + } + } + else { + $input.prop('checked', false); + + if (this.options.selectedClass) { + $input.closest('li') + .removeClass(this.options.selectedClass); + } + } + + if ($elem.is(":disabled")) { + $input.attr('disabled', 'disabled') + .prop('disabled', true) + .closest('li') + .addClass('disabled'); + } + else { + $input.prop('disabled', false) + .closest('li') + .removeClass('disabled'); + } + break; // assumes unique values + } + }, this)); + + this.updateButtonText(); + this.updateSelectAll(); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + }, + + /** + * Select all options of the given values. + * + * If triggerOnChange is set to true, the on change event is triggered if + * and only if one value is passed. + * + * @param {Array} selectValues + * @param {Boolean} triggerOnChange + */ + select: function(selectValues, triggerOnChange) { + if(!$.isArray(selectValues)) { + selectValues = [selectValues]; + } + + for (var i = 0; i < selectValues.length; i++) { + var value = selectValues[i]; + + if (value === null || value === undefined) { + continue; + } + + var $option = this.getOptionByValue(value); + var $checkbox = this.getInputByValue(value); + + if($option === undefined || $checkbox === undefined) { + continue; + } + + if (!this.options.multiple) { + this.deselectAll(false); + } + + if (this.options.selectedClass) { + $checkbox.closest('li') + .addClass(this.options.selectedClass); + } + + $checkbox.prop('checked', true); + $option.prop('selected', true); + + if (triggerOnChange) { + this.options.onChange($option, true); + } + } + + this.updateButtonText(); + this.updateSelectAll(); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + }, + + /** + * Clears all selected items. + */ + clearSelection: function () { + this.deselectAll(false); + this.updateButtonText(); + this.updateSelectAll(); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + }, + + /** + * Deselects all options of the given values. + * + * If triggerOnChange is set to true, the on change event is triggered, if + * and only if one value is passed. + * + * @param {Array} deselectValues + * @param {Boolean} triggerOnChange + */ + deselect: function(deselectValues, triggerOnChange) { + if(!$.isArray(deselectValues)) { + deselectValues = [deselectValues]; + } + + for (var i = 0; i < deselectValues.length; i++) { + var value = deselectValues[i]; + + if (value === null || value === undefined) { + continue; + } + + var $option = this.getOptionByValue(value); + var $checkbox = this.getInputByValue(value); + + if($option === undefined || $checkbox === undefined) { + continue; + } + + if (this.options.selectedClass) { + $checkbox.closest('li') + .removeClass(this.options.selectedClass); + } + + $checkbox.prop('checked', false); + $option.prop('selected', false); + + if (triggerOnChange) { + this.options.onChange($option, false); + } + } + + this.updateButtonText(); + this.updateSelectAll(); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + }, + + /** + * Selects all enabled & visible options. + * + * If justVisible is true or not specified, only visible options are selected. + * + * @param {Boolean} justVisible + * @param {Boolean} triggerOnSelectAll + */ + selectAll: function (justVisible, triggerOnSelectAll) { + + var justVisible = typeof justVisible === 'undefined' ? true : justVisible; + var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul); + var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible'); + + if(justVisible) { + $('input:enabled' , visibleLis).prop('checked', true); + visibleLis.addClass(this.options.selectedClass); + + $('input:enabled' , visibleLis).each($.proxy(function(index, element) { + var value = $(element).val(); + var option = this.getOptionByValue(value); + $(option).prop('selected', true); + }, this)); + } + else { + $('input:enabled' , allLis).prop('checked', true); + allLis.addClass(this.options.selectedClass); + + $('input:enabled' , allLis).each($.proxy(function(index, element) { + var value = $(element).val(); + var option = this.getOptionByValue(value); + $(option).prop('selected', true); + }, this)); + } + + $('li input[value="' + this.options.selectAllValue + '"]').prop('checked', true); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + + if (triggerOnSelectAll) { + this.options.onSelectAll(); + } + }, + + /** + * Deselects all options. + * + * If justVisible is true or not specified, only visible options are deselected. + * + * @param {Boolean} justVisible + */ + deselectAll: function (justVisible, triggerOnDeselectAll) { + + var justVisible = typeof justVisible === 'undefined' ? true : justVisible; + var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul); + var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible'); + + if(justVisible) { + $('input[type="checkbox"]:enabled' , visibleLis).prop('checked', false); + visibleLis.removeClass(this.options.selectedClass); + + $('input[type="checkbox"]:enabled' , visibleLis).each($.proxy(function(index, element) { + var value = $(element).val(); + var option = this.getOptionByValue(value); + $(option).prop('selected', false); + }, this)); + } + else { + $('input[type="checkbox"]:enabled' , allLis).prop('checked', false); + allLis.removeClass(this.options.selectedClass); + + $('input[type="checkbox"]:enabled' , allLis).each($.proxy(function(index, element) { + var value = $(element).val(); + var option = this.getOptionByValue(value); + $(option).prop('selected', false); + }, this)); + } + + $('li input[value="' + this.options.selectAllValue + '"]').prop('checked', false); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + + if (triggerOnDeselectAll) { + this.options.onDeselectAll(); + } + }, + + /** + * Rebuild the plugin. + * + * Rebuilds the dropdown, the filter and the select all option. + */ + rebuild: function() { + this.$ul.html(''); + + // Important to distinguish between radios and checkboxes. + this.options.multiple = this.$select.attr('multiple') === "multiple"; + + this.buildSelectAll(); + this.buildDropdownOptions(); + this.buildFilter(); + + this.updateButtonText(); + this.updateSelectAll(true); + + if (this.options.enableClickableOptGroups && this.options.multiple) { + this.updateOptGroups(); + } + + if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { + this.disable(); + } + else { + this.enable(); + } + + if (this.options.dropRight) { + this.$ul.addClass('pull-right'); + } + }, + + /** + * The provided data will be used to build the dropdown. + */ + dataprovider: function(dataprovider) { + + var groupCounter = 0; + var $select = this.$select.empty(); + + $.each(dataprovider, function (index, option) { + var $tag; + + if ($.isArray(option.children)) { // create optiongroup tag + groupCounter++; + + $tag = $('').attr({ + label: option.label || 'Group ' + groupCounter, + disabled: !!option.disabled + }); + + forEach(option.children, function(subOption) { // add children option tags + var attributes = { + value: subOption.value, + label: subOption.label || subOption.value, + title: subOption.title, + selected: !!subOption.selected, + disabled: !!subOption.disabled + }; + + //Loop through attributes object and add key-value for each attribute + for (var key in subOption.attributes) { + attributes['data-' + key] = subOption.attributes[key]; + } + //Append original attributes + new data attributes to option + $tag.append($('' + ); + }); + + return list.join(''); + }, + + reset: function (deep) { + if (!deep) { + this.output(PROVINCE); + this.output(CIRY); + this.output(DISTRICT); + } else if (this.$province) { + this.$province.find(':first').prop('selected', true).trigger(EVENT_CHANGE); + } + }, + + destroy: function () { + this.unbind(); + this.$element.removeData(NAMESPACE); + } + }; + + Distpicker.DEFAULTS = { + autoSelect: true, + placeholder: false, + province: '请选择省份', + city: '请选择城市', + district: '请选择地区' + }; + + Distpicker.setDefaults = function (options) { + $.extend(Distpicker.DEFAULTS, options); + }; + + // Save the other distpicker + Distpicker.other = $.fn.distpicker; + + // Register as jQuery plugin + $.fn.distpicker = function (option) { + var args = [].slice.call(arguments, 1); + + return this.each(function () { + var $this = $(this); + var data = $this.data(NAMESPACE); + var options; + var fn; + + if (!data) { + if (/destroy/.test(option)) { + return; + } + + options = $.extend({}, $this.data(), $.isPlainObject(option) && option); + $this.data(NAMESPACE, (data = new Distpicker(this, options))); + } + + if (typeof option === 'string' && $.isFunction(fn = data[option])) { + fn.apply(data, args); + } + }); + }; + + $.fn.distpicker.Constructor = Distpicker; + $.fn.distpicker.setDefaults = Distpicker.setDefaults; + + // No conflict + $.fn.distpicker.noConflict = function () { + $.fn.distpicker = Distpicker.other; + return this; + }; + + $(function () { + $('[data-toggle="distpicker"]').distpicker(); + }); +}); diff --git a/static/plugs/jquery/masonry.min.js b/static/plugs/jquery/masonry.min.js index a61c0ded6..d7f84ef16 100644 --- a/static/plugs/jquery/masonry.min.js +++ b/static/plugs/jquery/masonry.min.js @@ -1,9 +1,9 @@ /*! - * Masonry PACKAGED v4.2.0 + * Masonry PACKAGED v4.2.1 * Cascading grid layout library - * http://masonry.desandro.com + * https://masonry.desandro.com * MIT License * by David DeSandro */ -!function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,function(t,e){"use strict";function i(i,r,a){function h(t,e,n){var o,r="$()."+i+'("'+e+'")';return t.each(function(t,h){var u=a.data(h,i);if(!u)return void s(i+" not initialized. Cannot call methods, i.e. "+r);var d=u[e];if(!d||"_"==e.charAt(0))return void s(r+" is not a valid method");var l=d.apply(u,n);o=void 0===o?l:o}),void 0!==o?o:t}function u(t,e){t.each(function(t,n){var o=a.data(n,i);o?(o.option(e),o._init()):(o=new r(n,e),a.data(n,i,o))})}a=a||e||t.jQuery,a&&(r.prototype.option||(r.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(!0,this.options,t))}),a.fn[i]=function(t){if("string"==typeof t){var e=o.call(arguments,1);return h(this,t,e)}return u(this,t),this},n(a))}function n(t){!t||t&&t.bridget||(t.bridget=i)}var o=Array.prototype.slice,r=t.console,s="undefined"==typeof r?function(){}:function(t){r.error(t)};return n(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=0,o=i[n];e=e||[];for(var r=this._onceEvents&&this._onceEvents[t];o;){var s=r&&r[o];s&&(this.off(t,o),delete r[o]),o.apply(this,e),n+=s?0:1,o=i[n]}return this}},t}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("get-size/get-size",[],function(){return e()}):"object"==typeof module&&module.exports?module.exports=e():t.getSize=e()}(window,function(){"use strict";function t(t){var e=parseFloat(t),i=-1==t.indexOf("%")&&!isNaN(e);return i&&e}function e(){}function i(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},e=0;u>e;e++){var i=h[e];t[i]=0}return t}function n(t){var e=getComputedStyle(t);return e||a("Style returned "+e+". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1"),e}function o(){if(!d){d=!0;var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(e);var o=n(e);r.isBoxSizeOuter=s=200==t(o.width),i.removeChild(e)}}function r(e){if(o(),"string"==typeof e&&(e=document.querySelector(e)),e&&"object"==typeof e&&e.nodeType){var r=n(e);if("none"==r.display)return i();var a={};a.width=e.offsetWidth,a.height=e.offsetHeight;for(var d=a.isBorderBox="border-box"==r.boxSizing,l=0;u>l;l++){var c=h[l],f=r[c],m=parseFloat(f);a[c]=isNaN(m)?0:m}var p=a.paddingLeft+a.paddingRight,g=a.paddingTop+a.paddingBottom,y=a.marginLeft+a.marginRight,v=a.marginTop+a.marginBottom,_=a.borderLeftWidth+a.borderRightWidth,z=a.borderTopWidth+a.borderBottomWidth,E=d&&s,b=t(r.width);b!==!1&&(a.width=b+(E?0:p+_));var x=t(r.height);return x!==!1&&(a.height=x+(E?0:g+z)),a.innerWidth=a.width-(p+_),a.innerHeight=a.height-(g+z),a.outerWidth=a.width+y,a.outerHeight=a.height+v,a}}var s,a="undefined"==typeof console?e:function(t){console.error(t)},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],u=h.length,d=!1;return r}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("desandro-matches-selector/matches-selector",e):"object"==typeof module&&module.exports?module.exports=e():t.matchesSelector=e()}(window,function(){"use strict";var t=function(){var t=window.Element.prototype;if(t.matches)return"matches";if(t.matchesSelector)return"matchesSelector";for(var e=["webkit","moz","ms","o"],i=0;is?"round":"floor";r=Math[a](r),this.cols=Math.max(r,1)},n.getContainerWidth=function(){var t=this._getOption("fitWidth"),i=t?this.element.parentNode:this.element,n=e(i);this.containerWidth=n&&n.innerWidth},n._getItemLayoutPosition=function(t){t.getSize();var e=t.size.outerWidth%this.columnWidth,i=e&&1>e?"round":"ceil",n=Math[i](t.size.outerWidth/this.columnWidth);n=Math.min(n,this.cols);for(var o=this.options.horizontalOrder?"_getHorizontalColPosition":"_getTopColPosition",r=this[o](n,t),s={x:this.columnWidth*r.col,y:r.y},a=r.y+t.size.outerHeight,h=n+r.col,u=r.col;h>u;u++)this.colYs[u]=a;return s},n._getTopColPosition=function(t){var e=this._getTopColGroup(t),i=Math.min.apply(Math,e);return{col:e.indexOf(i),y:i}},n._getTopColGroup=function(t){if(2>t)return this.colYs;for(var e=[],i=this.cols+1-t,n=0;i>n;n++)e[n]=this._getColGroupY(n,t);return e},n._getColGroupY=function(t,e){if(2>e)return this.colYs[t];var i=this.colYs.slice(t,t+e);return Math.max.apply(Math,i)},n._getHorizontalColPosition=function(t,e){var i=this.horizontalColIndex%this.cols,n=t>1&&i+t>this.cols;i=n?0:i;var o=e.size.outerWidth&&e.size.outerHeight;return this.horizontalColIndex=o?i+t:this.horizontalColIndex,{col:i,y:this._getColGroupY(i,t)}},n._manageStamp=function(t){var i=e(t),n=this._getElementOffset(t),o=this._getOption("originLeft"),r=o?n.left:n.right,s=r+i.outerWidth,a=Math.floor(r/this.columnWidth);a=Math.max(0,a);var h=Math.floor(s/this.columnWidth);h-=s%this.columnWidth?0:1,h=Math.min(this.cols-1,h);for(var u=this._getOption("originTop"),d=(u?n.top:n.bottom)+i.outerHeight,l=a;h>=l;l++)this.colYs[l]=Math.max(d,this.colYs[l])},n._getContainerSize=function(){this.maxY=Math.max.apply(Math,this.colYs);var t={height:this.maxY};return this._getOption("fitWidth")&&(t.width=this._getContainerFitWidth()),t},n._getContainerFitWidth=function(){for(var t=0,e=this.cols;--e&&0===this.colYs[e];)t++;return(this.cols-t)*this.columnWidth-this.gutter},n.needsResizeLayout=function(){var t=this.containerWidth;return this.getContainerWidth(),t!=this.containerWidth},i}); \ No newline at end of file +!function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,function(t,e){"use strict";function i(i,r,a){function h(t,e,n){var o,r="$()."+i+'("'+e+'")';return t.each(function(t,h){var u=a.data(h,i);if(!u)return void s(i+" not initialized. Cannot call methods, i.e. "+r);var d=u[e];if(!d||"_"==e.charAt(0))return void s(r+" is not a valid method");var l=d.apply(u,n);o=void 0===o?l:o}),void 0!==o?o:t}function u(t,e){t.each(function(t,n){var o=a.data(n,i);o?(o.option(e),o._init()):(o=new r(n,e),a.data(n,i,o))})}a=a||e||t.jQuery,a&&(r.prototype.option||(r.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(!0,this.options,t))}),a.fn[i]=function(t){if("string"==typeof t){var e=o.call(arguments,1);return h(this,t,e)}return u(this,t),this},n(a))}function n(t){!t||t&&t.bridget||(t.bridget=i)}var o=Array.prototype.slice,r=t.console,s="undefined"==typeof r?function(){}:function(t){r.error(t)};return n(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){i=i.slice(0),e=e||[];for(var n=this._onceEvents&&this._onceEvents[t],o=0;oe;e++){var i=h[e];t[i]=0}return t}function n(t){var e=getComputedStyle(t);return e||a("Style returned "+e+". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1"),e}function o(){if(!d){d=!0;var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(e);var o=n(e);r.isBoxSizeOuter=s=200==t(o.width),i.removeChild(e)}}function r(e){if(o(),"string"==typeof e&&(e=document.querySelector(e)),e&&"object"==typeof e&&e.nodeType){var r=n(e);if("none"==r.display)return i();var a={};a.width=e.offsetWidth,a.height=e.offsetHeight;for(var d=a.isBorderBox="border-box"==r.boxSizing,l=0;u>l;l++){var c=h[l],f=r[c],m=parseFloat(f);a[c]=isNaN(m)?0:m}var p=a.paddingLeft+a.paddingRight,g=a.paddingTop+a.paddingBottom,y=a.marginLeft+a.marginRight,v=a.marginTop+a.marginBottom,_=a.borderLeftWidth+a.borderRightWidth,z=a.borderTopWidth+a.borderBottomWidth,E=d&&s,b=t(r.width);b!==!1&&(a.width=b+(E?0:p+_));var x=t(r.height);return x!==!1&&(a.height=x+(E?0:g+z)),a.innerWidth=a.width-(p+_),a.innerHeight=a.height-(g+z),a.outerWidth=a.width+y,a.outerHeight=a.height+v,a}}var s,a="undefined"==typeof console?e:function(t){console.error(t)},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],u=h.length,d=!1;return r}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("desandro-matches-selector/matches-selector",e):"object"==typeof module&&module.exports?module.exports=e():t.matchesSelector=e()}(window,function(){"use strict";var t=function(){var t=window.Element.prototype;if(t.matches)return"matches";if(t.matchesSelector)return"matchesSelector";for(var e=["webkit","moz","ms","o"],i=0;is?"round":"floor";r=Math[a](r),this.cols=Math.max(r,1)},n.getContainerWidth=function(){var t=this._getOption("fitWidth"),i=t?this.element.parentNode:this.element,n=e(i);this.containerWidth=n&&n.innerWidth},n._getItemLayoutPosition=function(t){t.getSize();var e=t.size.outerWidth%this.columnWidth,i=e&&1>e?"round":"ceil",n=Math[i](t.size.outerWidth/this.columnWidth);n=Math.min(n,this.cols);for(var o=this.options.horizontalOrder?"_getHorizontalColPosition":"_getTopColPosition",r=this[o](n,t),s={x:this.columnWidth*r.col,y:r.y},a=r.y+t.size.outerHeight,h=n+r.col,u=r.col;h>u;u++)this.colYs[u]=a;return s},n._getTopColPosition=function(t){var e=this._getTopColGroup(t),i=Math.min.apply(Math,e);return{col:e.indexOf(i),y:i}},n._getTopColGroup=function(t){if(2>t)return this.colYs;for(var e=[],i=this.cols+1-t,n=0;i>n;n++)e[n]=this._getColGroupY(n,t);return e},n._getColGroupY=function(t,e){if(2>e)return this.colYs[t];var i=this.colYs.slice(t,t+e);return Math.max.apply(Math,i)},n._getHorizontalColPosition=function(t,e){var i=this.horizontalColIndex%this.cols,n=t>1&&i+t>this.cols;i=n?0:i;var o=e.size.outerWidth&&e.size.outerHeight;return this.horizontalColIndex=o?i+t:this.horizontalColIndex,{col:i,y:this._getColGroupY(i,t)}},n._manageStamp=function(t){var i=e(t),n=this._getElementOffset(t),o=this._getOption("originLeft"),r=o?n.left:n.right,s=r+i.outerWidth,a=Math.floor(r/this.columnWidth);a=Math.max(0,a);var h=Math.floor(s/this.columnWidth);h-=s%this.columnWidth?0:1,h=Math.min(this.cols-1,h);for(var u=this._getOption("originTop"),d=(u?n.top:n.bottom)+i.outerHeight,l=a;h>=l;l++)this.colYs[l]=Math.max(d,this.colYs[l])},n._getContainerSize=function(){this.maxY=Math.max.apply(Math,this.colYs);var t={height:this.maxY};return this._getOption("fitWidth")&&(t.width=this._getContainerFitWidth()),t},n._getContainerFitWidth=function(){for(var t=0,e=this.cols;--e&&0===this.colYs[e];)t++;return(this.cols-t)*this.columnWidth-this.gutter},n.needsResizeLayout=function(){var t=this.containerWidth;return this.getContainerWidth(),t!=this.containerWidth},i}); \ No newline at end of file diff --git a/static/plugs/layui/css/layui.css b/static/plugs/layui/css/layui.css index 92fc5aa18..5ccaade1c 100644 --- a/static/plugs/layui/css/layui.css +++ b/static/plugs/layui/css/layui.css @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - .layui-btn,.layui-inline,img{vertical-align:middle}.layui-btn,.layui-disabled,.layui-icon,.layui-unselect{-webkit-user-select:none;-ms-user-select:none;-moz-user-select:none}blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,li,ol,p,pre,td,textarea,th,ul{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}a:active,a:hover{outline:0}img{display:inline-block;border:none}li{list-style:none}table{border-collapse:collapse;border-spacing:0}h1,h2,h3{font-size:14px;font-weight:400}h4,h5,h6{font-size:100%;font-weight:400}button,input,optgroup,option,select,textarea{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;outline:0}pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}@font-face{font-family:layui-icon;src:url(../font/iconfont.eot?v=213);src:url(../font/iconfont.eot?v=213#iefix) format('embedded-opentype'),url(../font/iconfont.svg?v=213#iconfont) format('svg'),url(../font/iconfont.woff?v=213) format('woff'),url(../font/iconfont.ttf?v=213) format('truetype')}.layui-icon{font-family:layui-icon!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{line-height:24px;font:14px Helvetica Neue,Helvetica,PingFang SC,\5FAE\8F6F\96C5\9ED1,Tahoma,Arial,sans-serif}hr{height:1px;margin:10px 0;border:0;background-color:#e2e2e2;clear:both}a{color:#333;text-decoration:none}a:hover{color:#777}a cite{font-style:normal;*cursor:pointer}.layui-border-box,.layui-border-box *{box-sizing:border-box}.layui-box,.layui-box *{box-sizing:content-box}.layui-clear{clear:both;*zoom:1}.layui-clear:after{content:'\20';clear:both;*zoom:1;display:block;height:0}.layui-btn,.layui-btn-group,.layui-inline{display:inline-block}.layui-inline{position:relative;*display:inline;*zoom:1}.layui-edge{position:absolute;width:0;height:0;border-style:dashed;border-color:transparent;overflow:hidden}.layui-elip{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-disabled,.layui-disabled:hover{color:#d2d2d2!important;cursor:not-allowed!important}.layui-circle{border-radius:100%}.layui-show{display:block!important}.layui-hide{display:none!important}.layui-main{position:relative;width:1140px;margin:0 auto}.layui-header{position:relative;z-index:1000;height:60px}.layui-header a:hover{transition:all .5s;-webkit-transition:all .5s}.layui-side{position:fixed;top:0;bottom:0;z-index:999;width:200px;overflow-x:hidden}.layui-side-scroll{width:220px;height:100%;overflow-x:hidden}.layui-body{position:absolute;left:200px;right:0;top:0;bottom:0;z-index:998;width:auto;overflow:hidden;overflow-y:auto;box-sizing:border-box}.layui-layout-admin .layui-header{background-color:#23262E}.layui-layout-admin .layui-side{top:60px;width:200px;overflow-x:hidden}.layui-layout-admin .layui-body{top:60px;bottom:44px}.layui-layout-admin .layui-main{width:auto;margin:0 15px}.layui-layout-admin .layui-footer{position:fixed;left:200px;right:0;bottom:0;height:44px;line-height:44px;padding:0 15px;background-color:#eee}.layui-layout-admin .layui-logo{position:absolute;left:0;top:0;width:200px;height:100%;line-height:60px;text-align:center;color:#009688;font-size:16px}.layui-layout-admin .layui-header .layui-nav{background:0 0}.layui-layout-left{position:absolute!important;left:200px;top:0}.layui-layout-right{position:absolute!important;right:0;top:0}.layui-container{position:relative;margin:0 auto;padding:0 15px;box-sizing:border-box}.layui-fluid{position:relative;margin:0 auto;padding:0 15px}.layui-row:after,.layui-row:before{content:'';display:block;clear:both}.layui-col-lg1,.layui-col-lg10,.layui-col-lg11,.layui-col-lg12,.layui-col-lg2,.layui-col-lg3,.layui-col-lg4,.layui-col-lg5,.layui-col-lg6,.layui-col-lg7,.layui-col-lg8,.layui-col-lg9,.layui-col-md1,.layui-col-md10,.layui-col-md11,.layui-col-md12,.layui-col-md2,.layui-col-md3,.layui-col-md4,.layui-col-md5,.layui-col-md6,.layui-col-md7,.layui-col-md8,.layui-col-md9,.layui-col-sm1,.layui-col-sm10,.layui-col-sm11,.layui-col-sm12,.layui-col-sm2,.layui-col-sm3,.layui-col-sm4,.layui-col-sm5,.layui-col-sm6,.layui-col-sm7,.layui-col-sm8,.layui-col-sm9,.layui-col-xs1,.layui-col-xs10,.layui-col-xs11,.layui-col-xs12,.layui-col-xs2,.layui-col-xs3,.layui-col-xs4,.layui-col-xs5,.layui-col-xs6,.layui-col-xs7,.layui-col-xs8,.layui-col-xs9{position:relative;display:block;box-sizing:border-box}.layui-col-xs1,.layui-col-xs10,.layui-col-xs11,.layui-col-xs12,.layui-col-xs2,.layui-col-xs3,.layui-col-xs4,.layui-col-xs5,.layui-col-xs6,.layui-col-xs7,.layui-col-xs8,.layui-col-xs9{float:left}.layui-col-xs1{width:8.33333333%}.layui-col-xs2{width:16.66666667%}.layui-col-xs3{width:25%}.layui-col-xs4{width:33.33333333%}.layui-col-xs5{width:41.66666667%}.layui-col-xs6{width:50%}.layui-col-xs7{width:58.33333333%}.layui-col-xs8{width:66.66666667%}.layui-col-xs9{width:75%}.layui-col-xs10{width:83.33333333%}.layui-col-xs11{width:91.66666667%}.layui-col-xs12{width:100%}.layui-col-xs-offset1{margin-left:8.33333333%}.layui-col-xs-offset2{margin-left:16.66666667%}.layui-col-xs-offset3{margin-left:25%}.layui-col-xs-offset4{margin-left:33.33333333%}.layui-col-xs-offset5{margin-left:41.66666667%}.layui-col-xs-offset6{margin-left:50%}.layui-col-xs-offset7{margin-left:58.33333333%}.layui-col-xs-offset8{margin-left:66.66666667%}.layui-col-xs-offset9{margin-left:75%}.layui-col-xs-offset10{margin-left:83.33333333%}.layui-col-xs-offset11{margin-left:91.66666667%}.layui-col-xs-offset12{margin-left:100%}@media screen and (max-width:768px){.layui-hide-xs{display:none!important}.layui-show-xs-block{display:block!important}.layui-show-xs-inline{display:inline!important}.layui-show-xs-inline-block{display:inline-block!important}}@media screen and (min-width:768px){.layui-container{width:750px}.layui-hide-sm{display:none!important}.layui-show-sm-block{display:block!important}.layui-show-sm-inline{display:inline!important}.layui-show-sm-inline-block{display:inline-block!important}.layui-col-sm1,.layui-col-sm10,.layui-col-sm11,.layui-col-sm12,.layui-col-sm2,.layui-col-sm3,.layui-col-sm4,.layui-col-sm5,.layui-col-sm6,.layui-col-sm7,.layui-col-sm8,.layui-col-sm9{float:left}.layui-col-sm1{width:8.33333333%}.layui-col-sm2{width:16.66666667%}.layui-col-sm3{width:25%}.layui-col-sm4{width:33.33333333%}.layui-col-sm5{width:41.66666667%}.layui-col-sm6{width:50%}.layui-col-sm7{width:58.33333333%}.layui-col-sm8{width:66.66666667%}.layui-col-sm9{width:75%}.layui-col-sm10{width:83.33333333%}.layui-col-sm11{width:91.66666667%}.layui-col-sm12{width:100%}.layui-col-sm-offset1{margin-left:8.33333333%}.layui-col-sm-offset2{margin-left:16.66666667%}.layui-col-sm-offset3{margin-left:25%}.layui-col-sm-offset4{margin-left:33.33333333%}.layui-col-sm-offset5{margin-left:41.66666667%}.layui-col-sm-offset6{margin-left:50%}.layui-col-sm-offset7{margin-left:58.33333333%}.layui-col-sm-offset8{margin-left:66.66666667%}.layui-col-sm-offset9{margin-left:75%}.layui-col-sm-offset10{margin-left:83.33333333%}.layui-col-sm-offset11{margin-left:91.66666667%}.layui-col-sm-offset12{margin-left:100%}}@media screen and (min-width:992px){.layui-container{width:970px}.layui-hide-md{display:none!important}.layui-show-md-block{display:block!important}.layui-show-md-inline{display:inline!important}.layui-show-md-inline-block{display:inline-block!important}.layui-col-md1,.layui-col-md10,.layui-col-md11,.layui-col-md12,.layui-col-md2,.layui-col-md3,.layui-col-md4,.layui-col-md5,.layui-col-md6,.layui-col-md7,.layui-col-md8,.layui-col-md9{float:left}.layui-col-md1{width:8.33333333%}.layui-col-md2{width:16.66666667%}.layui-col-md3{width:25%}.layui-col-md4{width:33.33333333%}.layui-col-md5{width:41.66666667%}.layui-col-md6{width:50%}.layui-col-md7{width:58.33333333%}.layui-col-md8{width:66.66666667%}.layui-col-md9{width:75%}.layui-col-md10{width:83.33333333%}.layui-col-md11{width:91.66666667%}.layui-col-md12{width:100%}.layui-col-md-offset1{margin-left:8.33333333%}.layui-col-md-offset2{margin-left:16.66666667%}.layui-col-md-offset3{margin-left:25%}.layui-col-md-offset4{margin-left:33.33333333%}.layui-col-md-offset5{margin-left:41.66666667%}.layui-col-md-offset6{margin-left:50%}.layui-col-md-offset7{margin-left:58.33333333%}.layui-col-md-offset8{margin-left:66.66666667%}.layui-col-md-offset9{margin-left:75%}.layui-col-md-offset10{margin-left:83.33333333%}.layui-col-md-offset11{margin-left:91.66666667%}.layui-col-md-offset12{margin-left:100%}}@media screen and (min-width:1200px){.layui-container{width:1170px}.layui-hide-lg{display:none!important}.layui-show-lg-block{display:block!important}.layui-show-lg-inline{display:inline!important}.layui-show-lg-inline-block{display:inline-block!important}.layui-col-lg1,.layui-col-lg10,.layui-col-lg11,.layui-col-lg12,.layui-col-lg2,.layui-col-lg3,.layui-col-lg4,.layui-col-lg5,.layui-col-lg6,.layui-col-lg7,.layui-col-lg8,.layui-col-lg9{float:left}.layui-col-lg1{width:8.33333333%}.layui-col-lg2{width:16.66666667%}.layui-col-lg3{width:25%}.layui-col-lg4{width:33.33333333%}.layui-col-lg5{width:41.66666667%}.layui-col-lg6{width:50%}.layui-col-lg7{width:58.33333333%}.layui-col-lg8{width:66.66666667%}.layui-col-lg9{width:75%}.layui-col-lg10{width:83.33333333%}.layui-col-lg11{width:91.66666667%}.layui-col-lg12{width:100%}.layui-col-lg-offset1{margin-left:8.33333333%}.layui-col-lg-offset2{margin-left:16.66666667%}.layui-col-lg-offset3{margin-left:25%}.layui-col-lg-offset4{margin-left:33.33333333%}.layui-col-lg-offset5{margin-left:41.66666667%}.layui-col-lg-offset6{margin-left:50%}.layui-col-lg-offset7{margin-left:58.33333333%}.layui-col-lg-offset8{margin-left:66.66666667%}.layui-col-lg-offset9{margin-left:75%}.layui-col-lg-offset10{margin-left:83.33333333%}.layui-col-lg-offset11{margin-left:91.66666667%}.layui-col-lg-offset12{margin-left:100%}}.layui-col-space1{margin:-.5px}.layui-col-space1>*{padding:.5px}.layui-col-space3{margin:-1.5px}.layui-col-space3>*{padding:1.5px}.layui-col-space5{margin:-2.5px}.layui-col-space5>*{padding:2.5px}.layui-col-space8{margin:-3.5px}.layui-col-space8>*{padding:3.5px}.layui-col-space10{margin:-5px}.layui-col-space10>*{padding:5px}.layui-col-space12{margin:-6px}.layui-col-space12>*{padding:6px}.layui-col-space15{margin:-7.5px}.layui-col-space15>*{padding:7.5px}.layui-col-space18{margin:-9px}.layui-col-space18>*{padding:9px}.layui-col-space20{margin:-10px}.layui-col-space20>*{padding:10px}.layui-col-space22{margin:-11px}.layui-col-space22>*{padding:11px}.layui-col-space25{margin:-12.5px}.layui-col-space25>*{padding:12.5px}.layui-col-space30{margin:-15px}.layui-col-space30>*{padding:15px}.layui-btn,.layui-input,.layui-select,.layui-textarea,.layui-upload-button{outline:0;transition:all .3s;-webkit-transition:all .3s;box-sizing:border-box}.layui-elem-quote{margin-bottom:10px;padding:15px;line-height:22px;border-left:5px solid #009688;border-radius:0 2px 2px 0;background-color:#f2f2f2}.layui-quote-nm{border-color:#e2e2e2;border-style:solid;border-width:1px 1px 1px 5px;background:0 0}.layui-elem-field{margin-bottom:10px;padding:0;border:1px solid #e2e2e2}.layui-elem-field legend{margin-left:20px;padding:0 10px;font-size:20px;font-weight:300}.layui-field-title{margin:10px 0 20px;border:none;border-top:1px solid #e2e2e2}.layui-field-box{padding:10px 15px}.layui-field-title .layui-field-box{padding:10px 0}.layui-progress{position:relative;height:6px;border-radius:20px;background-color:#e2e2e2}.layui-progress-bar{position:absolute;width:0;max-width:100%;height:6px;border-radius:20px;text-align:right;background-color:#5FB878;transition:all .3s;-webkit-transition:all .3s}.layui-progress-big,.layui-progress-big .layui-progress-bar{height:18px;line-height:18px}.layui-progress-text{position:relative;top:-18px;line-height:18px;font-size:12px;color:#666}.layui-progress-big .layui-progress-text{position:static;padding:0 10px;color:#fff}.layui-collapse{border:1px solid #e2e2e2;border-radius:2px}.layui-colla-item{border-top:1px solid #e2e2e2}.layui-colla-item:first-child{border-top:none}.layui-colla-title{position:relative;height:42px;line-height:42px;padding:0 15px 0 35px;color:#333;background-color:#f2f2f2;cursor:pointer}.layui-colla-content{display:none;padding:10px 15px;line-height:22px;border-top:1px solid #e2e2e2;color:#666}.layui-bg-black,.layui-bg-blue,.layui-bg-cyan,.layui-bg-green,.layui-bg-orange,.layui-bg-red{color:#fff!important}.layui-colla-icon{position:absolute;left:15px;top:0;font-size:14px}.layui-bg-red{background-color:#FF5722!important}.layui-bg-orange{background-color:#FFB800!important}.layui-bg-green{background-color:#009688!important}.layui-bg-cyan{background-color:#2F4056!important}.layui-bg-blue{background-color:#1E9FFF!important}.layui-bg-black{background-color:#393D49!important}.layui-bg-gray{background-color:#eee!important;color:#666!important}.layui-text{line-height:22px;font-size:14px;color:#666}.layui-text h1,.layui-text h2,.layui-text h3{font-weight:500;color:#333}.layui-text h1{font-size:30px}.layui-text h2{font-size:24px}.layui-text h3{font-size:18px}.layui-text a{color:#01AAED}.layui-text a:hover{text-decoration:underline}.layui-text ul{padding:5px 0 5px 15px}.layui-text ul li{margin-top:5px;list-style-type:disc}.layui-text em,.layui-word-aux{color:#999!important;padding:0 5px!important}.layui-btn{height:38px;line-height:38px;padding:0 18px;background-color:#009688;color:#fff;white-space:nowrap;text-align:center;font-size:14px;border:none;border-radius:2px;cursor:pointer;opacity:.9;filter:alpha(opacity=90)}.layui-btn:hover{opacity:.8;filter:alpha(opacity=80);color:#fff}.layui-btn:active{opacity:1;filter:alpha(opacity=100)}.layui-btn+.layui-btn{margin-left:10px}.layui-btn-radius{border-radius:100px}.layui-btn .layui-icon{margin-right:3px;font-size:18px;vertical-align:bottom;vertical-align:middle\9}.layui-btn-primary{line-height:36px;border:1px solid #C9C9C9;background-color:#fff;color:#555}.layui-btn-primary:hover{border-color:#009688;color:#333}.layui-btn-normal{background-color:#1E9FFF}.layui-btn-warm{background-color:#FFB800}.layui-btn-danger{background-color:#FF5722}.layui-btn-disabled,.layui-btn-disabled:active,.layui-btn-disabled:hover{border:1px solid #e6e6e6;background-color:#FBFBFB;color:#C9C9C9;cursor:not-allowed;opacity:1}.layui-btn-big{height:44px;line-height:44px;padding:0 25px;font-size:16px}.layui-btn-small{height:30px;line-height:30px;padding:0 10px;font-size:12px}.layui-btn-small i{font-size:16px!important}.layui-btn-mini{height:22px;line-height:22px;padding:0 5px;font-size:12px}.layui-btn-mini i{font-size:14px!important}.layui-btn-group{vertical-align:middle;font-size:0}.layui-btn-group .layui-btn{margin-left:0!important;margin-right:0!important;border-left:1px solid rgba(255,255,255,.5);border-radius:0}.layui-btn-group .layui-btn-primary{border-left:none}.layui-btn-group .layui-btn-primary:hover{border-color:#C9C9C9;color:#009688}.layui-btn-group .layui-btn:first-child{border-left:none;border-radius:2px 0 0 2px}.layui-btn-group .layui-btn-primary:first-child{border-left:1px solid #c9c9c9}.layui-btn-group .layui-btn:last-child{border-radius:0 2px 2px 0}.layui-btn-group .layui-btn+.layui-btn{margin-left:0}.layui-btn-group+.layui-btn-group{margin-left:10px}.layui-input,.layui-select,.layui-textarea{height:38px;line-height:36px\9;border:1px solid #e6e6e6;background-color:#fff;border-radius:2px}.layui-form-label,.layui-form-mid,.layui-textarea{line-height:20px;position:relative}.layui-input,.layui-textarea{display:block;width:100%;padding-left:10px}.layui-input:hover,.layui-textarea:hover{border-color:#D2D2D2!important}.layui-input:focus,.layui-textarea:focus{border-color:#C9C9C9!important}.layui-textarea{min-height:100px;height:auto;padding:6px 10px;resize:vertical}.layui-select{padding:0 10px}.layui-form input[type=checkbox],.layui-form input[type=radio],.layui-form select{display:none}.layui-form-item{margin-bottom:15px;clear:both;*zoom:1}.layui-form-item:after{content:'\20';clear:both;*zoom:1;display:block;height:0}.layui-form-label{float:left;display:block;padding:9px 15px;width:80px;font-weight:400;text-align:right}.layui-form-item .layui-inline{margin-bottom:5px;margin-right:10px}.layui-input-block,.layui-input-inline{position:relative}.layui-input-block{margin-left:110px;min-height:36px}.layui-input-inline{display:inline-block;vertical-align:middle}.layui-form-item .layui-input-inline{float:left;width:190px;margin-right:10px}.layui-form-text .layui-input-inline{width:auto}.layui-form-mid{float:left;display:block;padding:8px 0!important;margin-right:10px}.layui-form-danger+.layui-form-select .layui-input,.layui-form-danger:focus{border:1px solid #FF5722!important}.layui-form-select{position:relative}.layui-form-select .layui-input{padding-right:30px;cursor:pointer}.layui-form-select .layui-edge{position:absolute;right:10px;top:50%;margin-top:-3px;cursor:pointer;border-width:6px;border-top-color:#c2c2c2;border-top-style:solid;transition:all .3s;-webkit-transition:all .3s}.layui-form-select dl{display:none;position:absolute;left:0;top:42px;padding:5px 0;z-index:999;min-width:100%;border:1px solid #d2d2d2;max-height:300px;overflow-y:auto;background-color:#fff;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.12);box-sizing:border-box}.layui-form-select dl dd,.layui-form-select dl dt{padding:0 10px;line-height:36px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.layui-form-select dl dt{font-size:12px;color:#999}.layui-form-select dl dd{cursor:pointer}.layui-form-select dl dd:hover{background-color:#f2f2f2}.layui-form-select .layui-select-group dd{padding-left:20px}.layui-form-select dl dd.layui-select-tips{padding-left:10px!important;color:#999}.layui-form-select dl dd.layui-this{background-color:#5FB878;color:#fff}.layui-form-checkbox,.layui-form-select dl dd.layui-disabled{background-color:#fff}.layui-form-selected dl{display:block}.layui-form-checkbox,.layui-form-checkbox *,.layui-form-radio,.layui-form-radio *,.layui-form-switch{display:inline-block;vertical-align:middle}.layui-form-selected .layui-edge{margin-top:-9px;-webkit-transform:rotate(180deg);transform:rotate(180deg);margin-top:-3px\9}:root .layui-form-selected .layui-edge{margin-top:-9px\0/IE9}.layui-form-selectup dl{top:auto;bottom:42px}.layui-select-none{margin:5px 0;text-align:center;color:#999}.layui-select-disabled .layui-disabled{border-color:#eee!important}.layui-select-disabled .layui-edge{border-top-color:#d2d2d2}.layui-form-checkbox{position:relative;height:30px;line-height:28px;margin-right:10px;padding-right:30px;border:1px solid #d2d2d2;cursor:pointer;font-size:0;border-radius:2px;-webkit-transition:.1s linear;transition:.1s linear;box-sizing:border-box}.layui-form-checkbox:hover{border:1px solid #c2c2c2}.layui-form-checkbox span{padding:0 10px;height:100%;font-size:14px;background-color:#d2d2d2;color:#fff;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.layui-form-checkbox:hover span{background-color:#c2c2c2}.layui-form-checkbox i{position:absolute;right:0;width:30px;color:#fff;font-size:20px;text-align:center}.layui-form-checkbox:hover i{color:#c2c2c2}.layui-form-checked,.layui-form-checked:hover{border-color:#5FB878}.layui-form-checked span,.layui-form-checked:hover span{background-color:#5FB878}.layui-form-checked i,.layui-form-checked:hover i{color:#5FB878}.layui-form-item .layui-form-checkbox{margin-top:4px}.layui-form-checkbox[lay-skin=primary]{height:auto!important;line-height:normal!important;border:none!important;margin-right:0;padding-right:0;background:0 0}.layui-form-checkbox[lay-skin=primary] span{float:right;padding-right:15px;line-height:18px;background:0 0;color:#666}.layui-form-checkbox[lay-skin=primary] i{position:relative;top:0;width:16px;height:16px;line-height:16px;border:1px solid #d2d2d2;font-size:12px;border-radius:2px;background-color:#fff;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-checkbox[lay-skin=primary]:hover i{border-color:#5FB878;color:#fff}.layui-form-checked[lay-skin=primary] i{border-color:#5FB878;background-color:#5FB878;color:#fff}.layui-checkbox-disbaled[lay-skin=primary] span{background:0 0!important}.layui-checkbox-disbaled[lay-skin=primary]:hover i{border-color:#d2d2d2}.layui-form-item .layui-form-checkbox[lay-skin=primary]{margin-top:10px}.layui-form-switch{position:relative;height:22px;line-height:22px;width:42px;padding:0 5px;margin-top:8px;border:1px solid #d2d2d2;border-radius:20px;cursor:pointer;background-color:#fff;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-switch i{position:absolute;left:5px;top:3px;width:16px;height:16px;border-radius:20px;background-color:#d2d2d2;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-switch em{position:absolute;right:5px;top:0;width:25px;padding:0!important;text-align:center!important;color:#999!important;font-style:normal!important;font-size:12px}.layui-form-onswitch{border-color:#5FB878;background-color:#5FB878}.layui-form-onswitch i{left:32px;background-color:#fff}.layui-form-onswitch em{left:5px;right:auto;color:#fff!important}.layui-checkbox-disbaled{border-color:#e2e2e2!important}.layui-checkbox-disbaled span{background-color:#e2e2e2!important}.layui-checkbox-disbaled:hover i{color:#fff!important}.layui-form-radio{line-height:28px;margin:6px 10px 0 0;padding-right:10px;cursor:pointer;font-size:0}.layui-form-radio i{margin-right:8px;font-size:22px;color:#c2c2c2}.layui-form-radio span{font-size:14px}.layui-form-radio i:hover,.layui-form-radioed i{color:#5FB878}.layui-radio-disbaled i{color:#e2e2e2!important}.layui-form-pane .layui-form-label{width:110px;padding:8px 15px;height:38px;line-height:20px;border:1px solid #e6e6e6;border-radius:2px 0 0 2px;text-align:center;background-color:#FBFBFB;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;box-sizing:border-box}.layui-form-pane .layui-input-inline{margin-left:-1px}.layui-form-pane .layui-input-block{margin-left:110px;left:-1px}.layui-form-pane .layui-input{border-radius:0 2px 2px 0}.layui-form-pane .layui-form-text .layui-form-label{float:none;width:100%;border-right:1px solid #e6e6e6;border-radius:2px;box-sizing:border-box;text-align:left}.layui-form-pane .layui-form-text .layui-input-inline{display:block;margin:0;top:-1px;clear:both}.layui-form-pane .layui-form-text .layui-input-block{margin:0;left:0;top:-1px}.layui-form-pane .layui-form-text .layui-textarea{min-height:100px;border-radius:0 0 2px 2px}.layui-form-pane .layui-form-checkbox{margin:4px 0 4px 10px}.layui-form-pane .layui-form-radio,.layui-form-pane .layui-form-switch{margin-top:6px;margin-left:10px}.layui-form-pane .layui-form-item[pane]{position:relative;border:1px solid #e6e6e6}.layui-form-pane .layui-form-item[pane] .layui-form-label{position:absolute;left:0;top:0;height:100%;border-width:0 1px 0 0}.layui-form-pane .layui-form-item[pane] .layui-input-inline{margin-left:110px}@media screen and (max-width:450px){.layui-form-item .layui-form-label{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-form-item .layui-inline{display:block;margin-right:0;margin-bottom:20px;clear:both}.layui-form-item .layui-inline:after{content:'\20';clear:both;display:block;height:0}.layui-form-item .layui-input-inline{display:block;float:none;left:-3px;width:auto;margin:0 0 10px 112px}.layui-form-item .layui-input-inline+.layui-form-mid{margin-left:110px;top:-5px;padding:0}.layui-form-item .layui-form-checkbox{margin-right:5px;margin-bottom:5px}}.layui-layedit{border:1px solid #d2d2d2;border-radius:2px}.layui-layedit-tool{padding:3px 5px;border-bottom:1px solid #e2e2e2;font-size:0}.layedit-tool-fixed{position:fixed;top:0;border-top:1px solid #e2e2e2}.layui-layedit-tool .layedit-tool-mid,.layui-layedit-tool .layui-icon{display:inline-block;vertical-align:middle;text-align:center;font-size:14px}.layui-layedit-tool .layui-icon{position:relative;width:32px;height:30px;line-height:30px;margin:3px 5px;color:#777;cursor:pointer;border-radius:2px}.layui-layedit-tool .layui-icon:hover{color:#393D49}.layui-layedit-tool .layui-icon:active{color:#000}.layui-layedit-tool .layedit-tool-active{background-color:#e2e2e2;color:#000}.layui-layedit-tool .layui-disabled,.layui-layedit-tool .layui-disabled:hover{color:#d2d2d2;cursor:not-allowed}.layui-layedit-tool .layedit-tool-mid{width:1px;height:18px;margin:0 10px;background-color:#d2d2d2}.layedit-tool-html{width:50px!important;font-size:30px!important}.layedit-tool-b,.layedit-tool-code,.layedit-tool-help{font-size:16px!important}.layedit-tool-d,.layedit-tool-face,.layedit-tool-image,.layedit-tool-unlink{font-size:18px!important}.layedit-tool-image input{position:absolute;font-size:0;left:0;top:0;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}.layui-layedit-iframe iframe{display:block;width:100%}#LAY_layedit_code{overflow:hidden}.layui-laypage{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;margin:10px 0;font-size:0}.layui-laypage>a:first-child,.layui-laypage>a:first-child em{border-radius:2px 0 0 2px}.layui-laypage>a:last-child,.layui-laypage>a:last-child em{border-radius:0 2px 2px 0}.layui-laypage>:first-child{margin-left:0!important}.layui-laypage>:last-child{margin-right:0!important}.layui-laypage a,.layui-laypage button,.layui-laypage input,.layui-laypage select,.layui-laypage span{border:1px solid #e2e2e2}.layui-laypage a,.layui-laypage span{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding:0 15px;height:28px;line-height:28px;margin:0 -1px 5px 0;background-color:#fff;color:#333;font-size:12px}.layui-laypage a:hover{color:#009688}.layui-laypage em{font-style:normal}.layui-laypage .layui-laypage-spr{color:#999;font-weight:700}.layui-laypage a{text-decoration:none}.layui-laypage .layui-laypage-curr{position:relative}.layui-laypage .layui-laypage-curr em{position:relative;color:#fff}.layui-laypage .layui-laypage-curr .layui-laypage-em{position:absolute;left:-1px;top:-1px;padding:1px;width:100%;height:100%;background-color:#009688}.layui-laypage-em{border-radius:2px}.layui-laypage-next em,.layui-laypage-prev em{font-family:Sim sun;font-size:16px}.layui-laypage .layui-laypage-count,.layui-laypage .layui-laypage-limits,.layui-laypage .layui-laypage-skip{margin-left:10px;margin-right:10px;padding:0;border:none}.layui-laypage .layui-laypage-limits{vertical-align:top}.layui-laypage select{height:22px;padding:3px;border-radius:2px;cursor:pointer}.layui-laypage .layui-laypage-skip{height:30px;line-height:30px;color:#999}.layui-laypage button,.layui-laypage input{height:30px;line-height:30px;border:1px solid #e2e2e2;border-radius:2px;vertical-align:top;background-color:#fff;box-sizing:border-box}.layui-laypage input{display:inline-block;width:40px;margin:0 10px;padding:0 3px;text-align:center}.layui-laypage input:focus,.layui-laypage select:focus{border-color:#009688!important}.layui-laypage button{margin-left:10px;padding:0 10px;cursor:pointer}.layui-flow-more{margin:10px 0;text-align:center;color:#999;font-size:14px}.layui-flow-more a{height:32px;line-height:32px}.layui-flow-more a *{display:inline-block;vertical-align:top}.layui-flow-more a cite{padding:0 20px;border-radius:3px;background-color:#eee;color:#333;font-style:normal}.layui-flow-more a cite:hover{opacity:.8}.layui-flow-more a i{font-size:30px;color:#737383}.layui-table{width:100%;margin:10px 0;background-color:#fff}.layui-table tr{transition:all .3s;-webkit-transition:all .3s}.layui-table thead tr,.layui-table-fixed-l tr,.layui-table-header,.layui-table-mend,.layui-table-patch,.layui-table-tool{background-color:#f2f2f2}.layui-table th{text-align:left;font-weight:400}.layui-table td,.layui-table th,.layui-table-header,.layui-table-tool,.layui-table-view,.layui-table[lay-skin=row],.layui-table[lay-skin=line]{border:1px solid #e2e2e2}.layui-table td,.layui-table th{position:relative;padding:9px 15px;min-height:20px;line-height:20px;font-size:14px}.layui-table[lay-even] tr:nth-child(even){background-color:#f8f8f8}.layui-table tbody tr:hover,.layui-table-hover{background-color:#f2f2f2!important}.layui-table-click{background-color:#FFEEE8!important}.layui-table[lay-skin=line] td,.layui-table[lay-skin=line] th{border-width:0 0 1px}.layui-table[lay-skin=row] td,.layui-table[lay-skin=row] th{border-width:0 1px 0 0}.layui-table[lay-skin=nob] td,.layui-table[lay-skin=nob] th{border:none}.layui-table img{max-width:100px}.layui-table[lay-size=lg] td,.layui-table[lay-size=lg] th{padding:15px 30px}.layui-table-view .layui-table[lay-size=lg] .layui-table-cell{height:40px;line-height:40px}.layui-table[lay-size=sm] td,.layui-table[lay-size=sm] th{font-size:12px;padding:5px 10px}.layui-table-view .layui-table[lay-size=sm] .layui-table-cell{height:20px;line-height:20px}.layui-table[lay-data]{display:none}.layui-table-view{position:relative;margin:10px 0;overflow:hidden}.layui-table-view .layui-table{position:relative;width:auto;margin:0}.layui-table-body,.layui-table-header .layui-table,.layui-table-tool{margin-bottom:-1px}.layui-table-view .layui-table[lay-skin=line]{border-width:0 1px 0 0}.layui-table-view .layui-table[lay-skin=row]{border-width:0 0 1px}.layui-table-view .layui-table td,.layui-table-view .layui-table th{padding:5px 0;border-top:none;border-left:none}.layui-table-view .layui-table td{cursor:default}.layui-table-view .layui-form-checkbox[lay-skin=primary] i{width:18px;height:18px}.layui-table-header{border-width:0 0 1px;overflow:hidden}.layui-table-sort{width:20px;height:20px;margin-left:5px;cursor:pointer!important}.layui-table-sort .layui-edge{left:5px;border-width:5px}.layui-table-sort .layui-table-sort-asc{top:4px;border-top:none;border-bottom-style:solid;border-bottom-color:#b2b2b2}.layui-table-sort .layui-table-sort-asc:hover{border-bottom-color:#666}.layui-table-sort .layui-table-sort-desc{bottom:4px;border-bottom:none;border-top-style:solid;border-top-color:#b2b2b2}.layui-table-sort .layui-table-sort-desc:hover{border-top-color:#666}.layui-table-sort[lay-sort=asc] .layui-table-sort-asc{border-bottom-color:#000}.layui-table-sort[lay-sort=desc] .layui-table-sort-desc{border-top-color:#000}.layui-table-cell{height:28px;line-height:28px;padding:0 15px;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;box-sizing:border-box}.layui-table-cell .layui-form-checkbox{top:-1px}.layui-table-cell .layui-table-link{color:#01AAED}.laytable-cell-space{width:15px;padding:0;text-align:center}.layui-table-body{position:relative;overflow:auto;margin-right:-1px}.layui-table-body .layui-none{line-height:40px;text-align:center;color:#999}.layui-table-fixed{position:absolute;left:0;top:0}.layui-table-fixed .layui-table-body{overflow:hidden}.layui-table-fixed-r{left:auto;right:-1px;border-left:1px solid #e2e2e2;box-shadow:-1px 0 8px rgba(0,0,0,.1)}.layui-table-fixed-r .layui-table-header{position:relative;overflow:visible}.layui-table-mend{position:absolute;right:-49px;top:0;height:100%;width:50px}.layui-table-tool{position:relative;width:100%;padding:7px 10px 0 0;border-width:1px 0 0;height:41px;font-size:12px;white-space:nowrap}.layui-table-tool:hover{overflow-x:auto}.layui-table-page{height:26px}.layui-table-tool .layui-laypage{margin:0}.layui-table-tool .layui-laypage a,.layui-table-tool .layui-laypage span{height:26px;line-height:26px;border:none;background:0 0;padding:0 12px}.layui-table-tool .layui-laypage .layui-laypage-count,.layui-table-tool .layui-laypage .layui-laypage-limits,.layui-table-tool .layui-laypage .layui-laypage-skip{margin-left:0;padding:0}.layui-table-tool .layui-laypage .layui-laypage-total{padding:0 10px}.layui-table-tool .layui-laypage .layui-laypage-spr{padding:0}.layui-table-tool .layui-laypage button,.layui-table-tool .layui-laypage input{height:26px;line-height:26px}.layui-table-tool .layui-laypage input{width:40px}.layui-table-tool .layui-laypage button{padding:0 10px}.layui-table-view select[lay-ignore]{display:inline-block}.layui-table-tool select{height:18px}.layui-table-patch .layui-table-cell{padding:0;width:30px}.layui-table-edit{position:absolute;left:0;top:0;width:100%;height:100%;padding:0 15px 1px;border:none}.layui-table-edit:focus{background-color:#F0F9F2}body .layui-table-tips .layui-layer-content{background:0 0;padding:0;box-shadow:0 1px 6px rgba(0,0,0,.1)}.layui-table-tips-main{margin:-44px 0 0 -1px;max-height:150px;padding:8px 15px;font-size:14px;overflow-y:scroll;background-color:#fff;color:#333;border:1px solid #e2e2e2}.layui-code,.layui-upload-list{margin:10px 0}.layui-table-tips-c{position:absolute;right:-3px;top:-12px;width:18px;height:18px;padding:3px;text-align:center;font-weight:700;border-radius:100%;font-size:14px;cursor:pointer;background-color:#666}.layui-table-tips-c:hover{background-color:#999}.layui-upload-file{display:none!important;opacity:.01;filter:Alpha(opacity=1)}.layui-upload-drag,.layui-upload-form,.layui-upload-wrap{display:inline-block}.layui-upload-choose{padding:0 10px;color:#999}.layui-upload-drag{position:relative;padding:30px;border:1px dashed #e2e2e2;background-color:#fff;text-align:center;cursor:pointer;color:#999}.layui-upload-drag .layui-icon{font-size:50px;color:#009688}.layui-upload-drag[lay-over]{border-color:#009688}.layui-upload-iframe{position:absolute;width:0;height:0;border:0;visibility:hidden}.layui-upload-wrap{position:relative;vertical-align:middle}.layui-upload-wrap .layui-upload-file{display:block!important;position:absolute;left:0;top:0;z-index:10;font-size:100px;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}.layui-code{position:relative;padding:15px;line-height:20px;border:1px solid #ddd;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New;font-size:12px}.layui-tree{line-height:26px}.layui-tree li{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-tree li .layui-tree-spread,.layui-tree li a{display:inline-block;vertical-align:top;height:26px;*display:inline;*zoom:1;cursor:pointer}.layui-tree li a{font-size:0}.layui-tree li a i{font-size:16px}.layui-tree li a cite{padding:0 6px;font-size:14px;font-style:normal}.layui-tree li i{padding-left:6px;color:#333;-moz-user-select:none}.layui-tree li .layui-tree-check{font-size:13px}.layui-tree li .layui-tree-check:hover{color:#009E94}.layui-tree li ul{display:none;margin-left:20px}.layui-tree li .layui-tree-enter{line-height:24px;border:1px dotted #000}.layui-tree-drag{display:none;position:absolute;left:-666px;top:-666px;background-color:#f2f2f2;padding:5px 10px;border:1px dotted #000;white-space:nowrap}.layui-tree-drag i{padding-right:5px}.layui-nav{position:relative;padding:0 20px;background-color:#393D49;color:#fff;border-radius:2px;font-size:0;box-sizing:border-box}.layui-nav *{font-size:14px}.layui-nav .layui-nav-item{position:relative;display:inline-block;*display:inline;*zoom:1;vertical-align:middle;line-height:60px}.layui-nav .layui-nav-item a{display:block;padding:0 20px;color:#fff;color:rgba(255,255,255,.7);transition:all .3s;-webkit-transition:all .3s}.layui-nav .layui-this:after,.layui-nav-bar,.layui-nav-tree .layui-nav-itemed:after{position:absolute;left:0;top:0;width:0;height:5px;background-color:#5FB878;transition:all .2s;-webkit-transition:all .2s}.layui-nav-bar{z-index:1000}.layui-nav .layui-nav-item a:hover,.layui-nav .layui-this a{color:#fff}.layui-nav .layui-this:after{content:'';top:auto;bottom:0;width:100%}.layui-nav-img{width:30px;height:30px;margin-right:10px;border-radius:50%}.layui-nav .layui-nav-more{content:'';width:0;height:0;border-style:solid dashed dashed;border-color:#fff transparent transparent;overflow:hidden;cursor:pointer;transition:all .2s;-webkit-transition:all .2s;position:absolute;top:28px;right:3px;border-width:6px;border-top-color:rgba(255,255,255,.7)}.layui-nav .layui-nav-mored,.layui-nav-itemed .layui-nav-more{top:22px;border-style:dashed dashed solid;border-color:transparent transparent #fff}.layui-nav-child{display:none;position:absolute;left:0;top:65px;min-width:100%;line-height:36px;padding:5px 0;box-shadow:0 2px 4px rgba(0,0,0,.12);border:1px solid #d2d2d2;background-color:#fff;z-index:100;border-radius:2px;white-space:nowrap}.layui-nav .layui-nav-child a{color:#333}.layui-nav .layui-nav-child a:hover{background-color:#f2f2f2;color:#000}.layui-nav-child dd{position:relative}.layui-nav .layui-nav-child dd.layui-this a,.layui-nav-child dd.layui-this{background-color:#5FB878;color:#fff}.layui-nav-child dd.layui-this:after{display:none}.layui-nav-tree{width:200px;padding:0}.layui-nav-tree .layui-nav-item{display:block;width:100%;line-height:45px}.layui-nav-tree .layui-nav-item a{height:45px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-nav-tree .layui-nav-item a:hover{background-color:#4E5465}.layui-nav-tree .layui-nav-bar{width:5px;height:0;background-color:#009688}.layui-nav-tree .layui-nav-child dd.layui-this,.layui-nav-tree .layui-nav-child dd.layui-this a,.layui-nav-tree .layui-this,.layui-nav-tree .layui-this>a,.layui-nav-tree .layui-this>a:hover{background-color:#009688;color:#fff}.layui-nav-tree .layui-this:after{display:none}.layui-nav-itemed>a,.layui-nav-tree .layui-nav-title a,.layui-nav-tree .layui-nav-title a:hover{color:#fff!important}.layui-nav-tree .layui-nav-child{position:relative;z-index:0;top:0;border:none;box-shadow:none}.layui-nav-tree .layui-nav-child a{height:40px;line-height:40px;color:#fff;color:rgba(255,255,255,.7)}.layui-nav-tree .layui-nav-child,.layui-nav-tree .layui-nav-child a:hover{background:0 0;color:#fff}.layui-nav-tree .layui-nav-more{top:20px;right:10px}.layui-nav-itemed .layui-nav-more{top:14px}.layui-nav-itemed .layui-nav-child{display:block;padding:0;background-color:rgba(0,0,0,.3)!important}.layui-nav-side{position:fixed;top:0;bottom:0;left:0;overflow-x:hidden;z-index:999}.layui-bg-blue .layui-nav-bar,.layui-bg-blue .layui-nav-itemed:after,.layui-bg-blue .layui-this:after{background-color:#93D1FF}.layui-bg-blue .layui-nav-child dd.layui-this{background-color:#1E9FFF}.layui-bg-blue .layui-nav-itemed>a,.layui-nav-tree.layui-bg-blue .layui-nav-title a,.layui-nav-tree.layui-bg-blue .layui-nav-title a:hover{background-color:#007DDB!important}.layui-breadcrumb{visibility:hidden;font-size:0}.layui-breadcrumb a{padding-right:8px;line-height:22px;font-size:14px;color:#333!important}.layui-breadcrumb a:hover{color:#01AAED!important}.layui-breadcrumb a cite,.layui-breadcrumb a span{color:#666;cursor:text;font-style:normal}.layui-breadcrumb a span{padding-left:8px;font-family:Sim sun}.layui-tab{margin:10px 0;text-align:left!important}.layui-tab[overflow]>.layui-tab-title{overflow:hidden}.layui-tab-title{position:relative;left:0;height:40px;white-space:nowrap;font-size:0;border-bottom:1px solid #e2e2e2;transition:all .2s;-webkit-transition:all .2s}.layui-tab-title li{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;font-size:14px;transition:all .2s;-webkit-transition:all .2s;position:relative;line-height:40px;min-width:65px;padding:0 15px;text-align:center;cursor:pointer}.layui-tab-title li a{display:block}.layui-tab-title .layui-this{color:#000}.layui-tab-title .layui-this:after{position:absolute;left:0;top:0;content:'';width:100%;height:41px;border:1px solid #e2e2e2;border-bottom-color:#fff;border-radius:2px 2px 0 0;box-sizing:border-box;pointer-events:none}.layui-tab-bar{position:absolute;right:0;top:0;z-index:10;width:30px;height:39px;line-height:39px;border:1px solid #e2e2e2;border-radius:2px;text-align:center;background-color:#fff;cursor:pointer}.layui-tab-bar .layui-icon{position:relative;display:inline-block;top:3px;transition:all .3s;-webkit-transition:all .3s}.layui-tab-item{display:none}.layui-tab-more{padding-right:30px;height:auto!important;white-space:normal!important}.layui-tab-more li.layui-this:after{border-bottom-color:#e2e2e2;border-radius:2px}.layui-tab-more .layui-tab-bar .layui-icon{top:-2px;top:3px\9;-webkit-transform:rotate(180deg);transform:rotate(180deg)}:root .layui-tab-more .layui-tab-bar .layui-icon{top:-2px\0/IE9}.layui-tab-content{padding:10px}.layui-tab-title li .layui-tab-close{position:relative;display:inline-block;width:18px;height:18px;line-height:20px;margin-left:8px;top:1px;text-align:center;font-size:14px;color:#c2c2c2;transition:all .2s;-webkit-transition:all .2s}.layui-tab-title li .layui-tab-close:hover{border-radius:2px;background-color:#FF5722;color:#fff}.layui-tab-brief>.layui-tab-title .layui-this{color:#009688}.layui-tab-brief>.layui-tab-more li.layui-this:after,.layui-tab-brief>.layui-tab-title .layui-this:after{border:none;border-radius:0;border-bottom:2px solid #5FB878}.layui-tab-brief[overflow]>.layui-tab-title .layui-this:after{top:-1px}.layui-tab-card{border:1px solid #e2e2e2;border-radius:2px;box-shadow:0 2px 5px 0 rgba(0,0,0,.1)}.layui-tab-card>.layui-tab-title{background-color:#f2f2f2}.layui-tab-card>.layui-tab-title li{margin-right:-1px;margin-left:-1px}.layui-tab-card>.layui-tab-title .layui-this{background-color:#fff}.layui-tab-card>.layui-tab-title .layui-this:after{border-top:none;border-width:1px;border-bottom-color:#fff}.layui-tab-card>.layui-tab-title .layui-tab-bar{height:40px;line-height:40px;border-radius:0;border-top:none;border-right:none}.layui-tab-card>.layui-tab-more .layui-this{background:0 0;color:#5FB878}.layui-tab-card>.layui-tab-more .layui-this:after{border:none}.layui-timeline{padding-left:5px}.layui-timeline-item{position:relative;padding-bottom:20px}.layui-timeline-axis{position:absolute;left:-5px;top:0;z-index:10;width:20px;height:20px;line-height:20px;background-color:#fff;color:#5FB878;border-radius:50%;text-align:center;cursor:pointer}.layui-timeline-axis:hover{color:#FF5722}.layui-timeline-item:before{content:'';position:absolute;left:5px;top:0;z-index:0;width:1px;height:100%;background-color:#e2e2e2}.layui-timeline-item:last-child:before{display:none}.layui-timeline-item:first-child:before{display:block}.layui-timeline-content{padding-left:25px}.layui-badge,.layui-badge-rim{line-height:18px;padding:0 5px}.layui-timeline-title{position:relative;margin-bottom:10px}.layui-badge,.layui-badge-dot,.layui-badge-rim{position:relative;display:inline-block;font-size:12px;background-color:#FF5722;color:#fff}.layui-badge{min-width:8px;height:18px;text-align:center;border-radius:9px}.layui-badge-dot{width:8px;height:8px;border-radius:50%}.layui-badge-rim{height:18px;border:1px solid #e2e2e2;border-radius:3px;background-color:#fff;color:#666}.layui-btn .layui-badge,.layui-btn .layui-badge-dot{margin-left:5px}.layui-nav .layui-badge,.layui-nav .layui-badge-dot{position:absolute;top:50%;margin:-10px 6px 0}.layui-tab-title .layui-badge,.layui-tab-title .layui-badge-dot{left:5px;top:-2px}.layui-carousel{position:relative;left:0;top:0;background-color:#f2f2f2}.layui-carousel>[carousel-item]{position:relative;width:100%;height:100%;overflow:hidden}.layui-carousel>[carousel-item]:before{position:absolute;content:'\e63d';left:50%;top:50%;width:100px;line-height:20px;margin:-10px 0 0 -50px;text-align:center;color:#999;font-family:layui-icon!important;font-size:20px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-carousel>[carousel-item]>*{display:none;position:absolute;left:0;top:0;width:100%;height:100%;background-color:#f2f2f2;transition-duration:.3s;-webkit-transition-duration:.3s}.layui-carousel-updown>*{-webkit-transition:.3s ease-in-out up;transition:.3s ease-in-out up}.layui-carousel-arrow{display:none\9;opacity:0;position:absolute;left:10px;top:50%;margin-top:-18px;width:36px;height:36px;line-height:36px;text-align:center;font-size:20px;border:0;border-radius:50%;background-color:rgba(0,0,0,.2);color:#fff;-webkit-transition-duration:.3s;transition-duration:.3s;cursor:pointer}.layui-carousel-arrow[lay-type=add]{left:auto!important;right:10px}.layui-carousel:hover .layui-carousel-arrow[lay-type=add],.layui-carousel[lay-arrow=always] .layui-carousel-arrow[lay-type=add]{right:20px}.layui-carousel[lay-arrow=always] .layui-carousel-arrow{opacity:1;left:20px}.layui-carousel[lay-arrow=none] .layui-carousel-arrow{display:none}.layui-carousel-arrow:hover,.layui-carousel-ind ul:hover{background-color:rgba(0,0,0,.35)}.layui-carousel:hover .layui-carousel-arrow{display:block\9;opacity:1;left:20px}.layui-carousel-ind{position:relative;top:-35px;width:100%;line-height:0!important;text-align:center;font-size:0}.layui-carousel[lay-indicator=outside]{margin-bottom:30px}.layui-carousel[lay-indicator=outside] .layui-carousel-ind{top:10px}.layui-carousel[lay-indicator=outside] .layui-carousel-ind ul{background-color:rgba(0,0,0,.5)}.layui-carousel[lay-indicator=none] .layui-carousel-ind{display:none}.layui-carousel-ind ul{display:inline-block;padding:5px;background-color:rgba(0,0,0,.2);border-radius:10px;-webkit-transition-duration:.3s;transition-duration:.3s}.layui-carousel-ind li{display:inline-block;width:10px;height:10px;margin:0 3px;font-size:14px;background-color:#e2e2e2;background-color:rgba(255,255,255,.5);border-radius:50%;cursor:pointer;-webkit-transition-duration:.3s;transition-duration:.3s}.layui-carousel-ind li:hover{background-color:rgba(255,255,255,.7)}.layui-carousel-ind li.layui-this{background-color:#fff}.layui-carousel>[carousel-item]>.layui-carousel-next,.layui-carousel>[carousel-item]>.layui-carousel-prev,.layui-carousel>[carousel-item]>.layui-this{display:block}.layui-carousel>[carousel-item]>.layui-this{left:0}.layui-carousel>[carousel-item]>.layui-carousel-prev{left:-100%}.layui-carousel>[carousel-item]>.layui-carousel-next{left:100%}.layui-carousel>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel>[carousel-item]>.layui-carousel-prev.layui-carousel-right{left:0}.layui-carousel>[carousel-item]>.layui-this.layui-carousel-left{left:-100%}.layui-carousel>[carousel-item]>.layui-this.layui-carousel-right{left:100%}.layui-carousel[lay-anim=updown] .layui-carousel-arrow{left:50%!important;top:20px;margin:0 0 0 -18px}.layui-carousel[lay-anim=updown]>[carousel-item]>*,.layui-carousel[lay-anim=fade]>[carousel-item]>*{left:0!important}.layui-carousel[lay-anim=updown] .layui-carousel-arrow[lay-type=add]{top:auto!important;bottom:20px}.layui-carousel[lay-anim=updown] .layui-carousel-ind{position:absolute;top:50%;right:20px;width:auto;height:auto}.layui-carousel[lay-anim=updown] .layui-carousel-ind ul{padding:3px 5px}.layui-carousel[lay-anim=updown] .layui-carousel-ind li{display:block;margin:6px 0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this{top:0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-prev{top:-100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-next{top:100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-prev.layui-carousel-right{top:0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this.layui-carousel-left{top:-100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this.layui-carousel-right{top:100%}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-next,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-prev{opacity:0}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-prev.layui-carousel-right{opacity:1}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-this.layui-carousel-left,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-this.layui-carousel-right{opacity:0}.layui-fixbar{position:fixed;right:15px;bottom:15px;z-index:9999}.layui-fixbar li{width:50px;height:50px;line-height:50px;margin-bottom:1px;text-align:center;cursor:pointer;font-size:30px;background-color:#9F9F9F;color:#fff;border-radius:2px;opacity:.95}.layui-fixbar li:hover{opacity:.85}.layui-fixbar li:active{opacity:1}.layui-fixbar .layui-fixbar-top{display:none;font-size:40px}body .layui-util-face{border:none;background:0 0}body .layui-util-face .layui-layer-content{padding:0;background-color:#fff;color:#666;box-shadow:none}.layui-util-face .layui-layer-TipsG{display:none}.layui-util-face ul{position:relative;width:372px;padding:10px;border:1px solid #D9D9D9;background-color:#fff;box-shadow:0 0 20px rgba(0,0,0,.2)}.layui-util-face ul li{cursor:pointer;float:left;border:1px solid #e8e8e8;height:22px;width:26px;overflow:hidden;margin:-1px 0 0 -1px;padding:4px 2px;text-align:center}.layui-util-face ul li:hover{position:relative;z-index:2;border:1px solid #eb7350;background:#fff9ec}.layui-anim{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-anim.layui-icon{display:inline-block}.layui-anim-loop{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}@-webkit-keyframes layui-rotate{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@keyframes layui-rotate{from{transform:rotate(0)}to{transform:rotate(360deg)}}.layui-anim-rotate{-webkit-animation-name:layui-rotate;animation-name:layui-rotate;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-timing-function:linear;animation-timing-function:linear}@-webkit-keyframes layui-up{from{-webkit-transform:translate3d(0,100%,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes layui-up{from{transform:translate3d(0,100%,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-anim-up{-webkit-animation-name:layui-up;animation-name:layui-up}@-webkit-keyframes layui-upbit{from{-webkit-transform:translate3d(0,30px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes layui-upbit{from{transform:translate3d(0,30px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-anim-upbit{-webkit-animation-name:layui-upbit;animation-name:layui-upbit}@-webkit-keyframes layui-scale{0%{opacity:.3;-webkit-transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes layui-scale{0%{opacity:.3;-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-ms-transform:scale(1);transform:scale(1)}}.layui-anim-scale{-webkit-animation-name:layui-scale;animation-name:layui-scale}@-webkit-keyframes layui-scale-spring{0%{opacity:.5;-webkit-transform:scale(.5)}80%{opacity:.8;-webkit-transform:scale(1.1)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes layui-scale-spring{0%{opacity:.5;transform:scale(.5)}80%{opacity:.8;transform:scale(1.1)}100%{opacity:1;transform:scale(1)}}.layui-anim-scaleSpring{-webkit-animation-name:layui-scale-spring;animation-name:layui-scale-spring}@-webkit-keyframes layui-fadein{0%{opacity:0}100%{opacity:1}}@keyframes layui-fadein{0%{opacity:0}100%{opacity:1}}.layui-anim-fadein{-webkit-animation-name:layui-fadein;animation-name:layui-fadein}@-webkit-keyframes layui-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes layui-fadeout{0%{opacity:1}100%{opacity:0}}.layui-anim-fadeout{-webkit-animation-name:layui-fadeout;animation-name:layui-fadeout} \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + .layui-inline,img{display:inline-block;vertical-align:middle}h1,h2,h3,h4,h5,h6{font-weight:400}.layui-edge,.layui-header,.layui-inline,.layui-main{position:relative}.layui-btn,.layui-edge,.layui-inline,img{vertical-align:middle}.layui-btn,.layui-disabled,.layui-icon,.layui-unselect{-webkit-user-select:none;-ms-user-select:none;-moz-user-select:none}blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,li,ol,p,pre,td,textarea,th,ul{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}a:active,a:hover{outline:0}img{border:none}li{list-style:none}table{border-collapse:collapse;border-spacing:0}h4,h5,h6{font-size:100%}button,input,optgroup,option,select,textarea{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;outline:0}pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}body{line-height:24px;font:14px Helvetica Neue,Helvetica,PingFang SC,\5FAE\8F6F\96C5\9ED1,Tahoma,Arial,sans-serif}hr{height:1px;margin:10px 0;border:0;clear:both}a{color:#333;text-decoration:none}a:hover{color:#777}a cite{font-style:normal;*cursor:pointer}.layui-border-box,.layui-border-box *{box-sizing:border-box}.layui-box,.layui-box *{box-sizing:content-box}.layui-clear{clear:both;*zoom:1}.layui-clear:after{content:'\20';clear:both;*zoom:1;display:block;height:0}.layui-inline{*display:inline;*zoom:1}.layui-edge{display:inline-block;width:0;height:0;border-width:6px;border-style:dashed;border-color:transparent;overflow:hidden}.layui-edge-top{top:-4px;border-bottom-color:#999;border-bottom-style:solid}.layui-edge-right{border-left-color:#999;border-left-style:solid}.layui-edge-bottom{top:2px;border-top-color:#999;border-top-style:solid}.layui-edge-left{border-right-color:#999;border-right-style:solid}.layui-elip{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-disabled,.layui-disabled:hover{color:#d2d2d2!important;cursor:not-allowed!important}.layui-circle{border-radius:100%}.layui-show{display:block!important}.layui-hide{display:none!important}@font-face{font-family:layui-icon;src:url(../font/iconfont.eot?v=220);src:url(../font/iconfont.eot?v=220#iefix) format('embedded-opentype'),url(../font/iconfont.svg?v=220#iconfont) format('svg'),url(../font/iconfont.woff?v=220) format('woff'),url(../font/iconfont.ttf?v=220) format('truetype')}.layui-icon{font-family:layui-icon!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-icon-duihua:before{content:"\e611"}.layui-icon-shezhi:before{content:"\e614"}.layui-icon-yinshenim:before{content:"\e60f"}.layui-icon-search:before{content:"\e615"}.layui-icon-fenxiang1:before{content:"\e641"}.layui-icon-shezhi1:before{content:"\e620"}.layui-icon-yinqing:before{content:"\e628"}.layui-icon-yuejuancuohao:before{content:"\1006"}.layui-icon-cuo:before{content:"\1007"}.layui-icon-baobiao:before{content:"\e629"}.layui-icon-star:before{content:"\e600"}.layui-icon-yuandian:before{content:"\e617"}.layui-icon-chat:before{content:"\e606"}.layui-icon-logo:before{content:"\e609"}.layui-icon-list:before{content:"\e60a"}.layui-icon-tubiao:before{content:"\e62c"}.layui-icon-right:before{content:"\1005"}.layui-icon-huanfu2:before{content:"\e61b"}.layui-icon-On-line:before{content:"\e610"}.layui-icon-biaoge:before{content:"\e62d"}.layui-icon-youyou:before{content:"\e602"}.layui-icon-zuozuo:before{content:"\e603"}.layui-icon-cart-simple:before{content:"\e698"}.layui-icon-cry:before{content:"\e69c"}.layui-icon-smile:before{content:"\e6af"}.layui-icon-survey:before{content:"\e6b2"}.layui-icon-tree:before{content:"\e62e"}.layui-icon-iconfont17:before{content:"\e62f"}.layui-icon-tianjia:before{content:"\e61f"}.layui-icon-xiazai:before{content:"\e601"}.layui-icon-xuanzemoban48:before{content:"\e630"}.layui-icon-gongju:before{content:"\e631"}.layui-icon-face-surprised:before{content:"\e664"}.layui-icon-bianji:before{content:"\e642"}.layui-icon-speaker:before{content:"\e645"}.layui-icon-xiangxia:before{content:"\e61a"}.layui-icon-wenjian:before{content:"\e621"}.layui-icon-layouts:before{content:"\e632"}.layui-icon-duigou:before{content:"\e618"}.layui-icon-tianjia1:before{content:"\e608"}.layui-icon-yaoyaozhibofanye:before{content:"\e633"}.layui-icon-read:before{content:"\e705"}.layui-icon-404:before{content:"\e61c"}.layui-icon-lunbozutu:before{content:"\e634"}.layui-icon-help:before{content:"\e607"}.layui-icon-daima1:before{content:"\e635"}.layui-icon-jinshui:before{content:"\e636"}.layui-icon-find-fill:before{content:"\e670"}.layui-icon-about:before{content:"\e60b"}.layui-icon-location:before{content:"\e715"}.layui-icon-xiangshang:before{content:"\e619"}.layui-icon-pause:before{content:"\e651"}.layui-icon-riqi:before{content:"\e637"}.layui-icon-uploadfile:before{content:"\e61d"}.layui-icon-delete:before{content:"\e640"}.layui-icon-play:before{content:"\e652"}.layui-icon-top:before{content:"\e604"}.layui-icon-haoyouqingqiu:before{content:"\e612"}.layui-icon-weibiaoti1:before{content:"\e605"}.layui-icon-chuangkou:before{content:"\e638"}.layui-icon-comiisbiaoqing:before{content:"\e60c"}.layui-icon-zhengque:before{content:"\e616"}.layui-icon-dollar:before{content:"\e659"}.layui-icon-iconfontwodehaoyou:before{content:"\e613"}.layui-icon-wenjianxiazai:before{content:"\e61e"}.layui-icon-tupian:before{content:"\e60d"}.layui-icon-lianjie:before{content:"\e64c"}.layui-icon-diamond:before{content:"\e735"}.layui-icon-jilu:before{content:"\e60e"}.layui-icon-liucheng:before{content:"\e622"}.layui-icon-fontstrikethrough:before{content:"\e64f"}.layui-icon-unlink:before{content:"\e64d"}.layui-icon-bianjiwenzi:before{content:"\e639"}.layui-icon-sanjiao:before{content:"\e623"}.layui-icon-danxuankuanghouxuan:before{content:"\e63f"}.layui-icon-danxuankuangxuanzhong:before{content:"\e643"}.layui-icon-juzhongduiqi:before{content:"\e647"}.layui-icon-youduiqi:before{content:"\e648"}.layui-icon-zuoduiqi:before{content:"\e649"}.layui-icon-gongsisvgtubiaozongji22:before{content:"\e626"}.layui-icon-gongsisvgtubiaozongji23:before{content:"\e627"}.layui-icon-refresh-2:before{content:"\1002"}.layui-icon-loading-1:before{content:"\e63e"}.layui-icon-return:before{content:"\e65c"}.layui-icon-jiacu:before{content:"\e62b"}.layui-icon-uploading:before{content:"\e67c"}.layui-icon-liaotianduihuaimgoutong:before{content:"\e63a"}.layui-icon-video:before{content:"\e6ed"}.layui-icon-headset:before{content:"\e6fc"}.layui-icon-wenjianjiafan:before{content:"\e624"}.layui-icon-shouji:before{content:"\e63b"}.layui-icon-tianjia2:before{content:"\e654"}.layui-icon-wenjianjia:before{content:"\e7a0"}.layui-icon-biaoqing:before{content:"\e650"}.layui-icon-html:before{content:"\e64b"}.layui-icon-biaodan:before{content:"\e63c"}.layui-icon-cart:before{content:"\e657"}.layui-icon-camera-fill:before{content:"\e65d"}.layui-icon-25:before{content:"\e62a"}.layui-icon-emwdaima:before{content:"\e64e"}.layui-icon-fire:before{content:"\e756"}.layui-icon-set:before{content:"\e716"}.layui-icon-zitixiahuaxian:before{content:"\e646"}.layui-icon-sanjiao1:before{content:"\e625"}.layui-icon-tips:before{content:"\e702"}.layui-icon-tupian-copy-copy:before{content:"\e64a"}.layui-icon-more-vertical:before{content:"\e671"}.layui-icon-zhuti2:before{content:"\e66c"}.layui-icon-loading:before{content:"\e63d"}.layui-icon-xieti:before{content:"\e644"}.layui-icon-refresh-1:before{content:"\e666"}.layui-icon-rmb:before{content:"\e65e"}.layui-icon-home:before{content:"\e68e"}.layui-icon-user:before{content:"\e770"}.layui-icon-notice:before{content:"\e667"}.layui-icon-voice:before{content:"\e688"}.layui-icon-download:before{content:"\e681"}.layui-icon-snowflake:before{content:"\e6b1"}.layui-icon-yemian1:before{content:"\e655"}.layui-icon-template:before{content:"\e663"}.layui-icon-auz:before{content:"\e672"}.layui-icon-console:before{content:"\e665"}.layui-icon-app:before{content:"\e653"}.layui-icon-xiayiye:before{content:"\e65a"}.layui-icon-website:before{content:"\e7ae"}.layui-icon-xiayiye1:before{content:"\e65b"}.layui-icon-component:before{content:"\e857"}.layui-icon-more:before{content:"\e65f"}.layui-icon-shrink-right:before{content:"\e668"}.layui-icon-spread-left:before{content:"\e66b"}.layui-icon-camera:before{content:"\e660"}.layui-icon-note:before{content:"\e66e"}.layui-icon-refresh:before{content:"\e669"}.layui-icon-nv:before{content:"\e661"}.layui-icon-nan:before{content:"\e662"}.layui-icon-senior:before{content:"\e674"}.layui-icon-theme:before{content:"\e66a"}.layui-icon-tread:before{content:"\e6c5"}.layui-icon-praise:before{content:"\e6c6"}.layui-icon-star-fill:before{content:"\e658"}.layui-icon-template-1:before{content:"\e656"}.layui-icon-loading-2:before{content:"\e66d"}.layui-main{width:1140px;margin:0 auto}.layui-header{z-index:1000;height:60px}.layui-header a:hover{transition:all .5s;-webkit-transition:all .5s}.layui-side{position:fixed;top:0;bottom:0;z-index:999;width:200px;overflow-x:hidden}.layui-side-scroll{width:220px;height:100%;overflow-x:hidden}.layui-body{position:absolute;left:200px;right:0;top:0;bottom:0;z-index:998;width:auto;overflow:hidden;overflow-y:auto;box-sizing:border-box}.layui-layout-body{overflow:hidden}.layui-layout-admin .layui-header{background-color:#23262E}.layui-layout-admin .layui-side{top:60px;width:200px;overflow-x:hidden}.layui-layout-admin .layui-body{top:60px;bottom:44px}.layui-layout-admin .layui-main{width:auto;margin:0 15px}.layui-layout-admin .layui-footer{position:fixed;left:200px;right:0;bottom:0;height:44px;line-height:44px;padding:0 15px;background-color:#eee}.layui-layout-admin .layui-logo{position:absolute;left:0;top:0;width:200px;height:100%;line-height:60px;text-align:center;color:#009688;font-size:16px}.layui-layout-admin .layui-header .layui-nav{background:0 0}.layui-layout-left{position:absolute!important;left:200px;top:0}.layui-layout-right{position:absolute!important;right:0;top:0}.layui-container{position:relative;margin:0 auto;padding:0 15px;box-sizing:border-box}.layui-fluid{position:relative;margin:0 auto;padding:0 15px}.layui-row:after,.layui-row:before{content:'';display:block;clear:both}.layui-col-lg1,.layui-col-lg10,.layui-col-lg11,.layui-col-lg12,.layui-col-lg2,.layui-col-lg3,.layui-col-lg4,.layui-col-lg5,.layui-col-lg6,.layui-col-lg7,.layui-col-lg8,.layui-col-lg9,.layui-col-md1,.layui-col-md10,.layui-col-md11,.layui-col-md12,.layui-col-md2,.layui-col-md3,.layui-col-md4,.layui-col-md5,.layui-col-md6,.layui-col-md7,.layui-col-md8,.layui-col-md9,.layui-col-sm1,.layui-col-sm10,.layui-col-sm11,.layui-col-sm12,.layui-col-sm2,.layui-col-sm3,.layui-col-sm4,.layui-col-sm5,.layui-col-sm6,.layui-col-sm7,.layui-col-sm8,.layui-col-sm9,.layui-col-xs1,.layui-col-xs10,.layui-col-xs11,.layui-col-xs12,.layui-col-xs2,.layui-col-xs3,.layui-col-xs4,.layui-col-xs5,.layui-col-xs6,.layui-col-xs7,.layui-col-xs8,.layui-col-xs9{position:relative;display:block;box-sizing:border-box}.layui-col-xs1,.layui-col-xs10,.layui-col-xs11,.layui-col-xs12,.layui-col-xs2,.layui-col-xs3,.layui-col-xs4,.layui-col-xs5,.layui-col-xs6,.layui-col-xs7,.layui-col-xs8,.layui-col-xs9{float:left}.layui-col-xs1{width:8.33333333%}.layui-col-xs2{width:16.66666667%}.layui-col-xs3{width:25%}.layui-col-xs4{width:33.33333333%}.layui-col-xs5{width:41.66666667%}.layui-col-xs6{width:50%}.layui-col-xs7{width:58.33333333%}.layui-col-xs8{width:66.66666667%}.layui-col-xs9{width:75%}.layui-col-xs10{width:83.33333333%}.layui-col-xs11{width:91.66666667%}.layui-col-xs12{width:100%}.layui-col-xs-offset1{margin-left:8.33333333%}.layui-col-xs-offset2{margin-left:16.66666667%}.layui-col-xs-offset3{margin-left:25%}.layui-col-xs-offset4{margin-left:33.33333333%}.layui-col-xs-offset5{margin-left:41.66666667%}.layui-col-xs-offset6{margin-left:50%}.layui-col-xs-offset7{margin-left:58.33333333%}.layui-col-xs-offset8{margin-left:66.66666667%}.layui-col-xs-offset9{margin-left:75%}.layui-col-xs-offset10{margin-left:83.33333333%}.layui-col-xs-offset11{margin-left:91.66666667%}.layui-col-xs-offset12{margin-left:100%}@media screen and (max-width:768px){.layui-hide-xs{display:none!important}.layui-show-xs-block{display:block!important}.layui-show-xs-inline{display:inline!important}.layui-show-xs-inline-block{display:inline-block!important}}@media screen and (min-width:768px){.layui-container{width:750px}.layui-hide-sm{display:none!important}.layui-show-sm-block{display:block!important}.layui-show-sm-inline{display:inline!important}.layui-show-sm-inline-block{display:inline-block!important}.layui-col-sm1,.layui-col-sm10,.layui-col-sm11,.layui-col-sm12,.layui-col-sm2,.layui-col-sm3,.layui-col-sm4,.layui-col-sm5,.layui-col-sm6,.layui-col-sm7,.layui-col-sm8,.layui-col-sm9{float:left}.layui-col-sm1{width:8.33333333%}.layui-col-sm2{width:16.66666667%}.layui-col-sm3{width:25%}.layui-col-sm4{width:33.33333333%}.layui-col-sm5{width:41.66666667%}.layui-col-sm6{width:50%}.layui-col-sm7{width:58.33333333%}.layui-col-sm8{width:66.66666667%}.layui-col-sm9{width:75%}.layui-col-sm10{width:83.33333333%}.layui-col-sm11{width:91.66666667%}.layui-col-sm12{width:100%}.layui-col-sm-offset1{margin-left:8.33333333%}.layui-col-sm-offset2{margin-left:16.66666667%}.layui-col-sm-offset3{margin-left:25%}.layui-col-sm-offset4{margin-left:33.33333333%}.layui-col-sm-offset5{margin-left:41.66666667%}.layui-col-sm-offset6{margin-left:50%}.layui-col-sm-offset7{margin-left:58.33333333%}.layui-col-sm-offset8{margin-left:66.66666667%}.layui-col-sm-offset9{margin-left:75%}.layui-col-sm-offset10{margin-left:83.33333333%}.layui-col-sm-offset11{margin-left:91.66666667%}.layui-col-sm-offset12{margin-left:100%}}@media screen and (min-width:992px){.layui-container{width:970px}.layui-hide-md{display:none!important}.layui-show-md-block{display:block!important}.layui-show-md-inline{display:inline!important}.layui-show-md-inline-block{display:inline-block!important}.layui-col-md1,.layui-col-md10,.layui-col-md11,.layui-col-md12,.layui-col-md2,.layui-col-md3,.layui-col-md4,.layui-col-md5,.layui-col-md6,.layui-col-md7,.layui-col-md8,.layui-col-md9{float:left}.layui-col-md1{width:8.33333333%}.layui-col-md2{width:16.66666667%}.layui-col-md3{width:25%}.layui-col-md4{width:33.33333333%}.layui-col-md5{width:41.66666667%}.layui-col-md6{width:50%}.layui-col-md7{width:58.33333333%}.layui-col-md8{width:66.66666667%}.layui-col-md9{width:75%}.layui-col-md10{width:83.33333333%}.layui-col-md11{width:91.66666667%}.layui-col-md12{width:100%}.layui-col-md-offset1{margin-left:8.33333333%}.layui-col-md-offset2{margin-left:16.66666667%}.layui-col-md-offset3{margin-left:25%}.layui-col-md-offset4{margin-left:33.33333333%}.layui-col-md-offset5{margin-left:41.66666667%}.layui-col-md-offset6{margin-left:50%}.layui-col-md-offset7{margin-left:58.33333333%}.layui-col-md-offset8{margin-left:66.66666667%}.layui-col-md-offset9{margin-left:75%}.layui-col-md-offset10{margin-left:83.33333333%}.layui-col-md-offset11{margin-left:91.66666667%}.layui-col-md-offset12{margin-left:100%}}@media screen and (min-width:1200px){.layui-container{width:1170px}.layui-hide-lg{display:none!important}.layui-show-lg-block{display:block!important}.layui-show-lg-inline{display:inline!important}.layui-show-lg-inline-block{display:inline-block!important}.layui-col-lg1,.layui-col-lg10,.layui-col-lg11,.layui-col-lg12,.layui-col-lg2,.layui-col-lg3,.layui-col-lg4,.layui-col-lg5,.layui-col-lg6,.layui-col-lg7,.layui-col-lg8,.layui-col-lg9{float:left}.layui-col-lg1{width:8.33333333%}.layui-col-lg2{width:16.66666667%}.layui-col-lg3{width:25%}.layui-col-lg4{width:33.33333333%}.layui-col-lg5{width:41.66666667%}.layui-col-lg6{width:50%}.layui-col-lg7{width:58.33333333%}.layui-col-lg8{width:66.66666667%}.layui-col-lg9{width:75%}.layui-col-lg10{width:83.33333333%}.layui-col-lg11{width:91.66666667%}.layui-col-lg12{width:100%}.layui-col-lg-offset1{margin-left:8.33333333%}.layui-col-lg-offset2{margin-left:16.66666667%}.layui-col-lg-offset3{margin-left:25%}.layui-col-lg-offset4{margin-left:33.33333333%}.layui-col-lg-offset5{margin-left:41.66666667%}.layui-col-lg-offset6{margin-left:50%}.layui-col-lg-offset7{margin-left:58.33333333%}.layui-col-lg-offset8{margin-left:66.66666667%}.layui-col-lg-offset9{margin-left:75%}.layui-col-lg-offset10{margin-left:83.33333333%}.layui-col-lg-offset11{margin-left:91.66666667%}.layui-col-lg-offset12{margin-left:100%}}.layui-col-space1{margin:-.5px}.layui-col-space1>*{padding:.5px}.layui-col-space3{margin:-1.5px}.layui-col-space3>*{padding:1.5px}.layui-col-space5{margin:-2.5px}.layui-col-space5>*{padding:2.5px}.layui-col-space8{margin:-3.5px}.layui-col-space8>*{padding:3.5px}.layui-col-space10{margin:-5px}.layui-col-space10>*{padding:5px}.layui-col-space12{margin:-6px}.layui-col-space12>*{padding:6px}.layui-col-space15{margin:-7.5px}.layui-col-space15>*{padding:7.5px}.layui-col-space18{margin:-9px}.layui-col-space18>*{padding:9px}.layui-col-space20{margin:-10px}.layui-col-space20>*{padding:10px}.layui-col-space22{margin:-11px}.layui-col-space22>*{padding:11px}.layui-col-space25{margin:-12.5px}.layui-col-space25>*{padding:12.5px}.layui-col-space30{margin:-15px}.layui-col-space30>*{padding:15px}.layui-btn,.layui-input,.layui-select,.layui-textarea,.layui-upload-button{outline:0;transition:all .3s;-webkit-transition:all .3s;box-sizing:border-box}.layui-elem-quote{margin-bottom:10px;padding:15px;line-height:22px;border-left:5px solid #009688;border-radius:0 2px 2px 0;background-color:#f2f2f2}.layui-quote-nm{border-style:solid;border-width:1px 1px 1px 5px;background:0 0}.layui-elem-field{margin-bottom:10px;padding:0;border-width:1px;border-style:solid}.layui-elem-field legend{margin-left:20px;padding:0 10px;font-size:20px;font-weight:300}.layui-field-title{margin:10px 0 20px;border-width:1px 0 0}.layui-field-box{padding:10px 15px}.layui-field-title .layui-field-box{padding:10px 0}.layui-progress{position:relative;height:6px;border-radius:20px;background-color:#e2e2e2}.layui-progress-bar{position:absolute;left:0;top:0;width:0;max-width:100%;height:6px;border-radius:20px;text-align:right;background-color:#5FB878;transition:all .3s;-webkit-transition:all .3s}.layui-progress-big,.layui-progress-big .layui-progress-bar{height:18px;line-height:18px}.layui-progress-text{position:relative;top:-20px;line-height:18px;font-size:12px;color:#666}.layui-progress-big .layui-progress-text{position:static;padding:0 10px;color:#fff}.layui-collapse{border-width:1px;border-style:solid;border-radius:2px}.layui-colla-content,.layui-colla-item{border-top-width:1px;border-top-style:solid}.layui-colla-item:first-child{border-top:none}.layui-colla-title{position:relative;height:42px;line-height:42px;padding:0 15px 0 35px;color:#333;background-color:#f2f2f2;cursor:pointer;font-size:14px}.layui-colla-content{display:none;padding:10px 15px;line-height:22px;color:#666}.layui-colla-icon{position:absolute;left:15px;top:0;font-size:14px}.layui-card-body,.layui-card-header,.layui-form-label,.layui-form-mid,.layui-form-select,.layui-input-block,.layui-input-inline,.layui-textarea{position:relative}.layui-card{margin-bottom:15px;border-radius:2px;background-color:#fff;box-shadow:0 1px 2px 0 rgba(0,0,0,.05)}.layui-card:last-child{margin-bottom:0}.layui-card-header{height:42px;line-height:42px;padding:0 15px;border-bottom:1px solid #f6f6f6;color:#333;border-radius:2px 2px 0 0;font-size:14px}.layui-bg-black,.layui-bg-blue,.layui-bg-cyan,.layui-bg-green,.layui-bg-orange,.layui-bg-red{color:#fff!important}.layui-card-body{padding:10px 15px;line-height:24px}.layui-card-body .layui-table{margin:5px 0}.layui-card .layui-tab{margin:0}.layui-panel-window{position:relative;padding:15px;border-radius:0;border-top:5px solid #E6E6E6;background-color:#fff}.layui-bg-red{background-color:#FF5722!important}.layui-bg-orange{background-color:#FFB800!important}.layui-bg-green{background-color:#009688!important}.layui-bg-cyan{background-color:#2F4056!important}.layui-bg-blue{background-color:#1E9FFF!important}.layui-bg-black{background-color:#393D49!important}.layui-bg-gray{background-color:#eee!important;color:#666!important}.layui-badge-rim,.layui-colla-content,.layui-colla-item,.layui-collapse,.layui-elem-field,.layui-form-pane .layui-form-item[pane],.layui-form-pane .layui-form-label,.layui-input,.layui-layedit,.layui-layedit-tool,.layui-quote-nm,.layui-select,.layui-tab-bar,.layui-tab-card,.layui-tab-title,.layui-tab-title .layui-this:after,.layui-textarea{border-color:#e6e6e6}.layui-timeline-item:before,hr{background-color:#e6e6e6}.layui-text{line-height:22px;font-size:14px;color:#666}.layui-text h1,.layui-text h2,.layui-text h3{font-weight:500;color:#333}.layui-text h1{font-size:30px}.layui-text h2{font-size:24px}.layui-text h3{font-size:18px}.layui-text a:not(.layui-btn){color:#01AAED}.layui-text a:not(.layui-btn):hover{text-decoration:underline}.layui-text ul{padding:5px 0 5px 15px}.layui-text ul li{margin-top:5px;list-style-type:disc}.layui-text em,.layui-word-aux{color:#999!important;padding:0 5px!important}.layui-btn{display:inline-block;height:38px;line-height:38px;padding:0 18px;background-color:#009688;color:#fff;white-space:nowrap;text-align:center;font-size:14px;border:none;border-radius:2px;cursor:pointer}.layui-btn:hover{opacity:.8;filter:alpha(opacity=80);color:#fff}.layui-btn:active{opacity:1;filter:alpha(opacity=100)}.layui-btn+.layui-btn{margin-left:10px}.layui-btn-container{font-size:0}.layui-btn-container .layui-btn{margin-right:10px;margin-bottom:10px}.layui-btn-container .layui-btn+.layui-btn{margin-left:0}.layui-table .layui-btn-container .layui-btn{margin-bottom:9px}.layui-btn-radius{border-radius:100px}.layui-btn .layui-icon{margin-right:3px;font-size:18px;vertical-align:bottom;vertical-align:middle\9}.layui-btn-primary{border:1px solid #C9C9C9;background-color:#fff;color:#555}.layui-btn-primary:hover{border-color:#009688;color:#333}.layui-btn-normal{background-color:#1E9FFF}.layui-btn-warm{background-color:#FFB800}.layui-btn-danger{background-color:#FF5722}.layui-btn-disabled,.layui-btn-disabled:active,.layui-btn-disabled:hover{border:1px solid #e6e6e6;background-color:#FBFBFB;color:#C9C9C9;cursor:not-allowed;opacity:1}.layui-btn-lg{height:44px;line-height:44px;padding:0 25px;font-size:16px}.layui-btn-sm{height:30px;line-height:30px;padding:0 10px;font-size:12px}.layui-btn-sm i{font-size:16px!important}.layui-btn-xs{height:22px;line-height:22px;padding:0 5px;font-size:12px}.layui-btn-xs i{font-size:14px!important}.layui-btn-group{display:inline-block;vertical-align:middle;font-size:0}.layui-btn-group .layui-btn{margin-left:0!important;margin-right:0!important;border-left:1px solid rgba(255,255,255,.5);border-radius:0}.layui-btn-group .layui-btn-primary{border-left:none}.layui-btn-group .layui-btn-primary:hover{border-color:#C9C9C9;color:#009688}.layui-btn-group .layui-btn:first-child{border-left:none;border-radius:2px 0 0 2px}.layui-btn-group .layui-btn-primary:first-child{border-left:1px solid #c9c9c9}.layui-btn-group .layui-btn:last-child{border-radius:0 2px 2px 0}.layui-btn-group .layui-btn+.layui-btn{margin-left:0}.layui-btn-group+.layui-btn-group{margin-left:10px}.layui-btn-fluid{width:100%}.layui-input,.layui-select,.layui-textarea{height:38px;line-height:1.3;line-height:38px\9;border-width:1px;border-style:solid;background-color:#fff;border-radius:2px}.layui-input::-webkit-input-placeholder,.layui-select::-webkit-input-placeholder,.layui-textarea::-webkit-input-placeholder{line-height:1.3}.layui-input,.layui-textarea{display:block;width:100%;padding-left:10px}.layui-input:hover,.layui-textarea:hover{border-color:#D2D2D2!important}.layui-input:focus,.layui-textarea:focus{border-color:#C9C9C9!important}.layui-textarea{min-height:100px;height:auto;line-height:20px;padding:6px 10px;resize:vertical}.layui-select{padding:0 10px}.layui-form input[type=checkbox],.layui-form input[type=radio],.layui-form select{display:none}.layui-form [lay-ignore]{display:initial}.layui-form-item{margin-bottom:15px;clear:both;*zoom:1}.layui-form-item:after{content:'\20';clear:both;*zoom:1;display:block;height:0}.layui-form-label{float:left;display:block;padding:9px 15px;width:80px;font-weight:400;line-height:20px;text-align:right}.layui-form-label-col{display:block;float:none;padding:9px 0;line-height:20px;text-align:left}.layui-form-item .layui-inline{margin-bottom:5px;margin-right:10px}.layui-input-block{margin-left:110px;min-height:36px}.layui-input-inline{display:inline-block;vertical-align:middle}.layui-form-item .layui-input-inline{float:left;width:190px;margin-right:10px}.layui-form-text .layui-input-inline{width:auto}.layui-form-mid{float:left;display:block;padding:9px 0!important;line-height:20px;margin-right:10px}.layui-form-danger+.layui-form-select .layui-input,.layui-form-danger:focus{border-color:#FF5722!important}.layui-form-select .layui-input{padding-right:30px;cursor:pointer}.layui-form-select .layui-edge{position:absolute;right:10px;top:50%;margin-top:-3px;cursor:pointer;border-width:6px;border-top-color:#c2c2c2;border-top-style:solid;transition:all .3s;-webkit-transition:all .3s}.layui-form-select dl{display:none;position:absolute;left:0;top:42px;padding:5px 0;z-index:999;min-width:100%;border:1px solid #d2d2d2;max-height:300px;overflow-y:auto;background-color:#fff;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.12);box-sizing:border-box}.layui-form-select dl dd,.layui-form-select dl dt{padding:0 10px;line-height:36px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.layui-form-select dl dt{font-size:12px;color:#999}.layui-form-select dl dd{cursor:pointer}.layui-form-select dl dd:hover{background-color:#f2f2f2}.layui-form-select .layui-select-group dd{padding-left:20px}.layui-form-select dl dd.layui-select-tips{padding-left:10px!important;color:#999}.layui-form-select dl dd.layui-this{background-color:#5FB878;color:#fff}.layui-form-checkbox,.layui-form-select dl dd.layui-disabled{background-color:#fff}.layui-form-selected dl{display:block}.layui-form-checkbox,.layui-form-checkbox *,.layui-form-switch{display:inline-block;vertical-align:middle}.layui-form-selected .layui-edge{margin-top:-9px;-webkit-transform:rotate(180deg);transform:rotate(180deg);margin-top:-3px\9}:root .layui-form-selected .layui-edge{margin-top:-9px\0/IE9}.layui-form-selectup dl{top:auto;bottom:42px}.layui-select-none{margin:5px 0;text-align:center;color:#999}.layui-select-disabled .layui-disabled{border-color:#eee!important}.layui-select-disabled .layui-edge{border-top-color:#d2d2d2}.layui-form-checkbox{position:relative;height:30px;line-height:28px;margin-right:10px;padding-right:30px;border:1px solid #d2d2d2;cursor:pointer;font-size:0;border-radius:2px;-webkit-transition:.1s linear;transition:.1s linear;box-sizing:border-box}.layui-form-checkbox:hover{border:1px solid #c2c2c2}.layui-form-checkbox span{padding:0 10px;height:100%;font-size:14px;background-color:#d2d2d2;color:#fff;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.layui-form-checkbox:hover span{background-color:#c2c2c2}.layui-form-checkbox i{position:absolute;right:0;width:30px;color:#fff;font-size:20px;text-align:center}.layui-form-checkbox:hover i{color:#c2c2c2}.layui-form-checked,.layui-form-checked:hover{border-color:#5FB878}.layui-form-checked span,.layui-form-checked:hover span{background-color:#5FB878}.layui-form-checked i,.layui-form-checked:hover i{color:#5FB878}.layui-form-item .layui-form-checkbox{margin-top:4px}.layui-form-checkbox[lay-skin=primary]{height:auto!important;line-height:normal!important;border:none!important;margin-right:0;padding-right:0;background:0 0}.layui-form-checkbox[lay-skin=primary] span{float:right;padding-right:15px;line-height:18px;background:0 0;color:#666}.layui-form-checkbox[lay-skin=primary] i{position:relative;top:0;width:16px;height:16px;line-height:16px;border:1px solid #d2d2d2;font-size:12px;border-radius:2px;background-color:#fff;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-checkbox[lay-skin=primary]:hover i{border-color:#5FB878;color:#fff}.layui-form-checked[lay-skin=primary] i{border-color:#5FB878;background-color:#5FB878;color:#fff}.layui-checkbox-disbaled[lay-skin=primary] span{background:0 0!important;color:#c2c2c2}.layui-checkbox-disbaled[lay-skin=primary]:hover i{border-color:#d2d2d2}.layui-form-item .layui-form-checkbox[lay-skin=primary]{margin-top:10px}.layui-form-switch{position:relative;height:22px;line-height:22px;width:42px;padding:0 5px;margin-top:8px;border:1px solid #d2d2d2;border-radius:20px;cursor:pointer;background-color:#fff;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-switch i{position:absolute;left:5px;top:3px;width:16px;height:16px;border-radius:20px;background-color:#d2d2d2;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-switch em{position:absolute;right:5px;top:0;width:25px;padding:0!important;text-align:center!important;color:#999!important;font-style:normal!important;font-size:12px}.layui-form-onswitch{border-color:#5FB878;background-color:#5FB878}.layui-form-onswitch i{left:32px;background-color:#fff}.layui-form-onswitch em{left:5px;right:auto;color:#fff!important}.layui-checkbox-disbaled{border-color:#e2e2e2!important}.layui-checkbox-disbaled span{background-color:#e2e2e2!important}.layui-checkbox-disbaled:hover i{color:#fff!important}[lay-radio]{display:none}.layui-form-radio,.layui-form-radio *{display:inline-block;vertical-align:middle}.layui-form-radio{line-height:28px;margin:6px 10px 0 0;padding-right:10px;cursor:pointer;font-size:0}.layui-form-radio *{font-size:14px}.layui-form-radio>i{margin-right:8px;font-size:22px;color:#c2c2c2}.layui-form-radio>i:hover,.layui-form-radioed>i{color:#5FB878}.layui-radio-disbaled>i{color:#e2e2e2!important}.layui-form-pane .layui-form-label{width:110px;padding:8px 15px;height:38px;line-height:20px;border-width:1px;border-style:solid;border-radius:2px 0 0 2px;text-align:center;background-color:#FBFBFB;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;box-sizing:border-box}.layui-form-pane .layui-input-inline{margin-left:-1px}.layui-form-pane .layui-input-block{margin-left:110px;left:-1px}.layui-form-pane .layui-input{border-radius:0 2px 2px 0}.layui-form-pane .layui-form-text .layui-form-label{float:none;width:100%;border-radius:2px;box-sizing:border-box;text-align:left}.layui-form-pane .layui-form-text .layui-input-inline{display:block;margin:0;top:-1px;clear:both}.layui-form-pane .layui-form-text .layui-input-block{margin:0;left:0;top:-1px}.layui-form-pane .layui-form-text .layui-textarea{min-height:100px;border-radius:0 0 2px 2px}.layui-form-pane .layui-form-checkbox{margin:4px 0 4px 10px}.layui-form-pane .layui-form-radio,.layui-form-pane .layui-form-switch{margin-top:6px;margin-left:10px}.layui-form-pane .layui-form-item[pane]{position:relative;border-width:1px;border-style:solid}.layui-form-pane .layui-form-item[pane] .layui-form-label{position:absolute;left:0;top:0;height:100%;border-width:0 1px 0 0}.layui-form-pane .layui-form-item[pane] .layui-input-inline{margin-left:110px}@media screen and (max-width:450px){.layui-form-item .layui-form-label{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-form-item .layui-inline{display:block;margin-right:0;margin-bottom:20px;clear:both}.layui-form-item .layui-inline:after{content:'\20';clear:both;display:block;height:0}.layui-form-item .layui-input-inline{display:block;float:none;left:-3px;width:auto;margin:0 0 10px 112px}.layui-form-item .layui-input-inline+.layui-form-mid{margin-left:110px;top:-5px;padding:0}.layui-form-item .layui-form-checkbox{margin-right:5px;margin-bottom:5px}}.layui-layedit{border-width:1px;border-style:solid;border-radius:2px}.layui-layedit-tool{padding:3px 5px;border-bottom-width:1px;border-bottom-style:solid;font-size:0}.layedit-tool-fixed{position:fixed;top:0;border-top:1px solid #e2e2e2}.layui-layedit-tool .layedit-tool-mid,.layui-layedit-tool .layui-icon{display:inline-block;vertical-align:middle;text-align:center;font-size:14px}.layui-layedit-tool .layui-icon{position:relative;width:32px;height:30px;line-height:30px;margin:3px 5px;color:#777;cursor:pointer;border-radius:2px}.layui-layedit-tool .layui-icon:hover{color:#393D49}.layui-layedit-tool .layui-icon:active{color:#000}.layui-layedit-tool .layedit-tool-active{background-color:#e2e2e2;color:#000}.layui-layedit-tool .layui-disabled,.layui-layedit-tool .layui-disabled:hover{color:#d2d2d2;cursor:not-allowed}.layui-layedit-tool .layedit-tool-mid{width:1px;height:18px;margin:0 10px;background-color:#d2d2d2}.layedit-tool-html{width:50px!important;font-size:30px!important}.layedit-tool-b,.layedit-tool-code,.layedit-tool-help{font-size:16px!important}.layedit-tool-d,.layedit-tool-face,.layedit-tool-image,.layedit-tool-unlink{font-size:18px!important}.layedit-tool-image input{position:absolute;font-size:0;left:0;top:0;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}.layui-layedit-iframe iframe{display:block;width:100%}#LAY_layedit_code{overflow:hidden}.layui-laypage{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;margin:10px 0;font-size:0}.layui-laypage>a:first-child,.layui-laypage>a:first-child em{border-radius:2px 0 0 2px}.layui-laypage>a:last-child,.layui-laypage>a:last-child em{border-radius:0 2px 2px 0}.layui-laypage>:first-child{margin-left:0!important}.layui-laypage>:last-child{margin-right:0!important}.layui-laypage a,.layui-laypage button,.layui-laypage input,.layui-laypage select,.layui-laypage span{border:1px solid #e2e2e2}.layui-laypage a,.layui-laypage span{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding:0 15px;height:28px;line-height:28px;margin:0 -1px 5px 0;background-color:#fff;color:#333;font-size:12px}.layui-laypage a:hover{color:#009688}.layui-laypage em{font-style:normal}.layui-laypage .layui-laypage-spr{color:#999;font-weight:700}.layui-laypage a{text-decoration:none}.layui-laypage .layui-laypage-curr{position:relative}.layui-laypage .layui-laypage-curr em{position:relative;color:#fff}.layui-laypage .layui-laypage-curr .layui-laypage-em{position:absolute;left:-1px;top:-1px;padding:1px;width:100%;height:100%;background-color:#009688}.layui-laypage-em{border-radius:2px}.layui-laypage-next em,.layui-laypage-prev em{font-family:Sim sun;font-size:16px}.layui-laypage .layui-laypage-count,.layui-laypage .layui-laypage-limits,.layui-laypage .layui-laypage-skip{margin-left:10px;margin-right:10px;padding:0;border:none}.layui-laypage .layui-laypage-limits{vertical-align:top}.layui-laypage select{height:22px;padding:3px;border-radius:2px;cursor:pointer}.layui-laypage .layui-laypage-skip{height:30px;line-height:30px;color:#999}.layui-laypage button,.layui-laypage input{height:30px;line-height:30px;border-radius:2px;vertical-align:top;background-color:#fff;box-sizing:border-box}.layui-laypage input{display:inline-block;width:40px;margin:0 10px;padding:0 3px;text-align:center}.layui-laypage input:focus,.layui-laypage select:focus{border-color:#009688!important}.layui-laypage button{margin-left:10px;padding:0 10px;cursor:pointer}.layui-table,.layui-table-view{margin:10px 0}.layui-flow-more{margin:10px 0;text-align:center;color:#999;font-size:14px}.layui-flow-more a{height:32px;line-height:32px}.layui-flow-more a *{display:inline-block;vertical-align:top}.layui-flow-more a cite{padding:0 20px;border-radius:3px;background-color:#eee;color:#333;font-style:normal}.layui-flow-more a cite:hover{opacity:.8}.layui-flow-more a i{font-size:30px;color:#737383}.layui-table{width:100%;background-color:#fff;color:#666}.layui-table tr{transition:all .3s;-webkit-transition:all .3s}.layui-table th{text-align:left;font-weight:400}.layui-table tbody tr:hover,.layui-table thead tr,.layui-table-click,.layui-table-header,.layui-table-hover,.layui-table-mend,.layui-table-patch,.layui-table-tool,.layui-table[lay-even] tr:nth-child(even){background-color:#f2f2f2}.layui-table td,.layui-table th,.layui-table-fixed-r,.layui-table-header,.layui-table-page,.layui-table-tips-main,.layui-table-tool,.layui-table-view,.layui-table[lay-skin=line],.layui-table[lay-skin=row]{border-width:1px;border-style:solid;border-color:#e6e6e6}.layui-table td,.layui-table th{position:relative;padding:9px 15px;min-height:20px;line-height:20px;font-size:14px}.layui-table[lay-skin=line] td,.layui-table[lay-skin=line] th{border-width:0 0 1px}.layui-table[lay-skin=row] td,.layui-table[lay-skin=row] th{border-width:0 1px 0 0}.layui-table[lay-skin=nob] td,.layui-table[lay-skin=nob] th{border:none}.layui-table img{max-width:100px}.layui-table[lay-size=lg] td,.layui-table[lay-size=lg] th{padding:15px 30px}.layui-table-view .layui-table[lay-size=lg] .layui-table-cell{height:40px;line-height:40px}.layui-table[lay-size=sm] td,.layui-table[lay-size=sm] th{font-size:12px;padding:5px 10px}.layui-table-view .layui-table[lay-size=sm] .layui-table-cell{height:20px;line-height:20px}.layui-table[lay-data]{display:none}.layui-table-box,.layui-table-view{position:relative;overflow:hidden}.layui-table-view .layui-table{position:relative;width:auto;margin:0}.layui-table-body,.layui-table-header .layui-table,.layui-table-page{margin-bottom:-1px}.layui-table-view .layui-table[lay-skin=line]{border-width:0 1px 0 0}.layui-table-view .layui-table[lay-skin=row]{border-width:0 0 1px}.layui-table-view .layui-table td,.layui-table-view .layui-table th{padding:5px 0;border-top:none;border-left:none}.layui-table-view .layui-table td{cursor:default}.layui-table-view .layui-form-checkbox[lay-skin=primary] i{width:18px;height:18px}.layui-table-header{border-width:0 0 1px;overflow:hidden}.layui-table-sort{width:10px;height:20px;margin-left:5px;cursor:pointer!important}.layui-table-sort .layui-edge{position:absolute;left:5px;border-width:5px}.layui-table-sort .layui-table-sort-asc{top:4px;border-top:none;border-bottom-style:solid;border-bottom-color:#b2b2b2}.layui-table-sort .layui-table-sort-asc:hover{border-bottom-color:#666}.layui-table-sort .layui-table-sort-desc{bottom:4px;border-bottom:none;border-top-style:solid;border-top-color:#b2b2b2}.layui-table-sort .layui-table-sort-desc:hover{border-top-color:#666}.layui-table-sort[lay-sort=asc] .layui-table-sort-asc{border-bottom-color:#000}.layui-table-sort[lay-sort=desc] .layui-table-sort-desc{border-top-color:#000}.layui-table-cell{height:28px;line-height:28px;padding:0 15px;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;box-sizing:border-box}.layui-table-cell .layui-form-checkbox[lay-skin=primary]{top:-1px;vertical-align:middle}.layui-table-cell .layui-table-link{color:#01AAED}.laytable-cell-checkbox,.laytable-cell-numbers,.laytable-cell-space{padding:0;text-align:center}.layui-table-body{position:relative;overflow:auto;margin-right:-1px}.layui-table-body .layui-none{line-height:40px;text-align:center;color:#999}.layui-table-fixed{position:absolute;left:0;top:0}.layui-table-fixed .layui-table-body{overflow:hidden}.layui-table-fixed-l{box-shadow:0 -1px 8px rgba(0,0,0,.08)}.layui-table-fixed-r{left:auto;right:-1px;border-width:0 0 0 1px;box-shadow:-1px 0 8px rgba(0,0,0,.08)}.layui-table-fixed-r .layui-table-header{position:relative;overflow:visible}.layui-table-mend{position:absolute;right:-49px;top:0;height:100%;width:50px}.layui-table-tool{position:relative;width:100%;height:50px;line-height:30px;padding:10px 15px;border-width:0 0 1px}.layui-table-page{position:relative;width:100%;padding:7px 7px 0;border-width:1px 0 0;height:41px;font-size:12px}.layui-table-page>div{height:26px}.layui-table-page .layui-laypage{margin:0}.layui-table-page .layui-laypage a,.layui-table-page .layui-laypage span{height:26px;line-height:26px;margin-bottom:10px;border:none;background:0 0}.layui-table-page .layui-laypage a,.layui-table-page .layui-laypage span.layui-laypage-curr{padding:0 12px}.layui-table-page .layui-laypage span{margin-left:0;padding:0}.layui-table-page .layui-laypage .layui-laypage-prev{margin-left:-7px!important}.layui-table-page .layui-laypage .layui-laypage-curr .layui-laypage-em{left:0;top:0;padding:0}.layui-table-page .layui-laypage button,.layui-table-page .layui-laypage input{height:26px;line-height:26px}.layui-table-page .layui-laypage input{width:40px}.layui-table-page .layui-laypage button{padding:0 10px}.layui-table-page select{height:18px}.layui-table-view select[lay-ignore]{display:inline-block}.layui-table-patch .layui-table-cell{padding:0;width:30px}.layui-table-edit{position:absolute;left:0;top:0;width:100%;height:100%;padding:0 14px 1px;border-radius:0;box-shadow:1px 1px 20px rgba(0,0,0,.15)}.layui-table-edit:focus{border-color:#5FB878!important}select.layui-table-edit{padding:0 0 0 10px;border-color:#C9C9C9}.layui-table-view .layui-form-checkbox,.layui-table-view .layui-form-radio,.layui-table-view .layui-form-switch{top:0;margin:0;box-sizing:content-box}.layui-table-view .layui-form-checkbox{top:-1px;height:26px;line-height:26px}body .layui-table-tips .layui-layer-content{background:0 0;padding:0;box-shadow:0 1px 6px rgba(0,0,0,.1)}.layui-table-tips-main{margin:-44px 0 0 -1px;max-height:150px;padding:8px 15px;font-size:14px;overflow-y:scroll;background-color:#fff;color:#333}.layui-code,.layui-upload-list{margin:10px 0}.layui-table-tips-c{position:absolute;right:-3px;top:-12px;width:18px;height:18px;padding:3px;text-align:center;font-weight:700;border-radius:100%;font-size:14px;cursor:pointer;background-color:#666}.layui-table-tips-c:hover{background-color:#999}.layui-upload-file{display:none!important;opacity:.01;filter:Alpha(opacity=1)}.layui-upload-drag,.layui-upload-form,.layui-upload-wrap{display:inline-block}.layui-upload-choose{padding:0 10px;color:#999}.layui-upload-drag{position:relative;padding:30px;border:1px dashed #e2e2e2;background-color:#fff;text-align:center;cursor:pointer;color:#999}.layui-upload-drag .layui-icon{font-size:50px;color:#009688}.layui-upload-drag[lay-over]{border-color:#009688}.layui-upload-iframe{position:absolute;width:0;height:0;border:0;visibility:hidden}.layui-upload-wrap{position:relative;vertical-align:middle}.layui-upload-wrap .layui-upload-file{display:block!important;position:absolute;left:0;top:0;z-index:10;font-size:100px;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}.layui-code{position:relative;padding:15px;line-height:20px;border:1px solid #ddd;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New;font-size:12px}.layui-tree{line-height:26px}.layui-tree li{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-tree li .layui-tree-spread,.layui-tree li a{display:inline-block;vertical-align:top;height:26px;*display:inline;*zoom:1;cursor:pointer}.layui-tree li a{font-size:0}.layui-tree li a i{font-size:16px}.layui-tree li a cite{padding:0 6px;font-size:14px;font-style:normal}.layui-tree li i{padding-left:6px;color:#333;-moz-user-select:none}.layui-tree li .layui-tree-check{font-size:13px}.layui-tree li .layui-tree-check:hover{color:#009E94}.layui-tree li ul{display:none;margin-left:20px}.layui-tree li .layui-tree-enter{line-height:24px;border:1px dotted #000}.layui-tree-drag{display:none;position:absolute;left:-666px;top:-666px;background-color:#f2f2f2;padding:5px 10px;border:1px dotted #000;white-space:nowrap}.layui-tree-drag i{padding-right:5px}.layui-nav{position:relative;padding:0 20px;background-color:#393D49;color:#fff;border-radius:2px;font-size:0;box-sizing:border-box}.layui-nav *{font-size:14px}.layui-nav .layui-nav-item{position:relative;display:inline-block;*display:inline;*zoom:1;vertical-align:middle;line-height:60px}.layui-nav .layui-nav-item a{display:block;padding:0 20px;color:#fff;color:rgba(255,255,255,.7);transition:all .3s;-webkit-transition:all .3s}.layui-nav .layui-this:after,.layui-nav-bar,.layui-nav-tree .layui-nav-itemed:after{position:absolute;left:0;top:0;width:0;height:5px;background-color:#5FB878;transition:all .2s;-webkit-transition:all .2s}.layui-nav-bar{z-index:1000}.layui-nav .layui-nav-item a:hover,.layui-nav .layui-this a{color:#fff}.layui-nav .layui-this:after{content:'';top:auto;bottom:0;width:100%}.layui-nav-img{width:30px;height:30px;margin-right:10px;border-radius:50%}.layui-nav .layui-nav-more{content:'';width:0;height:0;border-style:solid dashed dashed;border-color:#fff transparent transparent;overflow:hidden;cursor:pointer;transition:all .2s;-webkit-transition:all .2s;position:absolute;top:50%;right:3px;margin-top:-3px;border-width:6px;border-top-color:rgba(255,255,255,.7)}.layui-nav .layui-nav-mored,.layui-nav-itemed .layui-nav-more{margin-top:-9px;border-style:dashed dashed solid;border-color:transparent transparent #fff}.layui-nav-child{display:none;position:absolute;left:0;top:65px;min-width:100%;line-height:36px;padding:5px 0;box-shadow:0 2px 4px rgba(0,0,0,.12);border:1px solid #d2d2d2;background-color:#fff;z-index:100;border-radius:2px;white-space:nowrap}.layui-nav .layui-nav-child a{color:#333}.layui-nav .layui-nav-child a:hover{background-color:#f2f2f2;color:#000}.layui-nav-child dd{position:relative}.layui-nav .layui-nav-child dd.layui-this a,.layui-nav-child dd.layui-this{background-color:#5FB878;color:#fff}.layui-nav-child dd.layui-this:after{display:none}.layui-nav-tree{width:200px;padding:0}.layui-nav-tree .layui-nav-item{display:block;width:100%;line-height:45px}.layui-nav-tree .layui-nav-item a{height:45px;line-height:45px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-nav-tree .layui-nav-item a:hover{background-color:#4E5465}.layui-nav-tree .layui-nav-bar{width:5px;height:0;background-color:#009688}.layui-nav-tree .layui-nav-child dd.layui-this,.layui-nav-tree .layui-nav-child dd.layui-this a,.layui-nav-tree .layui-this,.layui-nav-tree .layui-this>a,.layui-nav-tree .layui-this>a:hover{background-color:#009688;color:#fff}.layui-nav-tree .layui-this:after{display:none}.layui-nav-itemed>a,.layui-nav-tree .layui-nav-title a,.layui-nav-tree .layui-nav-title a:hover{color:#fff!important}.layui-nav-tree .layui-nav-child{position:relative;z-index:0;top:0;border:none;box-shadow:none}.layui-nav-tree .layui-nav-child a{height:40px;line-height:40px;color:#fff;color:rgba(255,255,255,.7)}.layui-nav-tree .layui-nav-child,.layui-nav-tree .layui-nav-child a:hover{background:0 0;color:#fff}.layui-nav-tree .layui-nav-more{top:20px;right:10px;margin:0}.layui-nav-itemed .layui-nav-more{top:14px}.layui-nav-itemed .layui-nav-child{display:block;padding:0;background-color:rgba(0,0,0,.3)!important}.layui-nav-side{position:fixed;top:0;bottom:0;left:0;overflow-x:hidden;z-index:999}.layui-bg-blue .layui-nav-bar,.layui-bg-blue .layui-nav-itemed:after,.layui-bg-blue .layui-this:after{background-color:#93D1FF}.layui-bg-blue .layui-nav-child dd.layui-this{background-color:#1E9FFF}.layui-bg-blue .layui-nav-itemed>a,.layui-nav-tree.layui-bg-blue .layui-nav-title a,.layui-nav-tree.layui-bg-blue .layui-nav-title a:hover{background-color:#007DDB!important}.layui-breadcrumb{visibility:hidden;font-size:0}.layui-breadcrumb>*{font-size:14px}.layui-breadcrumb a{color:#999!important}.layui-breadcrumb a:hover{color:#5FB878!important}.layui-breadcrumb a cite{color:#666;font-style:normal}.layui-breadcrumb span[lay-separator]{margin:0 10px;color:#999}.layui-tab{margin:10px 0;text-align:left!important}.layui-tab[overflow]>.layui-tab-title{overflow:hidden}.layui-tab-title{position:relative;left:0;height:40px;white-space:nowrap;font-size:0;border-bottom-width:1px;border-bottom-style:solid;transition:all .2s;-webkit-transition:all .2s}.layui-tab-title li{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;font-size:14px;transition:all .2s;-webkit-transition:all .2s;position:relative;line-height:40px;min-width:65px;padding:0 15px;text-align:center;cursor:pointer}.layui-tab-title li a{display:block}.layui-tab-title .layui-this{color:#000}.layui-tab-title .layui-this:after{position:absolute;left:0;top:0;content:'';width:100%;height:41px;border-width:1px;border-style:solid;border-bottom-color:#fff;border-radius:2px 2px 0 0;box-sizing:border-box;pointer-events:none}.layui-tab-bar{position:absolute;right:0;top:0;z-index:10;width:30px;height:39px;line-height:39px;border-width:1px;border-style:solid;border-radius:2px;text-align:center;background-color:#fff;cursor:pointer}.layui-tab-bar .layui-icon{position:relative;display:inline-block;top:3px;transition:all .3s;-webkit-transition:all .3s}.layui-tab-item{display:none}.layui-tab-more{padding-right:30px;height:auto!important;white-space:normal!important}.layui-tab-more li.layui-this:after{border-bottom-color:#e2e2e2;border-radius:2px}.layui-tab-more .layui-tab-bar .layui-icon{top:-2px;top:3px\9;-webkit-transform:rotate(180deg);transform:rotate(180deg)}:root .layui-tab-more .layui-tab-bar .layui-icon{top:-2px\0/IE9}.layui-tab-content{padding:10px}.layui-tab-title li .layui-tab-close{position:relative;display:inline-block;width:18px;height:18px;line-height:20px;margin-left:8px;top:1px;text-align:center;font-size:14px;color:#c2c2c2;transition:all .2s;-webkit-transition:all .2s}.layui-tab-title li .layui-tab-close:hover{border-radius:2px;background-color:#FF5722;color:#fff}.layui-tab-brief>.layui-tab-title .layui-this{color:#009688}.layui-tab-brief>.layui-tab-more li.layui-this:after,.layui-tab-brief>.layui-tab-title .layui-this:after{border:none;border-radius:0;border-bottom:2px solid #5FB878}.layui-tab-brief[overflow]>.layui-tab-title .layui-this:after{top:-1px}.layui-tab-card{border-width:1px;border-style:solid;border-radius:2px;box-shadow:0 2px 5px 0 rgba(0,0,0,.1)}.layui-tab-card>.layui-tab-title{background-color:#f2f2f2}.layui-tab-card>.layui-tab-title li{margin-right:-1px;margin-left:-1px}.layui-tab-card>.layui-tab-title .layui-this{background-color:#fff}.layui-tab-card>.layui-tab-title .layui-this:after{border-top:none;border-width:1px;border-bottom-color:#fff}.layui-tab-card>.layui-tab-title .layui-tab-bar{height:40px;line-height:40px;border-radius:0;border-top:none;border-right:none}.layui-tab-card>.layui-tab-more .layui-this{background:0 0;color:#5FB878}.layui-tab-card>.layui-tab-more .layui-this:after{border:none}.layui-timeline{padding-left:5px}.layui-timeline-item{position:relative;padding-bottom:20px}.layui-timeline-axis{position:absolute;left:-5px;top:0;z-index:10;width:20px;height:20px;line-height:20px;background-color:#fff;color:#5FB878;border-radius:50%;text-align:center;cursor:pointer}.layui-timeline-axis:hover{color:#FF5722}.layui-timeline-item:before{content:'';position:absolute;left:5px;top:0;z-index:0;width:1px;height:100%}.layui-timeline-item:last-child:before{display:none}.layui-timeline-item:first-child:before{display:block}.layui-timeline-content{padding-left:25px}.layui-timeline-title{position:relative;margin-bottom:10px}.layui-badge,.layui-badge-dot,.layui-badge-rim{position:relative;display:inline-block;padding:0 6px;font-size:12px;text-align:center;background-color:#FF5722;color:#fff;border-radius:2px}.layui-badge{height:18px;line-height:18px}.layui-badge-dot{width:8px;height:8px;padding:0;border-radius:50%}.layui-badge-rim{height:18px;line-height:18px;border-width:1px;border-style:solid;background-color:#fff;color:#666}.layui-btn .layui-badge,.layui-btn .layui-badge-dot{margin-left:5px}.layui-nav .layui-badge,.layui-nav .layui-badge-dot{position:absolute;top:50%;margin:-8px 6px 0}.layui-tab-title .layui-badge,.layui-tab-title .layui-badge-dot{left:5px;top:-2px}.layui-carousel{position:relative;left:0;top:0;background-color:#f8f8f8}.layui-carousel>[carousel-item]{position:relative;width:100%;height:100%;overflow:hidden}.layui-carousel>[carousel-item]:before{position:absolute;content:'\e63d';left:50%;top:50%;width:100px;line-height:20px;margin:-10px 0 0 -50px;text-align:center;color:#c2c2c2;font-family:layui-icon!important;font-size:30px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-carousel>[carousel-item]>*{display:none;position:absolute;left:0;top:0;width:100%;height:100%;background-color:#f8f8f8;transition-duration:.3s;-webkit-transition-duration:.3s}.layui-carousel-updown>*{-webkit-transition:.3s ease-in-out up;transition:.3s ease-in-out up}.layui-carousel-arrow{display:none\9;opacity:0;position:absolute;left:10px;top:50%;margin-top:-18px;width:36px;height:36px;line-height:36px;text-align:center;font-size:20px;border:0;border-radius:50%;background-color:rgba(0,0,0,.2);color:#fff;-webkit-transition-duration:.3s;transition-duration:.3s;cursor:pointer}.layui-carousel-arrow[lay-type=add]{left:auto!important;right:10px}.layui-carousel:hover .layui-carousel-arrow[lay-type=add],.layui-carousel[lay-arrow=always] .layui-carousel-arrow[lay-type=add]{right:20px}.layui-carousel[lay-arrow=always] .layui-carousel-arrow{opacity:1;left:20px}.layui-carousel[lay-arrow=none] .layui-carousel-arrow{display:none}.layui-carousel-arrow:hover,.layui-carousel-ind ul:hover{background-color:rgba(0,0,0,.35)}.layui-carousel:hover .layui-carousel-arrow{display:block\9;opacity:1;left:20px}.layui-carousel-ind{position:relative;top:-35px;width:100%;line-height:0!important;text-align:center;font-size:0}.layui-carousel[lay-indicator=outside]{margin-bottom:30px}.layui-carousel[lay-indicator=outside] .layui-carousel-ind{top:10px}.layui-carousel[lay-indicator=outside] .layui-carousel-ind ul{background-color:rgba(0,0,0,.5)}.layui-carousel[lay-indicator=none] .layui-carousel-ind{display:none}.layui-carousel-ind ul{display:inline-block;padding:5px;background-color:rgba(0,0,0,.2);border-radius:10px;-webkit-transition-duration:.3s;transition-duration:.3s}.layui-carousel-ind li{display:inline-block;width:10px;height:10px;margin:0 3px;font-size:14px;background-color:#e2e2e2;background-color:rgba(255,255,255,.5);border-radius:50%;cursor:pointer;-webkit-transition-duration:.3s;transition-duration:.3s}.layui-carousel-ind li:hover{background-color:rgba(255,255,255,.7)}.layui-carousel-ind li.layui-this{background-color:#fff}.layui-carousel>[carousel-item]>.layui-carousel-next,.layui-carousel>[carousel-item]>.layui-carousel-prev,.layui-carousel>[carousel-item]>.layui-this{display:block}.layui-carousel>[carousel-item]>.layui-this{left:0}.layui-carousel>[carousel-item]>.layui-carousel-prev{left:-100%}.layui-carousel>[carousel-item]>.layui-carousel-next{left:100%}.layui-carousel>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel>[carousel-item]>.layui-carousel-prev.layui-carousel-right{left:0}.layui-carousel>[carousel-item]>.layui-this.layui-carousel-left{left:-100%}.layui-carousel>[carousel-item]>.layui-this.layui-carousel-right{left:100%}.layui-carousel[lay-anim=updown] .layui-carousel-arrow{left:50%!important;top:20px;margin:0 0 0 -18px}.layui-carousel[lay-anim=updown]>[carousel-item]>*,.layui-carousel[lay-anim=fade]>[carousel-item]>*{left:0!important}.layui-carousel[lay-anim=updown] .layui-carousel-arrow[lay-type=add]{top:auto!important;bottom:20px}.layui-carousel[lay-anim=updown] .layui-carousel-ind{position:absolute;top:50%;right:20px;width:auto;height:auto}.layui-carousel[lay-anim=updown] .layui-carousel-ind ul{padding:3px 5px}.layui-carousel[lay-anim=updown] .layui-carousel-ind li{display:block;margin:6px 0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this{top:0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-prev{top:-100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-next{top:100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-prev.layui-carousel-right{top:0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this.layui-carousel-left{top:-100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this.layui-carousel-right{top:100%}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-next,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-prev{opacity:0}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-prev.layui-carousel-right{opacity:1}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-this.layui-carousel-left,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-this.layui-carousel-right{opacity:0}.layui-fixbar{position:fixed;right:15px;bottom:15px;z-index:9999}.layui-fixbar li{width:50px;height:50px;line-height:50px;margin-bottom:1px;text-align:center;cursor:pointer;font-size:30px;background-color:#9F9F9F;color:#fff;border-radius:2px;opacity:.95}.layui-fixbar li:hover{opacity:.85}.layui-fixbar li:active{opacity:1}.layui-fixbar .layui-fixbar-top{display:none;font-size:40px}body .layui-util-face{border:none;background:0 0}body .layui-util-face .layui-layer-content{padding:0;background-color:#fff;color:#666;box-shadow:none}.layui-util-face .layui-layer-TipsG{display:none}.layui-util-face ul{position:relative;width:372px;padding:10px;border:1px solid #D9D9D9;background-color:#fff;box-shadow:0 0 20px rgba(0,0,0,.2)}.layui-util-face ul li{cursor:pointer;float:left;border:1px solid #e8e8e8;height:22px;width:26px;overflow:hidden;margin:-1px 0 0 -1px;padding:4px 2px;text-align:center}.layui-util-face ul li:hover{position:relative;z-index:2;border:1px solid #eb7350;background:#fff9ec}.layui-anim{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-anim.layui-icon{display:inline-block}.layui-anim-loop{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}@-webkit-keyframes layui-rotate{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@keyframes layui-rotate{from{transform:rotate(0)}to{transform:rotate(360deg)}}.layui-anim-rotate{-webkit-animation-name:layui-rotate;animation-name:layui-rotate;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-timing-function:linear;animation-timing-function:linear}@-webkit-keyframes layui-up{from{-webkit-transform:translate3d(0,100%,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes layui-up{from{transform:translate3d(0,100%,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-anim-up{-webkit-animation-name:layui-up;animation-name:layui-up}@-webkit-keyframes layui-upbit{from{-webkit-transform:translate3d(0,30px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes layui-upbit{from{transform:translate3d(0,30px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-anim-upbit{-webkit-animation-name:layui-upbit;animation-name:layui-upbit}@-webkit-keyframes layui-scale{0%{opacity:.3;-webkit-transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes layui-scale{0%{opacity:.3;-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-ms-transform:scale(1);transform:scale(1)}}.layui-anim-scale{-webkit-animation-name:layui-scale;animation-name:layui-scale}@-webkit-keyframes layui-scale-spring{0%{opacity:.5;-webkit-transform:scale(.5)}80%{opacity:.8;-webkit-transform:scale(1.1)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes layui-scale-spring{0%{opacity:.5;transform:scale(.5)}80%{opacity:.8;transform:scale(1.1)}100%{opacity:1;transform:scale(1)}}.layui-anim-scaleSpring{-webkit-animation-name:layui-scale-spring;animation-name:layui-scale-spring}@-webkit-keyframes layui-fadein{0%{opacity:0}100%{opacity:1}}@keyframes layui-fadein{0%{opacity:0}100%{opacity:1}}.layui-anim-fadein{-webkit-animation-name:layui-fadein;animation-name:layui-fadein}@-webkit-keyframes layui-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes layui-fadeout{0%{opacity:1}100%{opacity:0}}.layui-anim-fadeout{-webkit-animation-name:layui-fadeout;animation-name:layui-fadeout} \ No newline at end of file diff --git a/static/plugs/layui/css/layui.mobile.css b/static/plugs/layui/css/layui.mobile.css index 6932851c8..8bfc0db91 100644 --- a/static/plugs/layui/css/layui.mobile.css +++ b/static/plugs/layui/css/layui.mobile.css @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ +/** layui-v2.2.5 MIT License By https://www.layui.com */ blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,legend,li,ol,p,td,textarea,th,ul{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}html{font:12px 'Helvetica Neue','PingFang SC',STHeitiSC-Light,Helvetica,Arial,sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}a,button,input{-webkit-tap-highlight-color:rgba(255,0,0,0)}a{text-decoration:none;background:0 0}a:active,a:hover{outline:0}table{border-collapse:collapse;border-spacing:0}li{list-style:none}b,strong{font-weight:700}h1,h2,h3,h4,h5,h6{font-weight:500}address,cite,dfn,em,var{font-style:normal}dfn{font-style:italic}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}img{border:0;vertical-align:bottom}.layui-inline,input,label{vertical-align:middle}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0;outline:0}button,select{text-transform:none}select{-webkit-appearance:none;border:none}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}@font-face{font-family:layui-icon;src:url(../font/iconfont.eot?v=1.0.7);src:url(../font/iconfont.eot?v=1.0.7#iefix) format('embedded-opentype'),url(../font/iconfont.woff?v=1.0.7) format('woff'),url(../font/iconfont.ttf?v=1.0.7) format('truetype'),url(../font/iconfont.svg?v=1.0.7#iconfont) format('svg')}.layui-icon{font-family:layui-icon!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-box,.layui-box *{-webkit-box-sizing:content-box!important;-moz-box-sizing:content-box!important;box-sizing:content-box!important}.layui-border-box,.layui-border-box *{-webkit-box-sizing:border-box!important;-moz-box-sizing:border-box!important;box-sizing:border-box!important}.layui-inline{position:relative;display:inline-block;*display:inline;*zoom:1}.layui-edge,.layui-upload-iframe{position:absolute;width:0;height:0}.layui-edge{border-style:dashed;border-color:transparent;overflow:hidden}.layui-elip{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-unselect{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-disabled,.layui-disabled:active{background-color:#d2d2d2!important;color:#fff!important;cursor:not-allowed!important}.layui-circle{border-radius:100%}.layui-show{display:block!important}.layui-hide{display:none!important}.layui-upload-iframe{border:0;visibility:hidden}.layui-upload-enter{border:1px solid #009E94;background-color:#009E94;color:#fff;-webkit-transform:scale(1.1);transform:scale(1.1)}@-webkit-keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.layui-m-anim-scale{animation-name:layui-m-anim-scale;-webkit-animation-name:layui-m-anim-scale}@-webkit-keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.layui-m-anim-up{-webkit-animation-name:layui-m-anim-up;animation-name:layui-m-anim-up}@-webkit-keyframes layui-m-anim-left{0%{-webkit-transform:translateX(100%);transform:translateX(100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes layui-m-anim-left{0%{-webkit-transform:translateX(100%);transform:translateX(100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}.layui-m-anim-left{-webkit-animation-name:layui-m-anim-left;animation-name:layui-m-anim-left}@-webkit-keyframes layui-m-anim-right{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes layui-m-anim-right{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}.layui-m-anim-right{-webkit-animation-name:layui-m-anim-right;animation-name:layui-m-anim-right}@-webkit-keyframes layui-m-anim-lout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}@keyframes layui-m-anim-lout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}.layui-m-anim-lout{-webkit-animation-name:layui-m-anim-lout;animation-name:layui-m-anim-lout}@-webkit-keyframes layui-m-anim-rout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(100%);transform:translateX(100%)}}@keyframes layui-m-anim-rout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(100%);transform:translateX(100%)}}.layui-m-anim-rout{-webkit-animation-name:layui-m-anim-rout;animation-name:layui-m-anim-rout}.layui-m-layer{position:relative;z-index:19891014}.layui-m-layer *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.layui-m-layermain,.layui-m-layershade{position:fixed;left:0;top:0;width:100%;height:100%}.layui-m-layershade{background-color:rgba(0,0,0,.7);pointer-events:auto}.layui-m-layermain{display:table;font-family:Helvetica,arial,sans-serif;pointer-events:none}.layui-m-layermain .layui-m-layersection{display:table-cell;vertical-align:middle;text-align:center}.layui-m-layerchild{position:relative;display:inline-block;text-align:left;background-color:#fff;font-size:14px;border-radius:5px;box-shadow:0 0 8px rgba(0,0,0,.1);pointer-events:auto;-webkit-overflow-scrolling:touch;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}.layui-m-layer0 .layui-m-layerchild{width:90%;max-width:640px}.layui-m-layer1 .layui-m-layerchild{border:none;border-radius:0}.layui-m-layer2 .layui-m-layerchild{width:auto;max-width:260px;min-width:40px;border:none;background:0 0;box-shadow:none;color:#fff}.layui-m-layerchild h3{padding:0 10px;height:60px;line-height:60px;font-size:16px;font-weight:400;border-radius:5px 5px 0 0;text-align:center}.layui-m-layerbtn span,.layui-m-layerchild h3{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-m-layercont{padding:50px 30px;line-height:22px;text-align:center}.layui-m-layer1 .layui-m-layercont{padding:0;text-align:left}.layui-m-layer2 .layui-m-layercont{text-align:center;padding:0;line-height:0}.layui-m-layer2 .layui-m-layercont i{width:25px;height:25px;margin-left:8px;display:inline-block;background-color:#fff;border-radius:100%;-webkit-animation:layui-m-anim-loading 1.4s infinite ease-in-out;animation:layui-m-anim-loading 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-m-layerbtn,.layui-m-layerbtn span{position:relative;text-align:center;border-radius:0 0 5px 5px}.layui-m-layer2 .layui-m-layercont p{margin-top:20px}@-webkit-keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}@keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}.layui-m-layer2 .layui-m-layercont i:first-child{margin-left:0;-webkit-animation-delay:-.32s;animation-delay:-.32s}.layui-m-layer2 .layui-m-layercont i.layui-m-layerload{-webkit-animation-delay:-.16s;animation-delay:-.16s}.layui-m-layer2 .layui-m-layercont>div{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} \ No newline at end of file diff --git a/static/plugs/layui/css/modules/code.css b/static/plugs/layui/css/modules/code.css index 04d262b18..08ba72e56 100644 --- a/static/plugs/layui/css/modules/code.css +++ b/static/plugs/layui/css/modules/code.css @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ +/** layui-v2.2.5 MIT License By https://www.layui.com */ html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none} \ No newline at end of file diff --git a/static/plugs/layui/css/modules/laydate/default/laydate.css b/static/plugs/layui/css/modules/laydate/default/laydate.css index d1ddd7959..6e970f34d 100644 --- a/static/plugs/layui/css/modules/laydate/default/laydate.css +++ b/static/plugs/layui/css/modules/laydate/default/laydate.css @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - .laydate-set-ym,.layui-laydate,.layui-laydate *,.layui-laydate-list{box-sizing:border-box}html #layuicss-laydate{display:none;position:absolute;width:1989px}.layui-laydate *{margin:0;padding:0}.layui-laydate{position:absolute;z-index:66666666;margin:5px 0;border-radius:2px;font-size:14px;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:laydate-upbit;animation-name:laydate-upbit}.layui-laydate-main{width:272px}.layui-laydate-content td,.layui-laydate-header *,.layui-laydate-list li{transition-duration:.3s;-webkit-transition-duration:.3s}@-webkit-keyframes laydate-upbit{from{-webkit-transform:translate3d(0,20px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes laydate-upbit{from{transform:translate3d(0,20px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-laydate-static{position:relative;z-index:0;display:inline-block;margin:0;-webkit-animation:none;animation:none}.laydate-ym-show .laydate-next-m,.laydate-ym-show .laydate-prev-m{display:none!important}.laydate-ym-show .laydate-next-y,.laydate-ym-show .laydate-prev-y{display:inline-block!important}.laydate-time-show .laydate-set-ym span[lay-type=month],.laydate-time-show .laydate-set-ym span[lay-type=year],.laydate-time-show .layui-laydate-header .layui-icon,.laydate-ym-show .laydate-set-ym span[lay-type=month]{display:none!important}.layui-laydate-header{position:relative;line-height:30px;padding:10px 70px 5px}.laydate-set-ym span,.layui-laydate-header i{padding:0 5px;cursor:pointer}.layui-laydate-header *{display:inline-block;vertical-align:bottom}.layui-laydate-header i{position:absolute;top:10px;color:#999;font-size:18px}.layui-laydate-header i.laydate-prev-y{left:15px}.layui-laydate-header i.laydate-prev-m{left:45px}.layui-laydate-header i.laydate-next-y{right:15px}.layui-laydate-header i.laydate-next-m{right:45px}.laydate-set-ym{width:100%;text-align:center;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.laydate-time-text{cursor:default!important}.layui-laydate-content{position:relative;padding:10px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-laydate-content table{border-collapse:collapse;border-spacing:0}.layui-laydate-content td,.layui-laydate-content th{width:36px;height:30px;padding:5px;text-align:center}.layui-laydate-content td{position:relative;cursor:pointer}.laydate-day-mark{position:absolute;left:0;top:0;width:100%;height:100%;line-height:30px;font-size:12px;overflow:hidden}.laydate-day-mark::after{position:absolute;content:'';right:2px;top:2px;width:5px;height:5px;border-radius:50%}.layui-laydate-footer{position:relative;height:46px;line-height:26px;padding:10px 20px}.layui-laydate-footer span{margin-right:15px;display:inline-block;cursor:pointer;font-size:12px}.layui-laydate-footer span:hover{color:#5FB878}.laydate-footer-btns{position:absolute;right:10px;top:10px}.laydate-footer-btns span{height:26px;line-height:26px;margin:0 0 0 -1px;padding:0 10px;border:1px solid #C9C9C9;background-color:#fff;white-space:nowrap;vertical-align:top;border-radius:2px}.layui-laydate-list>li,.layui-laydate-range .layui-laydate-main{display:inline-block;vertical-align:middle}.layui-laydate-list{position:absolute;left:0;top:0;width:100%;height:100%;padding:10px;background-color:#fff}.layui-laydate-list>li{position:relative;width:33.3%;height:36px;line-height:36px;margin:3px 0;text-align:center;cursor:pointer}.laydate-month-list>li{width:25%;margin:17px 0}.laydate-time-list>li{height:100%;margin:0;line-height:normal;cursor:default}.laydate-time-list p{position:relative;top:-4px;line-height:29px}.laydate-time-list ol{height:181px;overflow:hidden}.laydate-time-list>li:hover ol{overflow-y:auto}.laydate-time-list ol li{width:130%;padding-left:33px;line-height:30px;text-align:left;cursor:pointer}.layui-laydate-hint{position:absolute;top:115px;left:50%;width:250px;margin-left:-125px;line-height:20px;padding:15px;text-align:center;font-size:12px}.layui-laydate-range{width:546px}.layui-laydate-range .laydate-main-list-0 .laydate-next-m,.layui-laydate-range .laydate-main-list-0 .laydate-next-y,.layui-laydate-range .laydate-main-list-1 .laydate-prev-m,.layui-laydate-range .laydate-main-list-1 .laydate-prev-y{display:none}.layui-laydate-range .laydate-main-list-1 .layui-laydate-content{border-left:1px solid #e2e2e2}.layui-laydate,.layui-laydate-hint{border:1px solid #d2d2d2;box-shadow:0 2px 4px rgba(0,0,0,.12);background-color:#fff;color:#666}.layui-laydate-header{border-bottom:1px solid #e2e2e2}.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:#5FB878}.layui-laydate-content{border-top:none 0;border-bottom:none 0}.layui-laydate-content th{font-weight:400;color:#333}.layui-laydate-content td{color:#666}.layui-laydate-content td.laydate-selected{background-color:#00F7DE}.laydate-selected:hover{background-color:#00F7DE!important}.layui-laydate-content td:hover,.layui-laydate-list li:hover{background-color:#eaeaea;color:#333}.laydate-time-list li ol{margin:0;padding:0;border:1px solid #e2e2e2;border-left-width:0}.laydate-time-list li:first-child ol{border-left-width:1px}.laydate-time-list>li:hover{background:0 0}.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color:#d2d2d2}.laydate-selected.laydate-day-next,.laydate-selected.laydate-day-prev{color:#fff!important}.layui-laydate-footer{border-top:1px solid #e2e2e2}.layui-laydate-hint{color:#FF5722}.laydate-day-mark::after{background-color:#5FB878}.layui-laydate-content td.layui-this .laydate-day-mark::after{display:none}.layui-laydate-footer span[lay-type=date]{color:#5FB878}.layui-laydate .layui-this{background-color:#009688!important;color:#fff!important}.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{background:0 0!important;color:#d2d2d2!important;cursor:not-allowed!important;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.laydate-theme-molv{border:none}.laydate-theme-molv.layui-laydate-range{width:548px}.laydate-theme-molv .layui-laydate-main{width:274px}.laydate-theme-molv .layui-laydate-header{border:none;background-color:#009688}.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:#f6f6f6}.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color:#fff}.laydate-theme-molv .layui-laydate-content{border:1px solid #e2e2e2;border-top:none;border-bottom:none}.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content{border-left:none}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead,.laydate-theme-molv .layui-laydate-footer{border:1px solid #e2e2e2}.laydate-theme-grid .laydate-selected,.laydate-theme-grid .laydate-selected:hover{background-color:#f2f2f2!important;color:#009688!important}.laydate-theme-grid .laydate-selected.laydate-day-next,.laydate-theme-grid .laydate-selected.laydate-day-prev{color:#d2d2d2!important}.laydate-theme-grid .laydate-month-list,.laydate-theme-grid .laydate-year-list{margin:1px 0 0 1px}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li{margin:0 -1px -1px 0}.laydate-theme-grid .laydate-year-list>li{height:43px;line-height:43px}.laydate-theme-grid .laydate-month-list>li{height:71px;line-height:71px} \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + .laydate-set-ym,.layui-laydate,.layui-laydate *,.layui-laydate-list{box-sizing:border-box}html #layuicss-laydate{display:none;position:absolute;width:1989px}.layui-laydate *{margin:0;padding:0}.layui-laydate{position:absolute;z-index:66666666;margin:5px 0;border-radius:2px;font-size:14px;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:laydate-upbit;animation-name:laydate-upbit}.layui-laydate-main{width:272px}.layui-laydate-content td,.layui-laydate-header *,.layui-laydate-list li{transition-duration:.3s;-webkit-transition-duration:.3s}@-webkit-keyframes laydate-upbit{from{-webkit-transform:translate3d(0,20px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes laydate-upbit{from{transform:translate3d(0,20px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-laydate-static{position:relative;z-index:0;display:inline-block;margin:0;-webkit-animation:none;animation:none}.laydate-ym-show .laydate-next-m,.laydate-ym-show .laydate-prev-m{display:none!important}.laydate-ym-show .laydate-next-y,.laydate-ym-show .laydate-prev-y{display:inline-block!important}.laydate-time-show .laydate-set-ym span[lay-type=month],.laydate-time-show .laydate-set-ym span[lay-type=year],.laydate-time-show .layui-laydate-header .layui-icon,.laydate-ym-show .laydate-set-ym span[lay-type=month]{display:none!important}.layui-laydate-header{position:relative;line-height:30px;padding:10px 70px 5px}.laydate-set-ym span,.layui-laydate-header i{padding:0 5px;cursor:pointer}.layui-laydate-header *{display:inline-block;vertical-align:bottom}.layui-laydate-header i{position:absolute;top:10px;color:#999;font-size:18px}.layui-laydate-header i.laydate-prev-y{left:15px}.layui-laydate-header i.laydate-prev-m{left:45px}.layui-laydate-header i.laydate-next-y{right:15px}.layui-laydate-header i.laydate-next-m{right:45px}.laydate-set-ym{width:100%;text-align:center;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.laydate-time-text{cursor:default!important}.layui-laydate-content{position:relative;padding:10px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-laydate-content table{border-collapse:collapse;border-spacing:0}.layui-laydate-content td,.layui-laydate-content th{width:36px;height:30px;padding:5px;text-align:center}.layui-laydate-content td{position:relative;cursor:pointer}.laydate-day-mark{position:absolute;left:0;top:0;width:100%;height:100%;line-height:30px;font-size:12px;overflow:hidden}.laydate-day-mark::after{position:absolute;content:'';right:2px;top:2px;width:5px;height:5px;border-radius:50%}.layui-laydate-footer{position:relative;height:46px;line-height:26px;padding:10px 20px}.layui-laydate-footer span{margin-right:15px;display:inline-block;cursor:pointer;font-size:12px}.layui-laydate-footer span:hover{color:#5FB878}.laydate-footer-btns{position:absolute;right:10px;top:10px}.laydate-footer-btns span{height:26px;line-height:26px;margin:0 0 0 -1px;padding:0 10px;border:1px solid #C9C9C9;background-color:#fff;white-space:nowrap;vertical-align:top;border-radius:2px}.layui-laydate-list>li,.layui-laydate-range .layui-laydate-main{display:inline-block;vertical-align:middle}.layui-laydate-list{position:absolute;left:0;top:0;width:100%;height:100%;padding:10px;background-color:#fff}.layui-laydate-list>li{position:relative;width:33.3%;height:36px;line-height:36px;margin:3px 0;text-align:center;cursor:pointer}.laydate-month-list>li{width:25%;margin:17px 0}.laydate-time-list>li{height:100%;margin:0;line-height:normal;cursor:default}.laydate-time-list p{position:relative;top:-4px;line-height:29px}.laydate-time-list ol{height:181px;overflow:hidden}.laydate-time-list>li:hover ol{overflow-y:auto}.laydate-time-list ol li{width:130%;padding-left:33px;line-height:30px;text-align:left;cursor:pointer}.layui-laydate-hint{position:absolute;top:115px;left:50%;width:250px;margin-left:-125px;line-height:20px;padding:15px;text-align:center;font-size:12px}.layui-laydate-range{width:546px}.layui-laydate-range .laydate-main-list-0 .laydate-next-m,.layui-laydate-range .laydate-main-list-0 .laydate-next-y,.layui-laydate-range .laydate-main-list-1 .laydate-prev-m,.layui-laydate-range .laydate-main-list-1 .laydate-prev-y{display:none}.layui-laydate-range .laydate-main-list-1 .layui-laydate-content{border-left:1px solid #e2e2e2}.layui-laydate,.layui-laydate-hint{border:1px solid #d2d2d2;box-shadow:0 2px 4px rgba(0,0,0,.12);background-color:#fff;color:#666}.layui-laydate-header{border-bottom:1px solid #e2e2e2}.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:#5FB878}.layui-laydate-content{border-top:none 0;border-bottom:none 0}.layui-laydate-content th{font-weight:400;color:#333}.layui-laydate-content td{color:#666}.layui-laydate-content td.laydate-selected{background-color:#00F7DE}.laydate-selected:hover{background-color:#00F7DE!important}.layui-laydate-content td:hover,.layui-laydate-list li:hover{background-color:#eaeaea;color:#333}.laydate-time-list li ol{margin:0;padding:0;border:1px solid #e2e2e2;border-left-width:0}.laydate-time-list li:first-child ol{border-left-width:1px}.laydate-time-list>li:hover{background:0 0}.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color:#d2d2d2}.laydate-selected.laydate-day-next,.laydate-selected.laydate-day-prev{background-color:#f8f8f8!important}.layui-laydate-footer{border-top:1px solid #e2e2e2}.layui-laydate-hint{color:#FF5722}.laydate-day-mark::after{background-color:#5FB878}.layui-laydate-content td.layui-this .laydate-day-mark::after{display:none}.layui-laydate-footer span[lay-type=date]{color:#5FB878}.layui-laydate .layui-this{background-color:#009688!important;color:#fff!important}.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{background:0 0!important;color:#d2d2d2!important;cursor:not-allowed!important;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.laydate-theme-molv{border:none}.laydate-theme-molv.layui-laydate-range{width:548px}.laydate-theme-molv .layui-laydate-main{width:274px}.laydate-theme-molv .layui-laydate-header{border:none;background-color:#009688}.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:#f6f6f6}.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color:#fff}.laydate-theme-molv .layui-laydate-content{border:1px solid #e2e2e2;border-top:none;border-bottom:none}.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content{border-left:none}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead,.laydate-theme-molv .layui-laydate-footer{border:1px solid #e2e2e2}.laydate-theme-grid .laydate-selected,.laydate-theme-grid .laydate-selected:hover{background-color:#f2f2f2!important;color:#009688!important}.laydate-theme-grid .laydate-selected.laydate-day-next,.laydate-theme-grid .laydate-selected.laydate-day-prev{color:#d2d2d2!important}.laydate-theme-grid .laydate-month-list,.laydate-theme-grid .laydate-year-list{margin:1px 0 0 1px}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li{margin:0 -1px -1px 0}.laydate-theme-grid .laydate-year-list>li{height:43px;line-height:43px}.laydate-theme-grid .laydate-month-list>li{height:71px;line-height:71px} \ No newline at end of file diff --git a/static/plugs/layui/css/modules/layer/default/layer.css b/static/plugs/layui/css/modules/layer/default/layer.css index 2d60f1eaa..6a001539c 100644 --- a/static/plugs/layui/css/modules/layer/default/layer.css +++ b/static/plugs/layui/css/modules/layer/default/layer.css @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ +/** layui-v2.2.5 MIT License By https://www.layui.com */ .layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}html #layuicss-layer{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;border-radius:2px;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layer-anim{-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-00{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 15px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:5px 5px 0;padding:0 15px;border:1px solid #dedede;background-color:#fff;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#1E9FFF;background-color:#1E9FFF;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:8px 15px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:5px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#fff;border-color:#E9E7E7;color:#333}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95;border-color:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:230px;height:36px;margin:0 auto;line-height:30px;padding-left:10px;border:1px solid #e6e6e6;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px;padding:6px 10px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;overflow:hidden;cursor:pointer}.layui-layer-tab .layui-layer-title span.layui-this{height:43px;border-left:1px solid #eee;border-right:1px solid #eee;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.layui-this{display:block}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}} \ No newline at end of file diff --git a/static/plugs/layui/css/modules/layim/html/chatlog.html b/static/plugs/layui/css/modules/layim/html/chatlog.html deleted file mode 100644 index 9cbc5716c..000000000 --- a/static/plugs/layui/css/modules/layim/html/chatlog.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - -聊天记录 - - - - - - -
    -
      -
      - -
      - - - - - - - - - - - diff --git a/static/plugs/layui/css/modules/layim/html/find.html b/static/plugs/layui/css/modules/layim/html/find.html deleted file mode 100644 index ff5cab1d7..000000000 --- a/static/plugs/layui/css/modules/layim/html/find.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - -发现 - - - - - - -
      -
      此为自定义的【查找】页面,因需求不一,所以官方暂不提供该模版结构与样式,实际使用时,可移至该文件到你的项目中,对页面自行把控。 -
      文件所在目录(相对于layui.js):/css/modules/layim/html/find.html
      -
      - - - - - - - diff --git a/static/plugs/layui/css/modules/layim/html/getmsg.json b/static/plugs/layui/css/modules/layim/html/getmsg.json deleted file mode 100644 index 3d9b9d4c4..000000000 --- a/static/plugs/layui/css/modules/layim/html/getmsg.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "code": 0, - "pages": 1, - "data": [ - { - "id": 76, - "content": "申请添加你为好友", - "uid": 168, - "from": 166488, - "from_group": 0, - "type": 1, - "remark": "有问题要问", - "href": null, - "read": 1, - "time": "刚刚", - "user": { - "id": 166488, - "avatar": "http://q.qlogo.cn/qqapp/101235792/B704597964F9BD0DB648292D1B09F7E8/100", - "username": "李彦宏", - "sign": null - } - }, - { - "id": 75, - "content": "申请添加你为好友", - "uid": 168, - "from": 347592, - "from_group": 0, - "type": 1, - "remark": "你好啊!", - "href": null, - "read": 1, - "time": "刚刚", - "user": { - "id": 347592, - "avatar": "http://q.qlogo.cn/qqapp/101235792/B78751375E0531675B1272AD994BA875/100", - "username": "麻花疼", - "sign": null - } - }, - { - "id": 62, - "content": "雷军 拒绝了你的好友申请", - "uid": 168, - "from": null, - "from_group": null, - "type": 1, - "remark": null, - "href": null, - "read": 1, - "time": "10天前", - "user": { - "id": null - } - }, - { - "id": 60, - "content": "马小云 已经同意你的好友申请", - "uid": 168, - "from": null, - "from_group": null, - "type": 1, - "remark": null, - "href": null, - "read": 1, - "time": "10天前", - "user": { - "id": null - } - }, - { - "id": 61, - "content": "贤心 已经同意你的好友申请", - "uid": 168, - "from": null, - "from_group": null, - "type": 1, - "remark": null, - "href": null, - "read": 1, - "time": "10天前", - "user": { - "id": null - } - } - ] -} \ No newline at end of file diff --git a/static/plugs/layui/css/modules/layim/html/msgbox.html b/static/plugs/layui/css/modules/layim/html/msgbox.html deleted file mode 100644 index 0adf0028c..000000000 --- a/static/plugs/layui/css/modules/layim/html/msgbox.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - -消息盒子 - - - - - - -
        - -
        -
        注意:这些都是模拟数据,实际使用时,需将其中的模拟接口改为你的项目真实接口。 -
        该模版文件所在目录(相对于layui.js):/css/modules/layim/html/msgbox.html
        -
        - - - - - - - - - - diff --git a/static/plugs/layui/css/modules/layim/layim.css b/static/plugs/layui/css/modules/layim/layim.css deleted file mode 100644 index 3758ad12d..000000000 --- a/static/plugs/layui/css/modules/layim/layim.css +++ /dev/null @@ -1,2 +0,0 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - html #layuicss-skinlayimcss{display:none;position:absolute;width:1989px}body .layui-layim,body .layui-layim-chat{border:1px solid #D9D9D9;border-color:rgba(0,0,0,.05);background-repeat:no-repeat;background-color:#F6F6F6;color:#333;font-family:\5FAE\8F6F\96C5\9ED1}body .layui-layim-chat{background-size:cover}body .layui-layim .layui-layer-title{height:110px;border-bottom:none;background:0 0}.layui-layim-main{position:relative;top:-98px;left:0}body .layui-layim .layui-layer-content,body .layui-layim-chat .layui-layer-content{overflow:visible}.layui-layim cite,.layui-layim em,.layui-layim-chat cite,.layui-layim-chat em{font-style:normal}.layui-layim-info{height:50px;font-size:0;padding:0 15px}.layui-layim-info *{font-size:14px}.layim-tab-content li h5 *,.layui-layim-info div,.layui-layim-skin li,.layui-layim-tab li,.layui-layim-tool li{display:inline-block;vertical-align:top;*zoom:1;*display:inline}.layim-tab-content li h5 span,.layui-layim-info .layui-layim-user,.layui-layim-list li p,.layui-layim-list li span,.layui-layim-remark{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.layui-layim-info .layui-layim-user{max-width:150px;margin-right:5px;font-size:16px}.layui-layim-status{position:relative;top:2px;line-height:19px;cursor:pointer}.layim-status-online{color:#3FDD86}.layim-status-hide{color:#DD691D}.layim-menu-box{display:none;position:absolute;z-index:100;top:24px;left:-31px;padding:5px 0;width:85px;border:1px solid #E2E2E2;border-radius:2px;background-color:#fff;box-shadow:1px 1px 20px rgba(0,0,0,.1)}.layim-menu-box li{position:relative;line-height:22px;padding-left:30px;font-size:12px}.layim-menu-box li cite{padding-right:5px;font-size:14px}.layim-menu-box li i{display:none;position:absolute;left:0;top:0}.layim-menu-box .layim-this i{display:block}.layim-menu-box li:hover{background-color:#eee}.layui-layim-remark{position:relative;left:-6px;display:block;width:100%;border:1px solid transparent;margin-top:8px;padding:0 5px;height:26px;line-height:26px;background:0 0;border-radius:2px}.layui-layim-remark:focus,.layui-layim-remark:hover{border:1px solid #d2d2d2;border-color:rgba(0,0,0,.15)}.layui-layim-remark:focus{background-color:#fff}.layui-layim-tab{margin-top:10px;padding:9px 0;font-size:0}.layui-layim-tab li{position:relative;width:33.33%;height:24px;line-height:24px;font-size:22px;text-align:center;color:#666;color:rgba(0,0,0,.6);cursor:pointer}.layim-tab-two li{width:50%}.layui-layim-tab li.layim-this:after{content:'';position:absolute;left:0;bottom:-9px;width:100%;height:3px;background-color:#3FDD86}.layui-layim-tab li.layim-hide{display:none}.layui-layim-tab li:hover{opacity:.8;filter:Alpha(opacity=80)}.layim-tab-content{display:none;padding:10px 0;height:349px;overflow:hidden;background-color:#fff;background-color:rgba(255,255,255,.9)}.layim-tab-content:hover{overflow-y:auto}.layim-tab-content li h5{position:relative;margin-right:15px;padding-left:30px;height:28px;line-height:28px;cursor:pointer;font-size:0;white-space:nowrap;overflow:hidden}.layim-tab-content li h5 *{font-size:14px}.layim-tab-content li h5 span{max-width:125px}.layim-tab-content li h5 i{position:absolute;left:12px;top:0;color:#C9BDBB}.layim-tab-content li h5 em{padding-left:5px;color:#999}.layim-tab-content li h5[lay-type=true] i{top:2px}.layim-tab-content li ul{display:none;margin-bottom:10px}.layui-layim-list li{position:relative;height:42px;padding:5px 15px 5px 60px;font-size:0;cursor:pointer}.layui-layim-list li:hover{background-color:#F2F2F2;background-color:rgba(0,0,0,.05)}.layui-layim-list li.layim-null{height:20px;line-height:20px;padding:0;font-size:14px;color:#999;text-align:center;cursor:default}.layui-layim-list li.layim-null:hover{background:0 0}.layui-layim-list li *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:14px}.layui-layim-list li span{margin-top:4px;max-width:155px}.layui-layim-list li img{position:absolute;left:15px;top:8px;width:36px;height:36px;border-radius:100%}.layui-layim-list li p{display:block;padding-right:30px;line-height:18px;font-size:12px;color:#999}.layui-layim-list li .layim-msg-status{display:none;position:absolute;right:10px;bottom:7px;padding:0 5px;height:16px;line-height:16px;border-radius:16px;text-align:center;font-size:10px;background-color:#F74C31;color:#fff}.layim-list-gray{-webkit-filter:grayscale(100%);-ms-filter:grayscale(100%);filter:grayscale(100%);filter:gray}.layui-layim-tool{padding:0 10px;font-size:0;background-color:#F6F6F6;border-radius:0 0 2px 2px}.layui-layim-tool li{position:relative;width:48px;height:37px;line-height:40px;text-align:center;font-size:22px;cursor:pointer}.layui-layim-tool li:active{background-color:#e2e2e2}.layui-layim-tool .layim-tool-msgbox{line-height:37px}.layui-layim-tool .layim-tool-find{line-height:38px}.layui-layim-tool .layim-tool-skin{font-size:26px}.layim-tool-msgbox span{display:none;position:absolute;left:12px;top:-12px;height:20px;line-height:20px;padding:0 10px;border-radius:2px;background-color:#33DF83;color:#fff;font-size:12px;-webkit-animation-duration:1s;animation-duration:1s}.layim-tool-msgbox .layer-anim-05{display:block}.layui-layim-search{display:none;position:absolute;bottom:5px;left:5px;height:28px;line-height:28px}.layui-layim-search input{width:210px;padding:0 30px 0 10px;height:30px;line-height:30px;border:none;border-radius:3px;background-color:#ddd}.layui-layim-search label{position:absolute;right:6px;top:4px;font-size:20px;cursor:pointer;color:#333;font-weight:400}.layui-layim-skin{margin:10px 0 0 10px;font-size:0}.layui-layim-skin li{margin:0 10px 10px 0;line-height:60px;text-align:center;background-color:#f6f6f6}.layui-layim-skin li,.layui-layim-skin li img{width:86px;height:60px;cursor:pointer}.layui-layim-skin li img:hover{opacity:.8;filter:Alpha(opacity=80)}.layui-layim-skin li cite{font-size:14px;font-style:normal}body .layui-layim-chat{background-color:#fff}body .layui-layim-chat-list{width:760px}body .layui-layim-chat .layui-layer-title{height:80px;border-bottom:none;background-color:#F8F8F8;background-color:rgba(245,245,245,.7)}body .layui-layim-chat .layui-layer-content{background:0 0}.layim-chat-list li *,.layui-layim-min .layui-layer-content *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:14px}.layim-chat-list{display:none;position:absolute;z-index:1000;top:-80px;width:200px;height:100%;background-color:#D9D9D9;overflow:hidden;font-size:0}.layim-chat-list:hover{overflow-y:auto}.layim-chat-list li,.layui-layim-min .layui-layer-content{position:relative;margin:5px;padding:5px 30px 5px 5px;line-height:40px;cursor:pointer;border-radius:3px}.layim-chat-list li img,.layui-layim-min .layui-layer-content img{width:40px;height:40px;border-radius:100%}.layui-layim-photos{cursor:crosshair}.layim-chat-list li{white-space:nowrap}.layim-chat-list li span,.layui-layim-min .layui-layer-content span{width:100px;padding-left:10px;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.layim-chat-list li span cite{color:#999;padding-left:10px}.layim-chat-list li:hover{background-color:#E2E2E2}.layim-chat-list li.layim-this{background-color:#F3F3F3}.layim-chat-list li .layui-icon{display:none;position:absolute;right:5px;top:7px;color:#555;font-size:22px}.layim-chat-list li .layui-icon:hover{color:#c00}.layim-chat-list li:hover .layui-icon{display:inline-block}.layim-chat-system{margin:10px 0;text-align:center}.layim-chat-system span{display:inline-block;line-height:30px;padding:0 15px;border-radius:3px;background-color:#e2e2e2;cursor:default;font-size:14px}.layim-chat{display:none;position:relative;background-color:#fff;background-color:rgba(255,255,255,.9)}.layim-chat-title{position:absolute;top:-80px;height:80px}.layim-chat-other{position:relative;top:15px;left:15px;padding-left:60px;cursor:default}.layim-chat-other img{position:absolute;left:0;top:0;width:50px;height:50px;border-radius:100%}.layim-chat-username{position:relative;top:5px;font-size:18px}.layim-chat-status{margin-top:6px;font-size:14px;color:#999}.layim-chat-group .layim-chat-other .layim-chat-username{cursor:pointer}.layim-chat-group .layim-chat-other .layim-chat-username em{padding:0 10px;color:#999}.layim-chat-main{height:262px;padding:15px 15px 5px;overflow-x:hidden;overflow-y:auto}.layim-chat-main ul li{position:relative;font-size:0;margin-bottom:10px;padding-left:60px;min-height:68px}.layim-chat-text,.layim-chat-user{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:14px}.layim-chat-user{position:absolute;left:3px}.layim-chat-user img{width:40px;height:40px;border-radius:100%}.layim-chat-user cite{position:absolute;left:60px;top:-2px;width:500px;line-height:24px;font-size:12px;white-space:nowrap;color:#999;text-align:left;font-style:normal}.layim-chat-user cite i{padding-left:15px;font-style:normal}.layim-chat-text{position:relative;line-height:22px;margin-top:25px;padding:8px 15px;background-color:#e2e2e2;border-radius:3px;color:#333;word-break:break-all;max-width:462px\9}.layim-chat-text:after{content:'';position:absolute;left:-10px;top:13px;width:0;height:0;border-style:solid dashed dashed;border-color:#e2e2e2 transparent transparent;overflow:hidden;border-width:10px}.layim-chat-text a{color:#33DF83}.layim-chat-text img{max-width:100%;vertical-align:middle}.layim-chat-text .layui-layim-file,.layui-layim-file{display:block;text-align:center}.layim-chat-text .layui-layim-file{color:#333}.layui-layim-file:hover{opacity:.9}.layui-layim-file i{font-size:80px;line-height:80px}.layui-layim-file cite{display:block;line-height:20px;font-size:14px}.layui-layim-audio{text-align:center;cursor:pointer}.layui-layim-audio .layui-icon{position:relative;top:5px;font-size:24px}.layui-layim-audio p{margin-top:3px}.layui-layim-video{width:120px;height:80px;line-height:80px;background-color:#333;text-align:center;border-radius:3px}.layui-layim-video .layui-icon{font-size:36px;cursor:pointer;color:#fff}.layim-chat-main ul .layim-chat-system{min-height:0;padding:0}.layim-chat-main ul .layim-chat-mine{text-align:right;padding-left:0;padding-right:60px}.layim-chat-mine .layim-chat-user{left:auto;right:3px}.layim-chat-mine .layim-chat-user cite{left:auto;right:60px;text-align:right}.layim-chat-mine .layim-chat-user cite i{padding-left:0;padding-right:15px}.layim-chat-mine .layim-chat-text{margin-left:0;text-align:left;background-color:#5FB878;color:#fff}.layim-chat-mine .layim-chat-text:after{left:auto;right:-10px;border-top-color:#5FB878}.layim-chat-mine .layim-chat-text a{color:#fff}.layim-chat-footer{border-top:1px solid #F1F1F1}.layim-chat-tool{position:relative;padding:0 8px;height:38px;line-height:38px;font-size:0}.layim-chat-tool span{position:relative;margin:0 10px;display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:24px;cursor:pointer}.layim-chat-tool .layim-tool-log{position:absolute;right:5px;font-size:14px}.layim-tool-log i{position:relative;top:2px;margin-right:5px;font-size:20px;color:#999}.layim-tool-image input{position:absolute;font-size:0;left:0;top:0;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}body .layui-layim-face{margin:10px 0 0 -18px;border:none;background:0 0}body .layui-layim-face .layui-layer-content{padding:0;background-color:#fff;color:#666;box-shadow:none}.layui-layim-face .layui-layer-TipsG{display:none}.layui-layim-face ul{position:relative;width:372px;padding:10px;border:1px solid #D9D9D9;background-color:#fff;box-shadow:0 0 20px rgba(0,0,0,.2)}.layui-layim-face ul li{cursor:pointer;float:left;border:1px solid #e8e8e8;height:22px;width:26px;overflow:hidden;margin:-1px 0 0 -1px;padding:4px 2px;text-align:center}.layui-layim-face ul li:hover{position:relative;z-index:2;border:1px solid #eb7350;background:#fff9ec}.layim-chat-textarea{margin-left:10px}.layim-chat-textarea textarea{display:block;width:100%;padding:5px 0 0;height:68px;line-height:20px;border:none;overflow:auto;resize:none;background:0 0}.layim-chat-textarea textarea:focus{outline:0}.layim-chat-bottom{position:relative;height:46px}.layim-chat-send{position:absolute;right:15px;top:3px;height:32px;line-height:32px;font-size:0;cursor:pointer}.layim-chat-send span{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:14px;line-height:32px;margin-left:5px;padding:0 20px;background-color:#5FB878;color:#fff;border-radius:3px}.layim-chat-send span:hover{background-color:#69BC80}.layim-chat-send span:active{background-color:#59B573}.layim-chat-send .layim-send-btn{border-radius:3px 0 0 3px}.layim-chat-send .layim-send-set{position:relative;width:30px;height:32px;margin-left:0;padding:0;border-left:1px solid #85C998;border-radius:0 3px 3px 0}.layim-send-set .layui-edge{top:14px;left:9px;border-width:6px;border-top-style:solid;border-top-color:#fff}.layim-chat-send .layim-menu-box{left:auto;right:0;top:33px;width:180px;padding:10px 0}.layim-chat-send .layim-menu-box li{padding-right:15px;line-height:28px}body .layui-layim-min{border:1px solid #D9D9D9}.layui-layim-min .layui-layer-content{margin:0 5px;padding:5px 10px;white-space:nowrap}.layui-layim-close .layui-layer-content span{width:auto;max-width:120px}body .layui-layim-members{margin:25px 0 0 -75px;border:none;background:0 0}body .layui-layim-members .layui-layer-content{padding:0;background:0 0;color:#666;box-shadow:none}.layui-layim-members .layui-layer-TipsG{display:none}.layui-layim-members ul{position:relative;width:578px;height:200px;padding:10px 10px 0;border:1px solid #D9D9D9;background-color:#fff;background-color:rgba(255,255,255,.9);box-shadow:none;overflow:hidden;font-size:0}.layui-layim-members ul:hover{overflow:auto}.layim-add-img,.layim-add-remark,.layui-layim-members li{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:14px}.layui-layim-members li{width:112px;margin:10px 0;text-align:center}.layui-layim-members li a{position:relative;display:inline-block;max-width:100%}.layui-layim-members li a:after{content:'';position:absolute;width:46px;height:46px;left:50%;margin-left:-23px;top:0;border:1px solid #eee;border-color:rgba(0,0,0,.1);border-radius:100%}.layui-layim-members li img{width:48px;height:48px;border-radius:100%}.layui-layim-members li:hover{opacity:.9}.layui-layim-members li a cite{display:block;padding:0 3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}body .layui-layim-contextmenu{margin:70px 0 0 30px;width:200px;padding:5px 0;border:1px solid #ccc;background:#fff;border-radius:0;box-shadow:0 0 5px rgba(0,0,0,.2)}body .layui-layim-contextmenu .layui-layer-content{padding:0;background-color:#fff;color:#333;font-size:14px;box-shadow:none}.layui-layim-contextmenu .layui-layer-TipsG{display:none}.layui-layim-contextmenu li{padding:0 15px 0 35px;cursor:pointer;line-height:30px}.layui-layim-contextmenu li:hover{background-color:#F2F2F2}.layim-add-box{margin:15px;font-size:0}.layim-add-img img,.layim-add-remark p{margin-bottom:10px}.layim-add-img{width:100px;margin-right:20px;text-align:center}.layim-add-img img{width:100px;height:100px}.layim-add-remark{width:280px}.layim-add-remark .layui-select{width:100%;margin-bottom:10px}.layim-add-remark .layui-textarea{height:80px;min-height:80px;resize:none}.layim-tab-content,.layui-layim-face ul,.layui-layim-tab{margin-bottom:0}.layim-tab-content li h5{margin-top:0;margin-bottom:0},.layui-layim-face img{vertical-align:bottom}.layim-chat-other span{color:#444}.layim-chat-other span cite{padding:0 15px;color:#999}.layim-chat-other:hover{text-decoration:none} \ No newline at end of file diff --git a/static/plugs/layui/css/modules/layim/mobile/layim.css b/static/plugs/layui/css/modules/layim/mobile/layim.css deleted file mode 100644 index 084c10945..000000000 --- a/static/plugs/layui/css/modules/layim/mobile/layim.css +++ /dev/null @@ -1,2 +0,0 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - .layim-tab-content li h5,.layui-layim-list li{border-bottom:1px solid #f2f2f2;cursor:pointer}html #layuicss-skinlayim-mobilecss{display:none;position:absolute;width:1989px}.layim-tab-content li h5 *,.layui-layim-skin li,.layui-layim-tab li,.layui-layim-tool li{display:inline-block;vertical-align:top;*zoom:1;*display:inline}.layim-tab-content li h5 span,.layui-layim-list li p,.layui-layim-list li span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.layui-layim-tab{position:absolute;bottom:0;left:0;right:0;height:50px;border-top:1px solid #f2f2f2;background-color:#fff}.layui-layim-tab li{position:relative;width:33.33%;height:50px;text-align:center;color:#666;color:rgba(0,0,0,.6);cursor:pointer}.layui-layim-tab li .layui-icon{position:relative;top:7px;font-size:25px}.layui-layim-tab li span{position:relative;bottom:-3px;display:block;font-size:12px}.layui-layim-tab li[lay-type=more] .layui-icon{top:4px;font-size:22px}.layui-layim-tab li.layim-this{color:#3FDD86}.layim-new{display:none;position:absolute;top:5px;left:50%;margin-left:15px;width:10px;height:10px;border-radius:10px;background-color:#F74C31;color:#fff}.layim-list-top .layim-new{position:relative;vertical-align:top;top:10px;left:initial;margin-left:5px}.layim-list-top i.layui-show{display:inline-block!important}.layim-tab-content,.layim-tab-content li ul{display:none}.layui-layim{position:fixed;left:0;right:0;top:50px;bottom:50px;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch}.layim-tab-content li h5{position:relative;padding-left:35px;height:45px;line-height:45px;font-size:0;white-space:nowrap;overflow:hidden}.layim-tab-content li h5 *{font-size:17px}.layim-tab-content li h5 span{max-width:80%}.layim-tab-content li h5 i{position:absolute;left:12px;top:0;color:#C9BDBB}.layim-tab-content li h5 em{padding-left:5px;color:#999}.layim-list-friend,.layim-list-group{background-color:#fff}.layui-layim-list li{position:relative;height:42px;padding:5px 15px 5px 60px;font-size:0}.layui-layim-list li:active{background-color:#F2F2F2;background-color:rgba(0,0,0,.05)}.layui-layim-list li.layim-null{height:20px;line-height:20px;padding:10px 0;color:#999;text-align:center;cursor:default;font-size:14px}.layim-list-history li.layim-null{padding:30px 0;border-bottom:none;background-color:#eee}.layui-layim-list li *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:17px}.layui-layim-list li span{margin-top:2px;max-width:155px;font-size:17px}.layui-layim-list li img{position:absolute;left:12px;top:8px;width:36px;height:36px;border-radius:100%}.layui-layim-list li p{display:block;padding-right:30px;line-height:18px;font-size:13px;color:#999}.layui-layim-list li .layim-msg-status{display:none;position:absolute;right:10px;bottom:7px;padding:0 5px;height:17px;line-height:17px;border-radius:17px;text-align:center;font-size:10px;background-color:#F74C31;color:#fff}.layim-list-gray{-webkit-filter:grayscale(100%);-ms-filter:grayscale(100%);filter:grayscale(100%);filter:gray}.layim-list-top{background-color:#fff;font-size:17px}.layim-list-top li{position:relative;padding:0 15px 0 50px;line-height:45px;border-bottom:1px solid #f2f2f2;cursor:pointer}.layim-list-top li:last-child{margin-bottom:10px;border-bottom:none}.layim-list-top li .layui-icon{position:absolute;left:12px;top:0;margin-right:10px;color:#36373C;font-size:24px}.layim-list-top li[layim-event=newFriend] .layui-icon{left:15px}.layim-panel,.layim-title{position:fixed;left:0;right:0;top:0}.layim-list-top li[layim-event=group] .layui-icon{font-size:20px}.layim-list-top li[layim-event=about] .layui-icon{font-size:25px}.layim-panel{bottom:0;background-color:#eee;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}.layim-title{height:50px;line-height:50px;padding:0 15px;background-color:#36373C;color:#fff;font-size:18px}.layim-chat-status{padding-left:15px;font-size:14px;opacity:.7}.layim-title .layim-chat-back{display:inline-block;vertical-align:middle;position:relative;padding:0 15px;margin-left:-10px;top:0;font-size:24px;cursor:pointer}.layim-chat-detail{position:absolute;right:0;top:0;padding:0 15px;font-size:18px;cursor:pointer}.layim-chat-main,.layim-content{position:fixed;top:50px;left:0;right:0;overflow-y:auto;overflow-x:hidden}.layim-chat-detail:active,.layim-title .layim-chat-back:active{opacity:.8}.layui-layim .layim-title{text-align:left}.layui-layim .layim-title p{padding:0 15px}.layim-content{bottom:0}.layim-chat-main{width:100%;bottom:85px;padding:15px;-webkit-box-sizing:border-box!important;-moz-box-sizing:border-box!important;box-sizing:border-box!important}.layim-chat-main ul{overflow-x:hidden}.layim-chat-main ul li{position:relative;font-size:0;margin-bottom:10px;padding-left:60px;min-height:68px}.layim-chat-text,.layim-chat-user{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:15px}.layim-chat-user{position:absolute;left:3px}.layim-chat-user img{width:40px;height:40px;border-radius:100%}.layim-chat-user cite{position:absolute;left:60px;top:-2px;width:500px;line-height:24px;font-size:12px;white-space:nowrap;color:#999;text-align:left;font-style:normal}.layim-chat-user cite i{padding-left:15px;font-style:normal}.layim-chat-text{position:relative;min-height:22px;line-height:22px;margin-top:25px;padding:8px 15px;background-color:#fff;border-radius:3px;color:#333;word-break:break-all}.layim-chat-text:after{content:'';position:absolute;left:-10px;top:13px;width:0;height:0;border-style:solid dashed dashed;border-color:#fff transparent transparent;overflow:hidden;border-width:10px}.layim-chat-text a{color:#33DF83}.layim-chat-text img{max-width:100%;vertical-align:middle}.layim-chat-text .layui-layim-file,.layui-layim-file{display:block;text-align:center}.layim-chat-text .layui-layim-file{color:#333}.layui-layim-file:active{opacity:.9}.layui-layim-file i{font-size:80px;line-height:80px}.layui-layim-file cite{display:block;line-height:20px;font-size:17px}.layui-layim-audio{text-align:center;cursor:pointer}.layui-layim-audio .layui-icon{position:relative;top:5px;font-size:24px}.layui-layim-audio p{margin-top:3px}.layui-layim-video{width:120px;height:80px;line-height:80px;background-color:#333;text-align:center;border-radius:3px}.layui-layim-video .layui-icon{font-size:36px;cursor:pointer;color:#fff}.layim-chat-main ul .layim-chat-mine{text-align:right;padding-left:0;padding-right:60px}.layim-chat-mine .layim-chat-user{left:auto;right:3px}.layim-chat-mine .layim-chat-user cite{left:auto;right:60px;text-align:right}.layim-chat-mine .layim-chat-user cite i{padding-left:0;padding-right:15px}.layim-chat-mine .layim-chat-text{margin-left:0;text-align:left;background-color:#5FB878;color:#fff}.layim-chat-mine .layim-chat-text:after{left:auto;right:-10px;border-top-color:#5FB878}.layim-chat-mine .layim-chat-text a{color:#fff}.layim-chat-main ul .layim-chat-system{min-height:0;margin:20px 0 5px;padding:0}.layim-chat-system{margin:10px 0;text-align:center}.layim-chat-system span{display:inline-block;line-height:30px;padding:0 15px;border-radius:3px;background-color:#ddd;color:#fff;font-size:14px;cursor:pointer}.layim-chat-footer{position:fixed;bottom:0;left:10px;right:10px;height:80px}.layim-chat-send{display:-webkit-box;display:-webkit-flex;display:flex}.layim-chat-send input{-webkit-box-flex:1;-webkit-flex:1;flex:1;height:40px;padding-left:5px;border:0;background-color:#fff;border-radius:3px}.layim-chat-send button{border-radius:3px;height:40px;padding:0 20px;border:0;margin-left:10px;background-color:#5FB878;color:#fff}.layim-chat-tool{position:relative;width:100%;overflow-x:auto;padding:0;height:38px;line-height:38px;margin-top:3px;font-size:0;white-space:nowrap}.layim-chat-tool span{position:relative;margin:0 15px;display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:28px;cursor:pointer}.layim-chat-tool .layim-tool-log{position:absolute;right:5px;font-size:14px}.layim-tool-log i{position:relative;top:2px;margin-right:5px;font-size:20px;color:#999}.layim-tool-image input{position:absolute;font-size:0;left:0;top:0;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}.layim-layer{position:fixed;bottom:85px;left:10px;right:10px;margin:0 auto}.layui-layim-face{position:relative;max-height:180px;overflow:auto;padding:10px;font-size:0}.layui-layim-face li{cursor:pointer;display:inline-block;vertical-align:bottom;padding:5px 2px;text-align:center;width:10%;-webkit-box-sizing:border-box!important;-moz-box-sizing:border-box!important;box-sizing:border-box!important}.layui-layim-face li img{width:22px;height:22px}.layim-about{font-size:17px}.layim-about .layui-m-layercont{text-align:left}.layim-about .layui-m-layercont p{line-height:30px}.layim-about .layui-m-layercont a{color:#01AAED} \ No newline at end of file diff --git a/static/plugs/layui/css/modules/layim/skin/1.jpg b/static/plugs/layui/css/modules/layim/skin/1.jpg deleted file mode 100644 index d9f99263d65a31247c966813d155b83f1ebbd1ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18386 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 diff --git a/static/plugs/layui/css/modules/layim/skin/3.jpg b/static/plugs/layui/css/modules/layim/skin/3.jpg deleted file mode 100644 index 53ba9216283a962b6d325965a4a11c7504056e54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18610 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
        ",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
        是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window); \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if("interactive"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.1",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'
        '+(f?r.title[0]:r.title)+"
        ":"";return r.zIndex=s,t([r.shade?'
        ':"",'
        '+(e&&2!=r.type?"":u)+'
        '+(0==r.type&&r.icon!==-1?'':"")+(1==r.type&&e?"":r.content||"")+'
        '+function(){var e=c?'':"";return r.closeBtn&&(e+=''),e}()+""+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t'+r.btn[t]+"";return'
        '+e+"
        "}():"")+(r.resize?'':"")+"
        "],u,i('
        ')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"http://layer.layui.com","auto"];t.content='';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(t){s=t.find(".layui-layer-input"),s.val(e.value||"").focus(),"function"==typeof f&&f(t)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
          '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
        • '+(t[0].content||"no content")+"
        • ";i'+(t[i].content||"no content")+"";return a}()+"
        ",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
        '+(u.length>1?'':"")+'
        '+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
        ",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
        是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/layim.js b/static/plugs/layui/lay/modules/layim.js deleted file mode 100644 index de06ca148..000000000 --- a/static/plugs/layui/lay/modules/layim.js +++ /dev/null @@ -1,3 +0,0 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;layui.define(["layer","laytpl","upload"],function(i){var a="3.7.2 Pro",e=layui.$,t=layui.layer,n=layui.laytpl,l=layui.device(),s="layui-show",o="layim-this",d=20,r={},c=function(){this.v=a,e("body").on("click","*[layim-event]",function(i){var a=e(this),t=a.attr("layim-event");ti[t]?ti[t].call(this,a,i):""})};c.prototype.config=function(i){var a=[];if(layui.each(Array(5),function(i){a.push(layui.cache.dir+"css/modules/layim/skin/"+(i+1)+".jpg")}),i=i||{},i.skin=i.skin||[],layui.each(i.skin,function(i,e){a.unshift(e)}),i.skin=a,i=e.extend({isfriend:!0,isgroup:!0,voice:"default.mp3"},i),window.JSON&&window.JSON.parse)return H(i),this},c.prototype.on=function(i,a){return"function"==typeof a&&(r[i]?r[i].push(a):r[i]=[a]),this},c.prototype.cache=function(){return j},c.prototype.chat=function(i){if(window.JSON&&window.JSON.parse)return z(i),this},c.prototype.setChatMin=function(){return N(),this},c.prototype.setChatStatus=function(i){var a=_();if(a){var e=a.elem.find(".layim-chat-status");return e.html(i),this}},c.prototype.getMessage=function(i){return K(i),this},c.prototype.notice=function(i){return J(i),this},c.prototype.add=function(i){return M(i),this},c.prototype.setFriendGroup=function(i){return M(i,"setGroup"),this},c.prototype.msgbox=function(i){return V(i),this},c.prototype.addList=function(i){return U(i),this},c.prototype.removeList=function(i){return W(i),this},c.prototype.setFriendStatus=function(i,a){var t=e(".layim-friend"+i);t["online"===a?"removeClass":"addClass"]("layim-list-gray")},c.prototype.content=function(i){return layui.data.content(i)};var u=function(i){var a={friend:"该分组下暂无好友",group:"暂无群组",history:"暂无历史会话"};return i=i||{},i.item=i.item||"d."+i.type,["{{# var length = 0; layui.each("+i.item+", function(i, data){ length++; }}",'
      • {{ data.username||data.groupname||data.name||"佚名" }}

        {{ data.remark||data.sign||"" }}

        new
      • ',"{{# }); if(length === 0){ }}",'
      • '+(a[i.type]||"暂无数据")+"
      • ","{{# } }}"].join("")},y=['
        ','
        ','
        {{ d.mine.username }}
        ','
        ','{{# if(d.mine.status === "online"){ }}','','{{# } else if(d.mine.status === "hide") { }}','',"{{# } }}",'
          ','
        • 在线
        • ','
        • 隐身
        • ',"
        ","
        ",'',"
        ",'
          ','
        • ','
        • ','
        • ',"
        ",'
          ','{{# layui.each(d.friend, function(index, item){ var spread = d.local["spread"+index]; }}',"
        • ",'
          {{# if(spread === "true"){ }}{{# } else { }}{{# } }}{{ item.groupname||"未命名分组"+index }}( {{ (item.list||[]).length }})
          ','
            ',u({type:"friend",item:"item.list",index:"index"}),"
          ","
        • ","{{# }); if(d.friend.length === 0){ }}",'
          • 暂无联系人
          ',"{{# } }}","
        ",'
          ',"
        • ",'
            ',u({type:"group"}),"
          ","
        • ","
        ",'
          ',"
        • ",'
            ',u({type:"history"}),"
          ","
        • ","
        ",'
          ',"
        • ",'',"
        • ","
        ",'
          ','',"{{# if(d.base.msgbox){ }}",'
        • ',"{{# } }}","{{# if(d.base.find){ }}",'
        • ',"{{# } }}",'
        • ',"{{# if(!d.base.copyright){ }}",'
        • ',"{{# } }}","
        ",'',"
        "].join(""),m=['
          ',"{{# layui.each(d.skin, function(index, item){ }}",'
        • ',"{{# }); }}",'
        • 简约
        • ',"
        "].join(""),f=['
        ','
        ','
        ','{{ d.data.name||"佚名" }} {{d.data.temporary ? "临时会话" : ""}} {{# if(d.data.type==="group"){ }} {{# } }}','

        ',"
        ","
        ",'
        ',"
          ","
          ",'","
          "].join(""),p=['
          ','

          {{ d.data.name||"" }}

          ','
          ','{{# if(d.data.type === "friend" && d.type === "setGroup"){ }}',"

          选择分组

          ",'{{# } if(d.data.type === "friend"){ }}','","{{# } }}",'{{# if(d.data.type === "group"){ }}',"

          请输入验证信息

          ",'{{# } if(d.type !== "setGroup"){ }}','',"{{# } }}","
          ","
          "].join(""),h=['
        • ','
          ',"{{# if(d.mine){ }}",'{{ layui.data.date(d.timestamp) }}{{ d.username||"佚名" }}',"{{# } else { }}",'{{ d.username||"佚名" }}{{ layui.data.date(d.timestamp) }}',"{{# } }}","
          ",'
          {{ layui.data.content(d.content||" ") }}
          ',"
        • "].join(""),v='
        • {{ d.data.name||"佚名" }}{{# if(!d.base.brief){ }}{{# } }}
        • ',g=function(i){return i<10?"0"+(0|i):i};layui.data.date=function(i){var a=new Date(i||new Date);return a.getFullYear()+"-"+g(a.getMonth()+1)+"-"+g(a.getDate())+" "+g(a.getHours())+":"+g(a.getMinutes())+":"+g(a.getSeconds())},layui.data.content=function(i){var a=function(i){return new RegExp("\\n*\\["+(i||"")+"(code|pre|div|span|p|table|thead|th|tbody|tr|td|ul|li|ol|li|dl|dt|dd|h2|h3|h4|h5)([\\s\\S]*?)\\]\\n*","g")};return i=(i||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/@(\S+)(\s+?|$)/g,'@$1$2').replace(/face\[([^\s\[\]]+?)\]/g,function(i){var a=i.replace(/^face/g,"");return''+a+''}).replace(/img\[([^\s]+?)\]/g,function(i){return''}).replace(/file\([\s\S]+?\)\[[\s\S]*?\]/g,function(i){var a=(i.match(/file\(([\s\S]+?)\)\[/)||[])[1],e=(i.match(/\)\[([\s\S]*?)\]/)||[])[1];return a?''+(e||a)+"":i}).replace(/audio\[([^\s]+?)\]/g,function(i){return'

          音频消息

          '}).replace(/video\[([^\s]+?)\]/g,function(i){return'
          '}).replace(/a\([\s\S]+?\)\[[\s\S]*?\]/g,function(i){var a=(i.match(/a\(([\s\S]+?)\)\[/)||[])[1],e=(i.match(/\)\[([\s\S]*?)\]/)||[])[1];return a?''+(e||a)+"":i}).replace(a(),"<$1 $2>").replace(a("/"),"").replace(/\n/g,"
          ")};var x,b,w,k,C,S=function(i,a,n){return i=i||{},e.ajax({url:i.url,type:i.type||"get",data:i.data,dataType:i.dataType||"json",cache:!1,success:function(i){0==i.code?a&&a(i.data||{}):t.msg(i.msg||(n||"Error")+": LAYIM_NOT_GET_DATA",{time:5e3})},error:function(i,a){window.console&&console.log&&console.error("LAYIM_DATE_ERROR:"+a)}})},j={message:{},chat:[]},H=function(i){var a=i.init||{};return mine=a.mine||{},local=layui.data("layim")[mine.id]||{},obj={base:i,local:local,mine:mine,history:local.history||{}},create=function(a){var t=a.mine||{},l=layui.data("layim")[t.id]||{},s={base:i,local:l,mine:t,friend:a.friend||[],group:a.group||[],history:l.history||{}};j=e.extend(j,s),L(n(y).render(s)),(l.close||i.min)&&T(),layui.each(r.ready,function(i,a){a&&a(s)})},j=e.extend(j,obj),i.brief?layui.each(r.ready,function(i,a){a&&a(obj)}):void(a.url?S(a,create,"INIT"):create(a))},L=function(i){return t.open({type:1,area:["260px","520px"],skin:"layui-box layui-layim",title:"​",offset:"rb",id:"layui-layim",shade:!1,anim:2,resize:!1,content:i,success:function(i){x=i,R(i),j.base.right&&i.css("margin-left","-"+j.base.right),b&&t.close(b.attr("times"));var a=[],n=i.find(".layim-list-history");n.find("li").each(function(){a.push(e(this).prop("outerHTML"))}),a.length>0&&(a.reverse(),n.html(a.join(""))),A(),ti.sign()},cancel:function(i){T();var a=layui.data("layim")[j.mine.id]||{};return a.close=!0,layui.data("layim",{key:j.mine.id,value:a}),!1}})},A=function(){x.on("contextmenu",function(i){return i.cancelBubble=!0,i.returnValue=!1,!1});var i=function(){t.closeAll("tips")};x.find(".layim-list-history").on("contextmenu","li",function(a){var n=e(this),l='
          • 移除该会话
          • 清空全部会话列表
          ';n.hasClass("layim-null")||(t.tips(l,this,{tips:1,time:0,anim:5,fixed:!0,skin:"layui-box layui-layim-contextmenu",success:function(i){var a=function(i){ii(i)};i.off("mousedown",a).on("mousedown",a)}}),e(document).off("mousedown",i).on("mousedown",i),e(window).off("resize",i).on("resize",i))})},T=function(i){return b&&t.close(b.attr("times")),x&&x.hide(),j.mine=j.mine||{},t.open({type:1,title:!1,id:"layui-layim-close",skin:"layui-box layui-layim-min layui-layim-close",shade:!1,closeBtn:!1,anim:2,offset:"rb",resize:!1,content:''+(i||j.base.title||"我的LayIM")+"",move:"#layui-layim-close img",success:function(i,a){b=i,j.base.right&&i.css("margin-left","-"+j.base.right),i.on("click",function(){t.close(a),x.show();var i=layui.data("layim")[j.mine.id]||{};delete i.close,layui.data("layim",{key:j.mine.id,value:i})})}})},z=function(i){i=i||{};var a=e("#layui-layim-chat"),l={data:i,base:j.base,local:j.local};if(!i.id)return t.msg("非法用户");if(a[0]){var s=w.find(".layim-chat-list"),o=s.find(".layim-chatlist-"+i.type+i.id),d=w.find(".layui-layer-max").hasClass("layui-layer-maxmin"),c=a.children(".layim-chat-box");return"none"===w.css("display")&&w.show(),k&&t.close(k.attr("times")),1!==s.find("li").length||o[0]||(d||w.css("width",800),s.css({height:w.height()}).show(),c.css("margin-left","200px")),o[0]||(s.append(n(v).render(l)),c.append(n(f).render(l)),I(i),E()),O(s.find(".layim-chatlist-"+i.type+i.id)),o[0]||P(),$(i),Q(),C}l.first=!0;var u=C=t.open({type:1,area:"600px",skin:"layui-box layui-layim-chat",id:"layui-layim-chat",title:"​",shade:!1,maxmin:!0,offset:i.offset||"auto",anim:i.anim||0,closeBtn:!j.base.brief&&1,content:n('
            '+v+'
          '+f+"
          ").render(l),success:function(a){w=a,a.css({"min-width":"500px","min-height":"420px"}),I(i),"function"==typeof i.success&&i.success(a),Q(),R(a),$(i),P(),q(),layui.each(r.chatChange,function(i,a){a&&a(_())}),a.on("dblclick",".layui-layim-photos",function(){var i=this.src;t.close(z.photosIndex),t.photos({photos:{data:[{alt:"大图模式",src:i}]},shade:.01,closeBtn:2,anim:0,resize:!1,success:function(i,a){z.photosIndex=a}})})},full:function(i){t.style(u,{width:"100%",height:"100%"},!0),E()},resizing:E,restore:E,min:function(){return N(),!1},end:function(){t.closeAll("tips"),w=null}});return u},I=function(i){e(".layim-"+i.type+i.id).each(function(){e(this).hasClass("layim-list-gray")&&layui.layim.setFriendStatus(i.id,"offline")})},E=function(){var i=w.find(".layim-chat-list"),a=w.find(".layim-chat-main"),e=w.height();i.css({height:e}),a.css({height:e-20-80-158})},N=function(i){var a=i||_().data,n=layui.layim.cache().base;w&&!i&&w.hide(),t.close(N.index),N.index=t.open({type:1,title:!1,skin:"layui-box layui-layim-min",shade:!1,closeBtn:!1,anim:a.anim||2,offset:"b",move:"#layui-layim-min",resize:!1,area:["182px","50px"],content:''+a.name+"",success:function(a,l){i||(k=a),n.minRight&&t.style(l,{left:e(window).width()-a.outerWidth()-parseFloat(n.minRight)}),a.find(".layui-layer-content span").on("click",function(){t.close(l),i?layui.each(j.chat,function(i,a){z(a)}):w.show(),i&&(j.chat=[],Z())}),a.find(".layui-layer-content img").on("click",function(i){ii(i)})}})},M=function(i,a){return i=i||{},t.close(M.index),M.index=t.open({type:1,area:"430px",title:{friend:"添加好友",group:"加入群组"}[i.type]||"",shade:!1,resize:!1,btn:a?["确认","取消"]:["发送申请","关闭"],content:n(p).render({data:{name:i.username||i.groupname,avatar:i.avatar,group:i.group||parent.layui.layim.cache().friend||[],type:i.type},type:a}),yes:function(e,t){var n=t.find("#LAY_layimGroup"),l=t.find("#LAY_layimRemark");a?i.submit&&i.submit(n.val(),e):i.submit&&i.submit(n.val(),l.val(),e)}})},O=function(i,a){i=i||e(".layim-chat-list ."+o);var n=i.index()===-1?0:i.index(),l=".layim-chat",d=w.find(l).eq(n),c=w.find(".layui-layer-max").hasClass("layui-layer-maxmin");if(a){i.hasClass(o)&&O(0===n?i.next():i.prev());var u=w.find(l).length;return 1===u?t.close(C):(i.remove(),d.remove(),2===u&&(w.find(".layim-chat-list").hide(),c||w.css("width","600px"),w.find(".layim-chat-box").css("margin-left",0)),!1)}i.addClass(o).siblings().removeClass(o),d.addClass(s).siblings(l).removeClass(s),d.find("textarea").focus(),layui.each(r.chatChange,function(i,a){a&&a(_())}),q()},q=function(){var i=_(),a=j.message[i.data.type+i.data.id];a&&delete j.message[i.data.type+i.data.id]},_=function(){if(w){var i=e(".layim-chat-list ."+o).index(),a=w.find(".layim-chat").eq(i),t=JSON.parse(decodeURIComponent(a.find(".layim-chat-tool").data("json")));return{elem:a,data:t,textarea:a.find("textarea")}}},R=function(i){var a=layui.data("layim")[j.mine.id]||{},e=a.skin;i.css({"background-image":e?"url("+e+")":function(){return j.base.initSkin?"url("+(layui.cache.dir+"css/modules/layim/skin/"+j.base.initSkin)+")":"none"}()})},$=function(i){var a=layui.data("layim")[j.mine.id]||{},e={},t=a.history||{},l=t[i.type+i.id];if(x){var s=x.find(".layim-list-history");if(i.historyTime=(new Date).getTime(),t[i.type+i.id]=i,a.history=t,layui.data("layim",{key:j.mine.id,value:a}),!l){e[i.type+i.id]=i;var o=n(u({type:"history",item:"d.data"})).render({data:e});s.prepend(o),s.find(".layim-null").remove()}}},D=function(){var i={username:j.mine?j.mine.username:"访客",avatar:j.mine?j.mine.avatar:layui.cache.dir+"css/pc/layim/skin/logo.jpg",id:j.mine?j.mine.id:null,mine:!0},a=_(),e=a.elem.find(".layim-chat-main ul"),l=j.base.maxLength||3e3;if(i.content=a.textarea.val(),""!==i.content.replace(/\s/g,"")){if(i.content.length>l)return t.msg("内容最长不能超过"+l+"个字符");e.append(n(h).render(i));var s={mine:i,to:a.data},o={username:s.mine.username,avatar:s.mine.avatar,id:s.to.id,type:s.to.type,content:s.mine.content,timestamp:(new Date).getTime(),mine:!0};B(o),layui.each(r.sendMessage,function(i,a){a&&a(s)})}Z(),a.textarea.val("").focus()},J=function(i){if(i=i||{},window.Notification)if("granted"===Notification.permission){new Notification(i.title||"",{body:i.content||"",icon:i.avatar||"http://tp2.sinaimg.cn/5488749285/50/5719808192/1"})}else Notification.requestPermission()},F=function(){if(!(l.ie&&l.ie<9)){var i=document.createElement("audio");i.src=layui.cache.dir+"css/modules/layim/voice/"+j.base.voice,i.play()}},G={},K=function(i){i=i||{};var a=e(".layim-chatlist-"+i.type+i.id),t={},l=a.index();if(i.timestamp=i.timestamp||(new Date).getTime(),i.fromid==j.mine.id&&(i.mine=!0),i.system||B(i),G=JSON.parse(JSON.stringify(i)),j.base.voice&&F(),!w&&i.content||l===-1){if(j.message[i.type+i.id])j.message[i.type+i.id].push(i);else if(j.message[i.type+i.id]=[i],"friend"===i.type){var s;layui.each(j.friend,function(a,e){if(layui.each(e.list,function(a,e){if(e.id==i.id)return e.type="friend",e.name=e.username,j.chat.push(e),s=!0}),s)return!0}),s||(i.name=i.username,i.temporary=!0,j.chat.push(i))}else if("group"===i.type){var o;layui.each(j.group,function(a,e){if(e.id==i.id)return e.type="group",e.name=e.groupname,j.chat.push(e),o=!0}),o||(i.name=i.groupname,j.chat.push(i))}else i.name=i.name||i.username||i.groupname,j.chat.push(i);if("group"===i.type&&layui.each(j.group,function(a,e){if(e.id==i.id)return t.avatar=e.avatar,!0}),!i.system)return j.base.notice&&J({title:"来自 "+i.username+" 的消息",content:i.content,avatar:t.avatar||i.avatar}),N({name:"收到新消息",avatar:t.avatar||i.avatar,anim:6})}if(w){var d=_();d.data.type+d.data.id!==i.type+i.id&&(a.addClass("layui-anim layer-anim-06"),setTimeout(function(){a.removeClass("layui-anim layer-anim-06")},300));var r=w.find(".layim-chat").eq(l),c=r.find(".layim-chat-main ul");i.system?l!==-1&&c.append('
        • '+i.content+"
        • "):""!==i.content.replace(/\s/g,"")&&c.append(n(h).render(i)),Z()}},Y="layui-anim-loop layer-anim-05",V=function(i){var a=x.find(".layim-tool-msgbox");a.find("span").addClass(Y).html(i)},B=function(i){var a=layui.data("layim")[j.mine.id]||{};a.chatlog=a.chatlog||{};var e=a.chatlog[i.type+i.id];if(e){var t;layui.each(e,function(a,e){e.timestamp===i.timestamp&&e.type===i.type&&e.id===i.id&&e.content===i.content&&(t=!0)}),t||i.fromid==j.mine.id||e.push(i),e.length>d&&e.shift()}else a.chatlog[i.type+i.id]=[i];layui.data("layim",{key:j.mine.id,value:a})},P=function(){var i=layui.data("layim")[j.mine.id]||{},a=_(),e=i.chatlog||{},t=a.elem.find(".layim-chat-main ul");layui.each(e[a.data.type+a.data.id],function(i,a){t.append(n(h).render(a))}),Z()},U=function(i){var a,e={},l=x.find(".layim-list-"+i.type);if(j[i.type])if("friend"===i.type)layui.each(j.friend,function(n,l){if(i.groupid==l.id)return layui.each(j.friend[n].list,function(e,t){if(t.id==i.id)return a=!0}),a?t.msg("好友 ["+(i.username||"")+"] 已经存在列表中",{anim:6}):(j.friend[n].list=j.friend[n].list||[],e[j.friend[n].list.length]=i,i.groupIndex=n,j.friend[n].list.push(i),!0)});else if("group"===i.type){if(layui.each(j.group,function(e,t){if(t.id==i.id)return a=!0}),a)return t.msg("您已是 ["+(i.groupname||"")+"] 的群成员",{anim:6});e[j.group.length]=i,j.group.push(i)}if(!a){var s=n(u({type:i.type,item:"d.data",index:"friend"===i.type?"data.groupIndex":null})).render({data:e});if("friend"===i.type){var o=l.find(">li").eq(i.groupIndex);o.find(".layui-layim-list").append(s),o.find(".layim-count").html(j.friend[i.groupIndex].list.length),o.find(".layim-null")[0]&&o.find(".layim-null").remove()}else"group"===i.type&&(l.append(s),l.find(".layim-null")[0]&&l.find(".layim-null").remove())}},W=function(i){var a=x.find(".layim-list-"+i.type);j[i.type]&&("friend"===i.type?layui.each(j.friend,function(e,t){layui.each(t.list,function(t,n){if(i.id==n.id){var l=a.find(">li").eq(e);l.find(".layui-layim-list>li");return l.find(".layui-layim-list>li").eq(t).remove(),j.friend[e].list.splice(t,1),l.find(".layim-count").html(j.friend[e].list.length),0===j.friend[e].list.length&&l.find(".layui-layim-list").html('
        • 该分组下已无好友了
        • '),!0}})}):"group"===i.type&&layui.each(j.group,function(e,t){if(i.id==t.id)return a.find(">li").eq(e).remove(),j.group.splice(e,1),0===j.group.length&&a.html('
        • 暂无群组
        • '),!0}))},Z=function(){var i=_(),a=i.elem.find(".layim-chat-main"),e=a.find("ul"),t=e.find("li").length;if(t>=d){var n=e.find("li").eq(0);e.prev().hasClass("layim-chat-system")||e.before('
          查看更多记录
          '),t>d&&n.remove()}a.scrollTop(a[0].scrollHeight+1e3),a.find("ul li:last").find("img").load(function(){a.scrollTop(a[0].scrollHeight+1e3)})},Q=function(){var i=_(),a=i.textarea;a.focus(),a.off("keydown").on("keydown",function(i){var e=layui.data("layim")[j.mine.id]||{},t=i.keyCode;if("Ctrl+Enter"===e.sendHotKey)return void(i.ctrlKey&&13===t&&D());if(13===t){if(i.ctrlKey)return a.val(a.val()+"\n");if(i.shiftKey)return;i.preventDefault(),D()}})},X=function(){var i=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],a={};return layui.each(i,function(i,e){a[e]=layui.cache.dir+"images/face/"+i+".gif"}),a}(),ii=layui.stope,ai=function(i,a){var e,t=i.value;i.focus(),document.selection?(e=document.selection.createRange(),document.selection.empty(),e.text=a):(e=[t.substring(0,i.selectionStart),a,t.substr(i.selectionEnd)],i.focus(),i.value=e.join(""))},ei="layui-anim-upbit",ti={status:function(i,a){var t=function(){i.next().hide().removeClass(ei)},n=i.attr("lay-type");if("show"===n)ii(a),i.next().show().addClass(ei),e(document).off("click",t).on("click",t);else{var l=i.parent().prev();i.addClass(o).siblings().removeClass(o),l.html(i.find("cite").html()),l.removeClass("layim-status-"+("online"===n?"hide":"online")).addClass("layim-status-"+n),layui.each(r.online,function(i,a){a&&a(n)})}},sign:function(){var i=x.find(".layui-layim-remark");i.on("change",function(){var i=this.value;layui.each(r.sign,function(a,e){e&&e(i)})}),i.on("keyup",function(i){var a=i.keyCode;13===a&&this.blur()})},tab:function(i){var a,e=".layim-tab-content",t=x.find(".layui-layim-tab>li");"number"==typeof i?(a=i,i=t.eq(a)):a=i.index(),a>2?t.removeClass(o):(ti.tab.index=a,i.addClass(o).siblings().removeClass(o)),x.find(e).eq(a).addClass(s).siblings(e).removeClass(s)},spread:function(i){var a=i.attr("lay-type"),e="true"===a?"false":"true",t=layui.data("layim")[j.mine.id]||{};i.next()["true"===a?"removeClass":"addClass"](s),t["spread"+i.parent().index()]=e,layui.data("layim",{key:j.mine.id,value:t}),i.attr("lay-type",e),i.find(".layui-icon").html("true"===e?"":"")},search:function(i){var a=x.find(".layui-layim-search"),e=x.find("#layui-layim-search"),t=a.find("input"),n=function(i){var a=t.val().replace(/\s/);if(""===a)ti.tab(0|ti.tab.index);else{for(var n=[],l=j.friend||[],s=j.group||[],o="",d=0;d0)for(var u=0;u'+(n[u].username||n[u].groupname||"佚名")+"

          "+(n[u].remark||n[u].sign||"")+"

          ";else o='
        • 无搜索结果
        • ';e.html(o),ti.tab(3)}};!j.base.isfriend&&j.base.isgroup?ti.tab.index=1:j.base.isfriend||j.base.isgroup||(ti.tab.index=2),a.show(),t.focus(),t.off("keyup",n).on("keyup",n)},closeSearch:function(i){i.parent().hide(),ti.tab(0|ti.tab.index)},msgbox:function(){var i=x.find(".layim-tool-msgbox");return t.close(ti.msgbox.index),i.find("span").removeClass(Y).html(""),ti.msgbox.index=t.open({type:2,title:"消息盒子",shade:!1,maxmin:!0,area:["600px","520px"],skin:"layui-box layui-layer-border",resize:!1,content:j.base.msgbox})},find:function(){return t.close(ti.find.index),ti.find.index=t.open({type:2,title:"查找",shade:!1,maxmin:!0,area:["1000px","520px"],skin:"layui-box layui-layer-border",resize:!1,content:j.base.find})},skin:function(){t.open({type:1,title:"更换背景",shade:!1,area:"300px",skin:"layui-box layui-layer-border",id:"layui-layim-skin",zIndex:66666666,resize:!1,content:n(m).render({skin:j.base.skin})})},about:function(){t.alert("版本: "+a+'
          版权所有:layim.layui.com',{title:"关于 LayIM",shade:!1})},setSkin:function(i){var a=i.attr("src"),e=layui.data("layim")[j.mine.id]||{};e.skin=a,a||delete e.skin,layui.data("layim",{key:j.mine.id,value:e});try{x.css({"background-image":a?"url("+a+")":"none"}),w.css({"background-image":a?"url("+a+")":"none"})}catch(t){}layui.each(r.setSkin,function(i,e){var t=(a||"").replace(layui.cache.dir+"css/modules/layim/skin/","");e&&e(t,a)})},chat:function(i){var a=layui.data("layim")[j.mine.id]||{},e=i.data("type"),t=i.data("index"),n=i.attr("data-list")||i.index(),l={};"friend"===e?l=j[e][t].list[n]:"group"===e?l=j[e][n]:"history"===e&&(l=(a.history||{})[t]||{}),l.name=l.name||l.username||l.groupname,"history"!==e&&(l.type=e),z(l)},tabChat:function(i){O(i)},closeChat:function(i,a){O(i.parent(),1),ii(a)},closeThisChat:function(){O(null,1)},groupMembers:function(i,a){var n=i.find(".layui-icon"),l=function(){n.html(""),i.data("down",null),t.close(ti.groupMembers.index)},s=function(i){ii(i)};i.data("down")?l():(n.html(""),i.data("down",!0),ti.groupMembers.index=t.tips('
            ',i,{tips:3,time:0,anim:5,fixed:!0,skin:"layui-box layui-layim-members",success:function(a){var t=j.base.members||{},n=_(),s=a.find(".layim-members-list"),o="",d={},c=w.find(".layui-layer-max").hasClass("layui-layer-maxmin"),u="none"===w.find(".layim-chat-list").css("display");c&&s.css({width:e(window).width()-22-(u||200)}),t.data=e.extend(t.data,{id:n.data.id}),S(t,function(a){layui.each(a.list,function(i,a){o+='
          • '+a.username+"
          • ",d[a.id]=a}),s.html(o),i.find(".layim-chat-members").html(a.members||(a.list||[]).length+"人"),s.find("li").on("click",function(){var i=e(this).data("uid"),a=d[i];z({name:a.username,type:"friend",avatar:a.avatar,id:a.id}),l()}),layui.each(r.members,function(i,e){e&&e(a)})}),a.on("mousedown",function(i){ii(i)})}}),e(document).off("mousedown",l).on("mousedown",l),e(window).off("resize",l).on("resize",l),i.off("mousedown",s).on("mousedown",s))},send:function(){D()},setSend:function(i,a){var t=ti.setSend.box=i.siblings(".layim-menu-box"),n=i.attr("lay-type");if("show"===n)ii(a),t.show().addClass(ei),e(document).off("click",ti.setSendHide).on("click",ti.setSendHide);else{i.addClass(o).siblings().removeClass(o);var l=layui.data("layim")[j.mine.id]||{};l.sendHotKey=n,layui.data("layim",{key:j.mine.id,value:l}),ti.setSendHide(a,i.parent())}},setSendHide:function(i,a){(a||ti.setSend.box).hide().removeClass(ei)},face:function(i,a){var n="",l=_();for(var s in X)n+='
          • ';n='
              '+n+"
            ",ti.face.index=t.tips(n,i,{tips:1,time:0,fixed:!0,skin:"layui-box layui-layim-face",success:function(i){i.find(".layim-face-list>li").on("mousedown",function(i){ii(i)}).on("click",function(){ai(l.textarea[0],"face"+this.title+" "),t.close(ti.face.index)})}}), -e(document).off("mousedown",ti.faceHide).on("mousedown",ti.faceHide),e(window).off("resize",ti.faceHide).on("resize",ti.faceHide),ii(a)},faceHide:function(){t.close(ti.face.index)},image:function(i){var a=i.data("type")||"images",e={images:"uploadImage",file:"uploadFile"},n=_(),l=j.base[e[a]]||{};layui.upload.render({url:l.url||"",method:l.type,elem:i.find("input")[0],accept:a,done:function(i){0==i.code?(i.data=i.data||{},"images"===a?ai(n.textarea[0],"img["+(i.data.src||"")+"]"):"file"===a&&ai(n.textarea[0],"file("+(i.data.src||"")+")["+(i.data.name||"下载文件")+"]"),D()):t.msg(i.msg||"上传失败")}})},media:function(i){var a=i.data("type"),n={audio:"音频",video:"视频"},l=_();t.prompt({title:"请输入网络"+n[a]+"地址",shade:!1,offset:[i.offset().top-e(window).scrollTop()-158+"px",i.offset().left+"px"]},function(i,e){ai(l.textarea[0],a+"["+i+"]"),D(),t.close(e)})},extend:function(i){var a=i.attr("lay-filter"),e=_();layui.each(r["tool("+a+")"],function(a,t){t&&t.call(i,function(i){ai(e.textarea[0],i)},D,e)})},playAudio:function(i){var a=i.data("audio"),e=a||document.createElement("audio"),n=function(){e.pause(),i.removeAttr("status"),i.find("i").html("")};return i.data("error")?t.msg("播放音频源异常"):e.play?void(i.attr("status")?n():(a||(e.src=i.data("src")),e.play(),i.attr("status","pause"),i.data("audio",e),i.find("i").html(""),e.onended=function(){n()},e.onerror=function(){t.msg("播放音频源异常"),i.data("error",!0),n()})):t.msg("您的浏览器不支持audio")},playVideo:function(i){var a=i.data("src"),e=document.createElement("video");return e.play?(t.close(ti.playVideo.index),void(ti.playVideo.index=t.open({type:1,title:"播放视频",area:["460px","300px"],maxmin:!0,shade:!1,content:'
            '}))):t.msg("您的浏览器不支持video")},chatLog:function(i){var a=_();return j.base.chatLog?(t.close(ti.chatLog.index),ti.chatLog.index=t.open({type:2,maxmin:!0,title:"与 "+a.data.name+" 的聊天记录",area:["450px","100%"],shade:!1,offset:"rb",skin:"layui-box",anim:2,id:"layui-layim-chatlog",content:j.base.chatLog+"?id="+a.data.id+"&type="+a.data.type})):t.msg("未开启更多聊天记录")},menuHistory:function(i,a){var n=layui.data("layim")[j.mine.id]||{},l=i.parent(),s=i.data("type"),o=x.find(".layim-list-history"),d='
          • 暂无历史会话
          • ';if("one"===s){var r=n.history;delete r[l.data("index")],n.history=r,layui.data("layim",{key:j.mine.id,value:n}),e("#"+l.data("id")).remove(),0===o.find("li").length&&o.html(d)}else"all"===s&&(delete n.history,layui.data("layim",{key:j.mine.id,value:n}),o.html(d));t.closeAll("tips")}};i("layim",new c)}).addcss("modules/layim/layim.css","skinlayimcss"); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/laypage.js b/static/plugs/layui/lay/modules/laypage.js index d40a58790..54fe5d3ae 100644 --- a/static/plugs/layui/lay/modules/laypage.js +++ b/static/plugs/layui/lay/modules/laypage.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),skip:function(){return['到第','','页',""].join("")}()};return['
            ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
            "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),skip:function(){return['到第','','页',""].join("")}()};return['
            ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
            "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/laytpl.js b/static/plugs/layui/lay/modules/laytpl.js index 80bb18656..a6923a790 100644 --- a/static/plugs/layui/lay/modules/laytpl.js +++ b/static/plugs/layui/lay/modules/laytpl.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},n={exp:function(e){return new RegExp(e,"g")},query:function(e,n,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return c((n||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var n="Laytpl Error:";return"object"==typeof console&&console.error(n+e+"\n"+(r||"")),n+e}},c=n.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=c("^"+r.open+"#",""),l=c(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(c(r.open+"#"),r.open+"# ").replace(c(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(/(?="|')/g,"\\").replace(n.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(n.query(1),function(e){var n='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(c(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),n='"+_escape_('),n+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,n.escape)}catch(u){return delete o.cache,n.error(u,p)}},t.pt.render=function(e,r){var c,t=this;return e?(c=t.cache?t.cache(e,n.escape):t.parse(t.tpl,e),r?void r(c):c):n.error("no data")};var o=function(e){return"string"!=typeof e?n.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var n in e)r[n]=e[n]},o.v="1.2.0",e("laytpl",o)}); \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)}); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/mobile.js b/static/plugs/layui/lay/modules/mobile.js index 59dac81e4..264d9d341 100644 --- a/static/plugs/layui/lay/modules/mobile.js +++ b/static/plugs/layui/lay/modules/mobile.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;layui.define(function(i){i("layui.mobile",layui.v)});layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},n={exp:function(e){return new RegExp(e,"g")},query:function(e,n,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return c((n||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var n="Laytpl Error:";return"object"==typeof console&&console.error(n+e+"\n"+(r||"")),n+e}},c=n.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=c("^"+r.open+"#",""),l=c(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(c(r.open+"#"),r.open+"# ").replace(c(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(/(?="|')/g,"\\").replace(n.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(n.query(1),function(e){var n='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(c(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),n='"+_escape_('),n+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,n.escape)}catch(u){return delete o.cache,n.error(u,p)}},t.pt.render=function(e,r){var c,t=this;return e?(c=t.cache?t.cache(e,n.escape):t.parse(t.tpl,e),r?void r(c):c):n.error("no data")};var o=function(e){return"string"!=typeof e?n.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var n in e)r[n]=e[n]},o.v="1.2.0",e("laytpl",o)});layui.define(function(e){"use strict";var t=(window,document),i="querySelectorAll",n="getElementsByClassName",a=function(e){return t[i](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var i in e)t[i]=e[i];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var o=0,r=["layui-m-layer"],d=function(e){var t=this;t.config=l.extend(e),t.view()};d.prototype.view=function(){var e=this,i=e.config,s=t.createElement("div");e.id=s.id=r[0]+o,s.setAttribute("class",r[0]+" "+r[0]+(i.type||0)),s.setAttribute("index",o);var l=function(){var e="object"==typeof i.title;return i.title?'

            '+(e?i.title[0]:i.title)+"

            ":""}(),d=function(){"string"==typeof i.btn&&(i.btn=[i.btn]);var e,t=(i.btn||[]).length;return 0!==t&&i.btn?(e=''+i.btn[0]+"",2===t&&(e=''+i.btn[1]+""+e),'
            '+e+"
            "):""}();if(i.fixed||(i.top=i.hasOwnProperty("top")?i.top:100,i.style=i.style||"",i.style+=" top:"+(t.body.scrollTop+i.top)+"px"),2===i.type&&(i.content='

            '+(i.content||"")+"

            "),i.skin&&(i.anim="up"),"msg"===i.skin&&(i.shade=!1),s.innerHTML=(i.shade?"
            ':"")+'
            "+l+'
            '+i.content+"
            "+d+"
            ",!i.type||2===i.type){var y=t[n](r[0]+i.type),u=y.length;u>=1&&c.close(y[0].getAttribute("index"))}document.body.appendChild(s);var m=e.elem=a("#"+e.id)[0];i.success&&i.success(m),e.index=o++,e.action(i,m)},d.prototype.action=function(e,t){var i=this;e.time&&(l.timer[i.index]=setTimeout(function(){c.close(i.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),c.close(i.index)):e.yes?e.yes(i.index):c.close(i.index)};if(e.btn)for(var s=t[n]("layui-m-layerbtn")[0].children,o=s.length,r=0;r0&&e-1 in t)}function s(t){return A.call(t,function(t){return null!=t})}function u(t){return t.length>0?T.fn.concat.apply([],t):t}function c(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(t){return t in F?F[t]:F[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function f(t,e){return"number"!=typeof e||k[c(t)]?e:e+"px"}function h(t){var e,n;return $[t]||(e=L.createElement(t),L.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),$[t]=n),$[t]}function p(t){return"children"in t?D.call(t.children):T.map(t.childNodes,function(t){if(1==t.nodeType)return t})}function d(t,e){var n,r=t?t.length:0;for(n=0;n]*>/,R=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Z=/^(?:body|html)$/i,q=/([A-Z])/g,H=["val","css","html","text","data","width","height","offset"],I=["after","prepend","before","append"],V=L.createElement("table"),_=L.createElement("tr"),B={tr:L.createElement("tbody"),tbody:V,thead:V,tfoot:V,td:_,th:_,"*":L.createElement("div")},U=/complete|loaded|interactive/,X=/^[\w-]*$/,J={},W=J.toString,Y={},G=L.createElement("div"),K={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},Q=Array.isArray||function(t){return t instanceof Array};return Y.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=G).appendChild(t),r=~Y.qsa(i,e).indexOf(t),o&&G.removeChild(t),r},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return A.call(t,function(e,n){return t.indexOf(e)==n})},Y.fragment=function(t,e,n){var r,i,a;return R.test(t)&&(r=T(L.createElement(RegExp.$1))),r||(t.replace&&(t=t.replace(z,"<$1>")),e===E&&(e=M.test(t)&&RegExp.$1),e in B||(e="*"),a=B[e],a.innerHTML=""+t,r=T.each(D.call(a.childNodes),function(){a.removeChild(this)})),o(n)&&(i=T(r),T.each(n,function(t,e){H.indexOf(t)>-1?i[t](e):i.attr(t,e)})),r},Y.Z=function(t,e){return new d(t,e)},Y.isZ=function(t){return t instanceof Y.Z},Y.init=function(t,n){var r;if(!t)return Y.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&M.test(t))r=Y.fragment(t,RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}else{if(e(t))return T(L).ready(t);if(Y.isZ(t))return t;if(Q(t))r=s(t);else if(i(t))r=[t],t=null;else if(M.test(t))r=Y.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}}return Y.Z(r,t)},T=function(t,e){return Y.init(t,e)},T.extend=function(t){var e,n=D.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){m(t,n,e)}),t},Y.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,a=X.test(o);return t.getElementById&&a&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:D.call(a&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},T.contains=L.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},T.type=t,T.isFunction=e,T.isWindow=n,T.isArray=Q,T.isPlainObject=o,T.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},T.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},T.inArray=function(t,e,n){return O.indexOf.call(e,t,n)},T.camelCase=C,T.trim=function(t){return null==t?"":String.prototype.trim.call(t)},T.uuid=0,T.support={},T.expr={},T.noop=function(){},T.map=function(t,e){var n,r,i,o=[];if(a(t))for(r=0;r=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return O.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return e(t)?this.not(this.not(t)):T(A.call(this,function(e){return Y.matches(e,t)}))},add:function(t,e){return T(N(this.concat(T(t,e))))},is:function(t){return this.length>0&&Y.matches(this[0],t)},not:function(t){var n=[];if(e(t)&&t.call!==E)this.each(function(e){t.call(this,e)||n.push(this)});else{var r="string"==typeof t?this.filter(t):a(t)&&e(t.item)?D.call(t):T(t);this.forEach(function(t){r.indexOf(t)<0&&n.push(t)})}return T(n)},has:function(t){return this.filter(function(){return i(t)?T.contains(this,t):T(this).find(t).size()})},eq:function(t){return t===-1?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!i(t)?t:T(t)},last:function(){var t=this[this.length-1];return t&&!i(t)?t:T(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?T(t).filter(function(){var t=this;return O.some.call(n,function(e){return T.contains(e,t)})}):1==this.length?T(Y.qsa(this[0],t)):this.map(function(){return Y.qsa(this,t)}):T()},closest:function(t,e){var n=[],i="object"==typeof t&&T(t);return this.each(function(o,a){for(;a&&!(i?i.indexOf(a)>=0:Y.matches(a,t));)a=a!==e&&!r(a)&&a.parentNode;a&&n.indexOf(a)<0&&n.push(a)}),T(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=T.map(n,function(t){if((t=t.parentNode)&&!r(t)&&e.indexOf(t)<0)return e.push(t),t});return v(e,t)},parent:function(t){return v(N(this.pluck("parentNode")),t)},children:function(t){return v(this.map(function(){return p(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||D.call(this.childNodes)})},siblings:function(t){return v(this.map(function(t,e){return A.call(p(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return T.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=h(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var n=e(t);if(this[0]&&!n)var r=T(t).get(0),i=r.parentNode||this.length>1;return this.each(function(e){T(this).wrapAll(n?t.call(this,e):i?r.cloneNode(!0):r)})},wrapAll:function(t){if(this[0]){T(this[0]).before(t=T(t));for(var e;(e=t.children()).length;)t=e.first();T(t).append(this)}return this},wrapInner:function(t){var n=e(t);return this.each(function(e){var r=T(this),i=r.contents(),o=n?t.call(this,e):t;i.length?i.wrapAll(o):r.append(o)})},unwrap:function(){return this.parent().each(function(){T(this).replaceWith(T(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var e=T(this);(t===E?"none"==e.css("display"):t)?e.show():e.hide()})},prev:function(t){return T(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return T(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;T(this).empty().append(g(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=g(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,e){var n;return"string"!=typeof t||1 in arguments?this.each(function(n){if(1===this.nodeType)if(i(t))for(j in t)y(this,j,t[j]);else y(this,t,g(this,e,n,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(n=this[0].getAttribute(t))?n:E},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){y(this,t)},this)})},prop:function(t,e){return t=K[t]||t,1 in arguments?this.each(function(n){this[t]=g(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=K[t]||t,this.each(function(){delete this[t]})},data:function(t,e){var n="data-"+t.replace(q,"-$1").toLowerCase(),r=1 in arguments?this.attr(n,e):this.attr(n);return null!==r?b(r):E},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=g(this,t,e,this.value)})):this[0]&&(this[0].multiple?T(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var n=T(this),r=g(this,t,e,n.offset()),i=n.offsetParent().offset(),o={top:r.top-i.top,left:r.left-i.left};"static"==n.css("position")&&(o.position="relative"),n.css(o)});if(!this.length)return null;if(L.documentElement!==this[0]&&!T.contains(L.documentElement,this[0]))return{top:0,left:0};var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(e,n){if(arguments.length<2){var r=this[0];if("string"==typeof e){if(!r)return;return r.style[C(e)]||getComputedStyle(r,"").getPropertyValue(e)}if(Q(e)){if(!r)return;var i={},o=getComputedStyle(r,"");return T.each(e,function(t,e){i[e]=r.style[C(e)]||o.getPropertyValue(e)}),i}}var a="";if("string"==t(e))n||0===n?a=c(e)+":"+f(e,n):this.each(function(){this.style.removeProperty(c(e))});else for(j in e)e[j]||0===e[j]?a+=c(j)+":"+f(j,e[j])+";":this.each(function(){this.style.removeProperty(c(j))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(T(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return!!t&&O.some.call(this,function(t){return this.test(x(t))},l(t))},addClass:function(t){return t?this.each(function(e){if("className"in this){S=[];var n=x(this),r=g(this,t,e,n);r.split(/\s+/g).forEach(function(t){T(this).hasClass(t)||S.push(t)},this),S.length&&x(this,n+(n?" ":"")+S.join(" "))}}):this},removeClass:function(t){return this.each(function(e){if("className"in this){if(t===E)return x(this,"");S=x(this),g(this,t,e,S).split(/\s+/g).forEach(function(t){S=S.replace(l(t)," ")}),x(this,S.trim())}})},toggleClass:function(t,e){return t?this.each(function(n){var r=T(this),i=g(this,t,n,x(this));i.split(/\s+/g).forEach(function(t){(e===E?!r.hasClass(t):e)?r.addClass(t):r.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var e="scrollTop"in this[0];return t===E?e?this[0].scrollTop:this[0].pageYOffset:this.each(e?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var e="scrollLeft"in this[0];return t===E?e?this[0].scrollLeft:this[0].pageXOffset:this.each(e?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),r=Z.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(T(t).css("margin-top"))||0,n.left-=parseFloat(T(t).css("margin-left"))||0,r.top+=parseFloat(T(e[0]).css("border-top-width"))||0,r.left+=parseFloat(T(e[0]).css("border-left-width"))||0,{top:n.top-r.top,left:n.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||L.body;t&&!Z.test(t.nodeName)&&"static"==T(t).css("position");)t=t.offsetParent;return t})}},T.fn.detach=T.fn.remove,["width","height"].forEach(function(t){var e=t.replace(/./,function(t){return t[0].toUpperCase()});T.fn[t]=function(i){var o,a=this[0];return i===E?n(a)?a["inner"+e]:r(a)?a.documentElement["scroll"+e]:(o=this.offset())&&o[t]:this.each(function(e){a=T(this),a.css(t,g(this,i,e,a[t]()))})}}),I.forEach(function(e,n){var r=n%2;T.fn[e]=function(){var e,i,o=T.map(arguments,function(n){var r=[];return e=t(n),"array"==e?(n.forEach(function(t){return t.nodeType!==E?r.push(t):T.zepto.isZ(t)?r=r.concat(t.get()):void(r=r.concat(Y.fragment(t)))}),r):"object"==e||null==n?n:Y.fragment(n)}),a=this.length>1;return o.length<1?this:this.each(function(t,e){i=r?e:e.parentNode,e=0==n?e.nextSibling:1==n?e.firstChild:2==n?e:null;var s=T.contains(L.documentElement,i);o.forEach(function(t){if(a)t=t.cloneNode(!0);else if(!i)return T(t).remove();i.insertBefore(t,e),s&&w(t,function(t){if(!(null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src)){var e=t.ownerDocument?t.ownerDocument.defaultView:window;e.eval.call(e,t.innerHTML)}})})})},T.fn[r?e+"To":"insert"+(n?"Before":"After")]=function(t){return T(t)[e](this),this}}),Y.Z.prototype=d.prototype=T.fn,Y.uniq=N,Y.deserializeValue=b,T.zepto=Y,T}();!function(t){function e(t){return t._zid||(t._zid=h++)}function n(t,n,o,a){if(n=r(n),n.ns)var s=i(n.ns);return(v[e(t)]||[]).filter(function(t){return t&&(!n.e||t.e==n.e)&&(!n.ns||s.test(t.ns))&&(!o||e(t.fn)===e(o))&&(!a||t.sel==a)})}function r(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function i(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function o(t,e){return t.del&&!y&&t.e in x||!!e}function a(t){return b[t]||y&&x[t]||t}function s(n,i,s,u,l,h,p){var d=e(n),m=v[d]||(v[d]=[]);i.split(/\s/).forEach(function(e){if("ready"==e)return t(document).ready(s);var i=r(e);i.fn=s,i.sel=l,i.e in b&&(s=function(e){var n=e.relatedTarget;if(!n||n!==this&&!t.contains(this,n))return i.fn.apply(this,arguments)}),i.del=h;var d=h||s;i.proxy=function(t){if(t=c(t),!t.isImmediatePropagationStopped()){t.data=u;var e=d.apply(n,t._args==f?[t]:[t].concat(t._args));return e===!1&&(t.preventDefault(),t.stopPropagation()),e}},i.i=m.length,m.push(i),"addEventListener"in n&&n.addEventListener(a(i.e),i.proxy,o(i,p))})}function u(t,r,i,s,u){var c=e(t);(r||"").split(/\s/).forEach(function(e){n(t,e,i,s).forEach(function(e){delete v[c][e.i],"removeEventListener"in t&&t.removeEventListener(a(e.e),e.proxy,o(e,u))})})}function c(e,n){return!n&&e.isDefaultPrevented||(n||(n=e),t.each(T,function(t,r){var i=n[t];e[t]=function(){return this[r]=w,i&&i.apply(n,arguments)},e[r]=E}),e.timeStamp||(e.timeStamp=Date.now()),(n.defaultPrevented!==f?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(e.isDefaultPrevented=w)),e}function l(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===f||(n[e]=t[e]);return c(n,t)}var f,h=1,p=Array.prototype.slice,d=t.isFunction,m=function(t){return"string"==typeof t},v={},g={},y="onfocusin"in window,x={focus:"focusin",blur:"focusout"},b={mouseenter:"mouseover",mouseleave:"mouseout"};g.click=g.mousedown=g.mouseup=g.mousemove="MouseEvents",t.event={add:s,remove:u},t.proxy=function(n,r){var i=2 in arguments&&p.call(arguments,2);if(d(n)){var o=function(){return n.apply(r,i?i.concat(p.call(arguments)):arguments)};return o._zid=e(n),o}if(m(r))return i?(i.unshift(n[r],n),t.proxy.apply(null,i)):t.proxy(n[r],n);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var w=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,T={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,n,r,i,o){var a,c,h=this;return e&&!m(e)?(t.each(e,function(t,e){h.on(t,n,r,e,o)}),h):(m(n)||d(i)||i===!1||(i=r,r=n,n=f),i!==f&&r!==!1||(i=r,r=f),i===!1&&(i=E),h.each(function(f,h){o&&(a=function(t){return u(h,t.type,i),i.apply(this,arguments)}),n&&(c=function(e){var r,o=t(e.target).closest(n,h).get(0);if(o&&o!==h)return r=t.extend(l(e),{currentTarget:o,liveFired:h}),(a||i).apply(o,[r].concat(p.call(arguments,1)))}),s(h,e,i,r,n,c||a)}))},t.fn.off=function(e,n,r){var i=this;return e&&!m(e)?(t.each(e,function(t,e){i.off(t,n,e)}),i):(m(n)||d(r)||r===!1||(r=n,n=f),r===!1&&(r=E),i.each(function(){u(this,e,r,n)}))},t.fn.trigger=function(e,n){return e=m(e)||t.isPlainObject(e)?t.Event(e):c(e),e._args=n,this.each(function(){e.type in x&&"function"==typeof this[e.type]?this[e.type]():"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,r){var i,o;return this.each(function(a,s){i=l(m(e)?t.Event(e):e),i._args=r,i.target=s,t.each(n(s,e.type||e),function(t,e){if(o=e.proxy(i),i.isImmediatePropagationStopped())return!1})}),o},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return 0 in arguments?this.bind(e,t):this.trigger(e)}}),t.Event=function(t,e){m(t)||(e=t,t=e.type);var n=document.createEvent(g[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),c(n)}}(e),function(t){function e(e,n,r){var i=t.Event(n);return t(e).trigger(i,r),!i.isDefaultPrevented()}function n(t,n,r,i){if(t.global)return e(n||x,r,i)}function r(e){e.global&&0===t.active++&&n(e,null,"ajaxStart")}function i(e){e.global&&!--t.active&&n(e,null,"ajaxStop")}function o(t,e){var r=e.context;return e.beforeSend.call(r,t,e)!==!1&&n(e,r,"ajaxBeforeSend",[t,e])!==!1&&void n(e,r,"ajaxSend",[t,e])}function a(t,e,r,i){var o=r.context,a="success";r.success.call(o,t,a,e),i&&i.resolveWith(o,[t,a,e]),n(r,o,"ajaxSuccess",[e,r,t]),u(a,e,r)}function s(t,e,r,i,o){var a=i.context;i.error.call(a,r,e,t),o&&o.rejectWith(a,[r,e,t]),n(i,a,"ajaxError",[r,i,t||e]),u(e,r,i)}function u(t,e,r){var o=r.context;r.complete.call(o,e,t),n(r,o,"ajaxComplete",[e,r]),i(r)}function c(t,e,n){if(n.dataFilter==l)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function l(){}function f(t){return t&&(t=t.split(";",2)[0]),t&&(t==T?"html":t==j?"json":w.test(t)?"script":E.test(t)&&"xml")||"text"}function h(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function p(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()&&"jsonp"!=e.dataType||(e.url=h(e.url,e.data),e.data=void 0)}function d(e,n,r,i){return t.isFunction(n)&&(i=r,r=n,n=void 0),t.isFunction(r)||(i=r,r=void 0),{url:e,data:n,success:r,dataType:i}}function m(e,n,r,i){var o,a=t.isArray(n),s=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),i&&(n=r?i:i+"["+(s||"object"==o||"array"==o?n:"")+"]"),!i&&a?e.add(u.name,u.value):"array"==o||!r&&"object"==o?m(e,u,r,n):e.add(n,u)})}var v,g,y=+new Date,x=window.document,b=/)<[^<]*)*<\/script>/gi,w=/^(?:text|application)\/javascript/i,E=/^(?:text|application)\/xml/i,j="application/json",T="text/html",S=/^\s*$/,C=x.createElement("a");C.href=window.location.href,t.active=0,t.ajaxJSONP=function(e,n){if(!("type"in e))return t.ajax(e);var r,i,u=e.jsonpCallback,c=(t.isFunction(u)?u():u)||"Zepto"+y++,l=x.createElement("script"),f=window[c],h=function(e){t(l).triggerHandler("error",e||"abort")},p={abort:h};return n&&n.promise(p),t(l).on("load error",function(o,u){clearTimeout(i),t(l).off().remove(),"error"!=o.type&&r?a(r[0],p,e,n):s(null,u||"error",p,e,n),window[c]=f,r&&t.isFunction(f)&&f(r[0]),f=r=void 0}),o(p,e)===!1?(h("abort"),p):(window[c]=function(){r=arguments},l.src=e.url.replace(/\?(.+)=\?/,"?$1="+c),x.head.appendChild(l),e.timeout>0&&(i=setTimeout(function(){h("timeout")},e.timeout)),p)},t.ajaxSettings={type:"GET",beforeSend:l,success:l,error:l,complete:l,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:j,xml:"application/xml, text/xml",html:T,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0,dataFilter:l},t.ajax=function(e){var n,i,u=t.extend({},e||{}),d=t.Deferred&&t.Deferred();for(v in t.ajaxSettings)void 0===u[v]&&(u[v]=t.ajaxSettings[v]);r(u),u.crossDomain||(n=x.createElement("a"),n.href=u.url,n.href=n.href,u.crossDomain=C.protocol+"//"+C.host!=n.protocol+"//"+n.host),u.url||(u.url=window.location.toString()),(i=u.url.indexOf("#"))>-1&&(u.url=u.url.slice(0,i)),p(u);var m=u.dataType,y=/\?.+=\?/.test(u.url);if(y&&(m="jsonp"),u.cache!==!1&&(e&&e.cache===!0||"script"!=m&&"jsonp"!=m)||(u.url=h(u.url,"_="+Date.now())),"jsonp"==m)return y||(u.url=h(u.url,u.jsonp?u.jsonp+"=?":u.jsonp===!1?"":"callback=?")),t.ajaxJSONP(u,d);var b,w=u.accepts[m],E={},j=function(t,e){E[t.toLowerCase()]=[t,e]},T=/^([\w-]+:)\/\//.test(u.url)?RegExp.$1:window.location.protocol,N=u.xhr(),O=N.setRequestHeader;if(d&&d.promise(N),u.crossDomain||j("X-Requested-With","XMLHttpRequest"),j("Accept",w||"*/*"),(w=u.mimeType||w)&&(w.indexOf(",")>-1&&(w=w.split(",",2)[0]),N.overrideMimeType&&N.overrideMimeType(w)),(u.contentType||u.contentType!==!1&&u.data&&"GET"!=u.type.toUpperCase())&&j("Content-Type",u.contentType||"application/x-www-form-urlencoded"),u.headers)for(g in u.headers)j(g,u.headers[g]);if(N.setRequestHeader=j,N.onreadystatechange=function(){if(4==N.readyState){N.onreadystatechange=l,clearTimeout(b);var e,n=!1;if(N.status>=200&&N.status<300||304==N.status||0==N.status&&"file:"==T){if(m=m||f(u.mimeType||N.getResponseHeader("content-type")),"arraybuffer"==N.responseType||"blob"==N.responseType)e=N.response;else{e=N.responseText;try{e=c(e,m,u),"script"==m?(0,eval)(e):"xml"==m?e=N.responseXML:"json"==m&&(e=S.test(e)?null:t.parseJSON(e))}catch(r){n=r}if(n)return s(n,"parsererror",N,u,d)}a(e,N,u,d)}else s(N.statusText||null,N.status?"error":"abort",N,u,d)}},o(N,u)===!1)return N.abort(),s(null,"abort",N,u,d),N;var P=!("async"in u)||u.async;if(N.open(u.type,u.url,P,u.username,u.password),u.xhrFields)for(g in u.xhrFields)N[g]=u.xhrFields[g];for(g in E)O.apply(N,E[g]);return u.timeout>0&&(b=setTimeout(function(){N.onreadystatechange=l,N.abort(),s(null,"timeout",N,u,d)},u.timeout)),N.send(u.data?u.data:null),N},t.get=function(){return t.ajax(d.apply(null,arguments))},t.post=function(){var e=d.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=d.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,r){if(!this.length)return this;var i,o=this,a=e.split(/\s/),s=d(e,n,r),u=s.success;return a.length>1&&(s.url=a[0],i=a[1]),s.success=function(e){o.html(i?t("
            ").html(e.replace(b,"")).find(i):e),u&&u.apply(o,arguments)},t.ajax(s),this};var N=encodeURIComponent;t.param=function(e,n){var r=[];return r.add=function(e,n){t.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(N(e)+"="+N(n))},m(r,e,n),r.join("&").replace(/%20/g,"+")}}(e),function(t){t.fn.serializeArray=function(){var e,n,r=[],i=function(t){return t.forEach?t.forEach(i):void r.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(r,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&i(t(o).val())}),r},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(e),function(){try{getComputedStyle(void 0)}catch(t){var e=getComputedStyle;window.getComputedStyle=function(t,n){try{return e(t,n)}catch(r){return null}}}}(),t("zepto",e)});layui.define(["layer-mobile","zepto"],function(e){"use strict";var t=layui.zepto,a=layui["layer-mobile"],i=(layui.device(),"layui-upload-enter"),n="layui-upload-iframe",r={icon:2,shift:6},o={file:"文件",video:"视频",audio:"音频"};a.msg=function(e){return a.open({content:e||"",skin:"msg",time:2})};var s=function(e){this.options=e};s.prototype.init=function(){var e=this,a=e.options,r=t("body"),s=t(a.elem||".layui-upload-file"),u=t('');return t("#"+n)[0]||r.append(u),s.each(function(r,s){s=t(s);var u='
            ',l=s.attr("lay-type")||a.type;a.unwrap||(u='
            '+u+''+(s.attr("lay-title")||a.title||"上传"+(o[l]||"图片"))+"
            "),u=t(u),a.unwrap||u.on("dragover",function(e){e.preventDefault(),t(this).addClass(i)}).on("dragleave",function(){t(this).removeClass(i)}).on("drop",function(){t(this).removeClass(i)}),s.parent("form").attr("target")===n&&(a.unwrap?s.unwrap():(s.parent().next().remove(),s.unwrap().unwrap())),s.wrap(u),s.off("change").on("change",function(){e.action(this,l)})})},s.prototype.action=function(e,i){var o=this,s=o.options,u=e.value,l=t(e),p=l.attr("lay-ext")||s.ext||"";if(u){switch(i){case"file":if(p&&!RegExp("\\w\\.("+p+")$","i").test(escape(u)))return a.msg("不支持该文件格式",r),e.value="";break;case"video":if(!RegExp("\\w\\.("+(p||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(u)))return a.msg("不支持该视频格式",r),e.value="";break;case"audio":if(!RegExp("\\w\\.("+(p||"mp3|wav|mid")+")$","i").test(escape(u)))return a.msg("不支持该音频格式",r),e.value="";break;default:if(!RegExp("\\w\\.("+(p||"jpg|png|gif|bmp|jpeg")+")$","i").test(escape(u)))return a.msg("不支持该图片格式",r),e.value=""}s.before&&s.before(e),l.parent().submit();var c=t("#"+n),f=setInterval(function(){var t;try{t=c.contents().find("body").text()}catch(i){a.msg("上传接口存在跨域",r),clearInterval(f)}if(t){clearInterval(f),c.contents().find("body").html("");try{t=JSON.parse(t)}catch(i){return t={},a.msg("请对上传接口返回JSON字符",r)}"function"==typeof s.success&&s.success(t,e)}},30);e.value=""}},e("upload-mobile",function(e){var t=new s(e=e||{});t.init()})});layui.define(["laytpl","upload-mobile","layer-mobile","zepto"],function(i){var e="2.1.0",a=layui.zepto,t=layui.laytpl,n=layui["layer-mobile"],l=layui["upload-mobile"],s=layui.device(),o="layui-show",c="layim-this",d=20,r={},u=function(){this.v=e,m(a("body"),"*[layim-event]",function(i){var e=a(this),t=e.attr("layim-event");U[t]?U[t].call(this,e,i):""})},m=function(i,e,t){var n,l="function"==typeof e,s=function(i){var e=a(this);e.data("lock")||(n||t.call(this,i),n=!1,e.data("lock","true"),setTimeout(function(){e.removeAttr("data-lock")},e.data("locktime")||0))};return l&&(t=e),i="string"==typeof i?a(i):i,y?void(l?i.on("touchmove",function(){n=!0}).on("touchend",s):i.on("touchmove",e,function(){n=!0}).on("touchend",e,s)):void(l?i.on("click",s):i.on("click",e,s))},y=/Android|iPhone|SymbianOS|Windows Phone|iPad|iPod/.test(navigator.userAgent);n.popBottom=function(i){n.close(n.popBottom.index),n.popBottom.index=n.open(a.extend({type:1,content:i.content||"",shade:!1,className:"layim-layer"},i))},u.prototype.config=function(i){i=i||{},i=a.extend({title:"我的IM",isgroup:0,isNewFriend:!0,voice:"default.mp3",chatTitleColor:"#36373C"},i),k(i)},u.prototype.on=function(i,e){return"function"==typeof e&&(r[i]?r[i].push(e):r[i]=[e]),this},u.prototype.chat=function(i){if(window.JSON&&window.JSON.parse)return L(i,-1),this},u.prototype.panel=function(i){return N(i)},u.prototype.cache=function(){return C},u.prototype.getMessage=function(i){return M(i),this},u.prototype.addList=function(i){return O(i),this},u.prototype.removeList=function(i){return Y(i),this},u.prototype.setFriendStatus=function(i,e){var t=a(".layim-friend"+i);t["online"===e?"removeClass":"addClass"]("layim-list-gray")},u.prototype.setChatStatus=function(i){var e=A(),a=e.elem.find(".layim-chat-status");return a.html(i),this},u.prototype.showNew=function(i,e){I(i,e)},u.prototype.content=function(i){return layui.data.content(i)};var p=function(i){var e={friend:"该分组下暂无好友",group:"暂无群组",history:"暂无任何消息"};return i=i||{},"history"===i.type&&(i.item=i.item||"d.sortHistory"),["{{# var length = 0; layui.each("+i.item+", function(i, data){ length++; }}",'
          • {{ data.username||data.groupname||data.name||"佚名" }}

            {{ data.remark||data.sign||"" }}

            new
          • ',"{{# }); if(length === 0){ }}",'
          • '+(e[i.type]||"暂无数据")+"
          • ","{{# } }}"].join("")},f=function(i,e,a){return['
            ','
            ',"

            ",a?'':"",'{{ d.title || d.base.title }}',"{{# if(d.data){ }}",'{{# if(d.data.type === "group"){ }}','',"{{# } }}","{{# } }}","

            ","
            ",'
            ',i,"
            ","
            "].join("")},h=['
            ','
            ','
              ','
                ',p({type:"history"}),"
              ","
            ","
            ",'
            ','
              ',"{{# if(d.base.isNewFriend){ }}",'
            • 新的朋友
            • ',"{{# } if(d.base.isgroup){ }}",'
            • 群聊
            • ',"{{# } }}","
            ",'
              ','{{# layui.each(d.friend, function(index, item){ var spread = d.local["spread"+index]; }}',"
            • ",'
              {{# if(spread === "true"){ }}{{# } else { }}{{# } }}{{ item.groupname||"未命名分组"+index }}( {{ (item.list||[]).length }})
              ','
                ',p({type:"friend",item:"item.list",index:"index"}),"
              ","
            • ","{{# }); if(d.friend.length === 0){ }}",'
              • 暂无联系人
              ',"{{# } }}","
            ","
            ",'
            ','
              ',"{{# layui.each(d.base.moreList, function(index, item){ }}",'
            • ','{{item.iconUnicode||""}}{{item.title}}',"
            • ","{{# }); if(!d.base.copyright){ }}",'
            • 关于
            • ',"{{# } }}","
            ","
            ","
            ",'
              ','
            • 消息
            • ','
            • 联系人
            • ','
            • 更多
            • ',"
            "].join(""),v=['
            ','
            ',"
              ","
              ",'","
              "].join(""),g=function(i){return i<10?"0"+(0|i):i};layui.data.date=function(i){var e=new Date(i||new Date);return g(e.getMonth()+1)+"-"+g(e.getDate())+" "+g(e.getHours())+":"+g(e.getMinutes())},layui.data.content=function(i){var e=function(i){return new RegExp("\\n*\\["+(i||"")+"(pre|div|p|table|thead|th|tbody|tr|td|ul|li|ol|li|dl|dt|dd|h2|h3|h4|h5)([\\s\\S]*?)\\]\\n*","g")};return i=(i||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/@(\S+)(\s+?|$)/g,'@$1$2').replace(/face\[([^\s\[\]]+?)\]/g,function(i){var e=i.replace(/^face/g,"");return''+e+''}).replace(/img\[([^\s]+?)\]/g,function(i){return''}).replace(/file\([\s\S]+?\)\[[\s\S]*?\]/g,function(i){var e=(i.match(/file\(([\s\S]+?)\)\[/)||[])[1],a=(i.match(/\)\[([\s\S]*?)\]/)||[])[1];return e?''+(a||e)+"":i}).replace(/audio\[([^\s]+?)\]/g,function(i){return'

              音频消息

              '}).replace(/video\[([^\s]+?)\]/g,function(i){return'
              '}).replace(/a\([\s\S]+?\)\[[\s\S]*?\]/g,function(i){var e=(i.match(/a\(([\s\S]+?)\)\[/)||[])[1],a=(i.match(/\)\[([\s\S]*?)\]/)||[])[1];return e?''+(a||e)+"":i}).replace(e(),"<$1 $2>").replace(e("/"),"").replace(/\n/g,"
              ")};var b,x,w=['
            • ','
              ','{{ d.username||"佚名" }}',"
              ",'
              {{ layui.data.content(d.content||" ") }}
              ',"
            • "].join(""),C={message:{},chat:[]},k=function(i){var e=i.init||{};return mine=e.mine||{},local=layui.data("layim-mobile")[mine.id]||{},obj={base:i,local:local,mine:mine,history:local.history||[]},create=function(e){var n=e.mine||{},l=layui.data("layim-mobile")[n.id]||{},s={base:i,local:l,mine:n,friend:e.friend||[],group:e.group||[],history:l.history||[]};s.sortHistory=j(s.history,"historyTime"),C=a.extend(C,s),S(t(f(h)).render(s)),layui.each(r.ready,function(i,e){e&&e(s)})},C=a.extend(C,obj),i.brief?layui.each(r.ready,function(i,e){e&&e(obj)}):void create(e)},S=function(i){return n.open({type:1,shade:!1,shadeClose:!1,anim:-1,content:i,success:function(i){b=a(i),T(b.find(".layui-layim")),C.base.tabIndex&&U.tab(a(".layui-layim-tab>li").eq(C.base.tabIndex))}})},N=function(i,e){i=i||{};var l=a.extend({},C,{title:i.title||"",data:i.data});return n.open({type:1,shade:!1,shadeClose:!1,anim:-1,content:t(f(i.tpl,e!==-1,!0)).render(l),success:function(e){var t=a(e);t.prev().find(".layim-panel").addClass("layui-m-anim-lout"),i.success&&i.success(e),i.isChat||T(t.find(".layim-content"))},end:i.end})},L=function(i,e,t){return i=i||{},i.id?(n.close(L.index),L.index=N({tpl:v,data:i,title:i.name,isChat:!0,success:function(e){x=a(e),B(),$(),delete C.message[i.type+i.id],I("Msg");var t=A(),n=t.elem.find(".layim-chat-main");layui.each(r.chatChange,function(i,e){e&&e(t)}),T(n),t.textarea.on("focus",function(){setTimeout(function(){n.scrollTop(n[0].scrollHeight+1e3)},500)})},end:function(){x=null,q.time=0}},e)):n.msg("非法用户")},T=function(i){s.ios&&i.on("touchmove",function(e){var a=i.scrollTop();a<=0&&(i.scrollTop(1),e.preventDefault(e)),this.scrollHeight-a-i.height()<=0&&(i.scrollTop(i.scrollTop()-1),e.preventDefault(e))})},A=function(){if(!x)return{};var i=x.find(".layim-chat"),e=JSON.parse(decodeURIComponent(i.find(".layim-chat-tool").data("json")));return{elem:i,data:e,textarea:i.find("input")}},j=function(i,e,a){var t=[],n=function(i,a){var t=i[e],n=a[e];return nt?1:0};return layui.each(i,function(i,e){t.push(e)}),t.sort(n),a&&t.reverse(),t},H=function(i){var e=layui.data("layim-mobile")[C.mine.id]||{},a={},n=e.history||{};n[i.type+i.id];if(b){var l=b.find(".layim-list-history");i.historyTime=(new Date).getTime(),i.sign=i.content,n[i.type+i.id]=i,e.history=n,layui.data("layim-mobile",{key:C.mine.id,value:e});var s=l.find(".layim-"+i.type+i.id),c=(C.message[i.type+i.id]||[]).length,d=function(){s=l.find(".layim-"+i.type+i.id),s.find("p").html(i.content),c>0&&s.find(".layim-msg-status").html(c).addClass(o)};if(s.length>0)d(),l.prepend(s.clone()),s.remove();else{a[i.type+i.id]=i;var r=t(p({type:"history",item:"d.data"})).render({data:a});l.prepend(r),d(),l.find(".layim-null").remove()}I("Msg")}},I=function(i,e){if(!e){var e;layui.each(C.message,function(){return e=!0,!1})}a("#LAY_layimNew"+i)[e?"addClass":"removeClass"](o)},q=function(){var i={username:C.mine?C.mine.username:"访客",avatar:C.mine?C.mine.avatar:layui.cache.dir+"css/pc/layim/skin/logo.jpg",id:C.mine?C.mine.id:null,mine:!0},e=A(),a=e.elem.find(".layim-chat-main ul"),l=e.data,s=C.base.maxLength||3e3,o=(new Date).getTime(),c=e.textarea;if(i.content=c.val(),""!==i.content){if(i.content.length>s)return n.msg("内容最长不能超过"+s+"个字符");o-(q.time||0)>6e4&&(a.append('
            • '+layui.data.date()+"
            • "),q.time=o),a.append(t(w).render(i));var d={mine:i,to:l},u={username:d.mine.username,avatar:d.mine.avatar,id:l.id,type:l.type,content:d.mine.content,timestamp:o,mine:!0};F(u),layui.each(r.sendMessage,function(i,e){e&&e(d)}),l.content=i.content,H(l),J(),c.val(""),c.next().addClass("layui-disabled")}},_=function(){var i=document.createElement("audio");i.src=layui.cache.dir+"css/modules/layim/voice/"+C.base.voice,i.play()},D={},M=function(i){i=i||{};var e={},n=A(),l=n.data||{},s=l.id==i.id&&l.type==i.type;i.timestamp=i.timestamp||(new Date).getTime(),i.system||F(i),D=JSON.parse(JSON.stringify(i)),C.base.voice&&_(),(!x&&i.content||!s)&&(C.message[i.type+i.id]?C.message[i.type+i.id].push(i):C.message[i.type+i.id]=[i]);var e={};if("friend"===i.type){var o;layui.each(C.friend,function(e,a){if(layui.each(a.list,function(e,a){if(a.id==i.id)return i.type="friend",i.name=a.username,o=!0}),o)return!0}),o||(i.temporary=!0)}else"group"===i.type?layui.each(C.group,function(a,t){if(t.id==i.id)return i.type="group",i.name=i.groupname=t.groupname,e.avatar=t.avatar,!0}):i.name=i.name||i.username||i.groupname;var c=a.extend({},i,{avatar:e.avatar||i.avatar});if("group"===i.type&&delete c.username,H(c),x&&s){var d=x.find(".layim-chat"),r=d.find(".layim-chat-main ul");i.system?r.append('
            • '+i.content+"
            • "):""!==i.content.replace(/\s/g,"")&&(i.timestamp-(q.time||0)>6e4&&(r.append('
            • '+layui.data.date(i.timestamp)+"
            • "),q.time=i.timestamp),r.append(t(w).render(i))),J()}},F=function(i){var e=layui.data("layim-mobile")[C.mine.id]||{},a=e.chatlog||{};a[i.type+i.id]?(a[i.type+i.id].push(i),a[i.type+i.id].length>d&&a[i.type+i.id].shift()):a[i.type+i.id]=[i],e.chatlog=a,layui.data("layim-mobile",{key:C.mine.id,value:e})},$=function(){var i=layui.data("layim-mobile")[C.mine.id]||{},e=A(),a=i.chatlog||{},n=e.elem.find(".layim-chat-main ul");layui.each(a[e.data.type+e.data.id],function(i,e){(new Date).getTime()>e.timestamp&&e.timestamp-(q.time||0)>6e4&&(n.append('
            • '+layui.data.date(e.timestamp)+"
            • "),q.time=e.timestamp),n.append(t(w).render(e))}),J()},O=function(i){var e,a={},l=b.find(".layim-list-"+i.type);if(C[i.type])if("friend"===i.type)layui.each(C.friend,function(t,l){if(i.groupid==l.id)return layui.each(C.friend[t].list,function(a,t){if(t.id==i.id)return e=!0}),e?n.msg("好友 ["+(i.username||"")+"] 已经存在列表中",{anim:6}):(C.friend[t].list=C.friend[t].list||[],a[C.friend[t].list.length]=i,i.groupIndex=t,C.friend[t].list.push(i),!0)});else if("group"===i.type){if(layui.each(C.group,function(a,t){if(t.id==i.id)return e=!0}),e)return n.msg("您已是 ["+(i.groupname||"")+"] 的群成员",{anim:6});a[C.group.length]=i,C.group.push(i)}if(!e){var s=t(p({type:i.type,item:"d.data",index:"friend"===i.type?"data.groupIndex":null})).render({data:a});if("friend"===i.type){var o=l.children("li").eq(i.groupIndex);o.find(".layui-layim-list").append(s),o.find(".layim-count").html(C.friend[i.groupIndex].list.length),o.find(".layim-null")[0]&&o.find(".layim-null").remove()}else"group"===i.type&&(l.append(s),l.find(".layim-null")[0]&&l.find(".layim-null").remove())}},Y=function(i){var e=b.find(".layim-list-"+i.type);C[i.type]&&("friend"===i.type?layui.each(C.friend,function(a,t){layui.each(t.list,function(t,n){if(i.id==n.id){var l=e.children("li").eq(a);l.find(".layui-layim-list").children("li");return l.find(".layui-layim-list").children("li").eq(t).remove(),C.friend[a].list.splice(t,1),l.find(".layim-count").html(C.friend[a].list.length),0===C.friend[a].list.length&&l.find(".layui-layim-list").html('
            • 该分组下已无好友了
            • '),!0}})}):"group"===i.type&&layui.each(C.group,function(a,t){if(i.id==t.id)return e.children("li").eq(a).remove(),C.group.splice(a,1),0===C.group.length&&e.html('
            • 暂无群组
            • '),!0}))},J=function(){var i=A(),e=i.elem.find(".layim-chat-main"),a=e.find("ul"),t=a.children(".layim-chat-li");if(t.length>=d){var n=t.eq(0);n.prev().remove(),a.prev().hasClass("layim-chat-system")||a.before('
              查看更多记录
              '),n.remove()}e.scrollTop(e[0].scrollHeight+1e3)},B=function(){var i=A(),e=i.textarea,a=e.next();e.off("keyup").on("keyup",function(i){var t=i.keyCode;13===t&&(i.preventDefault(),q()),a[""===e.val()?"addClass":"removeClass"]("layui-disabled")})},E=function(){var i=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],e={};return layui.each(i,function(i,a){e[a]=layui.cache.dir+"images/face/"+i+".gif"}),e}(),P=layui.stope,R=function(i,e,a){var t,n=i.value;a||i.focus(),document.selection?(t=document.selection.createRange(),document.selection.empty(),t.text=e):(t=[n.substring(0,i.selectionStart),e,n.substr(i.selectionEnd)],a||i.focus(),i.value=t.join(""))},U={chat:function(i){var e=layui.data("layim-mobile")[C.mine.id]||{},t=i.data("type"),n=i.data("index"),l=i.attr("data-list")||i.index(),s={};"friend"===t?s=C[t][n].list[l]:"group"===t?s=C[t][l]:"history"===t&&(s=(e.history||{})[n]||{}),s.name=s.name||s.username||s.groupname,"history"!==t&&(s.type=t),L(s,!0),a(".layim-"+s.type+s.id).find(".layim-msg-status").removeClass(o)},spread:function(i){var e=i.attr("lay-type"),a="true"===e?"false":"true",t=layui.data("layim-mobile")[C.mine.id]||{};i.next()["true"===e?"removeClass":"addClass"](o),t["spread"+i.parent().index()]=a,layui.data("layim-mobile",{key:C.mine.id,value:t}),i.attr("lay-type",a),i.find(".layui-icon").html("true"===a?"":"")},tab:function(i){var e=i.index(),a=".layim-tab-content";i.addClass(c).siblings().removeClass(c),b.find(a).eq(e).addClass(o).siblings(a).removeClass(o)},back:function(i){var e=i.parents(".layui-m-layer").eq(0),a=e.attr("index"),t=".layim-panel";setTimeout(function(){n.close(a)},300),i.parents(t).eq(0).removeClass("layui-m-anim-left").addClass("layui-m-anim-rout"),e.prev().find(t).eq(0).removeClass("layui-m-anim-lout").addClass("layui-m-anim-right"),layui.each(r.back,function(i,e){setTimeout(function(){e&&e()},200)})},send:function(){q()},face:function(i,e){var t="",l=A(),s=l.textarea;layui.each(E,function(i,e){t+='
            • '}),t='
                '+t+"
              ",n.popBottom({content:t,success:function(i){var e=a(i).find(".layui-layim-face").children("li");m(e,function(){return R(s[0],"face"+this.title+" ",!0),s.next()[""===s.val()?"addClass":"removeClass"]("layui-disabled"),!1})}});var o=a(document);y?o.off("touchend",U.faceHide).on("touchend",U.faceHide):o.off("click",U.faceHide).on("click",U.faceHide),P(e)},faceHide:function(){n.close(n.popBottom.index),a(document).off("touchend",U.faceHide).off("click",U.faceHide)},image:function(i){var e=i.data("type")||"images",a={images:"uploadImage",file:"uploadFile"},t=A(),s=C.base[a[e]]||{};l({url:s.url||"",method:s.type,elem:i.find("input")[0],unwrap:!0,type:e,success:function(i){0==i.code?(i.data=i.data||{},"images"===e?R(t.textarea[0],"img["+(i.data.src||"")+"]"):"file"===e&&R(t.textarea[0],"file("+(i.data.src||"")+")["+(i.data.name||"下载文件")+"]"),q()):n.msg(i.msg||"上传失败")}})},extend:function(i){var e=i.attr("lay-filter"),a=A();layui.each(r["tool("+e+")"],function(e,t){t&&t.call(i,function(i){R(a.textarea[0],i)},q,a)})},newFriend:function(){layui.each(r.newFriend,function(i,e){e&&e()})},group:function(){N({title:"群聊",tpl:['
              ',p({type:"group",item:"d.group"}),"
              "].join(""),data:{}})},detail:function(){var i=A();layui.each(r.detail,function(e,a){a&&a(i.data)})},playAudio:function(i){var e=i.data("audio"),a=e||document.createElement("audio"),t=function(){a.pause(),i.removeAttr("status"),i.find("i").html("")};return i.data("error")?n.msg("播放音频源异常"):a.play?void(i.attr("status")?t():(e||(a.src=i.data("src")),a.play(),i.attr("status","pause"),i.data("audio",a),i.find("i").html(""),a.onended=function(){t()},a.onerror=function(){n.msg("播放音频源异常"),i.data("error",!0),t()})):n.msg("您的浏览器不支持audio")},playVideo:function(i){var e=i.data("src"),a=document.createElement("video");return a.play?(n.close(U.playVideo.index),void(U.playVideo.index=n.open({type:1,anim:!1,style:"width: 100%; height: 50%;",content:'
              '}))):n.msg("您的浏览器不支持video")},chatLog:function(i){var e=A();layui.each(r.chatlog,function(i,a){a&&a(e.data,e.elem.find(".layim-chat-main>ul"))})},moreList:function(i){var e=i.attr("lay-filter");layui.each(r.moreList,function(i,a){a&&a({alias:e})})},about:function(){n.open({content:'

              LayIM属于付费产品,欢迎通过官网获得授权,促进良性发展!

              当前版本:layim mobile v'+e+'

              版权所有:layim.layui.com

              ',className:"layim-about",shadeClose:!1,btn:"我知道了"})}};i("layim-mobile",new u)}).addcss("modules/layim/mobile/layim.css?v=2.10","skinlayim-mobilecss");layui["layui.mobile"]||layui.config({base:layui.cache.dir+"lay/modules/mobile/"}).extend({"layer-mobile":"layer-mobile",zepto:"zepto","upload-mobile":"upload-mobile","layim-mobile":"layim-mobile"}),layui.define(["layer-mobile","zepto","layim-mobile"],function(l){l("mobile",{layer:layui["layer-mobile"],layim:layui["layim-mobile"]})}); \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;layui.define(function(i){i("layui.mobile",layui.v)});layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)});layui.define(function(e){"use strict";var t=(window,document),i="querySelectorAll",n="getElementsByClassName",a=function(e){return t[i](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var i in e)t[i]=e[i];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var o=0,r=["layui-m-layer"],d=function(e){var t=this;t.config=l.extend(e),t.view()};d.prototype.view=function(){var e=this,i=e.config,s=t.createElement("div");e.id=s.id=r[0]+o,s.setAttribute("class",r[0]+" "+r[0]+(i.type||0)),s.setAttribute("index",o);var l=function(){var e="object"==typeof i.title;return i.title?'

              '+(e?i.title[0]:i.title)+"

              ":""}(),d=function(){"string"==typeof i.btn&&(i.btn=[i.btn]);var e,t=(i.btn||[]).length;return 0!==t&&i.btn?(e=''+i.btn[0]+"",2===t&&(e=''+i.btn[1]+""+e),'
              '+e+"
              "):""}();if(i.fixed||(i.top=i.hasOwnProperty("top")?i.top:100,i.style=i.style||"",i.style+=" top:"+(t.body.scrollTop+i.top)+"px"),2===i.type&&(i.content='

              '+(i.content||"")+"

              "),i.skin&&(i.anim="up"),"msg"===i.skin&&(i.shade=!1),s.innerHTML=(i.shade?"
              ':"")+'
              "+l+'
              '+i.content+"
              "+d+"
              ",!i.type||2===i.type){var y=t[n](r[0]+i.type),u=y.length;u>=1&&c.close(y[0].getAttribute("index"))}document.body.appendChild(s);var m=e.elem=a("#"+e.id)[0];i.success&&i.success(m),e.index=o++,e.action(i,m)},d.prototype.action=function(e,t){var i=this;e.time&&(l.timer[i.index]=setTimeout(function(){c.close(i.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),c.close(i.index)):e.yes?e.yes(i.index):c.close(i.index)};if(e.btn)for(var s=t[n]("layui-m-layerbtn")[0].children,o=s.length,r=0;r0&&e-1 in t)}function s(t){return A.call(t,function(t){return null!=t})}function u(t){return t.length>0?T.fn.concat.apply([],t):t}function c(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(t){return t in F?F[t]:F[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function f(t,e){return"number"!=typeof e||k[c(t)]?e:e+"px"}function h(t){var e,n;return $[t]||(e=L.createElement(t),L.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),$[t]=n),$[t]}function p(t){return"children"in t?D.call(t.children):T.map(t.childNodes,function(t){if(1==t.nodeType)return t})}function d(t,e){var n,r=t?t.length:0;for(n=0;n]*>/,R=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Z=/^(?:body|html)$/i,q=/([A-Z])/g,H=["val","css","html","text","data","width","height","offset"],I=["after","prepend","before","append"],V=L.createElement("table"),_=L.createElement("tr"),B={tr:L.createElement("tbody"),tbody:V,thead:V,tfoot:V,td:_,th:_,"*":L.createElement("div")},U=/complete|loaded|interactive/,X=/^[\w-]*$/,J={},W=J.toString,Y={},G=L.createElement("div"),K={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},Q=Array.isArray||function(t){return t instanceof Array};return Y.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=G).appendChild(t),r=~Y.qsa(i,e).indexOf(t),o&&G.removeChild(t),r},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return A.call(t,function(e,n){return t.indexOf(e)==n})},Y.fragment=function(t,e,n){var r,i,a;return R.test(t)&&(r=T(L.createElement(RegExp.$1))),r||(t.replace&&(t=t.replace(z,"<$1>")),e===E&&(e=M.test(t)&&RegExp.$1),e in B||(e="*"),a=B[e],a.innerHTML=""+t,r=T.each(D.call(a.childNodes),function(){a.removeChild(this)})),o(n)&&(i=T(r),T.each(n,function(t,e){H.indexOf(t)>-1?i[t](e):i.attr(t,e)})),r},Y.Z=function(t,e){return new d(t,e)},Y.isZ=function(t){return t instanceof Y.Z},Y.init=function(t,n){var r;if(!t)return Y.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&M.test(t))r=Y.fragment(t,RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}else{if(e(t))return T(L).ready(t);if(Y.isZ(t))return t;if(Q(t))r=s(t);else if(i(t))r=[t],t=null;else if(M.test(t))r=Y.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}}return Y.Z(r,t)},T=function(t,e){return Y.init(t,e)},T.extend=function(t){var e,n=D.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){m(t,n,e)}),t},Y.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,a=X.test(o);return t.getElementById&&a&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:D.call(a&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},T.contains=L.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},T.type=t,T.isFunction=e,T.isWindow=n,T.isArray=Q,T.isPlainObject=o,T.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},T.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},T.inArray=function(t,e,n){return O.indexOf.call(e,t,n)},T.camelCase=C,T.trim=function(t){return null==t?"":String.prototype.trim.call(t)},T.uuid=0,T.support={},T.expr={},T.noop=function(){},T.map=function(t,e){var n,r,i,o=[];if(a(t))for(r=0;r=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return O.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return e(t)?this.not(this.not(t)):T(A.call(this,function(e){return Y.matches(e,t)}))},add:function(t,e){return T(N(this.concat(T(t,e))))},is:function(t){return this.length>0&&Y.matches(this[0],t)},not:function(t){var n=[];if(e(t)&&t.call!==E)this.each(function(e){t.call(this,e)||n.push(this)});else{var r="string"==typeof t?this.filter(t):a(t)&&e(t.item)?D.call(t):T(t);this.forEach(function(t){r.indexOf(t)<0&&n.push(t)})}return T(n)},has:function(t){return this.filter(function(){return i(t)?T.contains(this,t):T(this).find(t).size()})},eq:function(t){return t===-1?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!i(t)?t:T(t)},last:function(){var t=this[this.length-1];return t&&!i(t)?t:T(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?T(t).filter(function(){var t=this;return O.some.call(n,function(e){return T.contains(e,t)})}):1==this.length?T(Y.qsa(this[0],t)):this.map(function(){return Y.qsa(this,t)}):T()},closest:function(t,e){var n=[],i="object"==typeof t&&T(t);return this.each(function(o,a){for(;a&&!(i?i.indexOf(a)>=0:Y.matches(a,t));)a=a!==e&&!r(a)&&a.parentNode;a&&n.indexOf(a)<0&&n.push(a)}),T(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=T.map(n,function(t){if((t=t.parentNode)&&!r(t)&&e.indexOf(t)<0)return e.push(t),t});return v(e,t)},parent:function(t){return v(N(this.pluck("parentNode")),t)},children:function(t){return v(this.map(function(){return p(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||D.call(this.childNodes)})},siblings:function(t){return v(this.map(function(t,e){return A.call(p(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return T.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=h(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var n=e(t);if(this[0]&&!n)var r=T(t).get(0),i=r.parentNode||this.length>1;return this.each(function(e){T(this).wrapAll(n?t.call(this,e):i?r.cloneNode(!0):r)})},wrapAll:function(t){if(this[0]){T(this[0]).before(t=T(t));for(var e;(e=t.children()).length;)t=e.first();T(t).append(this)}return this},wrapInner:function(t){var n=e(t);return this.each(function(e){var r=T(this),i=r.contents(),o=n?t.call(this,e):t;i.length?i.wrapAll(o):r.append(o)})},unwrap:function(){return this.parent().each(function(){T(this).replaceWith(T(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var e=T(this);(t===E?"none"==e.css("display"):t)?e.show():e.hide()})},prev:function(t){return T(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return T(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;T(this).empty().append(g(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=g(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,e){var n;return"string"!=typeof t||1 in arguments?this.each(function(n){if(1===this.nodeType)if(i(t))for(j in t)y(this,j,t[j]);else y(this,t,g(this,e,n,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(n=this[0].getAttribute(t))?n:E},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){y(this,t)},this)})},prop:function(t,e){return t=K[t]||t,1 in arguments?this.each(function(n){this[t]=g(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=K[t]||t,this.each(function(){delete this[t]})},data:function(t,e){var n="data-"+t.replace(q,"-$1").toLowerCase(),r=1 in arguments?this.attr(n,e):this.attr(n);return null!==r?b(r):E},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=g(this,t,e,this.value)})):this[0]&&(this[0].multiple?T(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var n=T(this),r=g(this,t,e,n.offset()),i=n.offsetParent().offset(),o={top:r.top-i.top,left:r.left-i.left};"static"==n.css("position")&&(o.position="relative"),n.css(o)});if(!this.length)return null;if(L.documentElement!==this[0]&&!T.contains(L.documentElement,this[0]))return{top:0,left:0};var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(e,n){if(arguments.length<2){var r=this[0];if("string"==typeof e){if(!r)return;return r.style[C(e)]||getComputedStyle(r,"").getPropertyValue(e)}if(Q(e)){if(!r)return;var i={},o=getComputedStyle(r,"");return T.each(e,function(t,e){i[e]=r.style[C(e)]||o.getPropertyValue(e)}),i}}var a="";if("string"==t(e))n||0===n?a=c(e)+":"+f(e,n):this.each(function(){this.style.removeProperty(c(e))});else for(j in e)e[j]||0===e[j]?a+=c(j)+":"+f(j,e[j])+";":this.each(function(){this.style.removeProperty(c(j))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(T(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return!!t&&O.some.call(this,function(t){return this.test(x(t))},l(t))},addClass:function(t){return t?this.each(function(e){if("className"in this){S=[];var n=x(this),r=g(this,t,e,n);r.split(/\s+/g).forEach(function(t){T(this).hasClass(t)||S.push(t)},this),S.length&&x(this,n+(n?" ":"")+S.join(" "))}}):this},removeClass:function(t){return this.each(function(e){if("className"in this){if(t===E)return x(this,"");S=x(this),g(this,t,e,S).split(/\s+/g).forEach(function(t){S=S.replace(l(t)," ")}),x(this,S.trim())}})},toggleClass:function(t,e){return t?this.each(function(n){var r=T(this),i=g(this,t,n,x(this));i.split(/\s+/g).forEach(function(t){(e===E?!r.hasClass(t):e)?r.addClass(t):r.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var e="scrollTop"in this[0];return t===E?e?this[0].scrollTop:this[0].pageYOffset:this.each(e?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var e="scrollLeft"in this[0];return t===E?e?this[0].scrollLeft:this[0].pageXOffset:this.each(e?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),r=Z.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(T(t).css("margin-top"))||0,n.left-=parseFloat(T(t).css("margin-left"))||0,r.top+=parseFloat(T(e[0]).css("border-top-width"))||0,r.left+=parseFloat(T(e[0]).css("border-left-width"))||0,{top:n.top-r.top,left:n.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||L.body;t&&!Z.test(t.nodeName)&&"static"==T(t).css("position");)t=t.offsetParent;return t})}},T.fn.detach=T.fn.remove,["width","height"].forEach(function(t){var e=t.replace(/./,function(t){return t[0].toUpperCase()});T.fn[t]=function(i){var o,a=this[0];return i===E?n(a)?a["inner"+e]:r(a)?a.documentElement["scroll"+e]:(o=this.offset())&&o[t]:this.each(function(e){a=T(this),a.css(t,g(this,i,e,a[t]()))})}}),I.forEach(function(e,n){var r=n%2;T.fn[e]=function(){var e,i,o=T.map(arguments,function(n){var r=[];return e=t(n),"array"==e?(n.forEach(function(t){return t.nodeType!==E?r.push(t):T.zepto.isZ(t)?r=r.concat(t.get()):void(r=r.concat(Y.fragment(t)))}),r):"object"==e||null==n?n:Y.fragment(n)}),a=this.length>1;return o.length<1?this:this.each(function(t,e){i=r?e:e.parentNode,e=0==n?e.nextSibling:1==n?e.firstChild:2==n?e:null;var s=T.contains(L.documentElement,i);o.forEach(function(t){if(a)t=t.cloneNode(!0);else if(!i)return T(t).remove();i.insertBefore(t,e),s&&w(t,function(t){if(!(null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src)){var e=t.ownerDocument?t.ownerDocument.defaultView:window;e.eval.call(e,t.innerHTML)}})})})},T.fn[r?e+"To":"insert"+(n?"Before":"After")]=function(t){return T(t)[e](this),this}}),Y.Z.prototype=d.prototype=T.fn,Y.uniq=N,Y.deserializeValue=b,T.zepto=Y,T}();!function(t){function e(t){return t._zid||(t._zid=h++)}function n(t,n,o,a){if(n=r(n),n.ns)var s=i(n.ns);return(v[e(t)]||[]).filter(function(t){return t&&(!n.e||t.e==n.e)&&(!n.ns||s.test(t.ns))&&(!o||e(t.fn)===e(o))&&(!a||t.sel==a)})}function r(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function i(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function o(t,e){return t.del&&!y&&t.e in x||!!e}function a(t){return b[t]||y&&x[t]||t}function s(n,i,s,u,l,h,p){var d=e(n),m=v[d]||(v[d]=[]);i.split(/\s/).forEach(function(e){if("ready"==e)return t(document).ready(s);var i=r(e);i.fn=s,i.sel=l,i.e in b&&(s=function(e){var n=e.relatedTarget;if(!n||n!==this&&!t.contains(this,n))return i.fn.apply(this,arguments)}),i.del=h;var d=h||s;i.proxy=function(t){if(t=c(t),!t.isImmediatePropagationStopped()){t.data=u;var e=d.apply(n,t._args==f?[t]:[t].concat(t._args));return e===!1&&(t.preventDefault(),t.stopPropagation()),e}},i.i=m.length,m.push(i),"addEventListener"in n&&n.addEventListener(a(i.e),i.proxy,o(i,p))})}function u(t,r,i,s,u){var c=e(t);(r||"").split(/\s/).forEach(function(e){n(t,e,i,s).forEach(function(e){delete v[c][e.i],"removeEventListener"in t&&t.removeEventListener(a(e.e),e.proxy,o(e,u))})})}function c(e,n){return!n&&e.isDefaultPrevented||(n||(n=e),t.each(T,function(t,r){var i=n[t];e[t]=function(){return this[r]=w,i&&i.apply(n,arguments)},e[r]=E}),e.timeStamp||(e.timeStamp=Date.now()),(n.defaultPrevented!==f?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(e.isDefaultPrevented=w)),e}function l(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===f||(n[e]=t[e]);return c(n,t)}var f,h=1,p=Array.prototype.slice,d=t.isFunction,m=function(t){return"string"==typeof t},v={},g={},y="onfocusin"in window,x={focus:"focusin",blur:"focusout"},b={mouseenter:"mouseover",mouseleave:"mouseout"};g.click=g.mousedown=g.mouseup=g.mousemove="MouseEvents",t.event={add:s,remove:u},t.proxy=function(n,r){var i=2 in arguments&&p.call(arguments,2);if(d(n)){var o=function(){return n.apply(r,i?i.concat(p.call(arguments)):arguments)};return o._zid=e(n),o}if(m(r))return i?(i.unshift(n[r],n),t.proxy.apply(null,i)):t.proxy(n[r],n);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var w=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,T={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,n,r,i,o){var a,c,h=this;return e&&!m(e)?(t.each(e,function(t,e){h.on(t,n,r,e,o)}),h):(m(n)||d(i)||i===!1||(i=r,r=n,n=f),i!==f&&r!==!1||(i=r,r=f),i===!1&&(i=E),h.each(function(f,h){o&&(a=function(t){return u(h,t.type,i),i.apply(this,arguments)}),n&&(c=function(e){var r,o=t(e.target).closest(n,h).get(0);if(o&&o!==h)return r=t.extend(l(e),{currentTarget:o,liveFired:h}),(a||i).apply(o,[r].concat(p.call(arguments,1)))}),s(h,e,i,r,n,c||a)}))},t.fn.off=function(e,n,r){var i=this;return e&&!m(e)?(t.each(e,function(t,e){i.off(t,n,e)}),i):(m(n)||d(r)||r===!1||(r=n,n=f),r===!1&&(r=E),i.each(function(){u(this,e,r,n)}))},t.fn.trigger=function(e,n){return e=m(e)||t.isPlainObject(e)?t.Event(e):c(e),e._args=n,this.each(function(){e.type in x&&"function"==typeof this[e.type]?this[e.type]():"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,r){var i,o;return this.each(function(a,s){i=l(m(e)?t.Event(e):e),i._args=r,i.target=s,t.each(n(s,e.type||e),function(t,e){if(o=e.proxy(i),i.isImmediatePropagationStopped())return!1})}),o},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return 0 in arguments?this.bind(e,t):this.trigger(e)}}),t.Event=function(t,e){m(t)||(e=t,t=e.type);var n=document.createEvent(g[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),c(n)}}(e),function(t){function e(e,n,r){var i=t.Event(n);return t(e).trigger(i,r),!i.isDefaultPrevented()}function n(t,n,r,i){if(t.global)return e(n||x,r,i)}function r(e){e.global&&0===t.active++&&n(e,null,"ajaxStart")}function i(e){e.global&&!--t.active&&n(e,null,"ajaxStop")}function o(t,e){var r=e.context;return e.beforeSend.call(r,t,e)!==!1&&n(e,r,"ajaxBeforeSend",[t,e])!==!1&&void n(e,r,"ajaxSend",[t,e])}function a(t,e,r,i){var o=r.context,a="success";r.success.call(o,t,a,e),i&&i.resolveWith(o,[t,a,e]),n(r,o,"ajaxSuccess",[e,r,t]),u(a,e,r)}function s(t,e,r,i,o){var a=i.context;i.error.call(a,r,e,t),o&&o.rejectWith(a,[r,e,t]),n(i,a,"ajaxError",[r,i,t||e]),u(e,r,i)}function u(t,e,r){var o=r.context;r.complete.call(o,e,t),n(r,o,"ajaxComplete",[e,r]),i(r)}function c(t,e,n){if(n.dataFilter==l)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function l(){}function f(t){return t&&(t=t.split(";",2)[0]),t&&(t==T?"html":t==j?"json":w.test(t)?"script":E.test(t)&&"xml")||"text"}function h(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function p(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()&&"jsonp"!=e.dataType||(e.url=h(e.url,e.data),e.data=void 0)}function d(e,n,r,i){return t.isFunction(n)&&(i=r,r=n,n=void 0),t.isFunction(r)||(i=r,r=void 0),{url:e,data:n,success:r,dataType:i}}function m(e,n,r,i){var o,a=t.isArray(n),s=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),i&&(n=r?i:i+"["+(s||"object"==o||"array"==o?n:"")+"]"),!i&&a?e.add(u.name,u.value):"array"==o||!r&&"object"==o?m(e,u,r,n):e.add(n,u)})}var v,g,y=+new Date,x=window.document,b=/)<[^<]*)*<\/script>/gi,w=/^(?:text|application)\/javascript/i,E=/^(?:text|application)\/xml/i,j="application/json",T="text/html",S=/^\s*$/,C=x.createElement("a");C.href=window.location.href,t.active=0,t.ajaxJSONP=function(e,n){if(!("type"in e))return t.ajax(e);var r,i,u=e.jsonpCallback,c=(t.isFunction(u)?u():u)||"Zepto"+y++,l=x.createElement("script"),f=window[c],h=function(e){t(l).triggerHandler("error",e||"abort")},p={abort:h};return n&&n.promise(p),t(l).on("load error",function(o,u){clearTimeout(i),t(l).off().remove(),"error"!=o.type&&r?a(r[0],p,e,n):s(null,u||"error",p,e,n),window[c]=f,r&&t.isFunction(f)&&f(r[0]),f=r=void 0}),o(p,e)===!1?(h("abort"),p):(window[c]=function(){r=arguments},l.src=e.url.replace(/\?(.+)=\?/,"?$1="+c),x.head.appendChild(l),e.timeout>0&&(i=setTimeout(function(){h("timeout")},e.timeout)),p)},t.ajaxSettings={type:"GET",beforeSend:l,success:l,error:l,complete:l,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:j,xml:"application/xml, text/xml",html:T,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0,dataFilter:l},t.ajax=function(e){var n,i,u=t.extend({},e||{}),d=t.Deferred&&t.Deferred();for(v in t.ajaxSettings)void 0===u[v]&&(u[v]=t.ajaxSettings[v]);r(u),u.crossDomain||(n=x.createElement("a"),n.href=u.url,n.href=n.href,u.crossDomain=C.protocol+"//"+C.host!=n.protocol+"//"+n.host),u.url||(u.url=window.location.toString()),(i=u.url.indexOf("#"))>-1&&(u.url=u.url.slice(0,i)),p(u);var m=u.dataType,y=/\?.+=\?/.test(u.url);if(y&&(m="jsonp"),u.cache!==!1&&(e&&e.cache===!0||"script"!=m&&"jsonp"!=m)||(u.url=h(u.url,"_="+Date.now())),"jsonp"==m)return y||(u.url=h(u.url,u.jsonp?u.jsonp+"=?":u.jsonp===!1?"":"callback=?")),t.ajaxJSONP(u,d);var b,w=u.accepts[m],E={},j=function(t,e){E[t.toLowerCase()]=[t,e]},T=/^([\w-]+:)\/\//.test(u.url)?RegExp.$1:window.location.protocol,N=u.xhr(),O=N.setRequestHeader;if(d&&d.promise(N),u.crossDomain||j("X-Requested-With","XMLHttpRequest"),j("Accept",w||"*/*"),(w=u.mimeType||w)&&(w.indexOf(",")>-1&&(w=w.split(",",2)[0]),N.overrideMimeType&&N.overrideMimeType(w)),(u.contentType||u.contentType!==!1&&u.data&&"GET"!=u.type.toUpperCase())&&j("Content-Type",u.contentType||"application/x-www-form-urlencoded"),u.headers)for(g in u.headers)j(g,u.headers[g]);if(N.setRequestHeader=j,N.onreadystatechange=function(){if(4==N.readyState){N.onreadystatechange=l,clearTimeout(b);var e,n=!1;if(N.status>=200&&N.status<300||304==N.status||0==N.status&&"file:"==T){if(m=m||f(u.mimeType||N.getResponseHeader("content-type")),"arraybuffer"==N.responseType||"blob"==N.responseType)e=N.response;else{e=N.responseText;try{e=c(e,m,u),"script"==m?(0,eval)(e):"xml"==m?e=N.responseXML:"json"==m&&(e=S.test(e)?null:t.parseJSON(e))}catch(r){n=r}if(n)return s(n,"parsererror",N,u,d)}a(e,N,u,d)}else s(N.statusText||null,N.status?"error":"abort",N,u,d)}},o(N,u)===!1)return N.abort(),s(null,"abort",N,u,d),N;var P=!("async"in u)||u.async;if(N.open(u.type,u.url,P,u.username,u.password),u.xhrFields)for(g in u.xhrFields)N[g]=u.xhrFields[g];for(g in E)O.apply(N,E[g]);return u.timeout>0&&(b=setTimeout(function(){N.onreadystatechange=l,N.abort(),s(null,"timeout",N,u,d)},u.timeout)),N.send(u.data?u.data:null),N},t.get=function(){return t.ajax(d.apply(null,arguments))},t.post=function(){var e=d.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=d.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,r){if(!this.length)return this;var i,o=this,a=e.split(/\s/),s=d(e,n,r),u=s.success;return a.length>1&&(s.url=a[0],i=a[1]),s.success=function(e){o.html(i?t("
              ").html(e.replace(b,"")).find(i):e),u&&u.apply(o,arguments)},t.ajax(s),this};var N=encodeURIComponent;t.param=function(e,n){var r=[];return r.add=function(e,n){t.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(N(e)+"="+N(n))},m(r,e,n),r.join("&").replace(/%20/g,"+")}}(e),function(t){t.fn.serializeArray=function(){var e,n,r=[],i=function(t){return t.forEach?t.forEach(i):void r.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(r,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&i(t(o).val())}),r},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(e),function(){try{getComputedStyle(void 0)}catch(t){var e=getComputedStyle;window.getComputedStyle=function(t,n){try{return e(t,n)}catch(r){return null}}}}(),t("zepto",e)});layui.define(["layer-mobile","zepto"],function(e){"use strict";var t=layui.zepto,a=layui["layer-mobile"],i=(layui.device(),"layui-upload-enter"),n="layui-upload-iframe",r={icon:2,shift:6},o={file:"文件",video:"视频",audio:"音频"};a.msg=function(e){return a.open({content:e||"",skin:"msg",time:2})};var s=function(e){this.options=e};s.prototype.init=function(){var e=this,a=e.options,r=t("body"),s=t(a.elem||".layui-upload-file"),u=t('');return t("#"+n)[0]||r.append(u),s.each(function(r,s){s=t(s);var u='
              ',l=s.attr("lay-type")||a.type;a.unwrap||(u='
              '+u+''+(s.attr("lay-title")||a.title||"上传"+(o[l]||"图片"))+"
              "),u=t(u),a.unwrap||u.on("dragover",function(e){e.preventDefault(),t(this).addClass(i)}).on("dragleave",function(){t(this).removeClass(i)}).on("drop",function(){t(this).removeClass(i)}),s.parent("form").attr("target")===n&&(a.unwrap?s.unwrap():(s.parent().next().remove(),s.unwrap().unwrap())),s.wrap(u),s.off("change").on("change",function(){e.action(this,l)})})},s.prototype.action=function(e,i){var o=this,s=o.options,u=e.value,l=t(e),p=l.attr("lay-ext")||s.ext||"";if(u){switch(i){case"file":if(p&&!RegExp("\\w\\.("+p+")$","i").test(escape(u)))return a.msg("不支持该文件格式",r),e.value="";break;case"video":if(!RegExp("\\w\\.("+(p||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(u)))return a.msg("不支持该视频格式",r),e.value="";break;case"audio":if(!RegExp("\\w\\.("+(p||"mp3|wav|mid")+")$","i").test(escape(u)))return a.msg("不支持该音频格式",r),e.value="";break;default:if(!RegExp("\\w\\.("+(p||"jpg|png|gif|bmp|jpeg")+")$","i").test(escape(u)))return a.msg("不支持该图片格式",r),e.value=""}s.before&&s.before(e),l.parent().submit();var c=t("#"+n),f=setInterval(function(){var t;try{t=c.contents().find("body").text()}catch(i){a.msg("上传接口存在跨域",r),clearInterval(f)}if(t){clearInterval(f),c.contents().find("body").html("");try{t=JSON.parse(t)}catch(i){return t={},a.msg("请对上传接口返回JSON字符",r)}"function"==typeof s.success&&s.success(t,e)}},30);e.value=""}},e("upload-mobile",function(e){var t=new s(e=e||{});t.init()})});layui.define(function(i){i("layim-mobile",layui.v)});layui["layui.mobile"]||layui.config({base:layui.cache.dir+"lay/modules/mobile/"}).extend({"layer-mobile":"layer-mobile",zepto:"zepto","upload-mobile":"upload-mobile","layim-mobile":"layim-mobile"}),layui.define(["layer-mobile","zepto","layim-mobile"],function(l){l("mobile",{layer:layui["layer-mobile"],layim:layui["layim-mobile"]})}); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/table.js b/static/plugs/layui/lay/modules/table.js index 80bbbfada..2237a172e 100644 --- a/static/plugs/layui/lay/modules/table.js +++ b/static/plugs/layui/lay/modules/table.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;layui.define(["laytpl","laypage","layer","form"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=layui.hint(),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,s,e,t)}},c=function(){var e=this,t=e.config,i=t.id;return i&&(c.config[i]=t),{reload:function(t){e.reload.call(e,t)},config:t}},s="table",u=".layui-table",f="layui-hide",h="layui-none",y="layui-table-view",p=".layui-table-header",m=".layui-table-body",v=".layui-table-main",g=".layui-table-fixed",x=".layui-table-fixed-l",b=".layui-table-fixed-r",k=".layui-table-tool",C=".layui-table-sort",w="layui-table-edit",N="layui-table-hover",z=function(e){return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),"{{# if(item2.checkbox){ }}",'',"{{# } else if(item2.space){ }}",'',"{{# } else { }}",'","{{# }; }}",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
              ',"{{# if(item2.colspan > 1){ }}",'
              ','{{item2.title||""}}',"
              ","{{# } else { }}",'
              ','{{item2.title||""}}',"{{# if(item2.sort){ }}",'',"{{# } }}","
              ","{{# } }}","
              "].join("")},F=['',"","
              "].join(""),T=['
              ',"{{# var left, right; }}",'
              ',z(),"
              ",'
              ',F,"
              ","{{# if(left){ }}",'
              ','
              ',z({fixed:!0}),"
              ",'
              ',F,"
              ","
              ","{{# }; }}","{{# if(right){ }}",'
              ','
              ',z({fixed:"right"}),'
              ',"
              ",'
              ',F,"
              ","
              ","{{# }; }}","{{# if(d.data.page){ }}",'
              ','
              ',"
              ","{{# } }}","","
              "].join(""),L=t(window),S=t(document),H=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};H.prototype.config={limit:30,loading:!0},H.prototype.render=function(e){var a,l=this;if(e&&(l.config=e),a=l.config,a.elem=t(a.elem),a.where=a.where||{},a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",countName:"count"},a.response),!a.elem[0])return l;var n=a.elem,o=n.next("."+y);a.height&&/^full-\d+$/.test(a.height)&&(l.fullHeightGap=a.height.split("-")[1],a.height=L.height()-l.fullHeightGap);var r=l.elem=t(i(T).render({VIEW_CLASS:y,data:a,index:l.index}));if(a.index=l.index,o[0]&&o.remove(),n.after(r),l.layHeader=r.find(p),l.layMain=r.find(v),l.layBody=r.find(m),l.layFixed=r.find(g),l.layFixLeft=r.find(x),l.layFixRight=r.find(b),l.layTool=r.find(k),a.height&&l.fullSize(),a.cols.length>1){var d=l.layFixed.find(p).find("th");d.height(l.layHeader.height()-1-parseFloat(d.css("padding-top"))-parseFloat(d.css("padding-bottom")))}l.pullData(1),l.events()},H.prototype.reload=function(e){var i=this;i.config=t.extend({},i.config,e),i.render()},H.prototype.pullData=function(e,i){var a=this,n=a.config,o=n.request,r=n.response,d=function(){"object"==typeof n.initSort&&a.sort(n.initSort.field,n.initSort.type)};if(n.url){var c={};c[o.pageName]=e,c[o.limitName]=n.limit,t.ajax({type:n.method||"get",url:n.url,data:t.extend(c,n.where),dataType:"json",success:function(t){return t[r.statusName]!=r.statusCode?(a.renderForm(),a.layMain.html('
              '+(t[r.msgName]||"返回的数据状态异常")+"
              ")):(a.renderData(t,e,t[r.countName]),d(),i&&l.close(i),void("function"==typeof n.done&&n.done(t,e,t[r.countName])))},error:function(e,t){a.layMain.html('
              数据接口请求异常
              '),a.renderForm(),i&&l.close(i)}})}else if(n.data&&n.data.constructor===Array){var s={},u=e*n.limit-n.limit;s[r.dataName]=n.data.concat().splice(u,n.limit),s[r.countName]=n.data.length,a.renderData(s,e,n.data.length),d(),"function"==typeof n.done&&n.done(s,e,s[r.countName])}},H.prototype.page=1,H.prototype.eachCols=function(e){var i=t.extend(!0,[],this.config.cols),a=[],l=0;layui.each(i,function(e,t){layui.each(t,function(t,n){if(n.colspan>1){var o=0;l++,n.CHILD_COLS=[],layui.each(i[e+1],function(e,t){t.PARENT_COL||o==n.colspan||(t.PARENT_COL=l,n.CHILD_COLS.push(t),o+=t.colspan>1?t.colspan:1)})}n.PARENT_COL||a.push(n)})});var n=function(t){layui.each(t||a,function(t,i){return i.CHILD_COLS?n(i.CHILD_COLS):void e(t,i)})};n()},H.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,u=e[s.response.dataName]||[],f=[],y=[],p=[],m=function(){return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(u,function(e,a){var l=[],n=[],o=[];0!==a.length&&(r||(a[d.config.indexName]=e),c.eachCols(function(e,r){var c=a[r.field||e];if(void 0!==c&&null!==c||(c=""),!(r.colspan>1)){var u=['",'
              '+function(){return r.checkbox?'":r.toolbar?i(t(r.toolbar).html()||"").render(a):r.templet?i(t(r.templet).html()||String(c)).render(a):c}(),"
              "].join("");l.push(u),r.fixed&&"right"!==r.fixed&&n.push(u),"right"===r.fixed&&o.push(u)}}),f.push(''+l.join("")+""),y.push(''+n.join("")+""),p.push(''+o.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+h).remove(),c.layMain.find("tbody").html(f.join("")),c.layFixLeft.find("tbody").html(y.join("")),c.layFixRight.find("tbody").html(p.join("")),c.renderForm(),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,void l.close(c.tipsIndex))};return c.key=s.id||s.index,d.cache[c.key]=u,r?m():0===u.length?(c.renderForm(),c.layFixed.remove(),c.layMain.find("tbody").html(""),c.layMain.find("."+h).remove(),c.layMain.append('
              无数据
              ')):(m(),void(s.page&&(c.page=n,c.count=o,a.render({elem:"layui-table-page"+s.index,count:o,groups:3,limits:s.limits||[10,20,30,40,50,60,70,80,90],limit:s.limit,curr:n,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.pullData(e.curr,c.loading()))}}),c.layTool.find(".layui-table-count span").html(o))))},H.prototype.renderForm=function(e){n.render(e||"checkbox","LAY-table-"+this.index)},H.prototype.sort=function(e,i,a,n){var r,c,u=this,f={},h=u.config,y=h.elem.attr("lay-filter"),p=d.cache[u.key];"string"==typeof e&&u.layHeader.find("th").each(function(i,a){var l=t(this),n=l.data("field");if(n===e)return e=l,r=n,!1});try{var r=r||e.data("field");if(u.sortKey&&!a&&r===u.sortKey.field&&i===u.sortKey.sort)return;var m=u.layHeader.find("th .laytable-cell-"+h.index+"-"+r).find(C);u.layHeader.find("th").find(C).removeAttr("lay-sort"),m.attr("lay-sort",i||null),u.layFixed.find("th")}catch(v){return o.error("Table modules: Did not match to field")}u.sortKey={field:r,sort:i},"asc"===i?c=layui.sort(p,r):"desc"===i?c=layui.sort(p,r,!0):(c=layui.sort(p,d.config.indexName),delete u.sortKey),f[h.response.dataName]=c,u.renderData(f,u.page,u.count,!0),l.close(u.tipsIndex),n&&layui.event.call(e,s,"sort("+y+")",{field:r,type:i})},H.prototype.loading=function(){var e=this,t=e.config;if(t.loading&&t.url)return l.msg("数据请求中",{icon:16,offset:[e.elem.offset().top+e.elem.height()/2-35-L.scrollTop()+"px",e.elem.offset().left+e.elem.width()/2-90-L.scrollLeft()+"px"],anim:-1,fixed:!1})},H.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&(l[e][a.checkName]=t)},H.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){a.checkbox&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm()),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm()),a(!1)))},H.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet,n=l.cssRules||l.rules;layui.each(n,function(a,l){if(l.selectorText===".laytable-cell-"+i.index+"-"+e)return t(l),!0})},H.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=L.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),e=parseFloat(a)-parseFloat(t.layHeader.height())-1,i.page&&(e-=parseFloat(t.layTool.outerHeight()+1)),t.layMain.css("height",e)},H.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight");if(a&&l){if(!e.elem.find(".layui-table-patch")[0]){var n=t('
              ');n.find("div").css({width:a}),e.layHeader.eq(0).find("thead tr").append(n)}}else e.layHeader.eq(0).find(".layui-table-patch").remove();var o=e.layMain.height(),r=o-l;e.layFixed.find(m).css("height",i.height()>r?r:"auto"),e.layFixRight[i.width()>e.layMain.width()?"removeClass":"addClass"](f),e.layFixRight.css("right",a-1)},H.prototype.events=function(){var e,a=this,n=a.config,o=t("body"),c={},u=a.layHeader.find("th"),f=".layui-table-cell",h=n.elem.attr("lay-filter");u.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.attr("colspan")>1||i.attr("unresize")||c.resizeStart||(c.allowResize=i.width()-l<=10,o.css("cursor",c.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);c.resizeStart||o.css("cursor","")}).on("mousedown",function(e){if(c.allowResize){var i=t(this).data("field");e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],a.getCssRule(i,function(e){c.rule=e,c.ruleWidth=parseFloat(e.style.width)})}}),S.on("mousemove",function(t){if(c.resizeStart){if(t.preventDefault(),c.rule){var i=c.ruleWidth+t.clientX-c.offset[0];c.rule.style.width=i+"px",l.close(a.tipsIndex)}e=1}}).on("mouseup",function(t){c.resizeStart&&(c={},o.css("cursor",""),a.scrollPatch()),2===e&&(e=null)}),u.on("click",function(){var i,l=t(this),n=l.find(C),o=n.attr("lay-sort");return n[0]&&1!==e?(i="asc"===o?"desc":"desc"===o?null:"asc",void a.sort(l,i,null,!0)):e=2}).find(C+" .layui-edge ").on("click",function(e){var i=t(this),l=i.index(),n=i.parents("th").eq(0).data("field");layui.stope(e),0===l?a.sort(n,"asc",null,!0):a.sort(n,"desc",null,!0)}),a.elem.on("click",'input[name="layTableCheckbox"]+',function(){var e=t(this).prev(),i=a.layBody.find('input[name="layTableCheckbox"]'),l=e.parents("tr").eq(0).data("index"),n=e[0].checked,o="layTableAllChoose"===e.attr("lay-filter");o?(i.each(function(e,t){t.checked=n,a.setCheckData(e,n)}),a.syncCheckAll(),a.renderForm()):(a.setCheckData(l,n),a.syncCheckAll()),layui.event.call(this,s,"checkbox("+h+")",{checked:n,data:d.cache[a.key][l],type:o?"all":"one"})}),a.layBody.on("mouseenter","tr",function(){var e=t(this),i=e.index();a.layBody.find("tr:eq("+i+")").addClass(N)}).on("mouseleave","tr",function(){var e=t(this),i=e.index();a.layBody.find("tr:eq("+i+")").removeClass(N)}),a.layBody.on("change","."+w,function(){var e=t(this),i=this.value,l=e.parent().data("field"),n=e.parents("tr").eq(0).data("index"),o=d.cache[a.key][n];o[l]=i,layui.event.call(this,s,"edit("+h+")",{value:i,data:o,field:l})}).on("blur","."+w,function(){var e,l=t(this),n=l.parent().data("field"),o=l.parents("tr").eq(0).data("index"),r=d.cache[a.key][o];a.eachCols(function(t,i){i.field==n&&i.templet&&(e=i.templet)}),l.siblings(f).html(e?i(t(e).html()||this.value).render(r):this.value),l.parent().data("content",this.value),l.remove()}),a.layBody.on("click","td",function(){var e=t(this),i=(e.data("field"),e.children(f));if(!e.data("off")){if(e.data("edit")){var o=t('');return o[0].value=e.data("content")||i.text(),e.find("."+w)[0]||e.append(o),o.focus()}i.prop("scrollWidth")>i.outerWidth()&&(a.tipsIndex=l.tips(['
              ',i.html(),"
              ",''].join(""),i[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:600,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}}))}}),a.layBody.on("click","*[lay-event]",function(){var e=t(this),l=e.parents("tr").eq(0).data("index"),n=a.layBody.find('tr[data-index="'+l+'"]'),o="layui-table-click",r=d.cache[a.key][l];layui.event.call(this,s,"tool("+h+")",{data:d.clearCacheKey(r),event:e.attr("lay-event"),tr:n,del:function(){d.cache[a.key][l]=[],n.remove(),a.scrollPatch()},update:function(e){e=e||{},layui.each(e,function(e,l){if(e in r){var o,d=n.children('td[data-field="'+e+'"]');r[e]=l,a.eachCols(function(t,i){i.field==e&&i.templet&&(o=i.templet)}),d.children(f).html(o?i(t(o).html()||l).render(r):l),d.data("content",l)}})}}),n.addClass(o).siblings("tr").removeClass(o)}),a.layMain.on("scroll",function(){var e=t(this),i=e.scrollLeft(),n=e.scrollTop();a.layHeader.scrollLeft(i),a.layFixed.find(m).scrollTop(n),l.close(a.tipsIndex)}),L.on("resize",function(){a.fullSize(),a.scrollPatch()})},d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':u+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},d.checkStatus=function(e){var t=0,i=[],a=d.cache[e];return a?(layui.each(a,function(e,a){a[d.config.checkName]&&(t++,i.push(d.clearCacheKey(a)))}),{data:i,isAll:t===a.length}):{}},c.config={},d.reload=function(e,i){var a=c.config[e];return a?d.render(t.extend({},a,i)):o.error("The ID option was not found in the table instance")},d.render=function(e){var t=new H(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(s,d)}); \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;layui.define(["laytpl","laypage","layer","form"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=layui.hint(),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,s,e,t)}},c=function(){var e=this,t=e.config,i=t.id;return i&&(c.config[i]=t),{reload:function(t){e.reload.call(e,t)},config:t}},s="table",u=".layui-table",h="layui-hide",f="layui-none",y="layui-table-view",p=".layui-table-header",m=".layui-table-body",v=".layui-table-main",g=".layui-table-fixed",x=".layui-table-fixed-l",b=".layui-table-fixed-r",k=".layui-table-tool",C=".layui-table-page",w=".layui-table-sort",N="layui-table-edit",F="layui-table-hover",W=function(e){var t='{{#if(item2.colspan){}} colspan="{{item2.colspan}}"{{#} if(item2.rowspan){}} rowspan="{{item2.rowspan}}"{{#}}}';return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),'",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
              ','
              1){ }}","group","{{# } else { }}","{{d.index}}-{{item2.field || i2}}",'{{# if(item2.type !== "normal"){ }}'," laytable-cell-{{ item2.type }}","{{# } }}","{{# } }}",'" {{#if(item2.align){}}align="{{item2.align}}"{{#}}}>','{{# if(item2.type === "checkbox"){ }}','',"{{# } else { }}",'{{item2.title||""}}',"{{# if(!(item2.colspan > 1) && item2.sort){ }}",'',"{{# } }}","{{# } }}","
              ","
              "].join("")},z=['',"","
              "].join(""),A=['
              ',"{{# if(d.data.toolbar){ }}",'
              ',"{{# } }}",'
              ',"{{# var left, right; }}",'
              ',W(),"
              ",'
              ',z,"
              ","{{# if(left){ }}",'
              ','
              ',W({fixed:!0}),"
              ",'
              ',z,"
              ","
              ","{{# }; }}","{{# if(right){ }}",'
              ','
              ',W({fixed:"right"}),'
              ',"
              ",'
              ',z,"
              ","
              ","{{# }; }}","
              ","{{# if(d.data.page){ }}",'
              ','
              ',"
              ","{{# } }}","","
              "].join(""),T=t(window),M=t(document),S=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};S.prototype.config={limit:10,loading:!0,cellMinWidth:60,text:{none:"无数据"}},S.prototype.render=function(){var e=this,a=e.config;if(a.elem=t(a.elem),a.where=a.where||{},a.id=a.id||a.elem.attr("id"),a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",countName:"count"},a.response),"object"==typeof a.page&&(a.limit=a.page.limit||a.limit,a.limits=a.page.limits||a.limits,e.page=a.page.curr=a.page.curr||1,delete a.page.elem,delete a.page.jump),!a.elem[0])return e;e.setArea();var l=a.elem,n=l.next("."+y),o=e.elem=t(i(A).render({VIEW_CLASS:y,data:a,index:e.index}));if(a.index=e.index,n[0]&&n.remove(),l.after(o),e.layHeader=o.find(p),e.layMain=o.find(v),e.layBody=o.find(m),e.layFixed=o.find(g),e.layFixLeft=o.find(x),e.layFixRight=o.find(b),e.layTool=o.find(k),e.layPage=o.find(C),e.layTool.html(i(t(a.toolbar).html()||"").render(a)),a.height&&e.fullSize(),a.cols.length>1){var r=e.layFixed.find(p).find("th");r.height(e.layHeader.height()-1-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom")))}e.pullData(e.page),e.events()},S.prototype.initOpts=function(e){var t=this,i=(t.config,{checkbox:48,space:15,numbers:40});e.checkbox&&(e.type="checkbox"),e.space&&(e.type="space"),e.type||(e.type="normal"),"normal"!==e.type&&(e.unresize=!0,e.width=e.width||i[e.type])},S.prototype.setArea=function(){var e=this,t=e.config,i=0,a=0,l=0,n=0,o=t.width||function(){var e=function(i){var a,l;i=i||t.elem.parent(),a=i.width();try{l="none"===i.css("display")}catch(n){}return!i[0]||a&&!l?a:e(i.parent())};return e()}();e.eachCols(function(){i++}),o-=function(){return"line"===t.skin||"nob"===t.skin?2:i+1}(),layui.each(t.cols,function(t,i){layui.each(i,function(t,l){var r;return l?(e.initOpts(l),r=l.width||0,void(l.colspan>1||(/\d+%$/.test(r)?l.width=r=Math.floor(parseFloat(r)/100*o):r||(l.width=r=0,a++),n+=r))):void i.splice(t,1)})}),e.autoColNums=a,o>n&&a&&(l=(o-n)/a),layui.each(t.cols,function(e,i){layui.each(i,function(e,i){var a=i.minWidth||t.cellMinWidth;i.colspan>1||0===i.width&&(i.width=Math.floor(l>=a?l:a))})}),t.height&&/^full-\d+$/.test(t.height)&&(e.fullHeightGap=t.height.split("-")[1],t.height=T.height()-e.fullHeightGap)},S.prototype.reload=function(e){var i=this;i.config.data&&i.config.data.constructor===Array&&delete i.config.data,i.config=t.extend({},i.config,e),i.render()},S.prototype.page=1,S.prototype.pullData=function(e,i){var a=this,n=a.config,o=n.request,r=n.response,d=function(){"object"==typeof n.initSort&&a.sort(n.initSort.field,n.initSort.type)};if(a.startTime=(new Date).getTime(),n.url){var c={};c[o.pageName]=e,c[o.limitName]=n.limit,t.ajax({type:n.method||"get",url:n.url,data:t.extend(c,n.where),dataType:"json",success:function(t){t[r.statusName]!=r.statusCode?(a.renderForm(),a.layMain.html('
              '+(t[r.msgName]||"返回的数据状态异常")+"
              ")):(a.renderData(t,e,t[r.countName]),d(),n.time=(new Date).getTime()-a.startTime+" ms"),i&&l.close(i),"function"==typeof n.done&&n.done(t,e,t[r.countName])},error:function(e,t){a.layMain.html('
              数据接口请求异常
              '),a.renderForm(),i&&l.close(i)}})}else if(n.data&&n.data.constructor===Array){var s={},u=e*n.limit-n.limit;s[r.dataName]=n.data.concat().splice(u,n.limit),s[r.countName]=n.data.length,a.renderData(s,e,n.data.length),d(),"function"==typeof n.done&&n.done(s,e,s[r.countName])}},S.prototype.eachCols=function(e){var i=t.extend(!0,[],this.config.cols),a=[],l=0;layui.each(i,function(e,t){layui.each(t,function(t,n){if(n.colspan>1){var o=0;l++,n.CHILD_COLS=[],layui.each(i[e+1],function(e,t){t.PARENT_COL||o==n.colspan||(t.PARENT_COL=l,n.CHILD_COLS.push(t),o+=t.colspan>1?t.colspan:1)})}n.PARENT_COL||a.push(n)})});var n=function(t){layui.each(t||a,function(t,i){return i.CHILD_COLS?n(i.CHILD_COLS):void e(t,i)})};n()},S.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,u=e[s.response.dataName]||[],y=[],p=[],m=[],v=function(){return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(u,function(e,a){var l=[],o=[],u=[],h=e+s.limit*(n-1)+1;0!==a.length&&(r||(a[d.config.indexName]=e),c.eachCols(function(e,n){var r=n.field||e,f=a[r];c.getColElem(c.layHeader,r);if(void 0!==f&&null!==f||(f=""),!(n.colspan>1)){var y=['",'
              '+function(){var e=t.extend(!0,{LAY_INDEX:h},a);return"checkbox"===n.type?'":"numbers"===n.type?h:n.toolbar?i(t(n.toolbar).html()||"").render(e):n.templet?function(){return"function"==typeof n.templet?n.templet(e):i(t(n.templet).html()||String(f)).render(e)}():f}(),"
              "].join("");l.push(y),n.fixed&&"right"!==n.fixed&&o.push(y),"right"===n.fixed&&u.push(y)}}),y.push(''+l.join("")+""),p.push(''+o.join("")+""),m.push(''+u.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+f).remove(),c.layMain.find("tbody").html(y.join("")),c.layFixLeft.find("tbody").html(p.join("")),c.layFixRight.find("tbody").html(m.join("")),c.renderForm(),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,void l.close(c.tipsIndex))};return c.key=s.id||s.index,d.cache[c.key]=u,c.layPage[0===u.length&&1==n?"addClass":"removeClass"](h),r?v():0===u.length?(c.renderForm(),c.layFixed.remove(),c.layMain.find("tbody").html(""),c.layMain.find("."+f).remove(),c.layMain.append('
              '+s.text.none+"
              ")):(v(),void(s.page&&(s.page=t.extend({elem:"layui-table-page"+s.index,count:o,limit:s.limit,limits:s.limits||[10,20,30,40,50,60,70,80,90],groups:3,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.pullData(e.curr,c.loading()))}},s.page),s.page.count=o,a.render(s.page))))},S.prototype.getColElem=function(e,t){var i=this,a=i.config;return e.eq(0).find(".laytable-cell-"+(a.index+"-"+t)+":eq(0)")},S.prototype.renderForm=function(e){n.render(e,"LAY-table-"+this.index)},S.prototype.sort=function(e,i,a,l){var n,r,c=this,u={},h=c.config,f=h.elem.attr("lay-filter"),y=d.cache[c.key];"string"==typeof e&&c.layHeader.find("th").each(function(i,a){var l=t(this),o=l.data("field");if(o===e)return e=l,n=o,!1});try{var n=n||e.data("field");if(c.sortKey&&!a&&n===c.sortKey.field&&i===c.sortKey.sort)return;var p=c.layHeader.find("th .laytable-cell-"+h.index+"-"+n).find(w);c.layHeader.find("th").find(w).removeAttr("lay-sort"),p.attr("lay-sort",i||null),c.layFixed.find("th")}catch(m){return o.error("Table modules: Did not match to field")}c.sortKey={field:n,sort:i},"asc"===i?r=layui.sort(y,n):"desc"===i?r=layui.sort(y,n,!0):(r=layui.sort(y,d.config.indexName),delete c.sortKey),u[h.response.dataName]=r,c.renderData(u,c.page,c.count,!0),l&&layui.event.call(e,s,"sort("+f+")",{field:n,type:i})},S.prototype.loading=function(){var e=this,t=e.config;if(t.loading&&t.url)return l.msg("数据请求中",{icon:16,offset:[e.elem.offset().top+e.elem.height()/2-35-T.scrollTop()+"px",e.elem.offset().left+e.elem.width()/2-90-T.scrollLeft()+"px"],time:-1,anim:-1,fixed:!1})},S.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&l[e].constructor!==Array&&(l[e][a.checkName]=t)},S.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){"checkbox"===a.type&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm("checkbox")),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm("checkbox")),a(!1)))},S.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet||{},n=l.cssRules||l.rules;layui.each(n,function(a,l){if(l.selectorText===".laytable-cell-"+i.index+"-"+e)return t(l),!0})},S.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=T.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),e=parseFloat(a)-parseFloat(t.layHeader.height())-1,i.toolbar&&(e-=t.layTool.outerHeight()),i.page&&(e=e-t.layPage.outerHeight()-1),t.layMain.css("height",e)},S.prototype.getScrollWidth=function(e){var t=0;return e?t=e.offsetWidth-e.clientWidth:(e=document.createElement("div"),e.style.width="100px",e.style.height="100px",e.style.overflowY="scroll",document.body.appendChild(e),t=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),t},S.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight"),n=e.getScrollWidth(e.layMain[0]),o=i.outerWidth()-e.layMain.width();if(e.autoColNums&&o<5&&!e.scrollPatchWStatus){var r=e.layHeader.eq(0).find("thead th:last-child"),d=r.data("field");e.getCssRule(d,function(t){var i=t.style.width||r.outerWidth();t.style.width=parseFloat(i)-n-o+"px",e.layMain.height()-e.layMain.prop("clientHeight")>0&&(t.style.width=parseFloat(t.style.width)-1+"px"),e.scrollPatchWStatus=!0})}if(a&&l){if(!e.elem.find(".layui-table-patch")[0]){var c=t('
              ');c.find("div").css({width:a}),e.layHeader.eq(0).find("thead tr").append(c)}}else e.layHeader.eq(0).find(".layui-table-patch").remove();var s=e.layMain.height(),u=s-l;e.layFixed.find(m).css("height",i.height()>u?u:"auto"),e.layFixRight[o>0?"removeClass":"addClass"](h),e.layFixRight.css("right",a-1)},S.prototype.events=function(){var e,a=this,n=a.config,o=t("body"),c={},u=a.layHeader.find("th"),h=".layui-table-cell",f=n.elem.attr("lay-filter");u.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.attr("colspan")>1||i.data("unresize")||c.resizeStart||(c.allowResize=i.width()-l<=10,o.css("cursor",c.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);c.resizeStart||o.css("cursor","")}).on("mousedown",function(e){var i=t(this);if(c.allowResize){var l=i.data("field");e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],a.getCssRule(l,function(e){var t=e.style.width||i.outerWidth();c.rule=e,c.ruleWidth=parseFloat(t),c.minWidth=i.data("minwidth")||n.cellMinWidth})}}),M.on("mousemove",function(t){if(c.resizeStart){if(t.preventDefault(),c.rule){var i=c.ruleWidth+t.clientX-c.offset[0];i');d[0].value=e.data("content")||o.text(),e.find("."+N)[0]||e.append(d),d.focus()}else o.find(".layui-form-switch,.layui-form-checkbox")[0]||Math.round(o.prop("scrollWidth"))>Math.round(o.outerWidth())&&(a.tipsIndex=l.tips(['
              ',o.html(),"
              ",''].join(""),o[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:600,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}}))}),a.layBody.on("click","*[lay-event]",function(){var e=t(this),l=e.parents("tr").eq(0).data("index"),n=a.layBody.find('tr[data-index="'+l+'"]'),o="layui-table-click",r=d.cache[a.key][l];layui.event.call(this,s,"tool("+f+")",{data:d.clearCacheKey(r),event:e.attr("lay-event"),tr:n,del:function(){d.cache[a.key][l]=[],n.remove(),a.scrollPatch()},update:function(e){e=e||{},layui.each(e,function(e,l){if(e in r){var o,d=n.children('td[data-field="'+e+'"]');r[e]=l,a.eachCols(function(t,i){i.field==e&&i.templet&&(o=i.templet)}),d.children(h).html(o?i(t(o).html()||l).render(r):l),d.data("content",l)}})}}),n.addClass(o).siblings("tr").removeClass(o)}),a.layMain.on("scroll",function(){var e=t(this),i=e.scrollLeft(),n=e.scrollTop();a.layHeader.scrollLeft(i),a.layFixed.find(m).scrollTop(n),l.close(a.tipsIndex)}),T.on("resize",function(){a.fullSize(),a.scrollPatch()})},d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':u+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},d.checkStatus=function(e){var t=0,i=0,a=[],l=d.cache[e]||[];return layui.each(l,function(e,l){return l.constructor===Array?void i++:void(l[d.config.checkName]&&(t++,a.push(d.clearCacheKey(l))))}),{data:a,isAll:!!l.length&&t===l.length-i}},c.config={},d.reload=function(e,i){var a=c.config[e];return i=i||{},a?(i.data&&i.data.constructor===Array&&delete a.data,d.render(t.extend(!0,{},a,i))):o.error("The ID option was not found in the table instance")},d.render=function(e){var t=new S(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(s,d)}); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/tree.js b/static/plugs/layui/lay/modules/tree.js index 13c86135d..aeabe40a9 100644 --- a/static/plugs/layui/lay/modules/tree.js +++ b/static/plugs/layui/lay/modules/tree.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ +/** layui-v2.2.5 MIT License By https://www.layui.com */ ;layui.define("jquery",function(e){"use strict";var o=layui.$,a=layui.hint(),i="layui-tree-enter",r=function(e){this.options=e},t={arrow:["",""],checkbox:["",""],radio:["",""],branch:["",""],leaf:""};r.prototype.init=function(e){var o=this;e.addClass("layui-box layui-tree"),o.options.skin&&e.addClass("layui-tree-skin-"+o.options.skin),o.tree(e),o.on(e)},r.prototype.tree=function(e,a){var i=this,r=i.options,n=a||r.nodes;layui.each(n,function(a,n){var l=n.children&&n.children.length>0,c=o('
                '),s=o(["
              • ",function(){return l?''+(n.spread?t.arrow[1]:t.arrow[0])+"":""}(),function(){return r.check?''+("checkbox"===r.check?t.checkbox[0]:"radio"===r.check?t.radio[0]:"")+"":""}(),function(){return'"+(''+(l?n.spread?t.branch[1]:t.branch[0]:t.leaf)+"")+(""+(n.name||"未命名")+"")}(),"
              • "].join(""));l&&(s.append(c),i.tree(c,n.children)),e.append(s),"function"==typeof r.click&&i.click(s,n),i.spread(s,n),r.drag&&i.drag(s,n)})},r.prototype.click=function(e,o){var a=this,i=a.options;e.children("a").on("click",function(e){layui.stope(e),i.click(o)})},r.prototype.spread=function(e,o){var a=this,i=(a.options,e.children(".layui-tree-spread")),r=e.children("ul"),n=e.children("a"),l=function(){e.data("spread")?(e.data("spread",null),r.removeClass("layui-show"),i.html(t.arrow[0]),n.find(".layui-icon").html(t.branch[0])):(e.data("spread",!0),r.addClass("layui-show"),i.html(t.arrow[1]),n.find(".layui-icon").html(t.branch[1]))};r[0]&&(i.on("click",l),n.on("dblclick",l))},r.prototype.on=function(e){var a=this,r=a.options,t="layui-tree-drag";e.find("i").on("selectstart",function(e){return!1}),r.drag&&o(document).on("mousemove",function(e){var i=a.move;if(i.from){var r=(i.to,o('
                '));e.preventDefault(),o("."+t)[0]||o("body").append(r);var n=o("."+t)[0]?o("."+t):r;n.addClass("layui-show").html(i.from.elem.children("a").html()),n.css({left:e.pageX+10,top:e.pageY+10})}}).on("mouseup",function(){var e=a.move;e.from&&(e.from.elem.children("a").removeClass(i),e.to&&e.to.elem.children("a").removeClass(i),a.move={},o("."+t).remove())})},r.prototype.move={},r.prototype.drag=function(e,a){var r=this,t=(r.options,e.children("a")),n=function(){var t=o(this),n=r.move;n.from&&(n.to={item:a,elem:e},t.addClass(i))};t.on("mousedown",function(){var o=r.move;o.from={item:a,elem:e}}),t.on("mouseenter",n).on("mousemove",n).on("mouseleave",function(){var e=o(this),a=r.move;a.from&&(delete a.to,e.removeClass(i))})},e("tree",function(e){var i=new r(e=e||{}),t=o(e.elem);return t[0]?void i.init(t):a.error("layui.tree 没有找到"+e.elem+"元素")})}); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/upload.js b/static/plugs/layui/lay/modules/upload.js index 346feb558..4fbf66e98 100644 --- a/static/plugs/layui/lay/modules/upload.js +++ b/static/plugs/layui/lay/modules/upload.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;layui.define("layer",function(e){"use strict";var i=layui.$,t=layui.layer,n=layui.hint(),a=layui.device(),o={config:{},set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,r,e,i)}},l=function(){var e=this;return{upload:function(i){e.upload.call(e,i)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",s="layui-upload-iframe",f="layui-upload-choose",p=function(e){var t=this;t.config=i.extend({},t.config,o.config,e),t.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",method:"post",data:{},drag:!0,size:0,multiple:!1},p.prototype.render=function(e){var t=this,e=t.config;e.elem=i(e.elem),e.bindAction=i(e.bindAction),t.file(),t.events()},p.prototype.file=function(){var e=this,t=e.config,n=e.elemFile=i(['"].join("")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap('
                '),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i(''),a=i(['
                ',"
                "].join(""));i("#"+s)[0]||i("body").append(n),t.elem.next().hasClass(s)||(e.elemFile.wrap(a),t.elem.next("."+s).append(function(){var e=[];return layui.each(t.data,function(i,t){e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){layui.each(e||o.files||o.chooseFiles||r.files,function(e,t){var n=new FormData;n.append(l.field,t),layui.each(l.data,function(e,i){n.append(e,i)}),i.ajax({url:l.url,type:l.method,data:n,contentType:!1,processData:!1,dataType:"json",success:function(i){d(e,i)},error:function(){o.msg("请求上传接口出现异常"),m(e)}})})},c=function(){var e=i("#"+s);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find("body");try{i=t.text()}catch(n){o.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(""),d(0,i))},30)},d=function(e,i){if(o.elemFile.next("."+f).remove(),r.value="","object"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},v=l.exts,h=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files}},y=function(){return"choose"===t?l.choose&&l.choose(g):(l.before&&l.before(g),a.ie?a.ie>9?u():c():void u())};switch(h=0===h.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":h,l.accept){case"file":if(v&&!RegExp("\\w\\.("+v+")$","i").test(escape(h)))return o.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(v||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(h)))return o.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(v||"mp3|wav|mid")+")$","i").test(escape(h)))return o.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(h,function(e,i){RegExp("\\w\\.("+(v||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(i))||(n=!0)}),n)return o.msg("选择的图片中包含不支持的格式"),r.value=""}if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?Math.floor(t)+(t%1>0?t.toFixed(1):0)+"MB":l.size+"KB",r.value="",F=t}}),F)return o.msg("文件不能超过"+F)}y()},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+"-"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+"个文件":(i[0]||{}).name||a[0].value.match(/[^\/\\]+\..+/g)||[]||"";a.next().hasClass(f)&&a.next().remove(),e.upload(null,"choose"),e.isFile()||t.choose||a.after(''+o+"")};t.elem.off("upload.start").on("upload.start",function(){var a=i(this),o=a.attr("lay-data");if(o)try{o=new Function("return "+o)(),e.config=i.extend({},t,o)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off("upload.over").on("upload.over",function(){var e=i(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=i(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),t.elem.data("haveEvents")||(e.elemFile.on("change",function(){i(this).trigger("upload.change")}),t.elem.on("click",function(){e.isFile()||i(this).trigger("upload.start")}),t.drag&&t.elem.on("dragover",function(e){e.preventDefault(),i(this).trigger("upload.over")}).on("dragleave",function(e){i(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),i(this).trigger("upload.drop",e)}),t.bindAction.on("click",function(){i(this).trigger("upload.action")}),t.elem.data("haveEvents",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)}); \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;layui.define("layer",function(e){"use strict";var i=layui.$,t=layui.layer,n=layui.hint(),a=layui.device(),o={config:{},set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,r,e,i)}},l=function(){var e=this;return{upload:function(i){e.upload.call(e,i)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",f="layui-upload-iframe",s="layui-upload-choose",p=function(e){var t=this;t.config=i.extend({},t.config,o.config,e),t.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",method:"post",data:{},drag:!0,size:0,number:0,multiple:!1},p.prototype.render=function(e){var t=this,e=t.config;e.elem=i(e.elem),e.bindAction=i(e.bindAction),t.file(),t.events()},p.prototype.file=function(){var e=this,t=e.config,n=e.elemFile=i(['"].join("")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap('
                '),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i(''),a=i(['
                ',"
                "].join(""));i("#"+f)[0]||i("body").append(n),t.elem.next().hasClass(f)||(e.elemFile.wrap(a),t.elem.next("."+f).append(function(){var e=[];return layui.each(t.data,function(i,t){e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){var t=0,n=0,a=e||o.files||o.chooseFiles||r.files,u=function(){l.multiple&&t+n===o.fileLength&&"function"==typeof l.allDone&&l.allDone({total:o.fileLength,successful:t,aborted:n})};layui.each(a,function(e,a){var r=new FormData;r.append(l.field,a),layui.each(l.data,function(e,i){r.append(e,i)}),i.ajax({url:l.url,type:l.method,data:r,contentType:!1,processData:!1,dataType:"json",success:function(i){t++,d(e,i),u()},error:function(){n++,o.msg("请求上传接口出现异常"),m(e),u()}})})},c=function(){var e=i("#"+f);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find("body");try{i=t.text()}catch(n){o.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(""),d(0,i))},30)},d=function(e,i){if(o.elemFile.next("."+s).remove(),r.value="","object"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},h=l.exts,v=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files}},y=function(){return"choose"===t?l.choose&&l.choose(g):(l.before&&l.before(g),a.ie?a.ie>9?u():c():void u())};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return o.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return o.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return o.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,i){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(i))||(n=!0)}),n)return o.msg("选择的图片中包含不支持的格式"),r.value=""}if(o.fileLength=function(){var i=0,t=e||o.files||o.chooseFiles||r.files;return layui.each(t,function(){i++}),i}(),l.number&&o.fileLength>l.number)return o.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?Math.floor(t)+(t%1>0?t.toFixed(1):0)+"MB":l.size+"KB",r.value="",F=t}}),F)return o.msg("文件不能超过"+F)}y()}},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+"-"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+"个文件":(i[0]||{}).name||a[0].value.match(/[^\/\\]+\..+/g)||[]||"";a.next().hasClass(s)&&a.next().remove(),e.upload(null,"choose"),e.isFile()||t.choose||a.after(''+o+"")};t.elem.off("upload.start").on("upload.start",function(){var a=i(this),o=a.attr("lay-data");if(o)try{o=new Function("return "+o)(),e.config=i.extend({},t,o)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off("upload.over").on("upload.over",function(){var e=i(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=i(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),t.elem.data("haveEvents")||(e.elemFile.on("change",function(){i(this).trigger("upload.change")}),t.elem.on("click",function(){e.isFile()||i(this).trigger("upload.start")}),t.drag&&t.elem.on("dragover",function(e){e.preventDefault(),i(this).trigger("upload.over")}).on("dragleave",function(e){i(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),i(this).trigger("upload.drop",e)}),t.bindAction.on("click",function(){i(this).trigger("upload.action")}),t.elem.data("haveEvents",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)}); \ No newline at end of file diff --git a/static/plugs/layui/lay/modules/util.js b/static/plugs/layui/lay/modules/util.js index 2e0480022..3a29621ae 100644 --- a/static/plugs/layui/lay/modules/util.js +++ b/static/plugs/layui/lay/modules/util.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;layui.define("jquery",function(e){"use strict";var o=layui.$,t={fixbar:function(e){var t,a,i="layui-fixbar",l="layui-fixbar-top",r=o(document),c=o("body");e=o.extend({showHeight:200},e),e.bar1=e.bar1===!0?"":e.bar1,e.bar2=e.bar2===!0?"":e.bar2,e.bgcolor=e.bgcolor?"background-color:"+e.bgcolor:"";var n=[e.bar1,e.bar2,""],u=o(['
                  ',e.bar1?'
                • '+n[0]+"
                • ":"",e.bar2?'
                • '+n[1]+"
                • ":"",'
                • '+n[2]+"
                • ","
                "].join("")),s=u.find("."+l),b=function(){var o=r.scrollTop();o>=e.showHeight?t||(s.show(),t=1):t&&(s.hide(),t=0)};o("."+i)[0]||("object"==typeof e.css&&u.css(e.css),c.append(u),b(),u.find("li").on("click",function(){var t=o(this),a=t.attr("lay-type");"top"===a&&o("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,a)}),r.on("scroll",function(){clearTimeout(a),a=setTimeout(function(){b()},100)}))},countdown:function(e,o,t){var a=this,i="function"==typeof o,l=new Date(e).getTime(),r=new Date(!o||i?(new Date).getTime():o).getTime(),c=l-r,n=[Math.floor(c/864e5),Math.floor(c/36e5)%24,Math.floor(c/6e4)%60,Math.floor(c/1e3)%60];i&&(t=o);var u=setTimeout(function(){a.countdown(e,r+1e3,t)},1e3);return t&&t(c>0?n:[0,0,0,0],o,u),c<=0&&clearTimeout(u),u},timeAgo:function(e,o){var t=(new Date).getTime()-new Date(e).getTime();return t>2592e6?(t=new Date(e).toLocaleString(),o&&(t=t.replace(/\s[\S]+$/g,"")),t):t>=864e5?(t/1e3/60/60/24|0)+"天前":t>=36e5?(t/1e3/60/60|0)+"小时前":t>=18e4?(t/1e3/60|0)+"分钟前":t<0?"未来":"刚刚"}};e("util",t)}); \ No newline at end of file +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;layui.define("jquery",function(e){"use strict";var t=layui.$,i={fixbar:function(e){var i,o,a="layui-fixbar",r="layui-fixbar-top",n=t(document),l=t("body");e=t.extend({showHeight:200},e),e.bar1=e.bar1===!0?"":e.bar1,e.bar2=e.bar2===!0?"":e.bar2,e.bgcolor=e.bgcolor?"background-color:"+e.bgcolor:"";var c=[e.bar1,e.bar2,""],g=t(['
                  ',e.bar1?'
                • '+c[0]+"
                • ":"",e.bar2?'
                • '+c[1]+"
                • ":"",'
                • '+c[2]+"
                • ","
                "].join("")),s=g.find("."+r),u=function(){var t=n.scrollTop();t>=e.showHeight?i||(s.show(),i=1):i&&(s.hide(),i=0)};t("."+a)[0]||("object"==typeof e.css&&g.css(e.css),l.append(g),u(),g.find("li").on("click",function(){var i=t(this),o=i.attr("lay-type");"top"===o&&t("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,o)}),n.on("scroll",function(){clearTimeout(o),o=setTimeout(function(){u()},100)}))},countdown:function(e,t,i){var o=this,a="function"==typeof t,r=new Date(e).getTime(),n=new Date(!t||a?(new Date).getTime():t).getTime(),l=r-n,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];a&&(i=t);var g=setTimeout(function(){o.countdown(e,n+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],t,g),l<=0&&clearTimeout(g),g},timeAgo:function(e,t){var i=this,o=[[],[]],a=(new Date).getTime()-new Date(e).getTime();return a>6912e5?(a=new Date(e),o[0][0]=i.digit(a.getFullYear(),4),o[0][1]=i.digit(a.getMonth()+1),o[0][2]=i.digit(a.getDate()),t||(o[1][0]=i.digit(a.getHours()),o[1][1]=i.digit(a.getMinutes()),o[1][2]=i.digit(a.getSeconds())),o[0].join("-")+" "+o[1].join(":")):a>=864e5?(a/1e3/60/60/24|0)+"天前":a>=36e5?(a/1e3/60/60|0)+"小时前":a>=12e4?(a/1e3/60|0)+"分钟前":a<0?"未来":"刚刚"},digit:function(e,t){var i="";e=String(e),t=t||2;for(var o=e.length;o1e3*o.timeout/4?a(f+" is not a valid module"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):"function"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){"jquery"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?a(f+" is not a valid module"):void("string"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":o.base||"")+(y.modules[f]||f)+".js";v.async=!0,v.charset="utf-8",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||i?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](o)},n.prototype.link=function(e,n,r){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof n&&(r=n);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(o.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof n?i:(function p(){return++y>1e3*o.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(c),"width"))?function(){n()}():setTimeout(p,100))}(),i)},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+"css/"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?a("模块名 "+o+" 已被占用"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,"").replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o){if(t=t||"layui",e.JSON&&e.JSON.parse){if(null===o)return delete localStorage[t];o="object"==typeof o?o:{key:o};try{var n=JSON.parse(localStorage[t])}catch(r){var n={}}return o.value&&(n[o.key]=o.value),o.remove&&delete n[o.key],localStorage[t]=JSON.stringify(n),o.key?n[o.key]:n}},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?"windows":/linux/.test(o)?"linux":/iphone|ipod|ipad|ios/.test(o)?"ios":/mac/.test(o)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((o.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:n("micromessenger")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios="ios"===r.os,r},n.prototype.hint=function(){return{error:a}},n.prototype.each=function(e,t){var o,n=this;if("function"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;oa?1:r/g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var n="Laytpl Error:";return"object"==typeof console&&console.error(n+e+"\n"+(r||"")),n+e}},c=n.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=c("^"+r.open+"#",""),l=c(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(c(r.open+"#"),r.open+"# ").replace(c(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(/(?="|')/g,"\\").replace(n.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(n.query(1),function(e){var n='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(c(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),n='"+_escape_('),n+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,n.escape)}catch(u){return delete o.cache,n.error(u,p)}},t.pt.render=function(e,r){var c,t=this;return e?(c=t.cache?t.cache(e,n.escape):t.parse(t.tpl,e),r?void r(c):c):n.error("no data")};var o=function(e){return"string"!=typeof e?n.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var n in e)r[n]=e[n]},o.v="1.2.0",e("laytpl",o)});layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),skip:function(){return['到第','','页',""].join("")}()};return['
                ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
                "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)});!function(){"use strict";var e=window.layui&&layui.define,t={getPath:function(){var e=document.scripts,t=e[e.length-1],n=t.src;if(!t.getAttribute("merge"))return n.substring(0,n.lastIndexOf("/")+1)}(),getStyle:function(e,t){var n=e.currentStyle?e.currentStyle:window.getComputedStyle(e,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](t)},link:function(e,a,i){if(n.path){var r=document.getElementsByTagName("head")[0],o=document.createElement("link");"string"==typeof a&&(i=a);var s=(i||e).replace(/\.|\//g,""),l="layuicss-"+s,d=0;o.rel="stylesheet",o.href=n.path+e,o.id=l,document.getElementById(l)||r.appendChild(o),"function"==typeof a&&!function c(){return++d>80?window.console&&console.error("laydate.css: Invalid"):void(1989===parseInt(t.getStyle(document.getElementById(l),"width"))?a():setTimeout(c,100))}()}}},n={v:"5.0.7",config:{},index:window.laydate&&window.laydate.v?1e5:0,path:t.getPath,set:function(e){var n=this;return n.config=t.extend({},n.config,e),n},ready:function(a){var i="laydate",r="",o=(e?"modules/laydate/":"theme/")+"default/laydate.css?v="+n.v+r;return e?layui.addcss(o,a,i):t.link(o,a,i),this}},a=function(){var e=this;return{hint:function(t){e.hint.call(e,t)},config:e.config}},i="laydate",r=".layui-laydate",o="layui-this",s="laydate-disabled",l="开始日期超出了结束日期
                建议重新选择",d=[100,2e5],c="layui-laydate-static",m="layui-laydate-list",u="laydate-selected",h="layui-laydate-hint",y="laydate-day-prev",f="laydate-day-next",p="layui-laydate-footer",g=".laydate-btns-confirm",v="laydate-time-text",D=".laydate-btns-time",T=function(e){var t=this;t.index=++n.index,t.config=w.extend({},t.config,n.config,e),n.ready(function(){t.init()})},w=function(e){return new C(e)},C=function(e){for(var t=0,n="object"==typeof e?[e]:(this.selector=e,document.querySelectorAll(e||null));t0)return n[0].getAttribute(e)}():n.each(function(n,a){a.setAttribute(e,t)})},C.prototype.removeAttr=function(e){return this.each(function(t,n){n.removeAttribute(e)})},C.prototype.html=function(e){return this.each(function(t,n){n.innerHTML=e})},C.prototype.val=function(e){return this.each(function(t,n){n.value=e})},C.prototype.append=function(e){return this.each(function(t,n){"object"==typeof e?n.appendChild(e):n.innerHTML=n.innerHTML+e})},C.prototype.remove=function(e){return this.each(function(t,n){e?n.removeChild(e):n.parentNode.removeChild(n)})},C.prototype.on=function(e,t){return this.each(function(n,a){a.attachEvent?a.attachEvent("on"+e,function(e){e.target=e.srcElement,t.call(a,e)}):a.addEventListener(e,t,!1)})},C.prototype.off=function(e,t){return this.each(function(n,a){a.detachEvent?a.detachEvent("on"+e,t):a.removeEventListener(e,t,!1)})},T.isLeapYear=function(e){return e%4===0&&e%100!==0||e%400===0},T.prototype.config={type:"date",range:!1,format:"yyyy-MM-dd",value:null,min:"1900-1-1",max:"2099-12-31",trigger:"focus",show:!1,showBottom:!0,btns:["clear","now","confirm"],lang:"cn",theme:"default",position:null,calendar:!1,mark:{},zIndex:null,done:null,change:null},T.prototype.lang=function(){var e=this,t=e.config,n={cn:{weeks:["日","一","二","三","四","五","六"],time:["时","分","秒"],timeTips:"选择时间",startTime:"开始时间",endTime:"结束时间",dateTips:"返回日期",month:["一","二","三","四","五","六","七","八","九","十","十一","十二"],tools:{confirm:"确定",clear:"清空",now:"现在"}},en:{weeks:["Su","Mo","Tu","We","Th","Fr","Sa"],time:["Hours","Minutes","Seconds"],timeTips:"Select Time",startTime:"Start Time",endTime:"End Time",dateTips:"Select Date",month:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],tools:{confirm:"Confirm",clear:"Clear",now:"Now"}}};return n[t.lang]||n.cn},T.prototype.init=function(){var e=this,t=e.config,n="yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s",a="static"===t.position,i={year:"yyyy",month:"yyyy-MM",date:"yyyy-MM-dd",time:"HH:mm:ss",datetime:"yyyy-MM-dd HH:mm:ss"};t.elem=w(t.elem),t.eventElem=w(t.eventElem),t.elem[0]&&(t.range===!0&&(t.range="-"),t.format===i.date&&(t.format=i[t.type]),e.format=t.format.match(new RegExp(n+"|.","g"))||[],e.EXP_IF="",e.EXP_SPLIT="",w.each(e.format,function(t,a){var i=new RegExp(n).test(a)?"\\d{"+function(){return new RegExp(n).test(e.format[0===t?t+1:t-1]||"")?/^yyyy|y$/.test(a)?4:a.length:/^yyyy$/.test(a)?"1,4":/^y$/.test(a)?"1,308":"1,2"}()+"}":"\\"+a;e.EXP_IF=e.EXP_IF+i,e.EXP_SPLIT=e.EXP_SPLIT+"("+i+")"}),e.EXP_IF=new RegExp("^"+(t.range?e.EXP_IF+"\\s\\"+t.range+"\\s"+e.EXP_IF:e.EXP_IF)+"$"),e.EXP_SPLIT=new RegExp("^"+e.EXP_SPLIT+"$",""),e.isInput(t.elem[0])||"focus"===t.trigger&&(t.trigger="click"),t.elem.attr("lay-key")||(t.elem.attr("lay-key",e.index),t.eventElem.attr("lay-key",e.index)),t.mark=w.extend({},t.calendar&&"cn"===t.lang?{"0-1-1":"元旦","0-2-14":"情人","0-3-8":"妇女","0-3-12":"植树","0-4-1":"愚人","0-5-1":"劳动","0-5-4":"青年","0-6-1":"儿童","0-9-10":"教师","0-9-18":"国耻","0-10-1":"国庆","0-12-25":"圣诞"}:{},t.mark),w.each(["min","max"],function(e,n){var a=[],i=[];if("number"==typeof t[n]){var r=t[n],o=(new Date).getTime(),s=864e5,l=new Date(r?r0)return!0;var a=w.elem("div",{"class":"layui-laydate-header"}),i=[function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-y"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-m"});return e.innerHTML="",e}(),function(){var e=w.elem("div",{"class":"laydate-set-ym"}),t=w.elem("span"),n=w.elem("span");return e.appendChild(t),e.appendChild(n),e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-m"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-y"});return e.innerHTML="",e}()],d=w.elem("div",{"class":"layui-laydate-content"}),c=w.elem("table"),m=w.elem("thead"),u=w.elem("tr");w.each(i,function(e,t){a.appendChild(t)}),m.appendChild(u),w.each(new Array(6),function(e){var t=c.insertRow(0);w.each(new Array(7),function(a){if(0===e){var i=w.elem("th");i.innerHTML=n.weeks[a],u.appendChild(i)}t.insertCell(a)})}),c.insertBefore(m,c.children[0]),d.appendChild(c),r[e]=w.elem("div",{"class":"layui-laydate-main laydate-main-list-"+e}),r[e].appendChild(a),r[e].appendChild(d),o.push(i),s.push(d),l.push(c)}),w(d).html(function(){var e=[],i=[];return"datetime"===t.type&&e.push(''+n.timeTips+""),w.each(t.btns,function(e,r){var o=n.tools[r]||"btn";t.range&&"now"===r||(a&&"clear"===r&&(o="cn"===t.lang?"重置":"Reset"),i.push(''+o+""))}),e.push('"),e.join("")}()),w.each(r,function(e,t){i.appendChild(t)}),t.showBottom&&i.appendChild(d),/^#/.test(t.theme)){var m=w.elem("style"),u=["#{{id}} .layui-laydate-header{background-color:{{theme}};}","#{{id}} .layui-this{background-color:{{theme}} !important;}"].join("").replace(/{{id}}/g,e.elemID).replace(/{{theme}}/g,t.theme);"styleSheet"in m?(m.setAttribute("type","text/css"),m.styleSheet.cssText=u):m.innerHTML=u,w(i).addClass("laydate-theme-molv"),i.appendChild(m)}e.remove(T.thisElem),a?t.elem.append(i):(document.body.appendChild(i),e.position()),e.checkDate().calendar(),e.changeEvent(),T.thisElem=e.elemID,"function"==typeof t.ready&&t.ready(w.extend({},t.dateTime,{month:t.dateTime.month+1}))},T.prototype.remove=function(e){var t=this,n=(t.config,w("#"+(e||t.elemID)));return n[0]&&!n.hasClass(c)&&t.checkDate(function(){n.remove()}),t},T.prototype.position=function(){var e=this,t=e.config,n=e.bindElem||t.elem[0],a=n.getBoundingClientRect(),i=e.elem.offsetWidth,r=e.elem.offsetHeight,o=function(e){return e=e?"scrollLeft":"scrollTop",document.body[e]|document.documentElement[e]},s=function(e){return document.documentElement[e?"clientWidth":"clientHeight"]},l=5,d=a.left,c=a.bottom;d+i+l>s("width")&&(d=s("width")-i-l),c+r+l>s()&&(c=a.top>r?a.top-r:s()-r,c-=2*l),t.position&&(e.elem.style.position=t.position),e.elem.style.left=d+("fixed"===t.position?0:o(1))+"px",e.elem.style.top=c+("fixed"===t.position?0:o())+"px"},T.prototype.hint=function(e){var t=this,n=(t.config,w.elem("div",{"class":h}));n.innerHTML=e||"",w(t.elem).find("."+h).remove(),t.elem.appendChild(n),clearTimeout(t.hinTimer),t.hinTimer=setTimeout(function(){w(t.elem).find("."+h).remove()},3e3)},T.prototype.getAsYM=function(e,t,n){return n?t--:t++,t<0&&(t=11,e--),t>11&&(t=0,e++),[e,t]},T.prototype.systemDate=function(e){var t=e||new Date;return{year:t.getFullYear(),month:t.getMonth(),date:t.getDate(),hours:e?e.getHours():0,minutes:e?e.getMinutes():0,seconds:e?e.getSeconds():0}},T.prototype.checkDate=function(e){var t,a,i=this,r=(new Date,i.config),o=r.dateTime=r.dateTime||i.systemDate(),s=i.bindElem||r.elem[0],l=(i.isInput(s)?"val":"html",i.isInput(s)?s.value:"static"===r.position?"":s.innerHTML),c=function(e){e.year>d[1]&&(e.year=d[1],a=!0),e.month>11&&(e.month=11,a=!0),e.hours>23&&(e.hours=0,a=!0),e.minutes>59&&(e.minutes=0,e.hours++,a=!0),e.seconds>59&&(e.seconds=0,e.minutes++,a=!0),t=n.getEndDate(e.month+1,e.year),e.date>t&&(e.date=t,a=!0)},m=function(e,t,n){var o=["startTime","endTime"];t=(t.match(i.EXP_SPLIT)||[]).slice(1),n=n||0,r.range&&(i[o[n]]=i[o[n]]||{}),w.each(i.format,function(s,l){var c=parseFloat(t[s]);t[s].length必须遵循下述格式:
                "+(r.range?r.format+" "+r.range+" "+r.format:r.format)+"
                已为你重置"),a=!0):l&&l.constructor===Date?r.dateTime=i.systemDate(l):(r.dateTime=i.systemDate(),delete i.startState,delete i.endState,delete i.startDate,delete i.endDate,delete i.startTime,delete i.endTime),c(o),a&&l&&i.setValue(r.range?i.endDate?i.parse():"":i.parse()),e&&e(),i)},T.prototype.mark=function(e,t){var n,a=this,i=a.config;return w.each(i.mark,function(e,a){var i=e.split("-");i[0]!=t[0]&&0!=i[0]||i[1]!=t[1]&&0!=i[1]||i[2]!=t[2]||(n=a||t[2])}),n&&e.html(''+n+""),a},T.prototype.limit=function(e,t,n,a){var i,r=this,o=r.config,l={},d=o[n>41?"endDate":"dateTime"],c=w.extend({},d,t||{});return w.each({now:c,min:o.min,max:o.max},function(e,t){l[e]=r.newDate(w.extend({year:t.year,month:t.month,date:t.date},function(){var e={};return w.each(a,function(n,a){e[a]=t[a]}),e}())).getTime()}),i=l.nowl.max,e&&e[i?"addClass":"removeClass"](s),i},T.prototype.calendar=function(e){var t,a,i,r=this,s=r.config,l=e||s.dateTime,c=new Date,m=r.lang(),u="date"!==s.type&&"datetime"!==s.type,h=e?1:0,y=w(r.table[h]).find("td"),f=w(r.elemHeader[h][2]).find("span");if(l.yeard[1]&&(l.year=d[1],r.hint("最高只能支持到公元"+d[1]+"年")),r.firstDate||(r.firstDate=w.extend({},l)),c.setFullYear(l.year,l.month,1),t=c.getDay(),a=n.getEndDate(l.month,l.year),i=n.getEndDate(l.month+1,l.year),w.each(y,function(e,n){var d=[l.year,l.month],c=0;n=w(n),n.removeAttr("class"),e=t&&e=n.firstDate.year&&(r.month=a.max.month,r.date=a.max.date),n.limit(w(i),r,t),M++}),w(u[f?0:1]).attr("lay-ym",M-8+"-"+T[1]).html(b+p+" - "+(M-1+p))}else if("month"===e)w.each(new Array(12),function(e){var i=w.elem("li",{"lay-ym":e}),s={year:T[0],month:e};e+1==T[1]&&w(i).addClass(o),i.innerHTML=r.month[e]+(f?"月":""),d.appendChild(i),T[0]=n.firstDate.year&&(s.date=a.max.date),n.limit(w(i),s,t)}),w(u[f?0:1]).attr("lay-ym",T[0]+"-"+T[1]).html(T[0]+p);else if("time"===e){var E=function(){w(d).find("ol").each(function(e,a){w(a).find("li").each(function(a,i){n.limit(w(i),[{hours:a},{hours:n[x].hours,minutes:a},{hours:n[x].hours,minutes:n[x].minutes,seconds:a}][e],t,[["hours"],["hours","minutes"],["hours","minutes","seconds"]][e])})}),a.range||n.limit(w(n.footer).find(g),n[x],0,["hours","minutes","seconds"])};a.range?n[x]||(n[x]={hours:0,minutes:0,seconds:0}):n[x]=i,w.each([24,60,60],function(e,t){var a=w.elem("li"),i=["

                "+r.time[e]+"

                  "];w.each(new Array(t),function(t){i.push(""+w.digit(t,2)+"")}),a.innerHTML=i.join("")+"
                ",d.appendChild(a)}),E()}if(y&&h.removeChild(y),h.appendChild(d),"year"===e||"month"===e)w(n.elemMain[t]).addClass("laydate-ym-show"),w(d).find("li").on("click",function(){var r=0|w(this).attr("lay-ym");if(!w(this).hasClass(s)){if(0===t)i[e]=r,l&&(n.startDate[e]=r),n.limit(w(n.footer).find(g),null,0);else if(l)n.endDate[e]=r;else{var c="year"===e?n.getAsYM(r,T[1]-1,"sub"):n.getAsYM(T[0],r,"sub");w.extend(i,{year:c[0],month:c[1]})}"year"===a.type||"month"===a.type?(w(d).find("."+o).removeClass(o),w(this).addClass(o),"month"===a.type&&"year"===e&&(n.listYM[t][0]=r,l&&(n[["startDate","endDate"][t]].year=r),n.list("month",t))):(n.checkDate("limit").calendar(),n.closeList()),n.setBtnStatus(),a.range||n.done(null,"change"),w(n.footer).find(D).removeClass(s)}});else{var S=w.elem("span",{"class":v}),k=function(){w(d).find("ol").each(function(e){var t=this,a=w(t).find("li");t.scrollTop=30*(n[x][C[e]]-2),t.scrollTop<=0&&a.each(function(e,n){if(!w(this).hasClass(s))return t.scrollTop=30*(e-2),!0})})},H=w(c[2]).find("."+v);k(),S.innerHTML=a.range?[r.startTime,r.endTime][t]:r.timeTips,w(n.elemMain[t]).addClass("laydate-time-show"),H[0]&&H.remove(),c[2].appendChild(S),w(d).find("ol").each(function(e){var t=this;w(t).find("li").on("click",function(){var r=0|this.innerHTML;w(this).hasClass(s)||(a.range?n[x][C[e]]=r:i[C[e]]=r,w(t).find("."+o).removeClass(o),w(this).addClass(o),E(),k(),(n.endDate||"time"===a.type)&&n.done(null,"change"),n.setBtnStatus())})})}return n},T.prototype.listYM=[],T.prototype.closeList=function(){var e=this;e.config;w.each(e.elemCont,function(t,n){w(this).find("."+m).remove(),w(e.elemMain[t]).removeClass("laydate-ym-show laydate-time-show")}),w(e.elem).find("."+v).remove()},T.prototype.setBtnStatus=function(e,t,n){var a,i=this,r=i.config,o=w(i.footer).find(g),d=r.range&&"date"!==r.type&&"time"!==r.type;d&&(t=t||i.startDate,n=n||i.endDate,a=i.newDate(t).getTime()>i.newDate(n).getTime(),i.limit(null,t)||i.limit(null,n)?o.addClass(s):o[a?"addClass":"removeClass"](s),e&&a&&i.hint("string"==typeof e?l.replace(/日期/g,e):l))},T.prototype.parse=function(e,t){var n=this,a=n.config,i=t||(e?w.extend({},n.endDate,n.endTime):a.range?w.extend({},n.startDate,n.startTime):a.dateTime),r=n.format.concat();return w.each(r,function(e,t){/yyyy|y/.test(t)?r[e]=w.digit(i.year,t.length):/MM|M/.test(t)?r[e]=w.digit(i.month+1,t.length):/dd|d/.test(t)?r[e]=w.digit(i.date,t.length):/HH|H/.test(t)?r[e]=w.digit(i.hours,t.length):/mm|m/.test(t)?r[e]=w.digit(i.minutes,t.length):/ss|s/.test(t)&&(r[e]=w.digit(i.seconds,t.length))}),a.range&&!e?r.join("")+" "+a.range+" "+n.parse(1):r.join("")},T.prototype.newDate=function(e){return new Date(e.year||1,e.month||0,e.date||1,e.hours||0,e.minutes||0,e.seconds||0)},T.prototype.setValue=function(e){var t=this,n=t.config,a=t.bindElem||n.elem[0],i=t.isInput(a)?"val":"html";return"static"===n.position||w(a)[i](e||""),this},T.prototype.stampRange=function(){var e,t,n=this,a=n.config,i=w(n.elem).find("td");if(a.range&&!n.endDate&&w(n.footer).find(g).addClass(s),n.endDate)return e=n.newDate({year:n.startDate.year,month:n.startDate.month,date:n.startDate.date}).getTime(),t=n.newDate({year:n.endDate.year,month:n.endDate.month,date:n.endDate.date}).getTime(),e>t?n.hint(l):void w.each(i,function(a,i){var r=w(i).attr("lay-ymd").split("-"),s=n.newDate({year:r[0],month:r[1]-1,date:r[2]}).getTime();w(i).removeClass(u+" "+o),s!==e&&s!==t||w(i).addClass(w(i).hasClass(y)||w(i).hasClass(f)?u:o),s>e&&s0&&t-1 in e)}function r(e,t,n){if(pe.isFunction(t))return pe.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return pe.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(Ce.test(t))return pe.filter(t,e,n);t=pe.filter(t,e)}return pe.grep(e,function(e){return pe.inArray(e,t)>-1!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t={};return pe.each(e.match(De)||[],function(e,n){t[n]=!0}),t}function a(){re.addEventListener?(re.removeEventListener("DOMContentLoaded",s),e.removeEventListener("load",s)):(re.detachEvent("onreadystatechange",s),e.detachEvent("onload",s))}function s(){(re.addEventListener||"load"===e.event.type||"complete"===re.readyState)&&(a(),pe.ready())}function u(e,t,n){if(void 0===n&&1===e.nodeType){var r="data-"+t.replace(_e,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:qe.test(n)?pe.parseJSON(n):n)}catch(i){}pe.data(e,t,n)}else n=void 0}return n}function l(e){var t;for(t in e)if(("data"!==t||!pe.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,r){if(He(e)){var i,o,a=pe.expando,s=e.nodeType,u=s?pe.cache:e,l=s?e[a]:e[a]&&a;if(l&&u[l]&&(r||u[l].data)||void 0!==n||"string"!=typeof t)return l||(l=s?e[a]=ne.pop()||pe.guid++:a),u[l]||(u[l]=s?{}:{toJSON:pe.noop}),"object"!=typeof t&&"function"!=typeof t||(r?u[l]=pe.extend(u[l],t):u[l].data=pe.extend(u[l].data,t)),o=u[l],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[pe.camelCase(t)]=n),"string"==typeof t?(i=o[t],null==i&&(i=o[pe.camelCase(t)])):i=o,i}}function f(e,t,n){if(He(e)){var r,i,o=e.nodeType,a=o?pe.cache:e,s=o?e[pe.expando]:pe.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){pe.isArray(t)?t=t.concat(pe.map(t,pe.camelCase)):t in r?t=[t]:(t=pe.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;for(;i--;)delete r[t[i]];if(n?!l(r):!pe.isEmptyObject(r))return}(n||(delete a[s].data,l(a[s])))&&(o?pe.cleanData([e],!0):fe.deleteExpando||a!=a.window?delete a[s]:a[s]=void 0)}}}function d(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return pe.css(e,t,"")},u=s(),l=n&&n[3]||(pe.cssNumber[t]?"":"px"),c=(pe.cssNumber[t]||"px"!==l&&+u)&&Me.exec(pe.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do o=o||".5",c/=o,pe.style(e,t,c+l);while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function p(e){var t=ze.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function h(e,t){var n,r,i=0,o="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||pe.nodeName(r,t)?o.push(r):pe.merge(o,h(r,t));return void 0===t||t&&pe.nodeName(e,t)?pe.merge([e],o):o}function g(e,t){for(var n,r=0;null!=(n=e[r]);r++)pe._data(n,"globalEval",!t||pe._data(t[r],"globalEval"))}function m(e){Be.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t,n,r,i){for(var o,a,s,u,l,c,f,d=e.length,y=p(t),v=[],x=0;x"!==f[1]||Ve.test(a)?0:u:u.firstChild,o=a&&a.childNodes.length;o--;)pe.nodeName(c=a.childNodes[o],"tbody")&&!c.childNodes.length&&a.removeChild(c);for(pe.merge(v,u.childNodes),u.textContent="";u.firstChild;)u.removeChild(u.firstChild);u=y.lastChild}else v.push(t.createTextNode(a));for(u&&y.removeChild(u),fe.appendChecked||pe.grep(h(v,"input"),m),x=0;a=v[x++];)if(r&&pe.inArray(a,r)>-1)i&&i.push(a);else if(s=pe.contains(a.ownerDocument,a),u=h(y.appendChild(a),"script"),s&&g(u),n)for(o=0;a=u[o++];)Ie.test(a.type||"")&&n.push(a);return u=null,y}function v(){return!0}function x(){return!1}function b(){try{return re.activeElement}catch(e){}}function w(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)w(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),i===!1)i=x;else if(!i)return e;return 1===o&&(a=i,i=function(e){return pe().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=pe.guid++)),e.each(function(){pe.event.add(this,t,i,r,n)})}function T(e,t){return pe.nodeName(e,"table")&&pe.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function C(e){return e.type=(null!==pe.find.attr(e,"type"))+"/"+e.type,e}function E(e){var t=it.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function N(e,t){if(1===t.nodeType&&pe.hasData(e)){var n,r,i,o=pe._data(e),a=pe._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;r1&&"string"==typeof p&&!fe.checkClone&&rt.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),S(o,t,n,r)});if(f&&(l=y(t,e[0].ownerDocument,!1,e,r),i=l.firstChild,1===l.childNodes.length&&(l=i),i||r)){for(s=pe.map(h(l,"script"),C),a=s.length;c")).appendTo(t.documentElement),t=(ut[0].contentWindow||ut[0].contentDocument).document,t.write(),t.close(),n=D(e,t),ut.detach()),lt[e]=n),n}function L(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function H(e){if(e in Et)return e;for(var t=e.charAt(0).toUpperCase()+e.slice(1),n=Ct.length;n--;)if(e=Ct[n]+t,e in Et)return e}function q(e,t){for(var n,r,i,o=[],a=0,s=e.length;a=0&&n=0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},isPlainObject:function(e){var t;if(!e||"object"!==pe.type(e)||e.nodeType||pe.isWindow(e))return!1;try{if(e.constructor&&!ce.call(e,"constructor")&&!ce.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}if(!fe.ownFirst)for(t in e)return ce.call(e,t);for(t in e);return void 0===t||ce.call(e,t)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?ue[le.call(e)]||"object":typeof e},globalEval:function(t){t&&pe.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(ge,"ms-").replace(me,ye)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var r,i=0;if(n(e))for(r=e.length;iT.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[];return e}function r(e){return e[P]=!0,e}function i(e){var t=H.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)T.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||V)-(~e.sourceIndex||V);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function u(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function l(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function c(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function f(){}function d(e){for(var t=0,n=e.length,r="";t1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,n,r){for(var i=0,o=n.length;i-1&&(r[l]=!(a[l]=f))}}else x=m(x===a?x.splice(h,x.length):x),o?o(null,a,x,u):Q.apply(a,x)})}function v(e){for(var t,n,r,i=e.length,o=T.relative[e[0].type],a=o||T.relative[" "],s=o?1:0,u=p(function(e){return e===t},a,!0),l=p(function(e){return ee(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==A)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];s1&&h(c),s>1&&d(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(se,"$1"),n,s0,o=e.length>0,a=function(r,a,s,u,l){var c,f,d,p=0,h="0",g=r&&[],y=[],v=A,x=r||o&&T.find.TAG("*",l),b=W+=null==v?1:Math.random()||.1,w=x.length;for(l&&(A=a===H||a||l);h!==w&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===H||(L(c),s=!_);d=e[f++];)if(d(c,a||H,s)){u.push(c);break}l&&(W=b)}i&&((c=!d&&c)&&p--,r&&g.push(c))}if(p+=h,i&&h!==p){for(f=0;d=n[f++];)d(g,y,a,s);if(r){if(p>0)for(;h--;)g[h]||y[h]||(y[h]=G.call(u));y=m(y)}Q.apply(u,y),l&&!r&&y.length>0&&p+n.length>1&&t.uniqueSort(u)}return l&&(W=b,A=v),g};return i?r(a):a}var b,w,T,C,E,N,k,S,A,D,j,L,H,q,_,F,M,O,R,P="sizzle"+1*new Date,B=e.document,W=0,I=0,$=n(),z=n(),X=n(),U=function(e,t){return e===t&&(j=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],G=J.pop,K=J.push,Q=J.push,Z=J.slice,ee=function(e,t){for(var n=0,r=e.length;n+~]|"+ne+")"+ne+"*"),ce=new RegExp("="+ne+"*([^\\]'\"]*?)"+ne+"*\\]","g"),fe=new RegExp(oe),de=new RegExp("^"+re+"$"),pe={ID:new RegExp("^#("+re+")"),CLASS:new RegExp("^\\.("+re+")"),TAG:new RegExp("^("+re+"|[*])"),ATTR:new RegExp("^"+ie),PSEUDO:new RegExp("^"+oe),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ne+"*(even|odd|(([+-]|)(\\d*)n|)"+ne+"*(?:([+-]|)"+ne+"*(\\d+)|))"+ne+"*\\)|)","i"),bool:new RegExp("^(?:"+te+")$","i"),needsContext:new RegExp("^"+ne+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ne+"*((?:-\\d)?\\d*)"+ne+"*\\)|)(?=[^-]|$)","i")},he=/^(?:input|select|textarea|button)$/i,ge=/^h\d$/i,me=/^[^{]+\{\s*\[native \w/,ye=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ve=/[+~]/,xe=/'|\\/g,be=new RegExp("\\\\([\\da-f]{1,6}"+ne+"?|("+ne+")|.)","ig"),we=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},Te=function(){L()};try{Q.apply(J=Z.call(B.childNodes),B.childNodes),J[B.childNodes.length].nodeType}catch(Ce){Q={apply:J.length?function(e,t){K.apply(e,Z.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}w=t.support={},E=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},L=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:B;return r!==H&&9===r.nodeType&&r.documentElement?(H=r,q=H.documentElement,_=!E(H),(n=H.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Te,!1):n.attachEvent&&n.attachEvent("onunload",Te)),w.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),w.getElementsByTagName=i(function(e){return e.appendChild(H.createComment("")),!e.getElementsByTagName("*").length}),w.getElementsByClassName=me.test(H.getElementsByClassName),w.getById=i(function(e){return q.appendChild(e).id=P,!H.getElementsByName||!H.getElementsByName(P).length}),w.getById?(T.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&_){var n=t.getElementById(e);return n?[n]:[]}},T.filter.ID=function(e){var t=e.replace(be,we);return function(e){return e.getAttribute("id")===t}}):(delete T.find.ID,T.filter.ID=function(e){var t=e.replace(be,we);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}}),T.find.TAG=w.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):w.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},T.find.CLASS=w.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&_)return t.getElementsByClassName(e)},M=[],F=[],(w.qsa=me.test(H.querySelectorAll))&&(i(function(e){q.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&F.push("[*^$]="+ne+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||F.push("\\["+ne+"*(?:value|"+te+")"),e.querySelectorAll("[id~="+P+"-]").length||F.push("~="),e.querySelectorAll(":checked").length||F.push(":checked"),e.querySelectorAll("a#"+P+"+*").length||F.push(".#.+[+~]")}),i(function(e){var t=H.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&F.push("name"+ne+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||F.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),F.push(",.*:")})),(w.matchesSelector=me.test(O=q.matches||q.webkitMatchesSelector||q.mozMatchesSelector||q.oMatchesSelector||q.msMatchesSelector))&&i(function(e){w.disconnectedMatch=O.call(e,"div"),O.call(e,"[s!='']:x"),M.push("!=",oe)}),F=F.length&&new RegExp(F.join("|")),M=M.length&&new RegExp(M.join("|")),t=me.test(q.compareDocumentPosition),R=t||me.test(q.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},U=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n?n:(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!w.sortDetached&&t.compareDocumentPosition(e)===n?e===H||e.ownerDocument===B&&R(B,e)?-1:t===H||t.ownerDocument===B&&R(B,t)?1:D?ee(D,e)-ee(D,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===H?-1:t===H?1:i?-1:o?1:D?ee(D,e)-ee(D,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===B?-1:u[r]===B?1:0},H):H},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==H&&L(e),n=n.replace(ce,"='$1']"),w.matchesSelector&&_&&!X[n+" "]&&(!M||!M.test(n))&&(!F||!F.test(n)))try{var r=O.call(e,n);if(r||w.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return t(n,H,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==H&&L(e),R(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==H&&L(e);var n=T.attrHandle[t.toLowerCase()],r=n&&Y.call(T.attrHandle,t.toLowerCase())?n(e,t,!_):void 0;return void 0!==r?r:w.attributes||!_?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!w.detectDuplicates,D=!w.sortStable&&e.slice(0),e.sort(U),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return D=null,e},C=t.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=C(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=C(t);return n},T=t.selectors={cacheLength:50,createPseudo:r,match:pe,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(be,we),e[3]=(e[3]||e[4]||e[5]||"").replace(be,we),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&fe.test(n)&&(t=N(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(be,we).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=$[e+" "];return t||(t=new RegExp("(^|"+ne+")"+e+"("+ne+"|$)"))&&$(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?"!="===n:!n||(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ae," ")+" ").indexOf(r)>-1:"|="===n&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,d,p,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s,x=!1;if(m){if(o){for(;g;){for(d=t;d=d[g];)if(s?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(d=m,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}), +/** layui-v2.2.5 MIT License By https://www.layui.com */ + ;!function(e){"use strict";var t=document,n={modules:{},status:{},timeout:10,event:{}},o=function(){this.v="2.2.5"},r=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,n=t.scripts,o=n.length-1,r=o;r>0;r--)if("interactive"===n[r].readyState){e=n[r].src;break}return e||n[o].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),a=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},i="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",util:"modules/util",flow:"modules/flow",carousel:"modules/carousel",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};o.prototype.cache=n,o.prototype.define=function(e,t){var o=this,r="function"==typeof e,a=function(){var e=function(e,t){layui[e]=t,n.status[e]=!0};return"function"==typeof t&&t(function(o,r){e(o,r),n.callback[o]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?a.call(o):(o.use(e,a),o)},o.prototype.use=function(e,o,l){function s(e,t){var o="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||o.test((e.currentTarget||e.srcElement).readyState))&&(n.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*n.timeout/4?a(f+" is not a valid module"):void(n.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),o,l):"function"==typeof o&&o.apply(layui,l)}var y=this,p=n.dir=n.dir?n.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,n){"jquery"===n&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],n.host=n.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(n.modules[f])!function g(){return++m>1e3*n.timeout/4?a(f+" is not a valid module"):void("string"==typeof n.modules[f]&&n.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":/^\{\/\}/.test(y.modules[f])?"":n.base||"")+(y.modules[f]||f)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=n.version===!0?n.v||(new Date).getTime():n.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||i?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),n.modules[f]=h}return y},o.prototype.getStyle=function(t,n){var o=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return o[o.getPropertyValue?"getPropertyValue":"getAttribute"](n)},o.prototype.link=function(e,o,r){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof o&&(r=o);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(n.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof o?i:(function p(){return++y>1e3*n.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(c),"width"))?function(){o()}():setTimeout(p,100))}(),i)},n.callback={},o.prototype.factory=function(e){if(layui[e])return"function"==typeof n.callback[e]?n.callback[e]:null},o.prototype.addcss=function(e,t,o){return layui.link(n.dir+"css/"+e,t,o)},o.prototype.img=function(e,t,n){var o=new Image;return o.src=e,o.complete?t(o):(o.onload=function(){o.onload=null,t(o)},void(o.onerror=function(e){o.onerror=null,n(e)}))},o.prototype.config=function(e){e=e||{};for(var t in e)n[t]=e[t];return this},o.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),o.prototype.extend=function(e){var t=this;e=e||{};for(var n in e)t[n]||t.modules[n]?a("模块名 "+n+" 已被占用"):t.modules[n]=e[n];return t},o.prototype.router=function(e){var t=this,e=e||location.hash,n={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(n.href=e=e.replace(/^#\//,""),e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),n.search[t[0]]=t[1]}():n.path.push(t)}),n):n},o.prototype.data=function(t,n,o){if(t=t||"layui",o=o||localStorage,e.JSON&&e.JSON.parse){if(null===n)return delete o[t];n="object"==typeof n?n:{key:n};try{var r=JSON.parse(o[t])}catch(a){var r={}}return"value"in n&&(r[n.key]=n.value),n.remove&&delete r[n.key],o[t]=JSON.stringify(r),n.key?r[n.key]:r}},o.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},o.prototype.device=function(t){var n=navigator.userAgent.toLowerCase(),o=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(n.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(n)?"windows":/linux/.test(n)?"linux":/iphone|ipod|ipad|ios/.test(n)?"ios":/mac/.test(n)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((n.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:o("micromessenger")};return t&&!r[t]&&(r[t]=o(t)),r.android=/android/.test(n),r.ios="ios"===r.os,r},o.prototype.hint=function(){return{error:a}},o.prototype.each=function(e,t){var n,o=this;if("function"!=typeof t)return o;if(e=e||[],e.constructor===Object){for(n in e)if(t.call(e[n],n,e[n]))break}else for(n=0;na?1:r/g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)});layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),skip:function(){return['到第','','页',""].join("")}()};return['
                ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
                "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)});!function(){"use strict";var e=window.layui&&layui.define,t={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,n=t.length-1,a=n;a>0;a--)if("interactive"===t[a].readyState){e=t[a].src;break}return e||t[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),getStyle:function(e,t){var n=e.currentStyle?e.currentStyle:window.getComputedStyle(e,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](t)},link:function(e,a,i){if(n.path){var r=document.getElementsByTagName("head")[0],o=document.createElement("link");"string"==typeof a&&(i=a);var s=(i||e).replace(/\.|\//g,""),l="layuicss-"+s,d=0;o.rel="stylesheet",o.href=n.path+e,o.id=l,document.getElementById(l)||r.appendChild(o),"function"==typeof a&&!function c(){return++d>80?window.console&&console.error("laydate.css: Invalid"):void(1989===parseInt(t.getStyle(document.getElementById(l),"width"))?a():setTimeout(c,100))}()}}},n={v:"5.0.9",config:{},index:window.laydate&&window.laydate.v?1e5:0,path:t.getPath,set:function(e){var t=this;return t.config=w.extend({},t.config,e),t},ready:function(a){var i="laydate",r="",o=(e?"modules/laydate/":"theme/")+"default/laydate.css?v="+n.v+r;return e?layui.addcss(o,a,i):t.link(o,a,i),this}},a=function(){var e=this;return{hint:function(t){e.hint.call(e,t)},config:e.config}},i="laydate",r=".layui-laydate",o="layui-this",s="laydate-disabled",l="开始日期超出了结束日期
                建议重新选择",d=[100,2e5],c="layui-laydate-static",m="layui-laydate-list",u="laydate-selected",h="layui-laydate-hint",y="laydate-day-prev",f="laydate-day-next",p="layui-laydate-footer",g=".laydate-btns-confirm",v="laydate-time-text",D=".laydate-btns-time",T=function(e){var t=this;t.index=++n.index,t.config=w.extend({},t.config,n.config,e),n.ready(function(){t.init()})},w=function(e){return new C(e)},C=function(e){for(var t=0,n="object"==typeof e?[e]:(this.selector=e,document.querySelectorAll(e||null));t0)return n[0].getAttribute(e)}():n.each(function(n,a){a.setAttribute(e,t)})},C.prototype.removeAttr=function(e){return this.each(function(t,n){n.removeAttribute(e)})},C.prototype.html=function(e){return this.each(function(t,n){n.innerHTML=e})},C.prototype.val=function(e){return this.each(function(t,n){n.value=e})},C.prototype.append=function(e){return this.each(function(t,n){"object"==typeof e?n.appendChild(e):n.innerHTML=n.innerHTML+e})},C.prototype.remove=function(e){return this.each(function(t,n){e?n.removeChild(e):n.parentNode.removeChild(n)})},C.prototype.on=function(e,t){return this.each(function(n,a){a.attachEvent?a.attachEvent("on"+e,function(e){e.target=e.srcElement,t.call(a,e)}):a.addEventListener(e,t,!1)})},C.prototype.off=function(e,t){return this.each(function(n,a){a.detachEvent?a.detachEvent("on"+e,t):a.removeEventListener(e,t,!1)})},T.isLeapYear=function(e){return e%4===0&&e%100!==0||e%400===0},T.prototype.config={type:"date",range:!1,format:"yyyy-MM-dd",value:null,min:"1900-1-1",max:"2099-12-31",trigger:"focus",show:!1,showBottom:!0,btns:["clear","now","confirm"],lang:"cn",theme:"default",position:null,calendar:!1,mark:{},zIndex:null,done:null,change:null},T.prototype.lang=function(){var e=this,t=e.config,n={cn:{weeks:["日","一","二","三","四","五","六"],time:["时","分","秒"],timeTips:"选择时间",startTime:"开始时间",endTime:"结束时间",dateTips:"返回日期",month:["一","二","三","四","五","六","七","八","九","十","十一","十二"],tools:{confirm:"确定",clear:"清空",now:"现在"}},en:{weeks:["Su","Mo","Tu","We","Th","Fr","Sa"],time:["Hours","Minutes","Seconds"],timeTips:"Select Time",startTime:"Start Time",endTime:"End Time",dateTips:"Select Date",month:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],tools:{confirm:"Confirm",clear:"Clear",now:"Now"}}};return n[t.lang]||n.cn},T.prototype.init=function(){var e=this,t=e.config,n="yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s",a="static"===t.position,i={year:"yyyy",month:"yyyy-MM",date:"yyyy-MM-dd",time:"HH:mm:ss",datetime:"yyyy-MM-dd HH:mm:ss"};t.elem=w(t.elem),t.eventElem=w(t.eventElem),t.elem[0]&&(t.range===!0&&(t.range="-"),t.format===i.date&&(t.format=i[t.type]),e.format=t.format.match(new RegExp(n+"|.","g"))||[],e.EXP_IF="",e.EXP_SPLIT="",w.each(e.format,function(t,a){var i=new RegExp(n).test(a)?"\\d{"+function(){return new RegExp(n).test(e.format[0===t?t+1:t-1]||"")?/^yyyy|y$/.test(a)?4:a.length:/^yyyy$/.test(a)?"1,4":/^y$/.test(a)?"1,308":"1,2"}()+"}":"\\"+a;e.EXP_IF=e.EXP_IF+i,e.EXP_SPLIT=e.EXP_SPLIT+"("+i+")"}),e.EXP_IF=new RegExp("^"+(t.range?e.EXP_IF+"\\s\\"+t.range+"\\s"+e.EXP_IF:e.EXP_IF)+"$"),e.EXP_SPLIT=new RegExp("^"+e.EXP_SPLIT+"$",""),e.isInput(t.elem[0])||"focus"===t.trigger&&(t.trigger="click"),t.elem.attr("lay-key")||(t.elem.attr("lay-key",e.index),t.eventElem.attr("lay-key",e.index)),t.mark=w.extend({},t.calendar&&"cn"===t.lang?{"0-1-1":"元旦","0-2-14":"情人","0-3-8":"妇女","0-3-12":"植树","0-4-1":"愚人","0-5-1":"劳动","0-5-4":"青年","0-6-1":"儿童","0-9-10":"教师","0-9-18":"国耻","0-10-1":"国庆","0-12-25":"圣诞"}:{},t.mark),w.each(["min","max"],function(e,n){var a=[],i=[];if("number"==typeof t[n]){var r=t[n],o=(new Date).getTime(),s=864e5,l=new Date(r?r0)return!0;var a=w.elem("div",{"class":"layui-laydate-header"}),i=[function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-y"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-m"});return e.innerHTML="",e}(),function(){var e=w.elem("div",{"class":"laydate-set-ym"}),t=w.elem("span"),n=w.elem("span");return e.appendChild(t),e.appendChild(n),e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-m"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-y"});return e.innerHTML="",e}()],d=w.elem("div",{"class":"layui-laydate-content"}),c=w.elem("table"),m=w.elem("thead"),u=w.elem("tr");w.each(i,function(e,t){a.appendChild(t)}),m.appendChild(u),w.each(new Array(6),function(e){var t=c.insertRow(0);w.each(new Array(7),function(a){if(0===e){var i=w.elem("th");i.innerHTML=n.weeks[a],u.appendChild(i)}t.insertCell(a)})}),c.insertBefore(m,c.children[0]),d.appendChild(c),r[e]=w.elem("div",{"class":"layui-laydate-main laydate-main-list-"+e}),r[e].appendChild(a),r[e].appendChild(d),o.push(i),s.push(d),l.push(c)}),w(d).html(function(){var e=[],i=[];return"datetime"===t.type&&e.push(''+n.timeTips+""),w.each(t.btns,function(e,r){var o=n.tools[r]||"btn";t.range&&"now"===r||(a&&"clear"===r&&(o="cn"===t.lang?"重置":"Reset"),i.push(''+o+""))}),e.push('"),e.join("")}()),w.each(r,function(e,t){i.appendChild(t)}),t.showBottom&&i.appendChild(d),/^#/.test(t.theme)){var m=w.elem("style"),u=["#{{id}} .layui-laydate-header{background-color:{{theme}};}","#{{id}} .layui-this{background-color:{{theme}} !important;}"].join("").replace(/{{id}}/g,e.elemID).replace(/{{theme}}/g,t.theme);"styleSheet"in m?(m.setAttribute("type","text/css"),m.styleSheet.cssText=u):m.innerHTML=u,w(i).addClass("laydate-theme-molv"),i.appendChild(m)}e.remove(T.thisElemDate),a?t.elem.append(i):(document.body.appendChild(i),e.position()),e.checkDate().calendar(),e.changeEvent(),T.thisElemDate=e.elemID,"function"==typeof t.ready&&t.ready(w.extend({},t.dateTime,{month:t.dateTime.month+1}))},T.prototype.remove=function(e){var t=this,n=(t.config,w("#"+(e||t.elemID)));return n.hasClass(c)||t.checkDate(function(){n.remove()}),t},T.prototype.position=function(){var e=this,t=e.config,n=e.bindElem||t.elem[0],a=n.getBoundingClientRect(),i=e.elem.offsetWidth,r=e.elem.offsetHeight,o=function(e){return e=e?"scrollLeft":"scrollTop",document.body[e]|document.documentElement[e]},s=function(e){return document.documentElement[e?"clientWidth":"clientHeight"]},l=5,d=a.left,c=a.bottom;d+i+l>s("width")&&(d=s("width")-i-l),c+r+l>s()&&(c=a.top>r?a.top-r:s()-r,c-=2*l),t.position&&(e.elem.style.position=t.position),e.elem.style.left=d+("fixed"===t.position?0:o(1))+"px",e.elem.style.top=c+("fixed"===t.position?0:o())+"px"},T.prototype.hint=function(e){var t=this,n=(t.config,w.elem("div",{"class":h}));n.innerHTML=e||"",w(t.elem).find("."+h).remove(),t.elem.appendChild(n),clearTimeout(t.hinTimer),t.hinTimer=setTimeout(function(){w(t.elem).find("."+h).remove()},3e3)},T.prototype.getAsYM=function(e,t,n){return n?t--:t++,t<0&&(t=11,e--),t>11&&(t=0,e++),[e,t]},T.prototype.systemDate=function(e){var t=e||new Date;return{year:t.getFullYear(),month:t.getMonth(),date:t.getDate(),hours:e?e.getHours():0,minutes:e?e.getMinutes():0,seconds:e?e.getSeconds():0}},T.prototype.checkDate=function(e){var t,a,i=this,r=(new Date,i.config),o=r.dateTime=r.dateTime||i.systemDate(),s=i.bindElem||r.elem[0],l=(i.isInput(s)?"val":"html",i.isInput(s)?s.value:"static"===r.position?"":s.innerHTML),c=function(e){e.year>d[1]&&(e.year=d[1],a=!0),e.month>11&&(e.month=11,a=!0),e.hours>23&&(e.hours=0,a=!0),e.minutes>59&&(e.minutes=0,e.hours++,a=!0),e.seconds>59&&(e.seconds=0,e.minutes++,a=!0),t=n.getEndDate(e.month+1,e.year),e.date>t&&(e.date=t,a=!0)},m=function(e,t,n){var o=["startTime","endTime"];t=(t.match(i.EXP_SPLIT)||[]).slice(1),n=n||0,r.range&&(i[o[n]]=i[o[n]]||{}),w.each(i.format,function(s,l){var c=parseFloat(t[s]);t[s].length必须遵循下述格式:
                "+(r.range?r.format+" "+r.range+" "+r.format:r.format)+"
                已为你重置"),a=!0):l&&l.constructor===Date?r.dateTime=i.systemDate(l):(r.dateTime=i.systemDate(),delete i.startState,delete i.endState,delete i.startDate,delete i.endDate,delete i.startTime,delete i.endTime),c(o),a&&l&&i.setValue(r.range?i.endDate?i.parse():"":i.parse()),e&&e(),i)},T.prototype.mark=function(e,t){var n,a=this,i=a.config;return w.each(i.mark,function(e,a){var i=e.split("-");i[0]!=t[0]&&0!=i[0]||i[1]!=t[1]&&0!=i[1]||i[2]!=t[2]||(n=a||t[2])}),n&&e.html(''+n+""),a},T.prototype.limit=function(e,t,n,a){var i,r=this,o=r.config,l={},d=o[n>41?"endDate":"dateTime"],c=w.extend({},d,t||{});return w.each({now:c,min:o.min,max:o.max},function(e,t){l[e]=r.newDate(w.extend({year:t.year,month:t.month,date:t.date},function(){var e={};return w.each(a,function(n,a){e[a]=t[a]}),e}())).getTime()}),i=l.nowl.max,e&&e[i?"addClass":"removeClass"](s),i},T.prototype.calendar=function(e){var t,a,i,r=this,s=r.config,l=e||s.dateTime,c=new Date,m=r.lang(),u="date"!==s.type&&"datetime"!==s.type,h=e?1:0,y=w(r.table[h]).find("td"),f=w(r.elemHeader[h][2]).find("span");if(l.yeard[1]&&(l.year=d[1],r.hint("最高只能支持到公元"+d[1]+"年")),r.firstDate||(r.firstDate=w.extend({},l)),c.setFullYear(l.year,l.month,1),t=c.getDay(),a=n.getEndDate(l.month||12,l.year),i=n.getEndDate(l.month+1,l.year),w.each(y,function(e,n){var d=[l.year,l.month],c=0;n=w(n),n.removeAttr("class"),e=t&&e=n.firstDate.year&&(r.month=a.max.month,r.date=a.max.date),n.limit(w(i),r,t),M++}),w(u[f?0:1]).attr("lay-ym",M-8+"-"+T[1]).html(b+p+" - "+(M-1+p))}else if("month"===e)w.each(new Array(12),function(e){var i=w.elem("li",{"lay-ym":e}),s={year:T[0],month:e};e+1==T[1]&&w(i).addClass(o),i.innerHTML=r.month[e]+(f?"月":""),d.appendChild(i),T[0]=n.firstDate.year&&(s.date=a.max.date),n.limit(w(i),s,t)}),w(u[f?0:1]).attr("lay-ym",T[0]+"-"+T[1]).html(T[0]+p);else if("time"===e){var E=function(){w(d).find("ol").each(function(e,a){w(a).find("li").each(function(a,i){n.limit(w(i),[{hours:a},{hours:n[x].hours,minutes:a},{hours:n[x].hours,minutes:n[x].minutes,seconds:a}][e],t,[["hours"],["hours","minutes"],["hours","minutes","seconds"]][e])})}),a.range||n.limit(w(n.footer).find(g),n[x],0,["hours","minutes","seconds"])};a.range?n[x]||(n[x]={hours:0,minutes:0,seconds:0}):n[x]=i,w.each([24,60,60],function(e,t){var a=w.elem("li"),i=["

                "+r.time[e]+"

                  "];w.each(new Array(t),function(t){i.push(""+w.digit(t,2)+"")}),a.innerHTML=i.join("")+"
                ",d.appendChild(a)}),E()}if(y&&h.removeChild(y),h.appendChild(d),"year"===e||"month"===e)w(n.elemMain[t]).addClass("laydate-ym-show"),w(d).find("li").on("click",function(){var r=0|w(this).attr("lay-ym");if(!w(this).hasClass(s)){if(0===t)i[e]=r,l&&(n.startDate[e]=r),n.limit(w(n.footer).find(g),null,0);else if(l)n.endDate[e]=r;else{var c="year"===e?n.getAsYM(r,T[1]-1,"sub"):n.getAsYM(T[0],r,"sub");w.extend(i,{year:c[0],month:c[1]})}"year"===a.type||"month"===a.type?(w(d).find("."+o).removeClass(o),w(this).addClass(o),"month"===a.type&&"year"===e&&(n.listYM[t][0]=r,l&&(n[["startDate","endDate"][t]].year=r),n.list("month",t))):(n.checkDate("limit").calendar(),n.closeList()),n.setBtnStatus(),a.range||n.done(null,"change"),w(n.footer).find(D).removeClass(s)}});else{var S=w.elem("span",{"class":v}),k=function(){w(d).find("ol").each(function(e){var t=this,a=w(t).find("li");t.scrollTop=30*(n[x][C[e]]-2),t.scrollTop<=0&&a.each(function(e,n){if(!w(this).hasClass(s))return t.scrollTop=30*(e-2),!0})})},H=w(c[2]).find("."+v);k(),S.innerHTML=a.range?[r.startTime,r.endTime][t]:r.timeTips,w(n.elemMain[t]).addClass("laydate-time-show"),H[0]&&H.remove(),c[2].appendChild(S),w(d).find("ol").each(function(e){var t=this;w(t).find("li").on("click",function(){var r=0|this.innerHTML;w(this).hasClass(s)||(a.range?n[x][C[e]]=r:i[C[e]]=r,w(t).find("."+o).removeClass(o),w(this).addClass(o),E(),k(),(n.endDate||"time"===a.type)&&n.done(null,"change"),n.setBtnStatus())})})}return n},T.prototype.listYM=[],T.prototype.closeList=function(){var e=this;e.config;w.each(e.elemCont,function(t,n){w(this).find("."+m).remove(),w(e.elemMain[t]).removeClass("laydate-ym-show laydate-time-show")}),w(e.elem).find("."+v).remove()},T.prototype.setBtnStatus=function(e,t,n){var a,i=this,r=i.config,o=w(i.footer).find(g),d=r.range&&"date"!==r.type&&"time"!==r.type;d&&(t=t||i.startDate,n=n||i.endDate,a=i.newDate(t).getTime()>i.newDate(n).getTime(),i.limit(null,t)||i.limit(null,n)?o.addClass(s):o[a?"addClass":"removeClass"](s),e&&a&&i.hint("string"==typeof e?l.replace(/日期/g,e):l))},T.prototype.parse=function(e,t){var n=this,a=n.config,i=t||(e?w.extend({},n.endDate,n.endTime):a.range?w.extend({},n.startDate,n.startTime):a.dateTime),r=n.format.concat();return w.each(r,function(e,t){/yyyy|y/.test(t)?r[e]=w.digit(i.year,t.length):/MM|M/.test(t)?r[e]=w.digit(i.month+1,t.length):/dd|d/.test(t)?r[e]=w.digit(i.date,t.length):/HH|H/.test(t)?r[e]=w.digit(i.hours,t.length):/mm|m/.test(t)?r[e]=w.digit(i.minutes,t.length):/ss|s/.test(t)&&(r[e]=w.digit(i.seconds,t.length))}),a.range&&!e?r.join("")+" "+a.range+" "+n.parse(1):r.join("")},T.prototype.newDate=function(e){return e=e||{},new Date(e.year||1,e.month||0,e.date||1,e.hours||0,e.minutes||0,e.seconds||0)},T.prototype.setValue=function(e){var t=this,n=t.config,a=t.bindElem||n.elem[0],i=t.isInput(a)?"val":"html";return"static"===n.position||w(a)[i](e||""),this},T.prototype.stampRange=function(){var e,t,n=this,a=n.config,i=w(n.elem).find("td");if(a.range&&!n.endDate&&w(n.footer).find(g).addClass(s),n.endDate)return e=n.newDate({year:n.startDate.year,month:n.startDate.month,date:n.startDate.date}).getTime(),t=n.newDate({year:n.endDate.year,month:n.endDate.month,date:n.endDate.date}).getTime(),e>t?n.hint(l):void w.each(i,function(a,i){var r=w(i).attr("lay-ymd").split("-"),s=n.newDate({year:r[0],month:r[1]-1,date:r[2]}).getTime();w(i).removeClass(u+" "+o),s!==e&&s!==t||w(i).addClass(w(i).hasClass(y)||w(i).hasClass(f)?u:o),s>e&&s0&&t-1 in e)}function r(e,t,n){if(pe.isFunction(t))return pe.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return pe.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(Ce.test(t))return pe.filter(t,e,n);t=pe.filter(t,e)}return pe.grep(e,function(e){return pe.inArray(e,t)>-1!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t={};return pe.each(e.match(De)||[],function(e,n){t[n]=!0}),t}function a(){re.addEventListener?(re.removeEventListener("DOMContentLoaded",s),e.removeEventListener("load",s)):(re.detachEvent("onreadystatechange",s),e.detachEvent("onload",s))}function s(){(re.addEventListener||"load"===e.event.type||"complete"===re.readyState)&&(a(),pe.ready())}function u(e,t,n){if(void 0===n&&1===e.nodeType){var r="data-"+t.replace(_e,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:qe.test(n)?pe.parseJSON(n):n)}catch(i){}pe.data(e,t,n)}else n=void 0}return n}function l(e){var t;for(t in e)if(("data"!==t||!pe.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,r){if(He(e)){var i,o,a=pe.expando,s=e.nodeType,u=s?pe.cache:e,l=s?e[a]:e[a]&&a;if(l&&u[l]&&(r||u[l].data)||void 0!==n||"string"!=typeof t)return l||(l=s?e[a]=ne.pop()||pe.guid++:a),u[l]||(u[l]=s?{}:{toJSON:pe.noop}),"object"!=typeof t&&"function"!=typeof t||(r?u[l]=pe.extend(u[l],t):u[l].data=pe.extend(u[l].data,t)),o=u[l],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[pe.camelCase(t)]=n),"string"==typeof t?(i=o[t],null==i&&(i=o[pe.camelCase(t)])):i=o,i}}function f(e,t,n){if(He(e)){var r,i,o=e.nodeType,a=o?pe.cache:e,s=o?e[pe.expando]:pe.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){pe.isArray(t)?t=t.concat(pe.map(t,pe.camelCase)):t in r?t=[t]:(t=pe.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;for(;i--;)delete r[t[i]];if(n?!l(r):!pe.isEmptyObject(r))return}(n||(delete a[s].data,l(a[s])))&&(o?pe.cleanData([e],!0):fe.deleteExpando||a!=a.window?delete a[s]:a[s]=void 0)}}}function d(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return pe.css(e,t,"")},u=s(),l=n&&n[3]||(pe.cssNumber[t]?"":"px"),c=(pe.cssNumber[t]||"px"!==l&&+u)&&Me.exec(pe.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do o=o||".5",c/=o,pe.style(e,t,c+l);while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function p(e){var t=ze.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function h(e,t){var n,r,i=0,o="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||pe.nodeName(r,t)?o.push(r):pe.merge(o,h(r,t));return void 0===t||t&&pe.nodeName(e,t)?pe.merge([e],o):o}function g(e,t){for(var n,r=0;null!=(n=e[r]);r++)pe._data(n,"globalEval",!t||pe._data(t[r],"globalEval"))}function m(e){Be.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t,n,r,i){for(var o,a,s,u,l,c,f,d=e.length,y=p(t),v=[],x=0;x"!==f[1]||Ve.test(a)?0:u:u.firstChild,o=a&&a.childNodes.length;o--;)pe.nodeName(c=a.childNodes[o],"tbody")&&!c.childNodes.length&&a.removeChild(c);for(pe.merge(v,u.childNodes),u.textContent="";u.firstChild;)u.removeChild(u.firstChild);u=y.lastChild}else v.push(t.createTextNode(a));for(u&&y.removeChild(u),fe.appendChecked||pe.grep(h(v,"input"),m),x=0;a=v[x++];)if(r&&pe.inArray(a,r)>-1)i&&i.push(a);else if(s=pe.contains(a.ownerDocument,a),u=h(y.appendChild(a),"script"),s&&g(u),n)for(o=0;a=u[o++];)Ie.test(a.type||"")&&n.push(a);return u=null,y}function v(){return!0}function x(){return!1}function b(){try{return re.activeElement}catch(e){}}function w(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)w(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),i===!1)i=x;else if(!i)return e;return 1===o&&(a=i,i=function(e){return pe().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=pe.guid++)),e.each(function(){pe.event.add(this,t,i,r,n)})}function T(e,t){return pe.nodeName(e,"table")&&pe.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function C(e){return e.type=(null!==pe.find.attr(e,"type"))+"/"+e.type,e}function E(e){var t=it.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function N(e,t){if(1===t.nodeType&&pe.hasData(e)){var n,r,i,o=pe._data(e),a=pe._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;r1&&"string"==typeof p&&!fe.checkClone&&rt.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),S(o,t,n,r)});if(f&&(l=y(t,e[0].ownerDocument,!1,e,r),i=l.firstChild,1===l.childNodes.length&&(l=i),i||r)){for(s=pe.map(h(l,"script"),C),a=s.length;c")).appendTo(t.documentElement),t=(ut[0].contentWindow||ut[0].contentDocument).document,t.write(),t.close(),n=D(e,t),ut.detach()),lt[e]=n),n}function L(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function H(e){if(e in Et)return e;for(var t=e.charAt(0).toUpperCase()+e.slice(1),n=Ct.length;n--;)if(e=Ct[n]+t,e in Et)return e}function q(e,t){for(var n,r,i,o=[],a=0,s=e.length;a=0&&n=0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},isPlainObject:function(e){var t;if(!e||"object"!==pe.type(e)||e.nodeType||pe.isWindow(e))return!1;try{if(e.constructor&&!ce.call(e,"constructor")&&!ce.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}if(!fe.ownFirst)for(t in e)return ce.call(e,t);for(t in e);return void 0===t||ce.call(e,t)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?ue[le.call(e)]||"object":typeof e},globalEval:function(t){t&&pe.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(ge,"ms-").replace(me,ye)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var r,i=0;if(n(e))for(r=e.length;iT.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[];return e}function r(e){return e[P]=!0,e}function i(e){var t=H.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)T.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||V)-(~e.sourceIndex||V);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function u(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function l(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function c(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function f(){}function d(e){for(var t=0,n=e.length,r="";t1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,n,r){for(var i=0,o=n.length;i-1&&(r[l]=!(a[l]=f))}}else x=m(x===a?x.splice(h,x.length):x),o?o(null,a,x,u):Q.apply(a,x)})}function v(e){for(var t,n,r,i=e.length,o=T.relative[e[0].type],a=o||T.relative[" "],s=o?1:0,u=p(function(e){return e===t},a,!0),l=p(function(e){return ee(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==A)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];s1&&h(c),s>1&&d(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(se,"$1"),n,s0,o=e.length>0,a=function(r,a,s,u,l){var c,f,d,p=0,h="0",g=r&&[],y=[],v=A,x=r||o&&T.find.TAG("*",l),b=W+=null==v?1:Math.random()||.1,w=x.length;for(l&&(A=a===H||a||l);h!==w&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===H||(L(c),s=!_);d=e[f++];)if(d(c,a||H,s)){u.push(c);break}l&&(W=b)}i&&((c=!d&&c)&&p--,r&&g.push(c))}if(p+=h,i&&h!==p){for(f=0;d=n[f++];)d(g,y,a,s);if(r){if(p>0)for(;h--;)g[h]||y[h]||(y[h]=G.call(u));y=m(y)}Q.apply(u,y),l&&!r&&y.length>0&&p+n.length>1&&t.uniqueSort(u)}return l&&(W=b,A=v),g};return i?r(a):a}var b,w,T,C,E,N,k,S,A,D,j,L,H,q,_,F,M,O,R,P="sizzle"+1*new Date,B=e.document,W=0,I=0,$=n(),z=n(),X=n(),U=function(e,t){return e===t&&(j=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],G=J.pop,K=J.push,Q=J.push,Z=J.slice,ee=function(e,t){for(var n=0,r=e.length;n+~]|"+ne+")"+ne+"*"),ce=new RegExp("="+ne+"*([^\\]'\"]*?)"+ne+"*\\]","g"),fe=new RegExp(oe),de=new RegExp("^"+re+"$"),pe={ID:new RegExp("^#("+re+")"),CLASS:new RegExp("^\\.("+re+")"),TAG:new RegExp("^("+re+"|[*])"),ATTR:new RegExp("^"+ie),PSEUDO:new RegExp("^"+oe),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ne+"*(even|odd|(([+-]|)(\\d*)n|)"+ne+"*(?:([+-]|)"+ne+"*(\\d+)|))"+ne+"*\\)|)","i"),bool:new RegExp("^(?:"+te+")$","i"),needsContext:new RegExp("^"+ne+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ne+"*((?:-\\d)?\\d*)"+ne+"*\\)|)(?=[^-]|$)","i")},he=/^(?:input|select|textarea|button)$/i,ge=/^h\d$/i,me=/^[^{]+\{\s*\[native \w/,ye=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ve=/[+~]/,xe=/'|\\/g,be=new RegExp("\\\\([\\da-f]{1,6}"+ne+"?|("+ne+")|.)","ig"),we=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},Te=function(){L()};try{Q.apply(J=Z.call(B.childNodes),B.childNodes),J[B.childNodes.length].nodeType}catch(Ce){Q={apply:J.length?function(e,t){K.apply(e,Z.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}w=t.support={},E=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},L=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:B;return r!==H&&9===r.nodeType&&r.documentElement?(H=r,q=H.documentElement,_=!E(H),(n=H.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Te,!1):n.attachEvent&&n.attachEvent("onunload",Te)),w.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),w.getElementsByTagName=i(function(e){return e.appendChild(H.createComment("")),!e.getElementsByTagName("*").length}),w.getElementsByClassName=me.test(H.getElementsByClassName),w.getById=i(function(e){return q.appendChild(e).id=P,!H.getElementsByName||!H.getElementsByName(P).length}),w.getById?(T.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&_){var n=t.getElementById(e);return n?[n]:[]}},T.filter.ID=function(e){var t=e.replace(be,we);return function(e){return e.getAttribute("id")===t}}):(delete T.find.ID,T.filter.ID=function(e){var t=e.replace(be,we);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}}),T.find.TAG=w.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):w.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},T.find.CLASS=w.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&_)return t.getElementsByClassName(e)},M=[],F=[],(w.qsa=me.test(H.querySelectorAll))&&(i(function(e){q.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&F.push("[*^$]="+ne+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||F.push("\\["+ne+"*(?:value|"+te+")"),e.querySelectorAll("[id~="+P+"-]").length||F.push("~="),e.querySelectorAll(":checked").length||F.push(":checked"),e.querySelectorAll("a#"+P+"+*").length||F.push(".#.+[+~]")}),i(function(e){var t=H.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&F.push("name"+ne+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||F.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),F.push(",.*:")})),(w.matchesSelector=me.test(O=q.matches||q.webkitMatchesSelector||q.mozMatchesSelector||q.oMatchesSelector||q.msMatchesSelector))&&i(function(e){w.disconnectedMatch=O.call(e,"div"),O.call(e,"[s!='']:x"),M.push("!=",oe)}),F=F.length&&new RegExp(F.join("|")),M=M.length&&new RegExp(M.join("|")),t=me.test(q.compareDocumentPosition),R=t||me.test(q.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},U=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n?n:(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!w.sortDetached&&t.compareDocumentPosition(e)===n?e===H||e.ownerDocument===B&&R(B,e)?-1:t===H||t.ownerDocument===B&&R(B,t)?1:D?ee(D,e)-ee(D,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===H?-1:t===H?1:i?-1:o?1:D?ee(D,e)-ee(D,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===B?-1:u[r]===B?1:0},H):H},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==H&&L(e),n=n.replace(ce,"='$1']"),w.matchesSelector&&_&&!X[n+" "]&&(!M||!M.test(n))&&(!F||!F.test(n)))try{var r=O.call(e,n);if(r||w.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return t(n,H,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==H&&L(e),R(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==H&&L(e);var n=T.attrHandle[t.toLowerCase()],r=n&&Y.call(T.attrHandle,t.toLowerCase())?n(e,t,!_):void 0;return void 0!==r?r:w.attributes||!_?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!w.detectDuplicates,D=!w.sortStable&&e.slice(0),e.sort(U),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return D=null,e},C=t.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=C(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=C(t);return n},T=t.selectors={cacheLength:50,createPseudo:r,match:pe,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(be,we),e[3]=(e[3]||e[4]||e[5]||"").replace(be,we),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&fe.test(n)&&(t=N(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(be,we).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=$[e+" "];return t||(t=new RegExp("(^|"+ne+")"+e+"("+ne+"|$)"))&&$(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?"!="===n:!n||(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ae," ")+" ").indexOf(r)>-1:"|="===n&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,d,p,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s,x=!1;if(m){if(o){for(;g;){for(d=t;d=d[g];)if(s?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(d=m,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}), l=c[e]||[],p=l[0]===W&&l[1],x=p&&l[2],d=p&&m.childNodes[p];d=++p&&d&&d[g]||(x=p=0)||h.pop();)if(1===d.nodeType&&++x&&d===t){c[e]=[W,p,x];break}}else if(v&&(d=t,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),l=c[e]||[],p=l[0]===W&&l[1],x=p),x===!1)for(;(d=++p&&d&&d[g]||(x=p=0)||h.pop())&&((s?d.nodeName.toLowerCase()!==y:1!==d.nodeType)||!++x||(v&&(f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),c[e]=[W,x]),d!==t)););return x-=i,x===r||x%r===0&&x/r>=0}}},PSEUDO:function(e,n){var i,o=T.pseudos[e]||T.setFilters[e.toLowerCase()]||t.error("unsupported pseudo: "+e);return o[P]?o(n):o.length>1?(i=[e,e,"",n],T.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),a=i.length;a--;)r=ee(e,i[a]),e[r]=!(t[r]=i[a])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=k(e.replace(se,"$1"));return i[P]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(be,we),function(t){return(t.textContent||t.innerText||C(t)).indexOf(e)>-1}}),lang:r(function(e){return de.test(e||"")||t.error("unsupported lang: "+e),e=e.replace(be,we).toLowerCase(),function(t){var n;do if(n=_?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===q},focus:function(e){return e===H.activeElement&&(!H.hasFocus||H.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!T.pseudos.empty(e)},header:function(e){return ge.test(e.nodeName)},input:function(e){return he.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:l(function(){return[0]}),last:l(function(e,t){return[t-1]}),eq:l(function(e,t,n){return[n<0?n+t:n]}),even:l(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:l(function(e,t,n){for(var r=n<0?n+t:n;++r2&&"ID"===(a=o[0]).type&&w.getById&&9===t.nodeType&&_&&T.relative[o[1].type]){if(t=(T.find.ID(a.matches[0].replace(be,we),t)||[])[0],!t)return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=pe.needsContext.test(e)?0:o.length;i--&&(a=o[i],!T.relative[s=a.type]);)if((u=T.find[s])&&(r=u(a.matches[0].replace(be,we),ve.test(o[0].type)&&c(t.parentNode)||t))){if(o.splice(i,1),e=r.length&&d(o),!e)return Q.apply(n,r),n;break}}return(l||k(e,f))(r,t,!_,n,!t||ve.test(e)&&c(t.parentNode)||t),n},w.sortStable=P.split("").sort(U).join("")===P,w.detectDuplicates=!!j,L(),w.sortDetached=i(function(e){return 1&e.compareDocumentPosition(H.createElement("div"))}),i(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||o("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),w.attributes&&i(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||o("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),i(function(e){return null==e.getAttribute("disabled")})||o(te,function(e,t,n){var r;if(!n)return e[t]===!0?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e);pe.find=ve,pe.expr=ve.selectors,pe.expr[":"]=pe.expr.pseudos,pe.uniqueSort=pe.unique=ve.uniqueSort,pe.text=ve.getText,pe.isXMLDoc=ve.isXML,pe.contains=ve.contains;var xe=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&pe(e).is(n))break;r.push(e)}return r},be=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},we=pe.expr.match.needsContext,Te=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Ce=/^.[^:#\[\.,]*$/;pe.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?pe.find.matchesSelector(r,e)?[r]:[]:pe.find.matches(e,pe.grep(t,function(e){return 1===e.nodeType}))},pe.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(pe(e).filter(function(){for(t=0;t1?pe.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},filter:function(e){return this.pushStack(r(this,e||[],!1))},not:function(e){return this.pushStack(r(this,e||[],!0))},is:function(e){return!!r(this,"string"==typeof e&&we.test(e)?pe(e):e||[],!1).length}});var Ee,Ne=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,ke=pe.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||Ee,"string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:Ne.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof pe?t[0]:t,pe.merge(this,pe.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:re,!0)),Te.test(r[1])&&pe.isPlainObject(t))for(r in t)pe.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}if(i=re.getElementById(r[2]),i&&i.parentNode){if(i.id!==r[2])return Ee.find(e);this.length=1,this[0]=i}return this.context=re,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):pe.isFunction(e)?"undefined"!=typeof n.ready?n.ready(e):e(pe):(void 0!==e.selector&&(this.selector=e.selector,this.context=e.context),pe.makeArray(e,this))};ke.prototype=pe.fn,Ee=pe(re);var Se=/^(?:parents|prev(?:Until|All))/,Ae={children:!0,contents:!0,next:!0,prev:!0};pe.fn.extend({has:function(e){var t,n=pe(e,this),r=n.length;return this.filter(function(){for(t=0;t-1:1===n.nodeType&&pe.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?pe.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?pe.inArray(this[0],pe(e)):pe.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(pe.uniqueSort(pe.merge(this.get(),pe(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),pe.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return xe(e,"parentNode")},parentsUntil:function(e,t,n){return xe(e,"parentNode",n)},next:function(e){return i(e,"nextSibling")},prev:function(e){return i(e,"previousSibling")},nextAll:function(e){return xe(e,"nextSibling")},prevAll:function(e){return xe(e,"previousSibling")},nextUntil:function(e,t,n){return xe(e,"nextSibling",n)},prevUntil:function(e,t,n){return xe(e,"previousSibling",n)},siblings:function(e){return be((e.parentNode||{}).firstChild,e)},children:function(e){return be(e.firstChild)},contents:function(e){return pe.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:pe.merge([],e.childNodes)}},function(e,t){pe.fn[e]=function(n,r){var i=pe.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=pe.filter(r,i)),this.length>1&&(Ae[e]||(i=pe.uniqueSort(i)),Se.test(e)&&(i=i.reverse())),this.pushStack(i)}});var De=/\S+/g;pe.Callbacks=function(e){e="string"==typeof e?o(e):pe.extend({},e);var t,n,r,i,a=[],s=[],u=-1,l=function(){for(i=e.once,r=t=!0;s.length;u=-1)for(n=s.shift();++u-1;)a.splice(n,1),n<=u&&u--}),this},has:function(e){return e?pe.inArray(e,a)>-1:a.length>0},empty:function(){return a&&(a=[]),this},disable:function(){return i=s=[],a=n="",this},disabled:function(){return!a},lock:function(){return i=!0,n||c.disable(),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],s.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},pe.extend({Deferred:function(e){var t=[["resolve","done",pe.Callbacks("once memory"),"resolved"],["reject","fail",pe.Callbacks("once memory"),"rejected"],["notify","progress",pe.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return pe.Deferred(function(n){pe.each(t,function(t,o){var a=pe.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&pe.isFunction(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[o[0]+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?pe.extend(e,r):r}},i={};return r.pipe=r.then,pe.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=ie.call(arguments),a=o.length,s=1!==a||e&&pe.isFunction(e.promise)?a:0,u=1===s?e:pe.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?ie.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=new Array(a),n=new Array(a),r=new Array(a);i0||(je.resolveWith(re,[pe]),pe.fn.triggerHandler&&(pe(re).triggerHandler("ready"),pe(re).off("ready"))))}}),pe.ready.promise=function(t){if(!je)if(je=pe.Deferred(),"complete"===re.readyState||"loading"!==re.readyState&&!re.documentElement.doScroll)e.setTimeout(pe.ready);else if(re.addEventListener)re.addEventListener("DOMContentLoaded",s),e.addEventListener("load",s);else{re.attachEvent("onreadystatechange",s),e.attachEvent("onload",s);var n=!1;try{n=null==e.frameElement&&re.documentElement}catch(r){}n&&n.doScroll&&!function i(){if(!pe.isReady){try{n.doScroll("left")}catch(t){return e.setTimeout(i,50)}a(),pe.ready()}}()}return je.promise(t)},pe.ready.promise();var Le;for(Le in pe(fe))break;fe.ownFirst="0"===Le,fe.inlineBlockNeedsLayout=!1,pe(function(){var e,t,n,r;n=re.getElementsByTagName("body")[0],n&&n.style&&(t=re.createElement("div"),r=re.createElement("div"),r.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",n.appendChild(r).appendChild(t),"undefined"!=typeof t.style.zoom&&(t.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",fe.inlineBlockNeedsLayout=e=3===t.offsetWidth,e&&(n.style.zoom=1)),n.removeChild(r))}),function(){var e=re.createElement("div");fe.deleteExpando=!0;try{delete e.test}catch(t){fe.deleteExpando=!1}e=null}();var He=function(e){var t=pe.noData[(e.nodeName+" ").toLowerCase()],n=+e.nodeType||1;return(1===n||9===n)&&(!t||t!==!0&&e.getAttribute("classid")===t)},qe=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,_e=/([A-Z])/g;pe.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?pe.cache[e[pe.expando]]:e[pe.expando],!!e&&!l(e)},data:function(e,t,n){return c(e,t,n)},removeData:function(e,t){return f(e,t)},_data:function(e,t,n){return c(e,t,n,!0)},_removeData:function(e,t){return f(e,t,!0)}}),pe.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=pe.data(o),1===o.nodeType&&!pe._data(o,"parsedAttrs"))){for(n=a.length;n--;)a[n]&&(r=a[n].name,0===r.indexOf("data-")&&(r=pe.camelCase(r.slice(5)),u(o,r,i[r])));pe._data(o,"parsedAttrs",!0)}return i}return"object"==typeof e?this.each(function(){pe.data(this,e)}):arguments.length>1?this.each(function(){pe.data(this,e,t)}):o?u(o,e,pe.data(o,e)):void 0},removeData:function(e){return this.each(function(){pe.removeData(this,e)})}}),pe.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=pe._data(e,t),n&&(!r||pe.isArray(n)?r=pe._data(e,t,pe.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=pe.queue(e,t),r=n.length,i=n.shift(),o=pe._queueHooks(e,t),a=function(){pe.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return pe._data(e,n)||pe._data(e,n,{empty:pe.Callbacks("once memory").add(function(){pe._removeData(e,t+"queue"),pe._removeData(e,n)})})}}),pe.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length
                a",fe.leadingWhitespace=3===e.firstChild.nodeType,fe.tbody=!e.getElementsByTagName("tbody").length,fe.htmlSerialize=!!e.getElementsByTagName("link").length,fe.html5Clone="<:nav>"!==re.createElement("nav").cloneNode(!0).outerHTML,n.type="checkbox",n.checked=!0,t.appendChild(n),fe.appendChecked=n.checked,e.innerHTML="",fe.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,t.appendChild(e),n=re.createElement("input"),n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),e.appendChild(n),fe.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,fe.noCloneEvent=!!e.addEventListener,e[pe.expando]=1,fe.attributes=!e.getAttribute(pe.expando)}();var Xe={option:[1,""],legend:[1,"
                ","
                "],area:[1,"",""],param:[1,"",""],thead:[1,"","
                "],tr:[2,"","
                "],col:[2,"","
                "],td:[3,"","
                "],_default:fe.htmlSerialize?[0,"",""]:[1,"X
                ","
                "]};Xe.optgroup=Xe.option,Xe.tbody=Xe.tfoot=Xe.colgroup=Xe.caption=Xe.thead,Xe.th=Xe.td;var Ue=/<|&#?\w+;/,Ve=/-1&&(h=p.split("."),p=h.shift(),h.sort()),a=p.indexOf(":")<0&&"on"+p,t=t[pe.expando]?t:new pe.Event(p,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=h.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:pe.makeArray(n,[t]),l=pe.event.special[p]||{},i||!l.trigger||l.trigger.apply(r,n)!==!1)){if(!i&&!l.noBubble&&!pe.isWindow(r)){for(u=l.delegateType||p,Ke.test(u+p)||(s=s.parentNode);s;s=s.parentNode)d.push(s),c=s;c===(r.ownerDocument||re)&&d.push(c.defaultView||c.parentWindow||e)}for(f=0;(s=d[f++])&&!t.isPropagationStopped();)t.type=f>1?u:l.bindType||p,o=(pe._data(s,"events")||{})[t.type]&&pe._data(s,"handle"),o&&o.apply(s,n),o=a&&s[a],o&&o.apply&&He(s)&&(t.result=o.apply(s,n),t.result===!1&&t.preventDefault());if(t.type=p,!i&&!t.isDefaultPrevented()&&(!l._default||l._default.apply(d.pop(),n)===!1)&&He(r)&&a&&r[p]&&!pe.isWindow(r)){c=r[a],c&&(r[a]=null),pe.event.triggered=p;try{r[p]()}catch(g){}pe.event.triggered=void 0,c&&(r[a]=c)}return t.result}},dispatch:function(e){e=pe.event.fix(e);var t,n,r,i,o,a=[],s=ie.call(arguments),u=(pe._data(this,"events")||{})[e.type]||[],l=pe.event.special[e.type]||{};if(s[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){for(a=pe.event.handlers.call(this,e,u),t=0;(i=a[t++])&&!e.isPropagationStopped();)for(e.currentTarget=i.elem,n=0;(o=i.handlers[n++])&&!e.isImmediatePropagationStopped();)e.rnamespace&&!e.rnamespace.test(o.namespace)||(e.handleObj=o,e.data=o.data,r=((pe.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s),void 0!==r&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()));return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,a=[],s=t.delegateCount,u=e.target;if(s&&u.nodeType&&("click"!==e.type||isNaN(e.button)||e.button<1))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(r=[],n=0;n-1:pe.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&a.push({elem:u,handlers:r})}return s]","i"),tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,nt=/\s*$/g,at=p(re),st=at.appendChild(re.createElement("div"));pe.extend({htmlPrefilter:function(e){return e.replace(tt,"<$1>")},clone:function(e,t,n){var r,i,o,a,s,u=pe.contains(e.ownerDocument,e);if(fe.html5Clone||pe.isXMLDoc(e)||!et.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(st.innerHTML=e.outerHTML,st.removeChild(o=st.firstChild)),!(fe.noCloneEvent&&fe.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||pe.isXMLDoc(e)))for(r=h(o),s=h(e),a=0;null!=(i=s[a]);++a)r[a]&&k(i,r[a]);if(t)if(n)for(s=s||h(e),r=r||h(o),a=0;null!=(i=s[a]);a++)N(i,r[a]);else N(e,o);return r=h(o,"script"),r.length>0&&g(r,!u&&h(e,"script")),r=s=i=null,o},cleanData:function(e,t){for(var n,r,i,o,a=0,s=pe.expando,u=pe.cache,l=fe.attributes,c=pe.event.special;null!=(n=e[a]);a++)if((t||He(n))&&(i=n[s],o=i&&u[i])){if(o.events)for(r in o.events)c[r]?pe.event.remove(n,r):pe.removeEvent(n,r,o.handle);u[i]&&(delete u[i],l||"undefined"==typeof n.removeAttribute?n[s]=void 0:n.removeAttribute(s),ne.push(i))}}}),pe.fn.extend({domManip:S,detach:function(e){return A(this,e,!0)},remove:function(e){return A(this,e)},text:function(e){return Pe(this,function(e){return void 0===e?pe.text(this):this.empty().append((this[0]&&this[0].ownerDocument||re).createTextNode(e))},null,e,arguments.length)},append:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.appendChild(e)}})},prepend:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++){for(1===e.nodeType&&pe.cleanData(h(e,!1));e.firstChild;)e.removeChild(e.firstChild);e.options&&pe.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return pe.clone(this,e,t)})},html:function(e){return Pe(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e)return 1===t.nodeType?t.innerHTML.replace(Ze,""):void 0;if("string"==typeof e&&!nt.test(e)&&(fe.htmlSerialize||!et.test(e))&&(fe.leadingWhitespace||!$e.test(e))&&!Xe[(We.exec(e)||["",""])[1].toLowerCase()]){e=pe.htmlPrefilter(e);try{for(;nt",t=l.getElementsByTagName("td"),t[0].style.cssText="margin:0;border:0;padding:0;display:none",o=0===t[0].offsetHeight,o&&(t[0].style.display="",t[1].style.display="none",o=0===t[0].offsetHeight)),f.removeChild(u)}var n,r,i,o,a,s,u=re.createElement("div"),l=re.createElement("div");l.style&&(l.style.cssText="float:left;opacity:.5",fe.opacity="0.5"===l.style.opacity,fe.cssFloat=!!l.style.cssFloat,l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",fe.clearCloneStyle="content-box"===l.style.backgroundClip,u=re.createElement("div"),u.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",l.innerHTML="",u.appendChild(l),fe.boxSizing=""===l.style.boxSizing||""===l.style.MozBoxSizing||""===l.style.WebkitBoxSizing,pe.extend(fe,{reliableHiddenOffsets:function(){return null==n&&t(),o},boxSizingReliable:function(){return null==n&&t(),i},pixelMarginRight:function(){return null==n&&t(),r},pixelPosition:function(){return null==n&&t(),n},reliableMarginRight:function(){return null==n&&t(),a},reliableMarginLeft:function(){return null==n&&t(),s}}))}();var ht,gt,mt=/^(top|right|bottom|left)$/;e.getComputedStyle?(ht=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n.getPropertyValue(t)||n[t]:void 0,""!==a&&void 0!==a||pe.contains(e.ownerDocument,e)||(a=pe.style(e,t)),n&&!fe.pixelMarginRight()&&ft.test(a)&&ct.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o),void 0===a?a:a+""}):pt.currentStyle&&(ht=function(e){return e.currentStyle},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n[t]:void 0,null==a&&s&&s[t]&&(a=s[t]),ft.test(a)&&!mt.test(t)&&(r=s.left,i=e.runtimeStyle,o=i&&i.left,o&&(i.left=e.currentStyle.left),s.left="fontSize"===t?"1em":a,a=s.pixelLeft+"px",s.left=r,o&&(i.left=o)),void 0===a?a:a+""||"auto"});var yt=/alpha\([^)]*\)/i,vt=/opacity\s*=\s*([^)]*)/i,xt=/^(none|table(?!-c[ea]).+)/,bt=new RegExp("^("+Fe+")(.*)$","i"),wt={position:"absolute",visibility:"hidden",display:"block"},Tt={letterSpacing:"0",fontWeight:"400"},Ct=["Webkit","O","Moz","ms"],Et=re.createElement("div").style;pe.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=gt(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":fe.cssFloat?"cssFloat":"styleFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=pe.camelCase(t),u=e.style;if(t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:u[t];if(o=typeof n,"string"===o&&(i=Me.exec(n))&&i[1]&&(n=d(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(pe.cssNumber[s]?"":"px")),fe.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),!(a&&"set"in a&&void 0===(n=a.set(e,n,r)))))try{u[t]=n}catch(l){}}},css:function(e,t,n,r){var i,o,a,s=pe.camelCase(t);return t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],a&&"get"in a&&(o=a.get(e,!0,n)),void 0===o&&(o=gt(e,t,r)),"normal"===o&&t in Tt&&(o=Tt[t]),""===n||n?(i=parseFloat(o),n===!0||isFinite(i)?i||0:o):o}}),pe.each(["height","width"],function(e,t){pe.cssHooks[t]={get:function(e,n,r){if(n)return xt.test(pe.css(e,"display"))&&0===e.offsetWidth?dt(e,wt,function(){return M(e,t,r)}):M(e,t,r)},set:function(e,n,r){var i=r&&ht(e);return _(e,n,r?F(e,t,r,fe.boxSizing&&"border-box"===pe.css(e,"boxSizing",!1,i),i):0)}}}),fe.opacity||(pe.cssHooks.opacity={get:function(e,t){return vt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=pe.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===pe.trim(o.replace(yt,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=yt.test(o)?o.replace(yt,i):o+" "+i)}}),pe.cssHooks.marginRight=L(fe.reliableMarginRight,function(e,t){if(t)return dt(e,{display:"inline-block"},gt,[e,"marginRight"])}),pe.cssHooks.marginLeft=L(fe.reliableMarginLeft,function(e,t){if(t)return(parseFloat(gt(e,"marginLeft"))||(pe.contains(e.ownerDocument,e)?e.getBoundingClientRect().left-dt(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}):0))+"px"}),pe.each({margin:"",padding:"",border:"Width"},function(e,t){pe.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+Oe[r]+t]=o[r]||o[r-2]||o[0];return i}},ct.test(e)||(pe.cssHooks[e+t].set=_)}),pe.fn.extend({css:function(e,t){return Pe(this,function(e,t,n){var r,i,o={},a=0;if(pe.isArray(t)){for(r=ht(e),i=t.length;a1)},show:function(){return q(this,!0)},hide:function(){return q(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Re(this)?pe(this).show():pe(this).hide()})}}),pe.Tween=O,O.prototype={constructor:O,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||pe.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(pe.cssNumber[n]?"":"px")},cur:function(){var e=O.propHooks[this.prop];return e&&e.get?e.get(this):O.propHooks._default.get(this)},run:function(e){var t,n=O.propHooks[this.prop];return this.options.duration?this.pos=t=pe.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):O.propHooks._default.set(this),this}},O.prototype.init.prototype=O.prototype,O.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=pe.css(e.elem,e.prop,""),t&&"auto"!==t?t:0)},set:function(e){pe.fx.step[e.prop]?pe.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[pe.cssProps[e.prop]]&&!pe.cssHooks[e.prop]?e.elem[e.prop]=e.now:pe.style(e.elem,e.prop,e.now+e.unit)}}},O.propHooks.scrollTop=O.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},pe.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},pe.fx=O.prototype.init,pe.fx.step={};var Nt,kt,St=/^(?:toggle|show|hide)$/,At=/queueHooks$/;pe.Animation=pe.extend($,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return d(n.elem,e,Me.exec(t),n),n}]},tweener:function(e,t){pe.isFunction(e)?(t=e,e=["*"]):e=e.match(De);for(var n,r=0,i=e.length;r
                a",e=n.getElementsByTagName("a")[0],t.setAttribute("type","checkbox"),n.appendChild(t),e=n.getElementsByTagName("a")[0],e.style.cssText="top:1px",fe.getSetAttribute="t"!==n.className,fe.style=/top/.test(e.getAttribute("style")),fe.hrefNormalized="/a"===e.getAttribute("href"),fe.checkOn=!!t.value,fe.optSelected=i.selected,fe.enctype=!!re.createElement("form").enctype,r.disabled=!0,fe.optDisabled=!i.disabled,t=re.createElement("input"),t.setAttribute("value",""),fe.input=""===t.getAttribute("value"),t.value="t",t.setAttribute("type","radio"),fe.radioValue="t"===t.value}();var Dt=/\r/g,jt=/[\x20\t\r\n\f]+/g;pe.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=pe.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,pe(this).val()):e,null==i?i="":"number"==typeof i?i+="":pe.isArray(i)&&(i=pe.map(i,function(e){return null==e?"":e+""})),t=pe.valHooks[this.type]||pe.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return t=pe.valHooks[i.type]||pe.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:(n=i.value,"string"==typeof n?n.replace(Dt,""):null==n?"":n)}}}),pe.extend({valHooks:{option:{get:function(e){var t=pe.find.attr(e,"value");return null!=t?t:pe.trim(pe.text(e)).replace(jt," ")}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||i<0,a=o?null:[],s=o?i+1:r.length,u=i<0?s:o?i:0;u-1)try{r.selected=n=!0}catch(s){r.scrollHeight}else r.selected=!1;return n||(e.selectedIndex=-1),i}}}}),pe.each(["radio","checkbox"],function(){pe.valHooks[this]={set:function(e,t){if(pe.isArray(t))return e.checked=pe.inArray(pe(e).val(),t)>-1}},fe.checkOn||(pe.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Lt,Ht,qt=pe.expr.attrHandle,_t=/^(?:checked|selected)$/i,Ft=fe.getSetAttribute,Mt=fe.input;pe.fn.extend({attr:function(e,t){return Pe(this,pe.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){pe.removeAttr(this,e)})}}),pe.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?pe.prop(e,t,n):(1===o&&pe.isXMLDoc(e)||(t=t.toLowerCase(),i=pe.attrHooks[t]||(pe.expr.match.bool.test(t)?Ht:Lt)),void 0!==n?null===n?void pe.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:(r=pe.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!fe.radioValue&&"radio"===t&&pe.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(De);if(o&&1===e.nodeType)for(;n=o[i++];)r=pe.propFix[n]||n,pe.expr.match.bool.test(n)?Mt&&Ft||!_t.test(n)?e[r]=!1:e[pe.camelCase("default-"+n)]=e[r]=!1:pe.attr(e,n,""),e.removeAttribute(Ft?n:r)}}),Ht={set:function(e,t,n){return t===!1?pe.removeAttr(e,n):Mt&&Ft||!_t.test(n)?e.setAttribute(!Ft&&pe.propFix[n]||n,n):e[pe.camelCase("default-"+n)]=e[n]=!0,n}},pe.each(pe.expr.match.bool.source.match(/\w+/g),function(e,t){var n=qt[t]||pe.find.attr;Mt&&Ft||!_t.test(t)?qt[t]=function(e,t,r){var i,o;return r||(o=qt[t],qt[t]=i,i=null!=n(e,t,r)?t.toLowerCase():null,qt[t]=o),i}:qt[t]=function(e,t,n){if(!n)return e[pe.camelCase("default-"+t)]?t.toLowerCase():null}}),Mt&&Ft||(pe.attrHooks.value={set:function(e,t,n){return pe.nodeName(e,"input")?void(e.defaultValue=t):Lt&&Lt.set(e,t,n)}}),Ft||(Lt={set:function(e,t,n){var r=e.getAttributeNode(n);if(r||e.setAttributeNode(r=e.ownerDocument.createAttribute(n)),r.value=t+="","value"===n||t===e.getAttribute(n))return t}},qt.id=qt.name=qt.coords=function(e,t,n){var r;if(!n)return(r=e.getAttributeNode(t))&&""!==r.value?r.value:null},pe.valHooks.button={get:function(e,t){var n=e.getAttributeNode(t);if(n&&n.specified)return n.value},set:Lt.set},pe.attrHooks.contenteditable={set:function(e,t,n){Lt.set(e,""!==t&&t,n)}},pe.each(["width","height"],function(e,t){pe.attrHooks[t]={set:function(e,n){if(""===n)return e.setAttribute(t,"auto"),n}}})),fe.style||(pe.attrHooks.style={get:function(e){return e.style.cssText||void 0},set:function(e,t){return e.style.cssText=t+""}});var Ot=/^(?:input|select|textarea|button|object)$/i,Rt=/^(?:a|area)$/i;pe.fn.extend({prop:function(e,t){return Pe(this,pe.prop,e,t,arguments.length>1)},removeProp:function(e){return e=pe.propFix[e]||e,this.each(function(){try{this[e]=void 0,delete this[e]}catch(t){}})}}),pe.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&pe.isXMLDoc(e)||(t=pe.propFix[t]||t,i=pe.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=pe.find.attr(e,"tabindex");return t?parseInt(t,10):Ot.test(e.nodeName)||Rt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),fe.hrefNormalized||pe.each(["href","src"],function(e,t){pe.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),fe.optSelected||(pe.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),pe.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){pe.propFix[this.toLowerCase()]=this}),fe.enctype||(pe.propFix.enctype="encoding");var Pt=/[\t\r\n\f]/g;pe.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).addClass(e.call(this,t,z(this)))});if("string"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(" "+i+" ").replace(Pt," ")){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");s=pe.trim(r),i!==s&&pe.attr(n,"class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).removeClass(e.call(this,t,z(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(" "+i+" ").replace(Pt," ")){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");s=pe.trim(r),i!==s&&pe.attr(n,"class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):pe.isFunction(e)?this.each(function(n){pe(this).toggleClass(e.call(this,n,z(this),t),t)}):this.each(function(){var t,r,i,o;if("string"===n)for(r=0,i=pe(this),o=e.match(De)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||(t=z(this),t&&pe._data(this,"__className__",t),pe.attr(this,"class",t||e===!1?"":pe._data(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+z(n)+" ").replace(Pt," ").indexOf(t)>-1)return!0;return!1}}),pe.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){pe.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),pe.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}});var Bt=e.location,Wt=pe.now(),It=/\?/,$t=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;pe.parseJSON=function(t){if(e.JSON&&e.JSON.parse)return e.JSON.parse(t+"");var n,r=null,i=pe.trim(t+"");return i&&!pe.trim(i.replace($t,function(e,t,i,o){return n&&t&&(r=0),0===r?e:(n=i||t,r+=!o-!i,"")}))?Function("return "+i)():pe.error("Invalid JSON: "+t)},pe.parseXML=function(t){var n,r;if(!t||"string"!=typeof t)return null;try{e.DOMParser?(r=new e.DOMParser,n=r.parseFromString(t,"text/xml")):(n=new e.ActiveXObject("Microsoft.XMLDOM"),n.async="false",n.loadXML(t))}catch(i){n=void 0}return n&&n.documentElement&&!n.getElementsByTagName("parsererror").length||pe.error("Invalid XML: "+t),n};var zt=/#.*$/,Xt=/([?&])_=[^&]*/,Ut=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Vt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Yt=/^(?:GET|HEAD)$/,Jt=/^\/\//,Gt=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Kt={},Qt={},Zt="*/".concat("*"),en=Bt.href,tn=Gt.exec(en.toLowerCase())||[];pe.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:en,type:"GET",isLocal:Vt.test(tn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Zt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":pe.parseJSON,"text xml":pe.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?V(V(e,pe.ajaxSettings),t):V(pe.ajaxSettings,e)},ajaxPrefilter:X(Kt),ajaxTransport:X(Qt),ajax:function(t,n){function r(t,n,r,i){var o,f,v,x,w,C=n;2!==b&&(b=2,u&&e.clearTimeout(u),c=void 0,s=i||"",T.readyState=t>0?4:0,o=t>=200&&t<300||304===t,r&&(x=Y(d,T,r)),x=J(d,x,T,o),o?(d.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(pe.lastModified[a]=w),w=T.getResponseHeader("etag"),w&&(pe.etag[a]=w)),204===t||"HEAD"===d.type?C="nocontent":304===t?C="notmodified":(C=x.state,f=x.data,v=x.error,o=!v)):(v=C,!t&&C||(C="error",t<0&&(t=0))),T.status=t,T.statusText=(n||C)+"",o?g.resolveWith(p,[f,C,T]):g.rejectWith(p,[T,C,v]),T.statusCode(y),y=void 0,l&&h.trigger(o?"ajaxSuccess":"ajaxError",[T,d,o?f:v]),m.fireWith(p,[T,C]),l&&(h.trigger("ajaxComplete",[T,d]),--pe.active||pe.event.trigger("ajaxStop")))}"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,d=pe.ajaxSetup({},n),p=d.context||d,h=d.context&&(p.nodeType||p.jquery)?pe(p):pe.event,g=pe.Deferred(),m=pe.Callbacks("once memory"),y=d.statusCode||{},v={},x={},b=0,w="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!f)for(f={};t=Ut.exec(s);)f[t[1].toLowerCase()]=t[2];t=f[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?s:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=x[n]=x[n]||e,v[e]=t),this},overrideMimeType:function(e){return b||(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(b<2)for(t in e)y[t]=[y[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||w;return c&&c.abort(t),r(0,t),this}};if(g.promise(T).complete=m.add,T.success=T.done,T.error=T.fail,d.url=((t||d.url||en)+"").replace(zt,"").replace(Jt,tn[1]+"//"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=pe.trim(d.dataType||"*").toLowerCase().match(De)||[""],null==d.crossDomain&&(i=Gt.exec(d.url.toLowerCase()),d.crossDomain=!(!i||i[1]===tn[1]&&i[2]===tn[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(tn[3]||("http:"===tn[1]?"80":"443")))),d.data&&d.processData&&"string"!=typeof d.data&&(d.data=pe.param(d.data,d.traditional)),U(Kt,d,n,T),2===b)return T;l=pe.event&&d.global,l&&0===pe.active++&&pe.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!Yt.test(d.type),a=d.url,d.hasContent||(d.data&&(a=d.url+=(It.test(a)?"&":"?")+d.data,delete d.data),d.cache===!1&&(d.url=Xt.test(a)?a.replace(Xt,"$1_="+Wt++):a+(It.test(a)?"&":"?")+"_="+Wt++)),d.ifModified&&(pe.lastModified[a]&&T.setRequestHeader("If-Modified-Since",pe.lastModified[a]),pe.etag[a]&&T.setRequestHeader("If-None-Match",pe.etag[a])),(d.data&&d.hasContent&&d.contentType!==!1||n.contentType)&&T.setRequestHeader("Content-Type",d.contentType),T.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+Zt+"; q=0.01":""):d.accepts["*"]);for(o in d.headers)T.setRequestHeader(o,d.headers[o]);if(d.beforeSend&&(d.beforeSend.call(p,T,d)===!1||2===b))return T.abort();w="abort";for(o in{success:1,error:1,complete:1})T[o](d[o]);if(c=U(Qt,d,n,T)){if(T.readyState=1,l&&h.trigger("ajaxSend",[T,d]),2===b)return T;d.async&&d.timeout>0&&(u=e.setTimeout(function(){T.abort("timeout")},d.timeout));try{b=1,c.send(v,r)}catch(C){if(!(b<2))throw C;r(-1,C)}}else r(-1,"No Transport");return T},getJSON:function(e,t,n){return pe.get(e,t,n,"json")},getScript:function(e,t){return pe.get(e,void 0,t,"script")}}),pe.each(["get","post"],function(e,t){pe[t]=function(e,n,r,i){return pe.isFunction(n)&&(i=i||r,r=n,n=void 0),pe.ajax(pe.extend({url:e,type:t,dataType:i,data:n,success:r},pe.isPlainObject(e)&&e))}}),pe._evalUrl=function(e){return pe.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},pe.fn.extend({wrapAll:function(e){if(pe.isFunction(e))return this.each(function(t){pe(this).wrapAll(e.call(this,t))});if(this[0]){var t=pe(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstChild&&1===e.firstChild.nodeType;)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return pe.isFunction(e)?this.each(function(t){pe(this).wrapInner(e.call(this,t))}):this.each(function(){var t=pe(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=pe.isFunction(e);return this.each(function(n){pe(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){pe.nodeName(this,"body")||pe(this).replaceWith(this.childNodes)}).end()}}),pe.expr.filters.hidden=function(e){return fe.reliableHiddenOffsets()?e.offsetWidth<=0&&e.offsetHeight<=0&&!e.getClientRects().length:K(e)},pe.expr.filters.visible=function(e){return!pe.expr.filters.hidden(e)};var nn=/%20/g,rn=/\[\]$/,on=/\r?\n/g,an=/^(?:submit|button|image|reset|file)$/i,sn=/^(?:input|select|textarea|keygen)/i;pe.param=function(e,t){var n,r=[],i=function(e,t){t=pe.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(void 0===t&&(t=pe.ajaxSettings&&pe.ajaxSettings.traditional),pe.isArray(e)||e.jquery&&!pe.isPlainObject(e))pe.each(e,function(){i(this.name,this.value)});else for(n in e)Q(n,e[n],t,i);return r.join("&").replace(nn,"+")},pe.fn.extend({serialize:function(){return pe.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=pe.prop(this,"elements");return e?pe.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!pe(this).is(":disabled")&&sn.test(this.nodeName)&&!an.test(e)&&(this.checked||!Be.test(e))}).map(function(e,t){var n=pe(this).val();return null==n?null:pe.isArray(n)?pe.map(n,function(e){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),pe.ajaxSettings.xhr=void 0!==e.ActiveXObject?function(){return this.isLocal?ee():re.documentMode>8?Z():/^(get|post|head|put|delete|options)$/i.test(this.type)&&Z()||ee()}:Z;var un=0,ln={},cn=pe.ajaxSettings.xhr();e.attachEvent&&e.attachEvent("onunload",function(){for(var e in ln)ln[e](void 0,!0)}),fe.cors=!!cn&&"withCredentials"in cn,cn=fe.ajax=!!cn,cn&&pe.ajaxTransport(function(t){if(!t.crossDomain||fe.cors){var n;return{send:function(r,i){var o,a=t.xhr(),s=++un;if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)a[o]=t.xhrFields[o];t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||r["X-Requested-With"]||(r["X-Requested-With"]="XMLHttpRequest");for(o in r)void 0!==r[o]&&a.setRequestHeader(o,r[o]+"");a.send(t.hasContent&&t.data||null),n=function(e,r){var o,u,l;if(n&&(r||4===a.readyState))if(delete ln[s],n=void 0,a.onreadystatechange=pe.noop,r)4!==a.readyState&&a.abort();else{l={},o=a.status,"string"==typeof a.responseText&&(l.text=a.responseText);try{u=a.statusText}catch(c){u=""}o||!t.isLocal||t.crossDomain?1223===o&&(o=204):o=l.text?200:404}l&&i(o,u,l,a.getAllResponseHeaders())},t.async?4===a.readyState?e.setTimeout(n):a.onreadystatechange=ln[s]=n:n()},abort:function(){n&&n(void 0,!0)}}}}),pe.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return pe.globalEval(e),e}}}),pe.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),pe.ajaxTransport("script",function(e){if(e.crossDomain){var t,n=re.head||pe("head")[0]||re.documentElement;return{send:function(r,i){t=re.createElement("script"),t.async=!0,e.scriptCharset&&(t.charset=e.scriptCharset),t.src=e.url,t.onload=t.onreadystatechange=function(e,n){(n||!t.readyState||/loaded|complete/.test(t.readyState))&&(t.onload=t.onreadystatechange=null,t.parentNode&&t.parentNode.removeChild(t),t=null,n||i(200,"success"))},n.insertBefore(t,n.firstChild)},abort:function(){t&&t.onload(void 0,!0)}}}});var fn=[],dn=/(=)\?(?=&|$)|\?\?/;pe.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=fn.pop()||pe.expando+"_"+Wt++;return this[e]=!0,e}}),pe.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,a,s=t.jsonp!==!1&&(dn.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&dn.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return i=t.jsonpCallback=pe.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(dn,"$1"+i):t.jsonp!==!1&&(t.url+=(It.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return a||pe.error(i+" was not called"),a[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?pe(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,fn.push(i)),a&&pe.isFunction(o)&&o(a[0]),a=o=void 0}),"script"}),pe.parseHTML=function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||re;var r=Te.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=y([e],t,i),i&&i.length&&pe(i).remove(),pe.merge([],r.childNodes))};var pn=pe.fn.load;return pe.fn.load=function(e,t,n){if("string"!=typeof e&&pn)return pn.apply(this,arguments);var r,i,o,a=this,s=e.indexOf(" ");return s>-1&&(r=pe.trim(e.slice(s,e.length)),e=e.slice(0,s)),pe.isFunction(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),a.length>0&&pe.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?pe("
                ").append(pe.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},pe.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){pe.fn[t]=function(e){return this.on(t,e)}}),pe.expr.filters.animated=function(e){return pe.grep(pe.timers,function(t){return e===t.elem}).length},pe.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=pe.css(e,"position"),f=pe(e),d={};"static"===c&&(e.style.position="relative"),s=f.offset(),o=pe.css(e,"top"),u=pe.css(e,"left"),l=("absolute"===c||"fixed"===c)&&pe.inArray("auto",[o,u])>-1,l?(r=f.position(),a=r.top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),pe.isFunction(t)&&(t=t.call(e,n,pe.extend({},s))),null!=t.top&&(d.top=t.top-s.top+a),null!=t.left&&(d.left=t.left-s.left+i),"using"in t?t.using.call(e,d):f.css(d)}},pe.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){pe.offset.setOffset(this,e,t)});var t,n,r={top:0,left:0},i=this[0],o=i&&i.ownerDocument;if(o)return t=o.documentElement,pe.contains(t,i)?("undefined"!=typeof i.getBoundingClientRect&&(r=i.getBoundingClientRect()),n=te(o),{top:r.top+(n.pageYOffset||t.scrollTop)-(t.clientTop||0),left:r.left+(n.pageXOffset||t.scrollLeft)-(t.clientLeft||0)}):r},position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===pe.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),pe.nodeName(e[0],"html")||(n=e.offset()),n.top+=pe.css(e[0],"borderTopWidth",!0),n.left+=pe.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-pe.css(r,"marginTop",!0),left:t.left-n.left-pe.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){ -for(var e=this.offsetParent;e&&!pe.nodeName(e,"html")&&"static"===pe.css(e,"position");)e=e.offsetParent;return e||pt})}}),pe.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n=/Y/.test(t);pe.fn[e]=function(r){return Pe(this,function(e,r,i){var o=te(e);return void 0===i?o?t in o?o[t]:o.document.documentElement[r]:e[r]:void(o?o.scrollTo(n?pe(o).scrollLeft():i,n?i:pe(o).scrollTop()):e[r]=i)},e,r,arguments.length,null)}}),pe.each(["top","left"],function(e,t){pe.cssHooks[t]=L(fe.pixelPosition,function(e,n){if(n)return n=gt(e,t),ft.test(n)?pe(e).position()[t]+"px":n})}),pe.each({Height:"height",Width:"width"},function(e,t){pe.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){pe.fn[r]=function(r,i){var o=arguments.length&&(n||"boolean"!=typeof r),a=n||(r===!0||i===!0?"margin":"border");return Pe(this,function(t,n,r){var i;return pe.isWindow(t)?t.document.documentElement["client"+e]:9===t.nodeType?(i=t.documentElement,Math.max(t.body["scroll"+e],i["scroll"+e],t.body["offset"+e],i["offset"+e],i["client"+e])):void 0===r?pe.css(t,n,a):pe.style(t,n,r,a)},t,o?r:void 0,o,null)}})}),pe.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),pe.fn.size=function(){return this.length},pe.fn.andSelf=pe.fn.addBack,layui.define(function(e){layui.$=pe,e("jquery",pe)}),pe});!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.scripts,t=e[e.length-1],i=t.src;if(!t.getAttribute("merge"))return i.substring(0,i.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.0",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'
                '+(f?r.title[0]:r.title)+"
                ":"";return r.zIndex=s,t([r.shade?'
                ':"",'
                '+(e&&2!=r.type?"":u)+'
                '+(0==r.type&&r.icon!==-1?'':"")+(1==r.type&&e?"":r.content||"")+'
                '+function(){var e=c?'':"";return r.closeBtn&&(e+=''),e}()+""+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t'+r.btn[t]+"";return'
                '+e+"
                "}():"")+(r.resize?'':"")+"
                "],u,i('
                ')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"http://layer.layui.com","auto"];t.content='';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(e){s=e.find(".layui-layer-input"),s.focus(),"function"==typeof f&&f(e)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
                  '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
                • '+(t[0].content||"no content")+"
                • ";i'+(t[i].content||"no content")+"";return a}()+"
                ",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
                '+(u.length>1?'':"")+'
                '+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
                ",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
                是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window);layui.define("jquery",function(i){"use strict";var a=layui.$,t=(layui.hint(),layui.device()),l="element",e="layui-this",n="layui-show",s=function(){this.config={}};s.prototype.set=function(i){var t=this;return a.extend(!0,t.config,i),t},s.prototype.on=function(i,a){return layui.onevent.call(this,l,i,a)},s.prototype.tabAdd=function(i,t){var l=".layui-tab-title",e=a(".layui-tab[lay-filter="+i+"]"),n=e.children(l),s=n.children(".layui-tab-bar"),o=e.children(".layui-tab-content"),c='
              • '+(t.title||"unnaming")+"
              • ";return s[0]?s.before(c):n.append(c),o.append('
                '+(t.content||"")+"
                "),y.hideTabMore(!0),y.tabAuto(),this},s.prototype.tabDelete=function(i,t){var l=".layui-tab-title",e=a(".layui-tab[lay-filter="+i+"]"),n=e.children(l),s=n.find('>li[lay-id="'+t+'"]');return y.tabDelete(null,s),this},s.prototype.tabChange=function(i,t){var l=".layui-tab-title",e=a(".layui-tab[lay-filter="+i+"]"),n=e.children(l),s=n.find('>li[lay-id="'+t+'"]');return y.tabClick(null,null,s),this},s.prototype.progress=function(i,t){var l="layui-progress",e=a("."+l+"[lay-filter="+i+"]"),n=e.find("."+l+"-bar"),s=n.find("."+l+"-text");return n.css("width",t),s.text(t),this};var o=".layui-nav",c="layui-nav-item",r="layui-nav-bar",u="layui-nav-tree",d="layui-nav-child",h="layui-nav-more",f="layui-anim layui-anim-upbit",y={tabClick:function(i,t,s){var o=s||a(this),t=t||o.parent().children("li").index(o),c=o.parents(".layui-tab").eq(0),r=c.children(".layui-tab-content").children(".layui-tab-item"),u=o.find("a"),d=c.attr("lay-filter");"javascript:;"!==u.attr("href")&&"_blank"===u.attr("target")||(o.addClass(e).siblings().removeClass(e),r.eq(t).addClass(n).siblings().removeClass(n)),layui.event.call(this,l,"tab("+d+")",{elem:c,index:t})},tabDelete:function(i,t){var l=t||a(this).parent(),n=l.index(),s=l.parents(".layui-tab").eq(0),o=s.children(".layui-tab-content").children(".layui-tab-item");l.hasClass(e)&&(l.next()[0]?y.tabClick.call(l.next()[0],null,n+1):l.prev()[0]&&y.tabClick.call(l.prev()[0],null,n-1)),l.remove(),o.eq(n).remove(),setTimeout(function(){y.tabAuto()},50)},tabAuto:function(){var i="layui-tab-more",l="layui-tab-bar",e="layui-tab-close",n=this;a(".layui-tab").each(function(){var s=a(this),o=s.children(".layui-tab-title"),c=(s.children(".layui-tab-content").children(".layui-tab-item"),'lay-stope="tabmore"'),r=a('');if(n===window&&8!=t.ie&&y.hideTabMore(!0),s.attr("lay-allowClose")&&o.find("li").each(function(){var i=a(this);if(!i.find("."+e)[0]){var t=a('');t.on("click",y.tabDelete),i.append(t)}}),o.prop("scrollWidth")>o.outerWidth()+1){if(o.find("."+l)[0])return;o.append(r),s.attr("overflow",""),r.on("click",function(a){o[this.title?"removeClass":"addClass"](i),this.title=this.title?"":"收缩"})}else o.find("."+l).remove(),s.removeAttr("overflow")})},hideTabMore:function(i){var t=a(".layui-tab-title");i!==!0&&"tabmore"===a(i.target).attr("lay-stope")||(t.removeClass("layui-tab-more"),t.find(".layui-tab-bar").attr("title",""))},clickThis:function(){var i=a(this),t=i.parents(o),n=t.attr("lay-filter"),s=i.find("a");i.find("."+d)[0]||("javascript:;"!==s.attr("href")&&"_blank"===s.attr("target")||(t.find("."+e).removeClass(e),i.addClass(e)),layui.event.call(this,l,"nav("+n+")",i))},clickChild:function(){var i=a(this),t=i.parents(o),n=t.attr("lay-filter");t.find("."+e).removeClass(e),i.addClass(e),layui.event.call(this,l,"nav("+n+")",i)},showChild:function(){var i=a(this),t=i.parents(o),l=i.parent(),e=i.siblings("."+d);t.hasClass(u)&&(e.removeClass(f),l["none"===e.css("display")?"addClass":"removeClass"](c+"ed"))},collapse:function(){var i=a(this),t=i.find(".layui-colla-icon"),e=i.siblings(".layui-colla-content"),s=i.parents(".layui-collapse").eq(0),o=s.attr("lay-filter"),c="none"===e.css("display");if("string"==typeof s.attr("lay-accordion")){var r=s.children(".layui-colla-item").children("."+n);r.siblings(".layui-colla-title").children(".layui-colla-icon").html(""),r.removeClass(n)}e[c?"addClass":"removeClass"](n),t.html(c?"":""),layui.event.call(this,l,"collapse("+o+")",{title:i,content:e,show:c})}};s.prototype.init=function(i){var l={tab:function(){y.tabAuto.call({})},nav:function(){var i=200,l={},e={},s={},p=function(o,c,r){var y=a(this),p=y.find("."+d);c.hasClass(u)?o.css({top:y.position().top,height:y.children("a").height(),opacity:1}):(p.addClass(f),o.css({left:y.position().left+parseFloat(y.css("marginLeft")),top:y.position().top+y.height()-5}),l[r]=setTimeout(function(){o.css({width:y.width(),opacity:1})},t.ie&&t.ie<10?0:i),clearTimeout(s[r]),"block"===p.css("display")&&clearTimeout(e[r]),e[r]=setTimeout(function(){p.addClass(n),y.find("."+h).addClass(h+"d")},300))};a(o).each(function(t){var o=a(this),f=a(''),v=o.find("."+c);o.find("."+r)[0]||(o.append(f),v.on("mouseenter",function(){p.call(this,f,o,t)}).on("mouseleave",function(){o.hasClass(u)||(clearTimeout(e[t]),e[t]=setTimeout(function(){o.find("."+d).removeClass(n),o.find("."+h).removeClass(h+"d")},300))}),o.on("mouseleave",function(){clearTimeout(l[t]),s[t]=setTimeout(function(){o.hasClass(u)?f.css({height:0,top:f.position().top+f.height()/2,opacity:0}):f.css({width:0,left:f.position().left+f.width()/2,opacity:0})},i)})),v.each(function(){var i=a(this),t=i.find("."+d);if(t[0]&&!i.find("."+h)[0]){var l=i.children("a");l.append('')}i.off("click",y.clickThis).on("click",y.clickThis),i.children("a").off("click",y.showChild).on("click",y.showChild),t.children("dd").off("click",y.clickChild).on("click",y.clickChild)})})},breadcrumb:function(){var i=".layui-breadcrumb";a(i).each(function(){var i=a(this),t=i.attr("lay-separator")||">",l=i.find("a");l.find(".layui-box")[0]||(l.each(function(i){i!==l.length-1&&a(this).append(''+t+"")}),i.css("visibility","visible"))})},progress:function(){var i="layui-progress";a("."+i).each(function(){var t=a(this),l=t.find(".layui-progress-bar"),e=l.attr("lay-percent");l.css("width",e),t.attr("lay-showPercent")&&setTimeout(function(){var a=Math.round(l.width()/t.width()*100);a>100&&(a=100),l.html(''+a+"%")},350)})},collapse:function(){var i="layui-collapse";a("."+i).each(function(){var i=a(this).find(".layui-colla-item");i.each(function(){var i=a(this),t=i.find(".layui-colla-title"),l=i.find(".layui-colla-content"),e="none"===l.css("display");t.find(".layui-colla-icon").remove(),t.append(''+(e?"":"")+""),t.off("click",y.collapse).on("click",y.collapse)})})}};return layui.each(l,function(i,a){a()})};var p=new s,v=a(document);p.init();var b=".layui-tab-title li";v.on("click",b,y.tabClick),v.on("click",y.hideTabMore),a(window).on("resize",y.tabAuto),i(l,p)});layui.define("layer",function(e){"use strict";var i=layui.$,t=layui.layer,n=layui.hint(),a=layui.device(),o={config:{},set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,r,e,i)}},l=function(){var e=this;return{upload:function(i){e.upload.call(e,i)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",s="layui-upload-iframe",f="layui-upload-choose",p=function(e){var t=this;t.config=i.extend({},t.config,o.config,e),t.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",method:"post",data:{},drag:!0,size:0,multiple:!1},p.prototype.render=function(e){var t=this,e=t.config;e.elem=i(e.elem),e.bindAction=i(e.bindAction),t.file(),t.events()},p.prototype.file=function(){var e=this,t=e.config,n=e.elemFile=i(['"].join("")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap('
                '),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i(''),a=i(['
                ',"
                "].join(""));i("#"+s)[0]||i("body").append(n),t.elem.next().hasClass(s)||(e.elemFile.wrap(a),t.elem.next("."+s).append(function(){var e=[];return layui.each(t.data,function(i,t){e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){layui.each(e||o.files||o.chooseFiles||r.files,function(e,t){var n=new FormData;n.append(l.field,t),layui.each(l.data,function(e,i){n.append(e,i)}),i.ajax({url:l.url,type:l.method,data:n,contentType:!1,processData:!1,dataType:"json",success:function(i){d(e,i)},error:function(){o.msg("请求上传接口出现异常"),m(e)}})})},c=function(){var e=i("#"+s);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find("body");try{i=t.text()}catch(n){o.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(""),d(0,i))},30)},d=function(e,i){if(o.elemFile.next("."+f).remove(),r.value="","object"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},v=l.exts,h=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files}},y=function(){return"choose"===t?l.choose&&l.choose(g):(l.before&&l.before(g),a.ie?a.ie>9?u():c():void u())};switch(h=0===h.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":h,l.accept){case"file":if(v&&!RegExp("\\w\\.("+v+")$","i").test(escape(h)))return o.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(v||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(h)))return o.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(v||"mp3|wav|mid")+")$","i").test(escape(h)))return o.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(h,function(e,i){RegExp("\\w\\.("+(v||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(i))||(n=!0)}),n)return o.msg("选择的图片中包含不支持的格式"),r.value=""}if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?Math.floor(t)+(t%1>0?t.toFixed(1):0)+"MB":l.size+"KB",r.value="",F=t}}),F)return o.msg("文件不能超过"+F)}y()},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+"-"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+"个文件":(i[0]||{}).name||a[0].value.match(/[^\/\\]+\..+/g)||[]||"";a.next().hasClass(f)&&a.next().remove(),e.upload(null,"choose"),e.isFile()||t.choose||a.after(''+o+"")};t.elem.off("upload.start").on("upload.start",function(){var a=i(this),o=a.attr("lay-data");if(o)try{o=new Function("return "+o)(),e.config=i.extend({},t,o)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off("upload.over").on("upload.over",function(){var e=i(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=i(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),t.elem.data("haveEvents")||(e.elemFile.on("change",function(){i(this).trigger("upload.change")}),t.elem.on("click",function(){e.isFile()||i(this).trigger("upload.start")}),t.drag&&t.elem.on("dragover",function(e){e.preventDefault(),i(this).trigger("upload.over")}).on("dragleave",function(e){i(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),i(this).trigger("upload.drop",e)}),t.bindAction.on("click",function(){i(this).trigger("upload.action")}),t.elem.data("haveEvents",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)});layui.define("layer",function(e){"use strict";var i=layui.$,t=layui.layer,a=layui.hint(),n=layui.device(),l="form",s=".layui-form",r="layui-this",u="layui-hide",c="layui-disabled",o=function(){this.config={verify:{required:[/[\S]+/,"必填项不能为空"],phone:[/^1\d{10}$/,"请输入正确的手机号"],email:[/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,"邮箱格式不正确"],url:[/(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/,"链接格式不正确"],number:function(e){if(!e||isNaN(e))return"只能填写数字"},date:[/^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/,"日期格式不正确"],identity:[/(^\d{15}$)|(^\d{17}(x|X|\d)$)/,"请输入正确的身份证号"]}}};o.prototype.set=function(e){var t=this;return i.extend(!0,t.config,e),t},o.prototype.verify=function(e){var t=this;return i.extend(!0,t.config.verify,e),t},o.prototype.on=function(e,i){return layui.onevent.call(this,l,e,i)},o.prototype.render=function(e,t){var n=this,o=i(s+function(){return t?'[lay-filter="'+t+'"]':""}()),d={select:function(){var e,t="请选择",a="layui-form-select",n="layui-select-title",s="layui-select-none",d="",f=o.find("select"),y=function(t,l){i(t.target).parent().hasClass(n)&&!l||(i("."+a).removeClass(a+"ed "+a+"up"),e&&d&&e.val(d)),e=null},h=function(t,o,f){var h=i(this),p=t.find("."+n),m=p.find("input"),k=t.find("dl"),g=k.children("dd");if(!o){var b=function(){var e=t.offset().top+t.outerHeight()+5-v.scrollTop(),i=k.outerHeight();t.addClass(a+"ed"),g.removeClass(u),e+i>v.height()&&e>=i&&t.addClass(a+"up")},x=function(e){t.removeClass(a+"ed "+a+"up"),m.blur(),e||C(m.val(),function(e){e&&(d=k.find("."+r).html(),m&&m.val(d))})};p.on("click",function(e){t.hasClass(a+"ed")?x():(y(e,!0),b()),k.find("."+s).remove()}),p.find(".layui-edge").on("click",function(){m.focus()}),m.on("keyup",function(e){var i=e.keyCode;9===i&&b()}).on("keydown",function(e){var i=e.keyCode;9===i?x():13===i&&e.preventDefault()});var C=function(e,t,a){var n=0;layui.each(g,function(){var t=i(this),l=t.text(),s=l.indexOf(e)===-1;(""===e||"blur"===a?e!==l:s)&&n++,"keyup"===a&&t[s?"addClass":"removeClass"](u)});var l=n===g.length;return t(l),l},w=function(e){var i=this.value,t=e.keyCode;return 9!==t&&13!==t&&37!==t&&38!==t&&39!==t&&40!==t&&(C(i,function(e){e?k.find("."+s)[0]||k.append('

                无匹配项

                '):k.find("."+s).remove()},"keyup"),void(""===i&&k.find("."+s).remove()))};f&&m.on("keyup",w).on("blur",function(i){e=m,d=k.find("."+r).html(),setTimeout(function(){C(m.val(),function(e){e&&!d&&m.val("")},"blur")},200)}),g.on("click",function(){var e=i(this),a=e.attr("lay-value"),n=h.attr("lay-filter");return!e.hasClass(c)&&(e.hasClass("layui-select-tips")?m.val(""):(m.val(e.text()),e.addClass(r)),e.siblings().removeClass(r),h.val(a).removeClass("layui-form-danger"),layui.event.call(this,l,"select("+n+")",{elem:h[0],value:a,othis:t}),x(!0),!1)}),t.find("dl>dt").on("click",function(e){return!1}),i(document).off("click",y).on("click",y)}};f.each(function(e,l){var s=i(this),u=s.next("."+a),o=this.disabled,d=l.value,f=i(l.options[l.selectedIndex]),y=l.options[0];if("string"==typeof s.attr("lay-ignore"))return s.show();var v="string"==typeof s.attr("lay-search"),p=y?y.value?t:y.innerHTML||t:t,m=i(['
                ','
                ','
                ','
                '+function(e){var i=[];return layui.each(e,function(e,a){0!==e||a.value?"optgroup"===a.tagName.toLowerCase()?i.push("
                "+a.label+"
                "):i.push('
                '+a.innerHTML+"
                "):i.push('
                '+(a.innerHTML||t)+"
                ")}),0===i.length&&i.push('
                没有选项
                '),i.join("")}(s.find("*"))+"
                ","
                "].join(""));u[0]&&u.remove(),s.after(m),h.call(this,m,o,v)})},checkbox:function(){var e={checkbox:["layui-form-checkbox","layui-form-checked","checkbox"],_switch:["layui-form-switch","layui-form-onswitch","switch"]},t=o.find("input[type=checkbox]"),a=function(e,t){var a=i(this);e.on("click",function(){var i=a.attr("lay-filter"),n=(a.attr("lay-text")||"").split("|");a[0].disabled||(a[0].checked?(a[0].checked=!1,e.removeClass(t[1]).find("em").text(n[1])):(a[0].checked=!0,e.addClass(t[1]).find("em").text(n[0])),layui.event.call(a[0],l,t[2]+"("+i+")",{elem:a[0],value:a[0].value,othis:e}))})};t.each(function(t,n){var l=i(this),s=l.attr("lay-skin"),r=(l.attr("lay-text")||"").split("|"),u=this.disabled;"switch"===s&&(s="_"+s);var o=e[s]||e.checkbox;if("string"==typeof l.attr("lay-ignore"))return l.show();var d=l.next("."+o[0]),f=i(['
                ',{_switch:""+((n.checked?r[0]:r[1])||"")+""}[s]||(n.title.replace(/\s/g,"")?""+n.title+"":"")+''+(s?"":"")+"","
                "].join(""));d[0]&&d.remove(),l.after(f),a.call(this,f,o)})},radio:function(){var e="layui-form-radio",t=["",""],a=o.find("input[type=radio]"),n=function(a){var n=i(this),r="layui-anim-scaleSpring";a.on("click",function(){var u=n[0].name,c=n.parents(s),o=n.attr("lay-filter"),d=c.find("input[name="+u.replace(/(\.|#|\[|\])/g,"\\$1")+"]");n[0].disabled||(layui.each(d,function(){var a=i(this).next("."+e);this.checked=!1,a.removeClass(e+"ed"),a.find(".layui-icon").removeClass(r).html(t[1])}),n[0].checked=!0,a.addClass(e+"ed"),a.find(".layui-icon").addClass(r).html(t[0]),layui.event.call(n[0],l,"radio("+o+")",{elem:n[0],value:n[0].value,othis:a}))})};a.each(function(a,l){var s=i(this),r=s.next("."+e),u=this.disabled;if("string"==typeof s.attr("lay-ignore"))return s.show();var o=i(['
                ',''+t[l.checked?0:1]+"",""+(l.title||"未命名")+"","
                "].join(""));r[0]&&r.remove(),s.after(o),n.call(this,o)})}};return e?d[e]?d[e]():a.error("不支持的"+e+"表单渲染"):layui.each(d,function(e,i){i()}),n};var d=function(){var e=i(this),a=f.config.verify,r=null,u="layui-form-danger",c={},o=e.parents(s),d=o.find("*[lay-verify]"),y=e.parents("form")[0],v=o.find("input,select,textarea"),h=e.attr("lay-filter");return layui.each(d,function(e,l){var s=i(this),c=s.attr("lay-verify").split("|"),o="",d=s.val();if(s.removeClass(u),layui.each(c,function(e,i){var c="function"==typeof a[i];if(a[i]&&(c?o=a[i](d,l):!a[i][0].test(d)))return t.msg(o||a[i][1],{icon:5,shift:6}),n.android||n.ios||l.focus(),s.addClass(u),r=!0}),r)return r}),!r&&(layui.each(v,function(e,i){i.name&&(/^checkbox|radio$/.test(i.type)&&!i.checked||(c[i.name]=i.value))}),layui.event.call(this,l,"submit("+h+")",{elem:this,form:y,field:c}))},f=new o,y=i(document),v=i(window);f.render(),y.on("reset",s,function(){var e=i(this).attr("lay-filter");setTimeout(function(){f.render(null,e)},50)}),y.on("submit",s,d).on("click","*[lay-submit]",d),e(l,f)});layui.define("jquery",function(e){"use strict";var o=layui.$,a=layui.hint(),i="layui-tree-enter",r=function(e){this.options=e},t={arrow:["",""],checkbox:["",""],radio:["",""],branch:["",""],leaf:""};r.prototype.init=function(e){var o=this;e.addClass("layui-box layui-tree"),o.options.skin&&e.addClass("layui-tree-skin-"+o.options.skin),o.tree(e),o.on(e)},r.prototype.tree=function(e,a){var i=this,r=i.options,n=a||r.nodes;layui.each(n,function(a,n){var l=n.children&&n.children.length>0,c=o('
                  '),s=o(["
                • ",function(){return l?''+(n.spread?t.arrow[1]:t.arrow[0])+"":""}(),function(){return r.check?''+("checkbox"===r.check?t.checkbox[0]:"radio"===r.check?t.radio[0]:"")+"":""}(),function(){return'"+(''+(l?n.spread?t.branch[1]:t.branch[0]:t.leaf)+"")+(""+(n.name||"未命名")+"")}(),"
                • "].join(""));l&&(s.append(c),i.tree(c,n.children)),e.append(s),"function"==typeof r.click&&i.click(s,n),i.spread(s,n),r.drag&&i.drag(s,n)})},r.prototype.click=function(e,o){var a=this,i=a.options;e.children("a").on("click",function(e){layui.stope(e),i.click(o)})},r.prototype.spread=function(e,o){var a=this,i=(a.options,e.children(".layui-tree-spread")),r=e.children("ul"),n=e.children("a"),l=function(){e.data("spread")?(e.data("spread",null),r.removeClass("layui-show"),i.html(t.arrow[0]),n.find(".layui-icon").html(t.branch[0])):(e.data("spread",!0),r.addClass("layui-show"),i.html(t.arrow[1]),n.find(".layui-icon").html(t.branch[1]))};r[0]&&(i.on("click",l),n.on("dblclick",l))},r.prototype.on=function(e){var a=this,r=a.options,t="layui-tree-drag";e.find("i").on("selectstart",function(e){return!1}),r.drag&&o(document).on("mousemove",function(e){var i=a.move;if(i.from){var r=(i.to,o('
                  '));e.preventDefault(),o("."+t)[0]||o("body").append(r);var n=o("."+t)[0]?o("."+t):r;n.addClass("layui-show").html(i.from.elem.children("a").html()),n.css({left:e.pageX+10,top:e.pageY+10})}}).on("mouseup",function(){var e=a.move;e.from&&(e.from.elem.children("a").removeClass(i),e.to&&e.to.elem.children("a").removeClass(i),a.move={},o("."+t).remove())})},r.prototype.move={},r.prototype.drag=function(e,a){var r=this,t=(r.options,e.children("a")),n=function(){var t=o(this),n=r.move;n.from&&(n.to={item:a,elem:e},t.addClass(i))};t.on("mousedown",function(){var o=r.move;o.from={item:a,elem:e}}),t.on("mouseenter",n).on("mousemove",n).on("mouseleave",function(){var e=o(this),a=r.move;a.from&&(delete a.to,e.removeClass(i))})},e("tree",function(e){var i=new r(e=e||{}),t=o(e.elem);return t[0]?void i.init(t):a.error("layui.tree 没有找到"+e.elem+"元素")})});layui.define(["laytpl","laypage","layer","form"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=layui.hint(),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,s,e,t)}},c=function(){var e=this,t=e.config,i=t.id;return i&&(c.config[i]=t),{reload:function(t){e.reload.call(e,t)},config:t}},s="table",u=".layui-table",f="layui-hide",h="layui-none",y="layui-table-view",p=".layui-table-header",m=".layui-table-body",v=".layui-table-main",g=".layui-table-fixed",x=".layui-table-fixed-l",b=".layui-table-fixed-r",k=".layui-table-tool",C=".layui-table-sort",w="layui-table-edit",N="layui-table-hover",z=function(e){return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),"{{# if(item2.checkbox){ }}",'',"{{# } else if(item2.space){ }}",'',"{{# } else { }}",'","{{# }; }}",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
                  ',"{{# if(item2.colspan > 1){ }}",'
                  ','{{item2.title||""}}',"
                  ","{{# } else { }}",'
                  ','{{item2.title||""}}',"{{# if(item2.sort){ }}",'',"{{# } }}","
                  ","{{# } }}","
                  "].join("")},F=['',"","
                  "].join(""),T=['
                  ',"{{# var left, right; }}",'
                  ',z(),"
                  ",'
                  ',F,"
                  ","{{# if(left){ }}",'
                  ','
                  ',z({fixed:!0}),"
                  ",'
                  ',F,"
                  ","
                  ","{{# }; }}","{{# if(right){ }}",'
                  ','
                  ',z({fixed:"right"}),'
                  ',"
                  ",'
                  ',F,"
                  ","
                  ","{{# }; }}","{{# if(d.data.page){ }}",'
                  ','
                  ',"
                  ","{{# } }}","","
                  "].join(""),L=t(window),S=t(document),H=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};H.prototype.config={limit:30,loading:!0},H.prototype.render=function(e){var a,l=this;if(e&&(l.config=e),a=l.config,a.elem=t(a.elem),a.where=a.where||{},a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",countName:"count"},a.response),!a.elem[0])return l;var n=a.elem,o=n.next("."+y);a.height&&/^full-\d+$/.test(a.height)&&(l.fullHeightGap=a.height.split("-")[1],a.height=L.height()-l.fullHeightGap);var r=l.elem=t(i(T).render({VIEW_CLASS:y,data:a,index:l.index}));if(a.index=l.index,o[0]&&o.remove(),n.after(r),l.layHeader=r.find(p),l.layMain=r.find(v),l.layBody=r.find(m),l.layFixed=r.find(g),l.layFixLeft=r.find(x),l.layFixRight=r.find(b),l.layTool=r.find(k),a.height&&l.fullSize(),a.cols.length>1){var d=l.layFixed.find(p).find("th");d.height(l.layHeader.height()-1-parseFloat(d.css("padding-top"))-parseFloat(d.css("padding-bottom")))}l.pullData(1),l.events()},H.prototype.reload=function(e){var i=this;i.config=t.extend({},i.config,e),i.render()},H.prototype.pullData=function(e,i){var a=this,n=a.config,o=n.request,r=n.response,d=function(){"object"==typeof n.initSort&&a.sort(n.initSort.field,n.initSort.type)};if(n.url){var c={};c[o.pageName]=e,c[o.limitName]=n.limit,t.ajax({type:n.method||"get",url:n.url,data:t.extend(c,n.where),dataType:"json",success:function(t){return t[r.statusName]!=r.statusCode?(a.renderForm(),a.layMain.html('
                  '+(t[r.msgName]||"返回的数据状态异常")+"
                  ")):(a.renderData(t,e,t[r.countName]),d(),i&&l.close(i),void("function"==typeof n.done&&n.done(t,e,t[r.countName])))},error:function(e,t){a.layMain.html('
                  数据接口请求异常
                  '),a.renderForm(),i&&l.close(i)}})}else if(n.data&&n.data.constructor===Array){var s={},u=e*n.limit-n.limit;s[r.dataName]=n.data.concat().splice(u,n.limit),s[r.countName]=n.data.length,a.renderData(s,e,n.data.length),d(),"function"==typeof n.done&&n.done(s,e,s[r.countName])}},H.prototype.page=1,H.prototype.eachCols=function(e){var i=t.extend(!0,[],this.config.cols),a=[],l=0;layui.each(i,function(e,t){layui.each(t,function(t,n){if(n.colspan>1){var o=0;l++,n.CHILD_COLS=[],layui.each(i[e+1],function(e,t){t.PARENT_COL||o==n.colspan||(t.PARENT_COL=l,n.CHILD_COLS.push(t),o+=t.colspan>1?t.colspan:1)})}n.PARENT_COL||a.push(n)})});var n=function(t){layui.each(t||a,function(t,i){return i.CHILD_COLS?n(i.CHILD_COLS):void e(t,i)})};n()},H.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,u=e[s.response.dataName]||[],f=[],y=[],p=[],m=function(){return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(u,function(e,a){var l=[],n=[],o=[];0!==a.length&&(r||(a[d.config.indexName]=e),c.eachCols(function(e,r){var c=a[r.field||e];if(void 0!==c&&null!==c||(c=""),!(r.colspan>1)){var u=['",'
                  '+function(){return r.checkbox?'":r.toolbar?i(t(r.toolbar).html()||"").render(a):r.templet?i(t(r.templet).html()||String(c)).render(a):c}(),"
                  "].join("");l.push(u),r.fixed&&"right"!==r.fixed&&n.push(u),"right"===r.fixed&&o.push(u)}}),f.push(''+l.join("")+""),y.push(''+n.join("")+""),p.push(''+o.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+h).remove(),c.layMain.find("tbody").html(f.join("")),c.layFixLeft.find("tbody").html(y.join("")),c.layFixRight.find("tbody").html(p.join("")),c.renderForm(),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,void l.close(c.tipsIndex))};return c.key=s.id||s.index,d.cache[c.key]=u,r?m():0===u.length?(c.renderForm(),c.layFixed.remove(),c.layMain.find("tbody").html(""),c.layMain.find("."+h).remove(),c.layMain.append('
                  无数据
                  ')):(m(),void(s.page&&(c.page=n,c.count=o,a.render({elem:"layui-table-page"+s.index,count:o,groups:3,limits:s.limits||[10,20,30,40,50,60,70,80,90],limit:s.limit,curr:n,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.pullData(e.curr,c.loading()))}}),c.layTool.find(".layui-table-count span").html(o))))},H.prototype.renderForm=function(e){n.render(e||"checkbox","LAY-table-"+this.index)},H.prototype.sort=function(e,i,a,n){var r,c,u=this,f={},h=u.config,y=h.elem.attr("lay-filter"),p=d.cache[u.key];"string"==typeof e&&u.layHeader.find("th").each(function(i,a){var l=t(this),n=l.data("field");if(n===e)return e=l,r=n,!1});try{var r=r||e.data("field");if(u.sortKey&&!a&&r===u.sortKey.field&&i===u.sortKey.sort)return;var m=u.layHeader.find("th .laytable-cell-"+h.index+"-"+r).find(C);u.layHeader.find("th").find(C).removeAttr("lay-sort"),m.attr("lay-sort",i||null),u.layFixed.find("th")}catch(v){return o.error("Table modules: Did not match to field")}u.sortKey={field:r,sort:i},"asc"===i?c=layui.sort(p,r):"desc"===i?c=layui.sort(p,r,!0):(c=layui.sort(p,d.config.indexName),delete u.sortKey),f[h.response.dataName]=c,u.renderData(f,u.page,u.count,!0),l.close(u.tipsIndex),n&&layui.event.call(e,s,"sort("+y+")",{field:r,type:i})},H.prototype.loading=function(){var e=this,t=e.config;if(t.loading&&t.url)return l.msg("数据请求中",{icon:16,offset:[e.elem.offset().top+e.elem.height()/2-35-L.scrollTop()+"px",e.elem.offset().left+e.elem.width()/2-90-L.scrollLeft()+"px"],anim:-1,fixed:!1})},H.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&(l[e][a.checkName]=t)},H.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){a.checkbox&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm()),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm()),a(!1)))},H.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet,n=l.cssRules||l.rules;layui.each(n,function(a,l){if(l.selectorText===".laytable-cell-"+i.index+"-"+e)return t(l),!0})},H.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=L.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),e=parseFloat(a)-parseFloat(t.layHeader.height())-1,i.page&&(e-=parseFloat(t.layTool.outerHeight()+1)),t.layMain.css("height",e)},H.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight");if(a&&l){if(!e.elem.find(".layui-table-patch")[0]){var n=t('
                  ');n.find("div").css({width:a}),e.layHeader.eq(0).find("thead tr").append(n)}}else e.layHeader.eq(0).find(".layui-table-patch").remove();var o=e.layMain.height(),r=o-l;e.layFixed.find(m).css("height",i.height()>r?r:"auto"),e.layFixRight[i.width()>e.layMain.width()?"removeClass":"addClass"](f),e.layFixRight.css("right",a-1)},H.prototype.events=function(){var e,a=this,n=a.config,o=t("body"),c={},u=a.layHeader.find("th"),f=".layui-table-cell",h=n.elem.attr("lay-filter");u.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.attr("colspan")>1||i.attr("unresize")||c.resizeStart||(c.allowResize=i.width()-l<=10,o.css("cursor",c.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);c.resizeStart||o.css("cursor","")}).on("mousedown",function(e){if(c.allowResize){var i=t(this).data("field");e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],a.getCssRule(i,function(e){c.rule=e,c.ruleWidth=parseFloat(e.style.width)})}}),S.on("mousemove",function(t){if(c.resizeStart){if(t.preventDefault(),c.rule){var i=c.ruleWidth+t.clientX-c.offset[0];c.rule.style.width=i+"px",l.close(a.tipsIndex)}e=1}}).on("mouseup",function(t){c.resizeStart&&(c={},o.css("cursor",""),a.scrollPatch()),2===e&&(e=null)}),u.on("click",function(){var i,l=t(this),n=l.find(C),o=n.attr("lay-sort");return n[0]&&1!==e?(i="asc"===o?"desc":"desc"===o?null:"asc",void a.sort(l,i,null,!0)):e=2}).find(C+" .layui-edge ").on("click",function(e){var i=t(this),l=i.index(),n=i.parents("th").eq(0).data("field");layui.stope(e),0===l?a.sort(n,"asc",null,!0):a.sort(n,"desc",null,!0)}),a.elem.on("click",'input[name="layTableCheckbox"]+',function(){var e=t(this).prev(),i=a.layBody.find('input[name="layTableCheckbox"]'),l=e.parents("tr").eq(0).data("index"),n=e[0].checked,o="layTableAllChoose"===e.attr("lay-filter");o?(i.each(function(e,t){t.checked=n,a.setCheckData(e,n)}),a.syncCheckAll(),a.renderForm()):(a.setCheckData(l,n),a.syncCheckAll()),layui.event.call(this,s,"checkbox("+h+")",{checked:n,data:d.cache[a.key][l],type:o?"all":"one"})}),a.layBody.on("mouseenter","tr",function(){var e=t(this),i=e.index();a.layBody.find("tr:eq("+i+")").addClass(N)}).on("mouseleave","tr",function(){var e=t(this),i=e.index();a.layBody.find("tr:eq("+i+")").removeClass(N)}),a.layBody.on("change","."+w,function(){var e=t(this),i=this.value,l=e.parent().data("field"),n=e.parents("tr").eq(0).data("index"),o=d.cache[a.key][n];o[l]=i,layui.event.call(this,s,"edit("+h+")",{value:i,data:o,field:l})}).on("blur","."+w,function(){var e,l=t(this),n=l.parent().data("field"),o=l.parents("tr").eq(0).data("index"),r=d.cache[a.key][o];a.eachCols(function(t,i){i.field==n&&i.templet&&(e=i.templet)}),l.siblings(f).html(e?i(t(e).html()||this.value).render(r):this.value),l.parent().data("content",this.value),l.remove()}),a.layBody.on("click","td",function(){var e=t(this),i=(e.data("field"),e.children(f));if(!e.data("off")){if(e.data("edit")){var o=t('');return o[0].value=e.data("content")||i.text(),e.find("."+w)[0]||e.append(o),o.focus()}i.prop("scrollWidth")>i.outerWidth()&&(a.tipsIndex=l.tips(['
                  ',i.html(),"
                  ",''].join(""),i[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:600,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}}))}}),a.layBody.on("click","*[lay-event]",function(){var e=t(this),l=e.parents("tr").eq(0).data("index"),n=a.layBody.find('tr[data-index="'+l+'"]'),o="layui-table-click",r=d.cache[a.key][l];layui.event.call(this,s,"tool("+h+")",{data:d.clearCacheKey(r),event:e.attr("lay-event"),tr:n,del:function(){d.cache[a.key][l]=[],n.remove(),a.scrollPatch()},update:function(e){e=e||{},layui.each(e,function(e,l){if(e in r){var o,d=n.children('td[data-field="'+e+'"]');r[e]=l,a.eachCols(function(t,i){i.field==e&&i.templet&&(o=i.templet)}),d.children(f).html(o?i(t(o).html()||l).render(r):l),d.data("content",l)}})}}),n.addClass(o).siblings("tr").removeClass(o)}),a.layMain.on("scroll",function(){var e=t(this),i=e.scrollLeft(),n=e.scrollTop();a.layHeader.scrollLeft(i),a.layFixed.find(m).scrollTop(n),l.close(a.tipsIndex)}),L.on("resize",function(){a.fullSize(),a.scrollPatch()})},d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':u+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},d.checkStatus=function(e){var t=0,i=[],a=d.cache[e];return a?(layui.each(a,function(e,a){a[d.config.checkName]&&(t++,i.push(d.clearCacheKey(a)))}),{data:i,isAll:t===a.length}):{}},c.config={},d.reload=function(e,i){var a=c.config[e];return a?d.render(t.extend({},a,i)):o.error("The ID option was not found in the table instance")},d.render=function(e){var t=new H(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(s,d)});layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",o=">*[carousel-item]>*",l="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(o),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.indicator(),e.elemItem.length<=1||(e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
                    ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
                  "].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a',e.bar1?'
                • '+n[0]+"
                • ":"",e.bar2?'
                • '+n[1]+"
                • ":"",'
                • '+n[2]+"
                • ",""].join("")),s=u.find("."+l),b=function(){var o=r.scrollTop();o>=e.showHeight?t||(s.show(),t=1):t&&(s.hide(),t=0)};o("."+i)[0]||("object"==typeof e.css&&u.css(e.css),c.append(u),b(),u.find("li").on("click",function(){var t=o(this),a=t.attr("lay-type");"top"===a&&o("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,a)}),r.on("scroll",function(){clearTimeout(a),a=setTimeout(function(){b()},100)}))},countdown:function(e,o,t){var a=this,i="function"==typeof o,l=new Date(e).getTime(),r=new Date(!o||i?(new Date).getTime():o).getTime(),c=l-r,n=[Math.floor(c/864e5),Math.floor(c/36e5)%24,Math.floor(c/6e4)%60,Math.floor(c/1e3)%60];i&&(t=o);var u=setTimeout(function(){a.countdown(e,r+1e3,t)},1e3);return t&&t(c>0?n:[0,0,0,0],o,u),c<=0&&clearTimeout(u),u},timeAgo:function(e,o){var t=(new Date).getTime()-new Date(e).getTime();return t>2592e6?(t=new Date(e).toLocaleString(),o&&(t=t.replace(/\s[\S]+$/g,"")),t):t>=864e5?(t/1e3/60/60/24|0)+"天前":t>=36e5?(t/1e3/60/60|0)+"小时前":t>=18e4?(t/1e3/60|0)+"分钟前":t<0?"未来":"刚刚"}};e("util",t)});layui.define("jquery",function(e){"use strict";var l=layui.$,o=function(e){},t='';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),i||(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)});layui.define(["layer","form"],function(t){"use strict";var e=layui.$,i=layui.layer,a=layui.form,l=(layui.hint(),layui.device()),n="layedit",o="layui-show",r="layui-disabled",c=function(){var t=this;t.index=0,t.config={tool:["strong","italic","underline","del","|","left","center","right","|","link","unlink","face","image"],hideTool:[],height:280}};c.prototype.set=function(t){var i=this;return e.extend(!0,i.config,t),i},c.prototype.on=function(t,e){return layui.onevent(n,t,e)},c.prototype.build=function(t,i){i=i||{};var a=this,n=a.config,r="layui-layedit",c=e("#"+t),u="LAY_layedit_"+ ++a.index,d=c.next("."+r),y=e.extend({},n,i),f=function(){var t=[],e={};return layui.each(y.hideTool,function(t,i){e[i]=!0}),layui.each(y.tool,function(i,a){C[a]&&!e[a]&&t.push(C[a])}),t.join("")}(),m=e(['
                  ','
                  '+f+"
                  ",'
                  ','',"
                  ","
                  "].join(""));return l.ie&&l.ie<8?c.removeClass("layui-hide").addClass(o):(d[0]&&d.remove(),s.call(a,m,c[0],y),c.addClass("layui-hide").after(m),a.index)},c.prototype.getContent=function(t){var e=u(t);if(e[0])return d(e[0].document.body.innerHTML)},c.prototype.getText=function(t){var i=u(t);if(i[0])return e(i[0].document.body).text()},c.prototype.setContent=function(t,i,a){var l=u(t);l[0]&&(a?e(l[0].document.body).append(i):e(l[0].document.body).html(i),layedit.sync(t))},c.prototype.sync=function(t){var i=u(t);if(i[0]){var a=e("#"+i[1].attr("textarea"));a.val(d(i[0].document.body.innerHTML))}},c.prototype.getSelection=function(t){var e=u(t);if(e[0]){var i=m(e[0].document);return document.selection?i.text:i.toString()}};var s=function(t,i,a){var l=this,n=t.find("iframe");n.css({height:a.height}).on("load",function(){var o=n.contents(),r=n.prop("contentWindow"),c=o.find("head"),s=e([""].join("")),u=o.find("body");c.append(s),u.attr("contenteditable","true").css({"min-height":a.height}).html(i.value||""),y.apply(l,[r,n,i,a]),g.call(l,r,t,a)})},u=function(t){var i=e("#LAY_layedit_"+t),a=i.prop("contentWindow");return[a,i]},d=function(t){return 8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),t},y=function(t,a,n,o){var r=t.document,c=e(r.body);c.on("keydown",function(t){var e=t.keyCode;if(13===e){var a=m(r),l=p(a),n=l.parentNode;if("pre"===n.tagName.toLowerCase()){if(t.shiftKey)return;return i.msg("请暂时用shift+enter"),!1}r.execCommand("formatBlock",!1,"

                  ")}}),e(n).parents("form").on("submit",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on("paste",function(e){r.execCommand("formatBlock",!1,"

                  "),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find("*[style]").each(function(){var t=this.style.textAlign;this.removeAttribute("style"),e(this).css({"text-align":t||""})}),t.find("table").addClass("layui-table"),t.find("script,link").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute("text"),l.selection){var r=a.text||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop("outerHTML")),a.select()}else{var r=a.toString()||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l="layedit-tool-active",n=p(m(a)),o=function(e){return t.find(".layedit-tool-"+e)};i&&i[i.hasClass(l)?"removeClass":"addClass"](l),t.find(">i").removeClass(l),o("unlink").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;"b"!==t&&"strong"!==t||o("b").addClass(l),"i"!==t&&"em"!==t||o("i").addClass(l),"u"===t&&o("u").addClass(l),"strike"===t&&o("d").addClass(l),"p"===t&&("center"===e?o("center").addClass(l):"right"===e?o("right").addClass(l):o("left").addClass(l)),"a"===t&&(o("link").addClass(l),o("unlink").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr("href"),target:l.attr("target")},function(e){var a=l[0];"A"===a.tagName?a.href=e.url:v.call(t,"a",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand("unlink")},face:function(e){x.call(this,function(i){v.call(t,"img",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use("upload",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find("input")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,"img",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||"上传失败")}})})},code:function(e){k.call(o,function(i){v.call(t,"pre",{text:i.code,"lay-lang":i.lang},e)})},help:function(){i.open({type:2,title:"帮助",area:["600px","380px"],shadeClose:!0,shade:.1,skin:"layui-layer-msg",content:["http://www.layui.com/about/layedit/help.html","no"]})}},s=a.find(".layui-layedit-tool"),u=function(){var i=e(this),a=i.attr("layedit-event"),l=i.attr("lay-command");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand("formatBlock",!1,"

                  "),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(">i").on("mousedown",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)||u.call(this)}).on("click",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)&&u.call(this)}),o.on("click",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:"LAY_layedit_link",area:"350px",shade:.05,shadeClose:!0,moveType:1,title:"超链接",skin:"layui-layer-msg",content:['

                    ','
                  • ','','
                    ','',"
                    ","
                  • ",'
                  • ','','
                    ','",'","
                    ","
                  • ",'
                  • ','','',"
                  • ","
                  "].join(""),success:function(t,n){var o="submit(layedit-link-yes)";a.render("radio"),t.find(".layui-btn-primary").on("click",function(){i.close(n),l.focus()}),a.on(o,function(t){i.close(b.index),e&&e(t.field)})}});b.index=n},x=function(t){var a=function(){var t=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],e={};return layui.each(t,function(t,i){e[i]=layui.cache.dir+"images/face/"+t+".gif"}),e}();return x.hide=x.hide||function(t){"face"!==e(t.target).attr("layedit-event")&&i.close(x.index)},x.index=i.tips(function(){var t=[];return layui.each(a,function(e,i){t.push('
                • '+e+'
                • ')}),'
                    '+t.join("")+"
                  "}(),this,{tips:1,time:0,skin:"layui-box layui-util-face",maxWidth:500,success:function(l,n){l.css({marginTop:-4,marginLeft:-10}).find(".layui-clear>li").on("click",function(){t&&t({src:a[this.title],alt:this.title}),i.close(n)}),e(document).off("click",x.hide).on("click",x.hide)}})},k=function(t){var e=this,l=i.open({type:1,id:"LAY_layedit_code",area:"550px",shade:.05,shadeClose:!0,moveType:1,title:"插入代码",skin:"layui-layer-msg",content:['
                    ','
                  • ','','
                    ','","
                    ","
                  • ",'
                  • ','','
                    ','',"
                    ","
                  • ",'
                  • ','','',"
                  • ","
                  "].join(""),success:function(l,n){var o="submit(layedit-code-yes)";a.render("select"),l.find(".layui-btn-primary").on("click",function(){i.close(n),e.focus()}),a.on(o,function(e){i.close(k.index),t&&t(e.field)})}});k.index=l},C={html:'',strong:'',italic:'',underline:'',del:'',"|":'',left:'',center:'',right:'',link:'',unlink:'',face:'',image:'',code:'',help:''},w=new c;t(n,w)});layui.define("jquery",function(e){"use strict";var a=layui.$,l="http://www.layui.com/doc/modules/code.html";e("code",function(e){var t=[];e=e||{},e.elem=a(e.elem||".layui-code"),e.about=!("about"in e)||e.about,e.elem.each(function(){t.push(this)}),layui.each(t.reverse(),function(t,i){var c=a(i),o=c.html();(c.attr("lay-encode")||e.encode)&&(o=o.replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
                  1. '+o.replace(/[\r\t\n]+/g,"
                  2. ")+"
                  "),c.find(">.layui-code-h3")[0]||c.prepend('

                  '+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

                  ");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); \ No newline at end of file +for(var e=this.offsetParent;e&&!pe.nodeName(e,"html")&&"static"===pe.css(e,"position");)e=e.offsetParent;return e||pt})}}),pe.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n=/Y/.test(t);pe.fn[e]=function(r){return Pe(this,function(e,r,i){var o=te(e);return void 0===i?o?t in o?o[t]:o.document.documentElement[r]:e[r]:void(o?o.scrollTo(n?pe(o).scrollLeft():i,n?i:pe(o).scrollTop()):e[r]=i)},e,r,arguments.length,null)}}),pe.each(["top","left"],function(e,t){pe.cssHooks[t]=L(fe.pixelPosition,function(e,n){if(n)return n=gt(e,t),ft.test(n)?pe(e).position()[t]+"px":n})}),pe.each({Height:"height",Width:"width"},function(e,t){pe.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){pe.fn[r]=function(r,i){var o=arguments.length&&(n||"boolean"!=typeof r),a=n||(r===!0||i===!0?"margin":"border");return Pe(this,function(t,n,r){var i;return pe.isWindow(t)?t.document.documentElement["client"+e]:9===t.nodeType?(i=t.documentElement,Math.max(t.body["scroll"+e],i["scroll"+e],t.body["offset"+e],i["offset"+e],i["client"+e])):void 0===r?pe.css(t,n,a):pe.style(t,n,r,a)},t,o?r:void 0,o,null)}})}),pe.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),pe.fn.size=function(){return this.length},pe.fn.andSelf=pe.fn.addBack,layui.define(function(e){layui.$=pe,e("jquery",pe)}),pe});!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if("interactive"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.1",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'
                  '+(f?r.title[0]:r.title)+"
                  ":"";return r.zIndex=s,t([r.shade?'
                  ':"",'
                  '+(e&&2!=r.type?"":u)+'
                  '+(0==r.type&&r.icon!==-1?'':"")+(1==r.type&&e?"":r.content||"")+'
                  '+function(){var e=c?'':"";return r.closeBtn&&(e+=''),e}()+""+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t'+r.btn[t]+"";return'
                  '+e+"
                  "}():"")+(r.resize?'':"")+"
                  "],u,i('
                  ')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"http://layer.layui.com","auto"];t.content='';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(t){s=t.find(".layui-layer-input"),s.val(e.value||"").focus(),"function"==typeof f&&f(t)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
                    '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
                  • '+(t[0].content||"no content")+"
                  • ";i'+(t[i].content||"no content")+"";return a}()+"
                  ",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
                  '+(u.length>1?'':"")+'
                  '+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
                  ",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
                  是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window);layui.define("jquery",function(i){"use strict";var t=layui.$,a=(layui.hint(),layui.device()),e="element",l="layui-this",n="layui-show",s=function(){this.config={}};s.prototype.set=function(i){var a=this;return t.extend(!0,a.config,i),a},s.prototype.on=function(i,t){return layui.onevent.call(this,e,i,t)},s.prototype.tabAdd=function(i,a){var e=".layui-tab-title",l=t(".layui-tab[lay-filter="+i+"]"),n=l.children(e),s=n.children(".layui-tab-bar"),o=l.children(".layui-tab-content"),c='
                • '+(a.title||"unnaming")+"
                • ";return s[0]?s.before(c):n.append(c),o.append('
                  '+(a.content||"")+"
                  "),y.hideTabMore(!0),y.tabAuto(),this},s.prototype.tabDelete=function(i,a){var e=".layui-tab-title",l=t(".layui-tab[lay-filter="+i+"]"),n=l.children(e),s=n.find('>li[lay-id="'+a+'"]');return y.tabDelete(null,s),this},s.prototype.tabChange=function(i,a){var e=".layui-tab-title",l=t(".layui-tab[lay-filter="+i+"]"),n=l.children(e),s=n.find('>li[lay-id="'+a+'"]');return y.tabClick.call(s[0],null,null,s),this},s.prototype.tab=function(i){i=i||{},v.on("click",i.headerElem,function(a){var e=t(this).index();y.tabClick.call(this,a,e,null,i)})},s.prototype.progress=function(i,a){var e="layui-progress",l=t("."+e+"[lay-filter="+i+"]"),n=l.find("."+e+"-bar"),s=n.find("."+e+"-text");return n.css("width",a),s.text(a),this};var o=".layui-nav",c="layui-nav-item",r="layui-nav-bar",u="layui-nav-tree",d="layui-nav-child",h="layui-nav-more",f="layui-anim layui-anim-upbit",y={tabClick:function(i,a,s,o){o=o||{};var c=s||t(this),a=a||c.parent().children("li").index(c),r=o.headerElem?c.parent():c.parents(".layui-tab").eq(0),u=o.bodyElem?t(o.bodyElem):r.children(".layui-tab-content").children(".layui-tab-item"),d=c.find("a"),h=r.attr("lay-filter");"javascript:;"!==d.attr("href")&&"_blank"===d.attr("target")||(c.addClass(l).siblings().removeClass(l),u.eq(a).addClass(n).siblings().removeClass(n)),layui.event.call(this,e,"tab("+h+")",{elem:r,index:a})},tabDelete:function(i,a){var n=a||t(this).parent(),s=n.index(),o=n.parents(".layui-tab").eq(0),c=o.children(".layui-tab-content").children(".layui-tab-item"),r=o.attr("lay-filter");n.hasClass(l)&&(n.next()[0]?y.tabClick.call(n.next()[0],null,s+1):n.prev()[0]&&y.tabClick.call(n.prev()[0],null,s-1)),n.remove(),c.eq(s).remove(),setTimeout(function(){y.tabAuto()},50),layui.event.call(this,e,"tabDelete("+r+")",{elem:o,index:s})},tabAuto:function(){var i="layui-tab-more",e="layui-tab-bar",l="layui-tab-close",n=this;t(".layui-tab").each(function(){var s=t(this),o=s.children(".layui-tab-title"),c=(s.children(".layui-tab-content").children(".layui-tab-item"),'lay-stope="tabmore"'),r=t('');if(n===window&&8!=a.ie&&y.hideTabMore(!0),s.attr("lay-allowClose")&&o.find("li").each(function(){var i=t(this);if(!i.find("."+l)[0]){var a=t('');a.on("click",y.tabDelete),i.append(a)}}),o.prop("scrollWidth")>o.outerWidth()+1){if(o.find("."+e)[0])return;o.append(r),s.attr("overflow",""),r.on("click",function(t){o[this.title?"removeClass":"addClass"](i),this.title=this.title?"":"收缩"})}else o.find("."+e).remove(),s.removeAttr("overflow")})},hideTabMore:function(i){var a=t(".layui-tab-title");i!==!0&&"tabmore"===t(i.target).attr("lay-stope")||(a.removeClass("layui-tab-more"),a.find(".layui-tab-bar").attr("title",""))},clickThis:function(){var i=t(this),a=i.parents(o),n=a.attr("lay-filter"),s=i.find("a"),c="string"==typeof i.attr("lay-unselect");i.find("."+d)[0]||("javascript:;"!==s.attr("href")&&"_blank"===s.attr("target")||c||(a.find("."+l).removeClass(l),i.addClass(l)),layui.event.call(this,e,"nav("+n+")",i))},clickChild:function(){var i=t(this),a=i.parents(o),n=a.attr("lay-filter");a.find("."+l).removeClass(l),i.addClass(l),layui.event.call(this,e,"nav("+n+")",i)},showChild:function(){var i=t(this),a=i.parents(o),e=i.parent(),l=i.siblings("."+d);a.hasClass(u)&&(l.removeClass(f),e["none"===l.css("display")?"addClass":"removeClass"](c+"ed"))},collapse:function(){var i=t(this),a=i.find(".layui-colla-icon"),l=i.siblings(".layui-colla-content"),s=i.parents(".layui-collapse").eq(0),o=s.attr("lay-filter"),c="none"===l.css("display");if("string"==typeof s.attr("lay-accordion")){var r=s.children(".layui-colla-item").children("."+n);r.siblings(".layui-colla-title").children(".layui-colla-icon").html(""),r.removeClass(n)}l[c?"addClass":"removeClass"](n),a.html(c?"":""),layui.event.call(this,e,"collapse("+o+")",{title:i,content:l,show:c})}};s.prototype.init=function(i,e){var l=function(){return e?'[lay-filter="'+e+'"]':""}(),s={tab:function(){y.tabAuto.call({})},nav:function(){var i=200,e={},s={},p={},v=function(l,o,c){var r=t(this),y=r.find("."+d);o.hasClass(u)?l.css({top:r.position().top,height:r.children("a").height(),opacity:1}):(y.addClass(f),l.css({left:r.position().left+parseFloat(r.css("marginLeft")),top:r.position().top+r.height()-l.height()}),e[c]=setTimeout(function(){l.css({width:r.width(),opacity:1})},a.ie&&a.ie<10?0:i),clearTimeout(p[c]),"block"===y.css("display")&&clearTimeout(s[c]),s[c]=setTimeout(function(){y.addClass(n),r.find("."+h).addClass(h+"d")},300))};t(o+l).each(function(a){var l=t(this),o=t(''),f=l.find("."+c);l.find("."+r)[0]||(l.append(o),f.on("mouseenter",function(){v.call(this,o,l,a)}).on("mouseleave",function(){l.hasClass(u)||(clearTimeout(s[a]),s[a]=setTimeout(function(){l.find("."+d).removeClass(n),l.find("."+h).removeClass(h+"d")},300))}),l.on("mouseleave",function(){clearTimeout(e[a]),p[a]=setTimeout(function(){l.hasClass(u)?o.css({height:0,top:o.position().top+o.height()/2,opacity:0}):o.css({width:0,left:o.position().left+o.width()/2,opacity:0})},i)})),f.each(function(){var i=t(this),a=i.find("."+d);if(a[0]&&!i.find("."+h)[0]){var e=i.children("a");e.append('')}i.off("click",y.clickThis).on("click",y.clickThis),i.children("a").off("click",y.showChild).on("click",y.showChild),a.children("dd").off("click",y.clickChild).on("click",y.clickChild)})})},breadcrumb:function(){var i=".layui-breadcrumb";t(i+l).each(function(){var i=t(this),a="lay-separator",e=i.attr(a)||"/",l=i.find("a");l.next("span["+a+"]")[0]||(l.each(function(i){i!==l.length-1&&t(this).after(""+e+"")}),i.css("visibility","visible"))})},progress:function(){var i="layui-progress";t("."+i+l).each(function(){var a=t(this),e=a.find(".layui-progress-bar"),l=e.attr("lay-percent");e.css("width",function(){return/^.+\/.+$/.test(l)?100*new Function("return "+l)()+"%":l}()),a.attr("lay-showPercent")&&setTimeout(function(){e.html(''+l+"")},350)})},collapse:function(){var i="layui-collapse";t("."+i+l).each(function(){var i=t(this).find(".layui-colla-item");i.each(function(){var i=t(this),a=i.find(".layui-colla-title"),e=i.find(".layui-colla-content"),l="none"===e.css("display");a.find(".layui-colla-icon").remove(),a.append(''+(l?"":"")+""),a.off("click",y.collapse).on("click",y.collapse)})})}};return s[i]?s[i]():layui.each(s,function(i,t){t()})},s.prototype.render=s.prototype.init;var p=new s,v=t(document);p.render();var b=".layui-tab-title li";v.on("click",b,y.tabClick),v.on("click",y.hideTabMore),t(window).on("resize",y.tabAuto),i(e,p)});layui.define("layer",function(e){"use strict";var i=layui.$,t=layui.layer,n=layui.hint(),a=layui.device(),o={config:{},set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,r,e,i)}},l=function(){var e=this;return{upload:function(i){e.upload.call(e,i)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",f="layui-upload-iframe",s="layui-upload-choose",p=function(e){var t=this;t.config=i.extend({},t.config,o.config,e),t.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",method:"post",data:{},drag:!0,size:0,number:0,multiple:!1},p.prototype.render=function(e){var t=this,e=t.config;e.elem=i(e.elem),e.bindAction=i(e.bindAction),t.file(),t.events()},p.prototype.file=function(){var e=this,t=e.config,n=e.elemFile=i(['"].join("")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap('
                  '),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i(''),a=i(['
                  ',"
                  "].join(""));i("#"+f)[0]||i("body").append(n),t.elem.next().hasClass(f)||(e.elemFile.wrap(a),t.elem.next("."+f).append(function(){var e=[];return layui.each(t.data,function(i,t){e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){var t=0,n=0,a=e||o.files||o.chooseFiles||r.files,u=function(){l.multiple&&t+n===o.fileLength&&"function"==typeof l.allDone&&l.allDone({total:o.fileLength,successful:t,aborted:n})};layui.each(a,function(e,a){var r=new FormData;r.append(l.field,a),layui.each(l.data,function(e,i){r.append(e,i)}),i.ajax({url:l.url,type:l.method,data:r,contentType:!1,processData:!1,dataType:"json",success:function(i){t++,d(e,i),u()},error:function(){n++,o.msg("请求上传接口出现异常"),m(e),u()}})})},c=function(){var e=i("#"+f);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find("body");try{i=t.text()}catch(n){o.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(""),d(0,i))},30)},d=function(e,i){if(o.elemFile.next("."+s).remove(),r.value="","object"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},h=l.exts,v=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files}},y=function(){return"choose"===t?l.choose&&l.choose(g):(l.before&&l.before(g),a.ie?a.ie>9?u():c():void u())};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return o.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return o.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return o.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,i){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(i))||(n=!0)}),n)return o.msg("选择的图片中包含不支持的格式"),r.value=""}if(o.fileLength=function(){var i=0,t=e||o.files||o.chooseFiles||r.files;return layui.each(t,function(){i++}),i}(),l.number&&o.fileLength>l.number)return o.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?Math.floor(t)+(t%1>0?t.toFixed(1):0)+"MB":l.size+"KB",r.value="",F=t}}),F)return o.msg("文件不能超过"+F)}y()}},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+"-"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+"个文件":(i[0]||{}).name||a[0].value.match(/[^\/\\]+\..+/g)||[]||"";a.next().hasClass(s)&&a.next().remove(),e.upload(null,"choose"),e.isFile()||t.choose||a.after(''+o+"")};t.elem.off("upload.start").on("upload.start",function(){var a=i(this),o=a.attr("lay-data");if(o)try{o=new Function("return "+o)(),e.config=i.extend({},t,o)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off("upload.over").on("upload.over",function(){var e=i(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=i(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),t.elem.data("haveEvents")||(e.elemFile.on("change",function(){i(this).trigger("upload.change")}),t.elem.on("click",function(){e.isFile()||i(this).trigger("upload.start")}),t.drag&&t.elem.on("dragover",function(e){e.preventDefault(),i(this).trigger("upload.over")}).on("dragleave",function(e){i(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),i(this).trigger("upload.drop",e)}),t.bindAction.on("click",function(){i(this).trigger("upload.action")}),t.elem.data("haveEvents",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)});layui.define("layer",function(e){"use strict";var t=layui.$,i=layui.layer,a=layui.hint(),n=layui.device(),l="form",r=".layui-form",s="layui-this",o="layui-hide",u="layui-disabled",c=function(){this.config={verify:{required:[/[\S]+/,"必填项不能为空"],phone:[/^1\d{10}$/,"请输入正确的手机号"],email:[/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,"邮箱格式不正确"],url:[/(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/,"链接格式不正确"],number:function(e){if(!e||isNaN(e))return"只能填写数字"},date:[/^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/,"日期格式不正确"],identity:[/(^\d{15}$)|(^\d{17}(x|X|\d)$)/,"请输入正确的身份证号"]}}};c.prototype.set=function(e){var i=this;return t.extend(!0,i.config,e),i},c.prototype.verify=function(e){var i=this;return t.extend(!0,i.config.verify,e),i},c.prototype.on=function(e,t){return layui.onevent.call(this,l,e,t)},c.prototype.render=function(e,i){var n=this,c=t(r+function(){return i?'[lay-filter="'+i+'"]':""}()),d={select:function(){var e,i="请选择",a="layui-form-select",n="layui-select-title",r="layui-select-none",d="",f=c.find("select"),y=function(i,l){t(i.target).parent().hasClass(n)&&!l||(t("."+a).removeClass(a+"ed "+a+"up"),e&&d&&e.val(d)),e=null},h=function(i,c,f){var h=t(this),p=i.find("."+n),m=p.find("input"),k=i.find("dl"),g=k.children("dd");if(!c){var x=function(){var e=i.offset().top+i.outerHeight()+5-v.scrollTop(),t=k.outerHeight();i.addClass(a+"ed"),g.removeClass(o),e+t>v.height()&&e>=t&&i.addClass(a+"up")},b=function(e){i.removeClass(a+"ed "+a+"up"),m.blur(),e||C(m.val(),function(e){e&&(d=k.find("."+s).html(),m&&m.val(d))})};p.on("click",function(e){i.hasClass(a+"ed")?b():(y(e,!0),x()),k.find("."+r).remove()}),p.find(".layui-edge").on("click",function(){m.focus()}),m.on("keyup",function(e){var t=e.keyCode;9===t&&x()}).on("keydown",function(e){var t=e.keyCode;9===t?b():13===t&&e.preventDefault()});var C=function(e,i,a){var n=0;layui.each(g,function(){var i=t(this),l=i.text(),r=l.indexOf(e)===-1;(""===e||"blur"===a?e!==l:r)&&n++,"keyup"===a&&i[r?"addClass":"removeClass"](o)});var l=n===g.length;return i(l),l},w=function(e){var t=this.value,i=e.keyCode;return 9!==i&&13!==i&&37!==i&&38!==i&&39!==i&&40!==i&&(C(t,function(e){e?k.find("."+r)[0]||k.append('

                  无匹配项

                  '):k.find("."+r).remove()},"keyup"),void(""===t&&k.find("."+r).remove()))};f&&m.on("keyup",w).on("blur",function(t){e=m,d=k.find("."+s).html(),setTimeout(function(){C(m.val(),function(e){d||m.val("")},"blur")},200)}),g.on("click",function(){var e=t(this),a=e.attr("lay-value"),n=h.attr("lay-filter");return!e.hasClass(u)&&(e.hasClass("layui-select-tips")?m.val(""):(m.val(e.text()),e.addClass(s)),e.siblings().removeClass(s),h.val(a).removeClass("layui-form-danger"),layui.event.call(this,l,"select("+n+")",{elem:h[0],value:a,othis:i}),b(!0),!1)}),i.find("dl>dt").on("click",function(e){return!1}),t(document).off("click",y).on("click",y)}};f.each(function(e,l){var r=t(this),o=r.next("."+a),c=this.disabled,d=l.value,f=t(l.options[l.selectedIndex]),y=l.options[0];if("string"==typeof r.attr("lay-ignore"))return r.show();var v="string"==typeof r.attr("lay-search"),p=y?y.value?i:y.innerHTML||i:i,m=t(['
                  ','
                  ','
                  ','
                  '+function(e){var t=[];return layui.each(e,function(e,a){0!==e||a.value?"optgroup"===a.tagName.toLowerCase()?t.push("
                  "+a.label+"
                  "):t.push('
                  '+a.innerHTML+"
                  "):t.push('
                  '+(a.innerHTML||i)+"
                  ")}),0===t.length&&t.push('
                  没有选项
                  '),t.join("")}(r.find("*"))+"
                  ","
                  "].join(""));o[0]&&o.remove(),r.after(m),h.call(this,m,c,v)})},checkbox:function(){var e={checkbox:["layui-form-checkbox","layui-form-checked","checkbox"],_switch:["layui-form-switch","layui-form-onswitch","switch"]},i=c.find("input[type=checkbox]"),a=function(e,i){var a=t(this);e.on("click",function(){var t=a.attr("lay-filter"),n=(a.attr("lay-text")||"").split("|");a[0].disabled||(a[0].checked?(a[0].checked=!1,e.removeClass(i[1]).find("em").text(n[1])):(a[0].checked=!0,e.addClass(i[1]).find("em").text(n[0])),layui.event.call(a[0],l,i[2]+"("+t+")",{elem:a[0],value:a[0].value,othis:e}))})};i.each(function(i,n){var l=t(this),r=l.attr("lay-skin"),s=(l.attr("lay-text")||"").split("|"),o=this.disabled;"switch"===r&&(r="_"+r);var c=e[r]||e.checkbox;if("string"==typeof l.attr("lay-ignore"))return l.show();var d=l.next("."+c[0]),f=t(['
                  ',{_switch:""+((n.checked?s[0]:s[1])||"")+""}[r]||(n.title.replace(/\s/g,"")?""+n.title+"":"")+''+(r?"":"")+"","
                  "].join(""));d[0]&&d.remove(),l.after(f),a.call(this,f,c)})},radio:function(){var e="layui-form-radio",i=["",""],a=c.find("input[type=radio]"),n=function(a){var n=t(this),s="layui-anim-scaleSpring";a.on("click",function(){var o=n[0].name,u=n.parents(r),c=n.attr("lay-filter"),d=u.find("input[name="+o.replace(/(\.|#|\[|\])/g,"\\$1")+"]");n[0].disabled||(layui.each(d,function(){var a=t(this).next("."+e);this.checked=!1,a.removeClass(e+"ed"),a.find(".layui-icon").removeClass(s).html(i[1])}),n[0].checked=!0,a.addClass(e+"ed"),a.find(".layui-icon").addClass(s).html(i[0]),layui.event.call(n[0],l,"radio("+c+")",{elem:n[0],value:n[0].value,othis:a}))})};a.each(function(a,l){var r=t(this),s=r.next("."+e),o=this.disabled;if("string"==typeof r.attr("lay-ignore"))return r.show();s[0]&&s.remove();var c=t(['
                  ',''+i[l.checked?0:1]+"","
                  "+function(){var e=l.title||"";return"string"==typeof r.next().attr("lay-radio")&&(e=r.next().html(),r.next().remove()),e}()+"
                  ","
                  "].join(""));r.after(c),n.call(this,c)})}};return e?d[e]?d[e]():a.error("不支持的"+e+"表单渲染"):layui.each(d,function(e,t){t()}),n};var d=function(){var e=t(this),a=f.config.verify,s=null,o="layui-form-danger",u={},c=e.parents(r),d=c.find("*[lay-verify]"),y=e.parents("form")[0],v=c.find("input,select,textarea"),h=e.attr("lay-filter");if(layui.each(d,function(e,l){var r=t(this),u=r.attr("lay-verify").split("|"),c=r.attr("lay-verType"),d=r.val();if(r.removeClass(o),layui.each(u,function(e,t){var u,f="",y="function"==typeof a[t];if(a[t]){var u=y?f=a[t](d,l):!a[t][0].test(d);if(f=f||a[t][1],u)return"tips"===c?i.tips(f,function(){return"string"==typeof r.attr("lay-ignore")||"select"!==l.tagName.toLowerCase()&&!/^checkbox|radio$/.test(l.type)?r:r.next()}(),{tips:1}):"alert"===c?i.alert(f,{title:"提示",shadeClose:!0}):i.msg(f,{icon:5,shift:6}),n.android||n.ios||l.focus(),r.addClass(o),s=!0}}),s)return s}),s)return!1;var p={};return layui.each(v,function(e,t){if(t.name=(t.name||"").replace(/^\s*|\s*&/,""),t.name){if(/^.*\[\]$/.test(t.name)){var i=t.name.match(/^(.*)\[\]$/g)[0];p[i]=0|p[i],t.name=t.name.replace(/^(.*)\[\]$/,"$1["+p[i]++ +"]")}/^checkbox|radio$/.test(t.type)&&!t.checked||(u[t.name]=t.value)}}),layui.event.call(this,l,"submit("+h+")",{elem:this,form:y,field:u})},f=new c,y=t(document),v=t(window);f.render(),y.on("reset",r,function(){var e=t(this).attr("lay-filter");setTimeout(function(){f.render(null,e)},50)}),y.on("submit",r,d).on("click","*[lay-submit]",d),e(l,f)});layui.define("jquery",function(e){"use strict";var o=layui.$,a=layui.hint(),i="layui-tree-enter",r=function(e){this.options=e},t={arrow:["",""],checkbox:["",""],radio:["",""],branch:["",""],leaf:""};r.prototype.init=function(e){var o=this;e.addClass("layui-box layui-tree"),o.options.skin&&e.addClass("layui-tree-skin-"+o.options.skin),o.tree(e),o.on(e)},r.prototype.tree=function(e,a){var i=this,r=i.options,n=a||r.nodes;layui.each(n,function(a,n){var l=n.children&&n.children.length>0,c=o('
                    '),s=o(["
                  • ",function(){return l?''+(n.spread?t.arrow[1]:t.arrow[0])+"":""}(),function(){return r.check?''+("checkbox"===r.check?t.checkbox[0]:"radio"===r.check?t.radio[0]:"")+"":""}(),function(){return'"+(''+(l?n.spread?t.branch[1]:t.branch[0]:t.leaf)+"")+(""+(n.name||"未命名")+"")}(),"
                  • "].join(""));l&&(s.append(c),i.tree(c,n.children)),e.append(s),"function"==typeof r.click&&i.click(s,n),i.spread(s,n),r.drag&&i.drag(s,n)})},r.prototype.click=function(e,o){var a=this,i=a.options;e.children("a").on("click",function(e){layui.stope(e),i.click(o)})},r.prototype.spread=function(e,o){var a=this,i=(a.options,e.children(".layui-tree-spread")),r=e.children("ul"),n=e.children("a"),l=function(){e.data("spread")?(e.data("spread",null),r.removeClass("layui-show"),i.html(t.arrow[0]),n.find(".layui-icon").html(t.branch[0])):(e.data("spread",!0),r.addClass("layui-show"),i.html(t.arrow[1]),n.find(".layui-icon").html(t.branch[1]))};r[0]&&(i.on("click",l),n.on("dblclick",l))},r.prototype.on=function(e){var a=this,r=a.options,t="layui-tree-drag";e.find("i").on("selectstart",function(e){return!1}),r.drag&&o(document).on("mousemove",function(e){var i=a.move;if(i.from){var r=(i.to,o('
                    '));e.preventDefault(),o("."+t)[0]||o("body").append(r);var n=o("."+t)[0]?o("."+t):r;n.addClass("layui-show").html(i.from.elem.children("a").html()),n.css({left:e.pageX+10,top:e.pageY+10})}}).on("mouseup",function(){var e=a.move;e.from&&(e.from.elem.children("a").removeClass(i),e.to&&e.to.elem.children("a").removeClass(i),a.move={},o("."+t).remove())})},r.prototype.move={},r.prototype.drag=function(e,a){var r=this,t=(r.options,e.children("a")),n=function(){var t=o(this),n=r.move;n.from&&(n.to={item:a,elem:e},t.addClass(i))};t.on("mousedown",function(){var o=r.move;o.from={item:a,elem:e}}),t.on("mouseenter",n).on("mousemove",n).on("mouseleave",function(){var e=o(this),a=r.move;a.from&&(delete a.to,e.removeClass(i))})},e("tree",function(e){var i=new r(e=e||{}),t=o(e.elem);return t[0]?void i.init(t):a.error("layui.tree 没有找到"+e.elem+"元素")})});layui.define(["laytpl","laypage","layer","form"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=layui.hint(),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,s,e,t)}},c=function(){var e=this,t=e.config,i=t.id;return i&&(c.config[i]=t),{reload:function(t){e.reload.call(e,t)},config:t}},s="table",u=".layui-table",h="layui-hide",f="layui-none",y="layui-table-view",p=".layui-table-header",m=".layui-table-body",v=".layui-table-main",g=".layui-table-fixed",x=".layui-table-fixed-l",b=".layui-table-fixed-r",k=".layui-table-tool",C=".layui-table-page",w=".layui-table-sort",N="layui-table-edit",F="layui-table-hover",W=function(e){var t='{{#if(item2.colspan){}} colspan="{{item2.colspan}}"{{#} if(item2.rowspan){}} rowspan="{{item2.rowspan}}"{{#}}}';return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),'",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
                    ','
                    1){ }}","group","{{# } else { }}","{{d.index}}-{{item2.field || i2}}",'{{# if(item2.type !== "normal"){ }}'," laytable-cell-{{ item2.type }}","{{# } }}","{{# } }}",'" {{#if(item2.align){}}align="{{item2.align}}"{{#}}}>','{{# if(item2.type === "checkbox"){ }}','',"{{# } else { }}",'{{item2.title||""}}',"{{# if(!(item2.colspan > 1) && item2.sort){ }}",'',"{{# } }}","{{# } }}","
                    ","
                    "].join("")},z=['',"","
                    "].join(""),A=['
                    ',"{{# if(d.data.toolbar){ }}",'
                    ',"{{# } }}",'
                    ',"{{# var left, right; }}",'
                    ',W(),"
                    ",'
                    ',z,"
                    ","{{# if(left){ }}",'
                    ','
                    ',W({fixed:!0}),"
                    ",'
                    ',z,"
                    ","
                    ","{{# }; }}","{{# if(right){ }}",'
                    ','
                    ',W({fixed:"right"}),'
                    ',"
                    ",'
                    ',z,"
                    ","
                    ","{{# }; }}","
                    ","{{# if(d.data.page){ }}",'
                    ','
                    ',"
                    ","{{# } }}","","
                    "].join(""),T=t(window),M=t(document),S=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};S.prototype.config={limit:10,loading:!0,cellMinWidth:60,text:{none:"无数据"}},S.prototype.render=function(){var e=this,a=e.config;if(a.elem=t(a.elem),a.where=a.where||{},a.id=a.id||a.elem.attr("id"),a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",countName:"count"},a.response),"object"==typeof a.page&&(a.limit=a.page.limit||a.limit,a.limits=a.page.limits||a.limits,e.page=a.page.curr=a.page.curr||1,delete a.page.elem,delete a.page.jump),!a.elem[0])return e;e.setArea();var l=a.elem,n=l.next("."+y),o=e.elem=t(i(A).render({VIEW_CLASS:y,data:a,index:e.index}));if(a.index=e.index,n[0]&&n.remove(),l.after(o),e.layHeader=o.find(p),e.layMain=o.find(v),e.layBody=o.find(m),e.layFixed=o.find(g),e.layFixLeft=o.find(x),e.layFixRight=o.find(b),e.layTool=o.find(k),e.layPage=o.find(C),e.layTool.html(i(t(a.toolbar).html()||"").render(a)),a.height&&e.fullSize(),a.cols.length>1){var r=e.layFixed.find(p).find("th");r.height(e.layHeader.height()-1-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom")))}e.pullData(e.page),e.events()},S.prototype.initOpts=function(e){var t=this,i=(t.config,{checkbox:48,space:15,numbers:40});e.checkbox&&(e.type="checkbox"),e.space&&(e.type="space"),e.type||(e.type="normal"),"normal"!==e.type&&(e.unresize=!0,e.width=e.width||i[e.type])},S.prototype.setArea=function(){var e=this,t=e.config,i=0,a=0,l=0,n=0,o=t.width||function(){var e=function(i){var a,l;i=i||t.elem.parent(),a=i.width();try{l="none"===i.css("display")}catch(n){}return!i[0]||a&&!l?a:e(i.parent())};return e()}();e.eachCols(function(){i++}),o-=function(){return"line"===t.skin||"nob"===t.skin?2:i+1}(),layui.each(t.cols,function(t,i){layui.each(i,function(t,l){var r;return l?(e.initOpts(l),r=l.width||0,void(l.colspan>1||(/\d+%$/.test(r)?l.width=r=Math.floor(parseFloat(r)/100*o):r||(l.width=r=0,a++),n+=r))):void i.splice(t,1)})}),e.autoColNums=a,o>n&&a&&(l=(o-n)/a),layui.each(t.cols,function(e,i){layui.each(i,function(e,i){var a=i.minWidth||t.cellMinWidth;i.colspan>1||0===i.width&&(i.width=Math.floor(l>=a?l:a))})}),t.height&&/^full-\d+$/.test(t.height)&&(e.fullHeightGap=t.height.split("-")[1],t.height=T.height()-e.fullHeightGap)},S.prototype.reload=function(e){var i=this;i.config.data&&i.config.data.constructor===Array&&delete i.config.data,i.config=t.extend({},i.config,e),i.render()},S.prototype.page=1,S.prototype.pullData=function(e,i){var a=this,n=a.config,o=n.request,r=n.response,d=function(){"object"==typeof n.initSort&&a.sort(n.initSort.field,n.initSort.type)};if(a.startTime=(new Date).getTime(),n.url){var c={};c[o.pageName]=e,c[o.limitName]=n.limit,t.ajax({type:n.method||"get",url:n.url,data:t.extend(c,n.where),dataType:"json",success:function(t){t[r.statusName]!=r.statusCode?(a.renderForm(),a.layMain.html('
                    '+(t[r.msgName]||"返回的数据状态异常")+"
                    ")):(a.renderData(t,e,t[r.countName]),d(),n.time=(new Date).getTime()-a.startTime+" ms"),i&&l.close(i),"function"==typeof n.done&&n.done(t,e,t[r.countName])},error:function(e,t){a.layMain.html('
                    数据接口请求异常
                    '),a.renderForm(),i&&l.close(i)}})}else if(n.data&&n.data.constructor===Array){var s={},u=e*n.limit-n.limit;s[r.dataName]=n.data.concat().splice(u,n.limit),s[r.countName]=n.data.length,a.renderData(s,e,n.data.length),d(),"function"==typeof n.done&&n.done(s,e,s[r.countName])}},S.prototype.eachCols=function(e){var i=t.extend(!0,[],this.config.cols),a=[],l=0;layui.each(i,function(e,t){layui.each(t,function(t,n){if(n.colspan>1){var o=0;l++,n.CHILD_COLS=[],layui.each(i[e+1],function(e,t){t.PARENT_COL||o==n.colspan||(t.PARENT_COL=l,n.CHILD_COLS.push(t),o+=t.colspan>1?t.colspan:1)})}n.PARENT_COL||a.push(n)})});var n=function(t){layui.each(t||a,function(t,i){return i.CHILD_COLS?n(i.CHILD_COLS):void e(t,i)})};n()},S.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,u=e[s.response.dataName]||[],y=[],p=[],m=[],v=function(){return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(u,function(e,a){var l=[],o=[],u=[],h=e+s.limit*(n-1)+1;0!==a.length&&(r||(a[d.config.indexName]=e),c.eachCols(function(e,n){var r=n.field||e,f=a[r];c.getColElem(c.layHeader,r);if(void 0!==f&&null!==f||(f=""),!(n.colspan>1)){var y=['",'
                    '+function(){var e=t.extend(!0,{LAY_INDEX:h},a);return"checkbox"===n.type?'":"numbers"===n.type?h:n.toolbar?i(t(n.toolbar).html()||"").render(e):n.templet?function(){return"function"==typeof n.templet?n.templet(e):i(t(n.templet).html()||String(f)).render(e)}():f}(),"
                    "].join("");l.push(y),n.fixed&&"right"!==n.fixed&&o.push(y),"right"===n.fixed&&u.push(y)}}),y.push(''+l.join("")+""),p.push(''+o.join("")+""),m.push(''+u.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+f).remove(),c.layMain.find("tbody").html(y.join("")),c.layFixLeft.find("tbody").html(p.join("")),c.layFixRight.find("tbody").html(m.join("")),c.renderForm(),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,void l.close(c.tipsIndex))};return c.key=s.id||s.index,d.cache[c.key]=u,c.layPage[0===u.length&&1==n?"addClass":"removeClass"](h),r?v():0===u.length?(c.renderForm(),c.layFixed.remove(),c.layMain.find("tbody").html(""),c.layMain.find("."+f).remove(),c.layMain.append('
                    '+s.text.none+"
                    ")):(v(),void(s.page&&(s.page=t.extend({elem:"layui-table-page"+s.index,count:o,limit:s.limit,limits:s.limits||[10,20,30,40,50,60,70,80,90],groups:3,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.pullData(e.curr,c.loading()))}},s.page),s.page.count=o,a.render(s.page))))},S.prototype.getColElem=function(e,t){var i=this,a=i.config;return e.eq(0).find(".laytable-cell-"+(a.index+"-"+t)+":eq(0)")},S.prototype.renderForm=function(e){n.render(e,"LAY-table-"+this.index)},S.prototype.sort=function(e,i,a,l){var n,r,c=this,u={},h=c.config,f=h.elem.attr("lay-filter"),y=d.cache[c.key];"string"==typeof e&&c.layHeader.find("th").each(function(i,a){var l=t(this),o=l.data("field");if(o===e)return e=l,n=o,!1});try{var n=n||e.data("field");if(c.sortKey&&!a&&n===c.sortKey.field&&i===c.sortKey.sort)return;var p=c.layHeader.find("th .laytable-cell-"+h.index+"-"+n).find(w);c.layHeader.find("th").find(w).removeAttr("lay-sort"),p.attr("lay-sort",i||null),c.layFixed.find("th")}catch(m){return o.error("Table modules: Did not match to field")}c.sortKey={field:n,sort:i},"asc"===i?r=layui.sort(y,n):"desc"===i?r=layui.sort(y,n,!0):(r=layui.sort(y,d.config.indexName),delete c.sortKey),u[h.response.dataName]=r,c.renderData(u,c.page,c.count,!0),l&&layui.event.call(e,s,"sort("+f+")",{field:n,type:i})},S.prototype.loading=function(){var e=this,t=e.config;if(t.loading&&t.url)return l.msg("数据请求中",{icon:16,offset:[e.elem.offset().top+e.elem.height()/2-35-T.scrollTop()+"px",e.elem.offset().left+e.elem.width()/2-90-T.scrollLeft()+"px"],time:-1,anim:-1,fixed:!1})},S.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&l[e].constructor!==Array&&(l[e][a.checkName]=t)},S.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){"checkbox"===a.type&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm("checkbox")),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm("checkbox")),a(!1)))},S.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet||{},n=l.cssRules||l.rules;layui.each(n,function(a,l){if(l.selectorText===".laytable-cell-"+i.index+"-"+e)return t(l),!0})},S.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=T.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),e=parseFloat(a)-parseFloat(t.layHeader.height())-1,i.toolbar&&(e-=t.layTool.outerHeight()),i.page&&(e=e-t.layPage.outerHeight()-1),t.layMain.css("height",e)},S.prototype.getScrollWidth=function(e){var t=0;return e?t=e.offsetWidth-e.clientWidth:(e=document.createElement("div"),e.style.width="100px",e.style.height="100px",e.style.overflowY="scroll",document.body.appendChild(e),t=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),t},S.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight"),n=e.getScrollWidth(e.layMain[0]),o=i.outerWidth()-e.layMain.width();if(e.autoColNums&&o<5&&!e.scrollPatchWStatus){var r=e.layHeader.eq(0).find("thead th:last-child"),d=r.data("field");e.getCssRule(d,function(t){var i=t.style.width||r.outerWidth();t.style.width=parseFloat(i)-n-o+"px",e.layMain.height()-e.layMain.prop("clientHeight")>0&&(t.style.width=parseFloat(t.style.width)-1+"px"),e.scrollPatchWStatus=!0})}if(a&&l){if(!e.elem.find(".layui-table-patch")[0]){var c=t('
                    ');c.find("div").css({width:a}),e.layHeader.eq(0).find("thead tr").append(c)}}else e.layHeader.eq(0).find(".layui-table-patch").remove();var s=e.layMain.height(),u=s-l;e.layFixed.find(m).css("height",i.height()>u?u:"auto"),e.layFixRight[o>0?"removeClass":"addClass"](h),e.layFixRight.css("right",a-1)},S.prototype.events=function(){var e,a=this,n=a.config,o=t("body"),c={},u=a.layHeader.find("th"),h=".layui-table-cell",f=n.elem.attr("lay-filter");u.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.attr("colspan")>1||i.data("unresize")||c.resizeStart||(c.allowResize=i.width()-l<=10,o.css("cursor",c.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);c.resizeStart||o.css("cursor","")}).on("mousedown",function(e){var i=t(this);if(c.allowResize){var l=i.data("field");e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],a.getCssRule(l,function(e){var t=e.style.width||i.outerWidth();c.rule=e,c.ruleWidth=parseFloat(t),c.minWidth=i.data("minwidth")||n.cellMinWidth})}}),M.on("mousemove",function(t){if(c.resizeStart){if(t.preventDefault(),c.rule){var i=c.ruleWidth+t.clientX-c.offset[0];i');d[0].value=e.data("content")||o.text(),e.find("."+N)[0]||e.append(d),d.focus()}else o.find(".layui-form-switch,.layui-form-checkbox")[0]||Math.round(o.prop("scrollWidth"))>Math.round(o.outerWidth())&&(a.tipsIndex=l.tips(['
                    ',o.html(),"
                    ",''].join(""),o[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:600,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}}))}),a.layBody.on("click","*[lay-event]",function(){var e=t(this),l=e.parents("tr").eq(0).data("index"),n=a.layBody.find('tr[data-index="'+l+'"]'),o="layui-table-click",r=d.cache[a.key][l];layui.event.call(this,s,"tool("+f+")",{data:d.clearCacheKey(r),event:e.attr("lay-event"),tr:n,del:function(){d.cache[a.key][l]=[],n.remove(),a.scrollPatch()},update:function(e){e=e||{},layui.each(e,function(e,l){if(e in r){var o,d=n.children('td[data-field="'+e+'"]');r[e]=l,a.eachCols(function(t,i){i.field==e&&i.templet&&(o=i.templet)}),d.children(h).html(o?i(t(o).html()||l).render(r):l),d.data("content",l)}})}}),n.addClass(o).siblings("tr").removeClass(o)}),a.layMain.on("scroll",function(){var e=t(this),i=e.scrollLeft(),n=e.scrollTop();a.layHeader.scrollLeft(i),a.layFixed.find(m).scrollTop(n),l.close(a.tipsIndex)}),T.on("resize",function(){a.fullSize(),a.scrollPatch()})},d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':u+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},d.checkStatus=function(e){var t=0,i=0,a=[],l=d.cache[e]||[];return layui.each(l,function(e,l){return l.constructor===Array?void i++:void(l[d.config.checkName]&&(t++,a.push(d.clearCacheKey(l))))}),{data:a,isAll:!!l.length&&t===l.length-i}},c.config={},d.reload=function(e,i){var a=c.config[e];return i=i||{},a?(i.data&&i.data.constructor===Array&&delete a.data,d.render(t.extend(!0,{},a,i))):o.error("The ID option was not found in the table instance")},d.render=function(e){var t=new S(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(s,d)});layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",l=">*[carousel-item]>*",o="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
                      ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
                    "].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a',e.bar1?'
                  • '+c[0]+"
                  • ":"",e.bar2?'
                  • '+c[1]+"
                  • ":"",'
                  • '+c[2]+"
                  • ",""].join("")),s=g.find("."+r),u=function(){var t=n.scrollTop();t>=e.showHeight?i||(s.show(),i=1):i&&(s.hide(),i=0)};t("."+a)[0]||("object"==typeof e.css&&g.css(e.css),l.append(g),u(),g.find("li").on("click",function(){var i=t(this),o=i.attr("lay-type");"top"===o&&t("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,o)}),n.on("scroll",function(){clearTimeout(o),o=setTimeout(function(){u()},100)}))},countdown:function(e,t,i){var o=this,a="function"==typeof t,r=new Date(e).getTime(),n=new Date(!t||a?(new Date).getTime():t).getTime(),l=r-n,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];a&&(i=t);var g=setTimeout(function(){o.countdown(e,n+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],t,g),l<=0&&clearTimeout(g),g},timeAgo:function(e,t){var i=this,o=[[],[]],a=(new Date).getTime()-new Date(e).getTime();return a>6912e5?(a=new Date(e),o[0][0]=i.digit(a.getFullYear(),4),o[0][1]=i.digit(a.getMonth()+1),o[0][2]=i.digit(a.getDate()),t||(o[1][0]=i.digit(a.getHours()),o[1][1]=i.digit(a.getMinutes()),o[1][2]=i.digit(a.getSeconds())),o[0].join("-")+" "+o[1].join(":")):a>=864e5?(a/1e3/60/60/24|0)+"天前":a>=36e5?(a/1e3/60/60|0)+"小时前":a>=12e4?(a/1e3/60|0)+"分钟前":a<0?"未来":"刚刚"},digit:function(e,t){var i="";e=String(e),t=t||2;for(var o=e.length;o';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),i||(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)});layui.define(["layer","form"],function(t){"use strict";var e=layui.$,i=layui.layer,a=layui.form,l=(layui.hint(),layui.device()),n="layedit",o="layui-show",r="layui-disabled",c=function(){var t=this;t.index=0,t.config={tool:["strong","italic","underline","del","|","left","center","right","|","link","unlink","face","image"],hideTool:[],height:280}};c.prototype.set=function(t){var i=this;return e.extend(!0,i.config,t),i},c.prototype.on=function(t,e){return layui.onevent(n,t,e)},c.prototype.build=function(t,i){i=i||{};var a=this,n=a.config,r="layui-layedit",c=e("#"+t),u="LAY_layedit_"+ ++a.index,d=c.next("."+r),y=e.extend({},n,i),f=function(){var t=[],e={};return layui.each(y.hideTool,function(t,i){e[i]=!0}),layui.each(y.tool,function(i,a){C[a]&&!e[a]&&t.push(C[a])}),t.join("")}(),m=e(['
                    ','
                    '+f+"
                    ",'
                    ','',"
                    ","
                    "].join(""));return l.ie&&l.ie<8?c.removeClass("layui-hide").addClass(o):(d[0]&&d.remove(),s.call(a,m,c[0],y),c.addClass("layui-hide").after(m),a.index)},c.prototype.getContent=function(t){var e=u(t);if(e[0])return d(e[0].document.body.innerHTML)},c.prototype.getText=function(t){var i=u(t);if(i[0])return e(i[0].document.body).text()},c.prototype.setContent=function(t,i,a){var l=u(t);l[0]&&(a?e(l[0].document.body).append(i):e(l[0].document.body).html(i),layedit.sync(t))},c.prototype.sync=function(t){var i=u(t);if(i[0]){var a=e("#"+i[1].attr("textarea"));a.val(d(i[0].document.body.innerHTML))}},c.prototype.getSelection=function(t){var e=u(t);if(e[0]){var i=m(e[0].document);return document.selection?i.text:i.toString()}};var s=function(t,i,a){var l=this,n=t.find("iframe");n.css({height:a.height}).on("load",function(){var o=n.contents(),r=n.prop("contentWindow"),c=o.find("head"),s=e([""].join("")),u=o.find("body");c.append(s),u.attr("contenteditable","true").css({"min-height":a.height}).html(i.value||""),y.apply(l,[r,n,i,a]),g.call(l,r,t,a)})},u=function(t){var i=e("#LAY_layedit_"+t),a=i.prop("contentWindow");return[a,i]},d=function(t){return 8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),t},y=function(t,a,n,o){var r=t.document,c=e(r.body);c.on("keydown",function(t){var e=t.keyCode;if(13===e){var a=m(r),l=p(a),n=l.parentNode;if("pre"===n.tagName.toLowerCase()){if(t.shiftKey)return;return i.msg("请暂时用shift+enter"),!1}r.execCommand("formatBlock",!1,"

                    ")}}),e(n).parents("form").on("submit",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on("paste",function(e){r.execCommand("formatBlock",!1,"

                    "),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find("*[style]").each(function(){var t=this.style.textAlign;this.removeAttribute("style"),e(this).css({"text-align":t||""})}),t.find("table").addClass("layui-table"),t.find("script,link").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute("text"),l.selection){var r=a.text||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop("outerHTML")),a.select()}else{var r=a.toString()||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l="layedit-tool-active",n=p(m(a)),o=function(e){return t.find(".layedit-tool-"+e)};i&&i[i.hasClass(l)?"removeClass":"addClass"](l),t.find(">i").removeClass(l),o("unlink").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;"b"!==t&&"strong"!==t||o("b").addClass(l),"i"!==t&&"em"!==t||o("i").addClass(l),"u"===t&&o("u").addClass(l),"strike"===t&&o("d").addClass(l),"p"===t&&("center"===e?o("center").addClass(l):"right"===e?o("right").addClass(l):o("left").addClass(l)),"a"===t&&(o("link").addClass(l),o("unlink").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr("href"),target:l.attr("target")},function(e){var a=l[0];"A"===a.tagName?a.href=e.url:v.call(t,"a",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand("unlink")},face:function(e){x.call(this,function(i){v.call(t,"img",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use("upload",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find("input")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,"img",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||"上传失败")}})})},code:function(e){k.call(o,function(i){v.call(t,"pre",{text:i.code,"lay-lang":i.lang},e)})},help:function(){i.open({type:2,title:"帮助",area:["600px","380px"],shadeClose:!0,shade:.1,skin:"layui-layer-msg",content:["http://www.layui.com/about/layedit/help.html","no"]})}},s=a.find(".layui-layedit-tool"),u=function(){var i=e(this),a=i.attr("layedit-event"),l=i.attr("lay-command");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand("formatBlock",!1,"

                    "),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(">i").on("mousedown",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)||u.call(this)}).on("click",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)&&u.call(this)}),o.on("click",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:"LAY_layedit_link",area:"350px",shade:.05,shadeClose:!0,moveType:1,title:"超链接",skin:"layui-layer-msg",content:['

                      ','
                    • ','','
                      ','',"
                      ","
                    • ",'
                    • ','','
                      ','",'","
                      ","
                    • ",'
                    • ','','',"
                    • ","
                    "].join(""),success:function(t,n){var o="submit(layedit-link-yes)";a.render("radio"),t.find(".layui-btn-primary").on("click",function(){i.close(n),l.focus()}),a.on(o,function(t){i.close(b.index),e&&e(t.field)})}});b.index=n},x=function(t){var a=function(){var t=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],e={};return layui.each(t,function(t,i){e[i]=layui.cache.dir+"images/face/"+t+".gif"}),e}();return x.hide=x.hide||function(t){"face"!==e(t.target).attr("layedit-event")&&i.close(x.index)},x.index=i.tips(function(){var t=[];return layui.each(a,function(e,i){t.push('
                  • '+e+'
                  • ')}),'
                      '+t.join("")+"
                    "}(),this,{tips:1,time:0,skin:"layui-box layui-util-face",maxWidth:500,success:function(l,n){l.css({marginTop:-4,marginLeft:-10}).find(".layui-clear>li").on("click",function(){t&&t({src:a[this.title],alt:this.title}),i.close(n)}),e(document).off("click",x.hide).on("click",x.hide)}})},k=function(t){var e=this,l=i.open({type:1,id:"LAY_layedit_code",area:"550px",shade:.05,shadeClose:!0,moveType:1,title:"插入代码",skin:"layui-layer-msg",content:['
                      ','
                    • ','','
                      ','","
                      ","
                    • ",'
                    • ','','
                      ','',"
                      ","
                    • ",'
                    • ','','',"
                    • ","
                    "].join(""),success:function(l,n){var o="submit(layedit-code-yes)";a.render("select"),l.find(".layui-btn-primary").on("click",function(){i.close(n),e.focus()}),a.on(o,function(e){i.close(k.index),t&&t(e.field)})}});k.index=l},C={html:'',strong:'',italic:'',underline:'',del:'',"|":'',left:'',center:'',right:'',link:'',unlink:'',face:'',image:'',code:'',help:''},w=new c;t(n,w)});layui.define("jquery",function(e){"use strict";var a=layui.$,l="http://www.layui.com/doc/modules/code.html";e("code",function(e){var t=[];e=e||{},e.elem=a(e.elem||".layui-code"),e.about=!("about"in e)||e.about,e.elem.each(function(){t.push(this)}),layui.each(t.reverse(),function(t,i){var c=a(i),o=c.html();(c.attr("lay-encode")||e.encode)&&(o=o.replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
                    1. '+o.replace(/[\r\t\n]+/g,"
                    2. ")+"
                    "),c.find(">.layui-code-h3")[0]||c.prepend('

                    '+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

                    ");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); \ No newline at end of file diff --git a/static/plugs/layui/layui.js b/static/plugs/layui/layui.js index b553fb846..8faa576fb 100644 --- a/static/plugs/layui/layui.js +++ b/static/plugs/layui/layui.js @@ -1,2 +1,2 @@ -/** layui-v2.1.4 MIT License By http://www.layui.com */ - ;!function(e){"use strict";var t=document,o={modules:{},status:{},timeout:10,event:{}},n=function(){this.v="2.1.4"},r=function(){var e=t.scripts,o=e[e.length-1].src;return o.substring(0,o.lastIndexOf("/")+1)}(),a=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},i="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",util:"modules/util",flow:"modules/flow",carousel:"modules/carousel",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};n.prototype.cache=o,n.prototype.define=function(e,t){var n=this,r="function"==typeof e,a=function(){return"function"==typeof t&&t(function(e,t){layui[e]=t,o.status[e]=!0}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?a.call(n):(n.use(e,a),n)},n.prototype.use=function(e,n,l){function s(e,t){var n="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||n.test((e.currentTarget||e.srcElement).readyState))&&(o.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*o.timeout/4?a(f+" is not a valid module"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):"function"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){"jquery"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?a(f+" is not a valid module"):void("string"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":o.base||"")+(y.modules[f]||f)+".js";v.async=!0,v.charset="utf-8",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||i?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](o)},n.prototype.link=function(e,n,r){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof n&&(r=n);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(o.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof n?i:(function p(){return++y>1e3*o.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(c),"width"))?function(){n()}():setTimeout(p,100))}(),i)},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+"css/"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?a("模块名 "+o+" 已被占用"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,"").replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o){if(t=t||"layui",e.JSON&&e.JSON.parse){if(null===o)return delete localStorage[t];o="object"==typeof o?o:{key:o};try{var n=JSON.parse(localStorage[t])}catch(r){var n={}}return o.value&&(n[o.key]=o.value),o.remove&&delete n[o.key],localStorage[t]=JSON.stringify(n),o.key?n[o.key]:n}},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?"windows":/linux/.test(o)?"linux":/iphone|ipod|ipad|ios/.test(o)?"ios":/mac/.test(o)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((o.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:n("micromessenger")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios="ios"===r.os,r},n.prototype.hint=function(){return{error:a}},n.prototype.each=function(e,t){var o,n=this;if("function"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;oa?1:r0;r--)if("interactive"===n[r].readyState){e=n[r].src;break}return e||n[o].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),a=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},i="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",util:"modules/util",flow:"modules/flow",carousel:"modules/carousel",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};o.prototype.cache=n,o.prototype.define=function(e,t){var o=this,r="function"==typeof e,a=function(){var e=function(e,t){layui[e]=t,n.status[e]=!0};return"function"==typeof t&&t(function(o,r){e(o,r),n.callback[o]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?a.call(o):(o.use(e,a),o)},o.prototype.use=function(e,o,l){function s(e,t){var o="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||o.test((e.currentTarget||e.srcElement).readyState))&&(n.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*n.timeout/4?a(f+" is not a valid module"):void(n.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),o,l):"function"==typeof o&&o.apply(layui,l)}var y=this,p=n.dir=n.dir?n.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,n){"jquery"===n&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],n.host=n.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(n.modules[f])!function g(){return++m>1e3*n.timeout/4?a(f+" is not a valid module"):void("string"==typeof n.modules[f]&&n.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":/^\{\/\}/.test(y.modules[f])?"":n.base||"")+(y.modules[f]||f)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=n.version===!0?n.v||(new Date).getTime():n.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||i?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),n.modules[f]=h}return y},o.prototype.getStyle=function(t,n){var o=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return o[o.getPropertyValue?"getPropertyValue":"getAttribute"](n)},o.prototype.link=function(e,o,r){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof o&&(r=o);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(n.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof o?i:(function p(){return++y>1e3*n.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(c),"width"))?function(){o()}():setTimeout(p,100))}(),i)},n.callback={},o.prototype.factory=function(e){if(layui[e])return"function"==typeof n.callback[e]?n.callback[e]:null},o.prototype.addcss=function(e,t,o){return layui.link(n.dir+"css/"+e,t,o)},o.prototype.img=function(e,t,n){var o=new Image;return o.src=e,o.complete?t(o):(o.onload=function(){o.onload=null,t(o)},void(o.onerror=function(e){o.onerror=null,n(e)}))},o.prototype.config=function(e){e=e||{};for(var t in e)n[t]=e[t];return this},o.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),o.prototype.extend=function(e){var t=this;e=e||{};for(var n in e)t[n]||t.modules[n]?a("模块名 "+n+" 已被占用"):t.modules[n]=e[n];return t},o.prototype.router=function(e){var t=this,e=e||location.hash,n={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(n.href=e=e.replace(/^#\//,""),e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),n.search[t[0]]=t[1]}():n.path.push(t)}),n):n},o.prototype.data=function(t,n,o){if(t=t||"layui",o=o||localStorage,e.JSON&&e.JSON.parse){if(null===n)return delete o[t];n="object"==typeof n?n:{key:n};try{var r=JSON.parse(o[t])}catch(a){var r={}}return"value"in n&&(r[n.key]=n.value),n.remove&&delete r[n.key],o[t]=JSON.stringify(r),n.key?r[n.key]:r}},o.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},o.prototype.device=function(t){var n=navigator.userAgent.toLowerCase(),o=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(n.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(n)?"windows":/linux/.test(n)?"linux":/iphone|ipod|ipad|ios/.test(n)?"ios":/mac/.test(n)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((n.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:o("micromessenger")};return t&&!r[t]&&(r[t]=o(t)),r.android=/android/.test(n),r.ios="ios"===r.os,r},o.prototype.hint=function(){return{error:a}},o.prototype.each=function(e,t){var n,o=this;if("function"!=typeof t)return o;if(e=e||[],e.constructor===Object){for(n in e)if(t.call(e[n],n,e[n]))break}else for(n=0;na?1:r-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h,m,g=i&&i.split("/"),v=y.map,x=v&&v["*"];if(e&&(e=e.split("/"),d=e.length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[d])&&(e[d]=e[d].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&g&&(m=g.slice(0,g.length-1),e=m.concat(e)),t(e),e=e.join("/")),r&&v&&(g||x)){a=e.split("/");e:for(s=a.length;s>0;s-=1){if(c=a.slice(0,s).join("/"),g)for(u=g.length;u>0;u-=1)if(o=getOwn(v,g.slice(0,u).join("/")),o&&(o=getOwn(o,c))){p=o,f=s;break e}!l&&x&&getOwn(x,c)&&(l=getOwn(x,c),h=s)}!p&&l&&(p=l,f=h),p&&(a.splice(0,f,p),e=a.join("/"))}return n=getOwn(y.pkgs,e),n?n:e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(T+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):e.indexOf("!")===-1?i(e,p,n):e:(h=i(e,p,n),c=o(h),d=c[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(A+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e),n.error&&"error"===t?i(n.error):n.on(t,i)):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(e=O.shift(),null===e[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},T=1,A=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;return this.shim?void q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()})):e.prefix?this.callPlugin():this.load()}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&(t=this.module,t?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),d=getOwn(S,o.id),void(d&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):(n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})}),n.error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(i.indexOf("?")===-1?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(y.paths[i]=e.location),y.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),f=s(a(null,t)),f.skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return n!==-1&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){var t=getOwn(S,e.id);t&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(i=O.shift(),null===i[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,n=e.split("/"),o=n.length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js"),s=("/"===s.charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.2",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if("undefined"==typeof define){if("undefined"!=typeof requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}"undefined"==typeof require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),n=getOwn(contexts,a),n||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick="undefined"!=typeof setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],baseElement=document.getElementsByTagName("base")[0],baseElement&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return r=req.createNode(n,t,i),r.setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||mainScript.indexOf("!")!==-1||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript(),r&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")])),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout); \ No newline at end of file +var requirejs,require,define;!function(global,setTimeout){function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){if(e){var i;for(i=0;i-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h=i&&i.split("/"),m=y.map,g=m&&m["*"];if(e&&(c=(e=e.split("/")).length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),t(e),e=e.join("/")),r&&m&&(h||g)){e:for(a=(o=e.split("/")).length;a>0;a-=1){if(u=o.slice(0,a).join("/"),h)for(s=h.length;s>0;s-=1)if((n=getOwn(m,h.slice(0,s).join("/")))&&(n=getOwn(n,u))){d=n,p=a;break e}!f&&g&&getOwn(g,u)&&(f=getOwn(g,u),l=a)}!d&&f&&(d=f,p=l),d&&(o.splice(0,p,d),e=o.join("/"))}return getOwn(y.pkgs,e)||e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(T+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=r?e:s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):-1===e.indexOf("!")?i(e,p,n):e:(d=(c=o(h=i(e,p,n)))[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(A+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e)).error&&"error"===t?i(n.error):n.on(t,i):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(null===(e=O.shift())[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},T=1,A=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;if(!this.shim)return e.prefix?this.callPlugin():this.load();q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()}))}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&((t=this.module)?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap,!0),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),void((d=getOwn(S,o.id))&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):((n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})})).error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(-1===i.indexOf("?")?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t;t=(e="string"==typeof e?{name:e}:e).name,e.location&&(y.paths[t]=e.location),y.pkgs[t]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){return function(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),(f=s(a(null,t))).skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return-1!==n&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){getOwn(S,e.id)&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(null===(i=O.shift())[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,o=(n=e.split("/")).length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s=("/"===(s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js")).charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.5",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if(void 0===define){if(void 0!==requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}void 0===require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),(n=getOwn(contexts,a))||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick=void 0!==setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],(baseElement=document.getElementsByTagName("base")[0])&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return(r=req.createNode(n,t,i)).setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||-1!==mainScript.indexOf("!")||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript())&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")]),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout); \ No newline at end of file diff --git a/static/theme/default/css/animate.css b/static/theme/css/animate.css similarity index 95% rename from static/theme/default/css/animate.css rename to static/theme/css/animate.css index 1cc38b6bd..a7e47e97a 100644 --- a/static/theme/default/css/animate.css +++ b/static/theme/css/animate.css @@ -1,2860 +1,2860 @@ -@charset "UTF-8"; - -/* +---------------------------------------------------------------------- -// | ThinkAdmin -// +---------------------------------------------------------------------- -// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] -// +---------------------------------------------------------------------- -// | 官方网站: http://think.ctolog.com -// +---------------------------------------------------------------------- -// | 开源协议 ( https://mit-license.org ) -// +---------------------------------------------------------------------- -// | github开源项目:https://github.com/zoujingli/ThinkAdmin -// +--------------------------------------------------------------------*/ - -/*! -Animate.css - http://daneden.me/animate -Licensed under the MIT license - -Copyright (c) 2013 Daniel Eden - -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. -*/ - -.animated { - -webkit-animation-duration: 1s; - animation-duration: 1s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - z-index: 100; -} - -.animated.infinite { - -webkit-animation-iteration-count: infinite; - animation-iteration-count: infinite; -} - -.animated.hinge { - -webkit-animation-duration: 2s; - animation-duration: 2s; -} - -@-webkit-keyframes bounce { - 0%, 20%, 50%, 80%, 100% { - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 40% { - -webkit-transform: translateY(-30px); - transform: translateY(-30px); - } - - 60% { - -webkit-transform: translateY(-15px); - transform: translateY(-15px); - } -} - -@keyframes bounce { - 0%, 20%, 50%, 80%, 100% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 40% { - -webkit-transform: translateY(-30px); - -ms-transform: translateY(-30px); - transform: translateY(-30px); - } - - 60% { - -webkit-transform: translateY(-15px); - -ms-transform: translateY(-15px); - transform: translateY(-15px); - } -} - -.bounce { - -webkit-animation-name: bounce; - animation-name: bounce; -} - -@-webkit-keyframes flash { - 0%, 50%, 100% { - opacity: 1; - } - - 25%, 75% { - opacity: 0; - } -} - -@keyframes flash { - 0%, 50%, 100% { - opacity: 1; - } - - 25%, 75% { - opacity: 0; - } -} - -.flash { - -webkit-animation-name: flash; - animation-name: flash; -} - -/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ - -@-webkit-keyframes pulse { - 0% { - -webkit-transform: scale(1); - transform: scale(1); - } - - 50% { - -webkit-transform: scale(1.1); - transform: scale(1.1); - } - - 100% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes pulse { - 0% { - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - - 50% { - -webkit-transform: scale(1.1); - -ms-transform: scale(1.1); - transform: scale(1.1); - } - - 100% { - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } -} - -.pulse { - -webkit-animation-name: pulse; - animation-name: pulse; -} - -@-webkit-keyframes rubberBand { - 0% { - -webkit-transform: scale(1); - transform: scale(1); - } - - 30% { - -webkit-transform: scaleX(1.25) scaleY(0.75); - transform: scaleX(1.25) scaleY(0.75); - } - - 40% { - -webkit-transform: scaleX(0.75) scaleY(1.25); - transform: scaleX(0.75) scaleY(1.25); - } - - 60% { - -webkit-transform: scaleX(1.15) scaleY(0.85); - transform: scaleX(1.15) scaleY(0.85); - } - - 100% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes rubberBand { - 0% { - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - - 30% { - -webkit-transform: scaleX(1.25) scaleY(0.75); - -ms-transform: scaleX(1.25) scaleY(0.75); - transform: scaleX(1.25) scaleY(0.75); - } - - 40% { - -webkit-transform: scaleX(0.75) scaleY(1.25); - -ms-transform: scaleX(0.75) scaleY(1.25); - transform: scaleX(0.75) scaleY(1.25); - } - - 60% { - -webkit-transform: scaleX(1.15) scaleY(0.85); - -ms-transform: scaleX(1.15) scaleY(0.85); - transform: scaleX(1.15) scaleY(0.85); - } - - 100% { - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } -} - -.rubberBand { - -webkit-animation-name: rubberBand; - animation-name: rubberBand; -} - -@-webkit-keyframes shake { - 0%, 100% { - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 10%, 30%, 50%, 70%, 90% { - -webkit-transform: translateX(-10px); - transform: translateX(-10px); - } - - 20%, 40%, 60%, 80% { - -webkit-transform: translateX(10px); - transform: translateX(10px); - } -} - -@keyframes shake { - 0%, 100% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 10%, 30%, 50%, 70%, 90% { - -webkit-transform: translateX(-10px); - -ms-transform: translateX(-10px); - transform: translateX(-10px); - } - - 20%, 40%, 60%, 80% { - -webkit-transform: translateX(10px); - -ms-transform: translateX(10px); - transform: translateX(10px); - } -} - -.shake { - -webkit-animation-name: shake; - animation-name: shake; -} - -@-webkit-keyframes swing { - 20% { - -webkit-transform: rotate(15deg); - transform: rotate(15deg); - } - - 40% { - -webkit-transform: rotate(-10deg); - transform: rotate(-10deg); - } - - 60% { - -webkit-transform: rotate(5deg); - transform: rotate(5deg); - } - - 80% { - -webkit-transform: rotate(-5deg); - transform: rotate(-5deg); - } - - 100% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } -} - -@keyframes swing { - 20% { - -webkit-transform: rotate(15deg); - -ms-transform: rotate(15deg); - transform: rotate(15deg); - } - - 40% { - -webkit-transform: rotate(-10deg); - -ms-transform: rotate(-10deg); - transform: rotate(-10deg); - } - - 60% { - -webkit-transform: rotate(5deg); - -ms-transform: rotate(5deg); - transform: rotate(5deg); - } - - 80% { - -webkit-transform: rotate(-5deg); - -ms-transform: rotate(-5deg); - transform: rotate(-5deg); - } - - 100% { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } -} - -.swing { - -webkit-transform-origin: top center; - -ms-transform-origin: top center; - transform-origin: top center; - -webkit-animation-name: swing; - animation-name: swing; -} - -@-webkit-keyframes tada { - 0% { - -webkit-transform: scale(1); - transform: scale(1); - } - - 10%, 20% { - -webkit-transform: scale(0.9) rotate(-3deg); - transform: scale(0.9) rotate(-3deg); - } - - 30%, 50%, 70%, 90% { - -webkit-transform: scale(1.1) rotate(3deg); - transform: scale(1.1) rotate(3deg); - } - - 40%, 60%, 80% { - -webkit-transform: scale(1.1) rotate(-3deg); - transform: scale(1.1) rotate(-3deg); - } - - 100% { - -webkit-transform: scale(1) rotate(0); - transform: scale(1) rotate(0); - } -} - -@keyframes tada { - 0% { - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - - 10%, 20% { - -webkit-transform: scale(0.9) rotate(-3deg); - -ms-transform: scale(0.9) rotate(-3deg); - transform: scale(0.9) rotate(-3deg); - } - - 30%, 50%, 70%, 90% { - -webkit-transform: scale(1.1) rotate(3deg); - -ms-transform: scale(1.1) rotate(3deg); - transform: scale(1.1) rotate(3deg); - } - - 40%, 60%, 80% { - -webkit-transform: scale(1.1) rotate(-3deg); - -ms-transform: scale(1.1) rotate(-3deg); - transform: scale(1.1) rotate(-3deg); - } - - 100% { - -webkit-transform: scale(1) rotate(0); - -ms-transform: scale(1) rotate(0); - transform: scale(1) rotate(0); - } -} - -.tada { - -webkit-animation-name: tada; - animation-name: tada; -} - -/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ - -@-webkit-keyframes wobble { - 0% { - -webkit-transform: translateX(0%); - transform: translateX(0%); - } - - 15% { - -webkit-transform: translateX(-25%) rotate(-5deg); - transform: translateX(-25%) rotate(-5deg); - } - - 30% { - -webkit-transform: translateX(20%) rotate(3deg); - transform: translateX(20%) rotate(3deg); - } - - 45% { - -webkit-transform: translateX(-15%) rotate(-3deg); - transform: translateX(-15%) rotate(-3deg); - } - - 60% { - -webkit-transform: translateX(10%) rotate(2deg); - transform: translateX(10%) rotate(2deg); - } - - 75% { - -webkit-transform: translateX(-5%) rotate(-1deg); - transform: translateX(-5%) rotate(-1deg); - } - - 100% { - -webkit-transform: translateX(0%); - transform: translateX(0%); - } -} - -@keyframes wobble { - 0% { - -webkit-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 15% { - -webkit-transform: translateX(-25%) rotate(-5deg); - -ms-transform: translateX(-25%) rotate(-5deg); - transform: translateX(-25%) rotate(-5deg); - } - - 30% { - -webkit-transform: translateX(20%) rotate(3deg); - -ms-transform: translateX(20%) rotate(3deg); - transform: translateX(20%) rotate(3deg); - } - - 45% { - -webkit-transform: translateX(-15%) rotate(-3deg); - -ms-transform: translateX(-15%) rotate(-3deg); - transform: translateX(-15%) rotate(-3deg); - } - - 60% { - -webkit-transform: translateX(10%) rotate(2deg); - -ms-transform: translateX(10%) rotate(2deg); - transform: translateX(10%) rotate(2deg); - } - - 75% { - -webkit-transform: translateX(-5%) rotate(-1deg); - -ms-transform: translateX(-5%) rotate(-1deg); - transform: translateX(-5%) rotate(-1deg); - } - - 100% { - -webkit-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } -} - -.wobble { - -webkit-animation-name: wobble; - animation-name: wobble; -} - -@-webkit-keyframes bounceIn { - 0% { - opacity: 0; - -webkit-transform: scale(.3); - transform: scale(.3); - } - - 50% { - opacity: 1; - -webkit-transform: scale(1.05); - transform: scale(1.05); - } - - 70% { - -webkit-transform: scale(.9); - transform: scale(.9); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes bounceIn { - 0% { - opacity: 0; - -webkit-transform: scale(.3); - -ms-transform: scale(.3); - transform: scale(.3); - } - - 50% { - opacity: 1; - -webkit-transform: scale(1.05); - -ms-transform: scale(1.05); - transform: scale(1.05); - } - - 70% { - -webkit-transform: scale(.9); - -ms-transform: scale(.9); - transform: scale(.9); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } -} - -.bounceIn { - -webkit-animation-name: bounceIn; - animation-name: bounceIn; -} - -@-webkit-keyframes bounceInDown { - 0% { - opacity: 0; - -webkit-transform: translateY(-2000px); - transform: translateY(-2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateY(30px); - transform: translateY(30px); - } - - 80% { - -webkit-transform: translateY(-10px); - transform: translateY(-10px); - } - - 100% { - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes bounceInDown { - 0% { - opacity: 0; - -webkit-transform: translateY(-2000px); - -ms-transform: translateY(-2000px); - transform: translateY(-2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateY(30px); - -ms-transform: translateY(30px); - transform: translateY(30px); - } - - 80% { - -webkit-transform: translateY(-10px); - -ms-transform: translateY(-10px); - transform: translateY(-10px); - } - - 100% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } -} - -.bounceInDown { - -webkit-animation-name: bounceInDown; - animation-name: bounceInDown; -} - -@-webkit-keyframes bounceInLeft { - 0% { - opacity: 0; - -webkit-transform: translateX(-2000px); - transform: translateX(-2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(30px); - transform: translateX(30px); - } - - 80% { - -webkit-transform: translateX(-10px); - transform: translateX(-10px); - } - - 100% { - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes bounceInLeft { - 0% { - opacity: 0; - -webkit-transform: translateX(-2000px); - -ms-transform: translateX(-2000px); - transform: translateX(-2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(30px); - -ms-transform: translateX(30px); - transform: translateX(30px); - } - - 80% { - -webkit-transform: translateX(-10px); - -ms-transform: translateX(-10px); - transform: translateX(-10px); - } - - 100% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.bounceInLeft { - -webkit-animation-name: bounceInLeft; - animation-name: bounceInLeft; -} - -@-webkit-keyframes bounceInRight { - 0% { - opacity: 0; - -webkit-transform: translateX(2000px); - transform: translateX(2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(-30px); - transform: translateX(-30px); - } - - 80% { - -webkit-transform: translateX(10px); - transform: translateX(10px); - } - - 100% { - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes bounceInRight { - 0% { - opacity: 0; - -webkit-transform: translateX(2000px); - -ms-transform: translateX(2000px); - transform: translateX(2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(-30px); - -ms-transform: translateX(-30px); - transform: translateX(-30px); - } - - 80% { - -webkit-transform: translateX(10px); - -ms-transform: translateX(10px); - transform: translateX(10px); - } - - 100% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.bounceInRight { - -webkit-animation-name: bounceInRight; - animation-name: bounceInRight; -} - -@-webkit-keyframes bounceInUp { - 0% { - opacity: 0; - -webkit-transform: translateY(2000px); - transform: translateY(2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateY(-30px); - transform: translateY(-30px); - } - - 80% { - -webkit-transform: translateY(10px); - transform: translateY(10px); - } - - 100% { - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes bounceInUp { - 0% { - opacity: 0; - -webkit-transform: translateY(2000px); - -ms-transform: translateY(2000px); - transform: translateY(2000px); - } - - 60% { - opacity: 1; - -webkit-transform: translateY(-30px); - -ms-transform: translateY(-30px); - transform: translateY(-30px); - } - - 80% { - -webkit-transform: translateY(10px); - -ms-transform: translateY(10px); - transform: translateY(10px); - } - - 100% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } -} - -.bounceInUp { - -webkit-animation-name: bounceInUp; - animation-name: bounceInUp; -} - -@-webkit-keyframes bounceOut { - 0% { - -webkit-transform: scale(1); - transform: scale(1); - } - - 25% { - -webkit-transform: scale(.95); - transform: scale(.95); - } - - 50% { - opacity: 1; - -webkit-transform: scale(1.1); - transform: scale(1.1); - } - - 100% { - opacity: 0; - -webkit-transform: scale(.3); - transform: scale(.3); - } -} - -@keyframes bounceOut { - 0% { - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - - 25% { - -webkit-transform: scale(.95); - -ms-transform: scale(.95); - transform: scale(.95); - } - - 50% { - opacity: 1; - -webkit-transform: scale(1.1); - -ms-transform: scale(1.1); - transform: scale(1.1); - } - - 100% { - opacity: 0; - -webkit-transform: scale(.3); - -ms-transform: scale(.3); - transform: scale(.3); - } -} - -.bounceOut { - -webkit-animation-name: bounceOut; - animation-name: bounceOut; -} - -@-webkit-keyframes bounceOutDown { - 0% { - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateY(-20px); - transform: translateY(-20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(2000px); - transform: translateY(2000px); - } -} - -@keyframes bounceOutDown { - 0% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateY(-20px); - -ms-transform: translateY(-20px); - transform: translateY(-20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(2000px); - -ms-transform: translateY(2000px); - transform: translateY(2000px); - } -} - -.bounceOutDown { - -webkit-animation-name: bounceOutDown; - animation-name: bounceOutDown; -} - -@-webkit-keyframes bounceOutLeft { - 0% { - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateX(20px); - transform: translateX(20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-2000px); - transform: translateX(-2000px); - } -} - -@keyframes bounceOutLeft { - 0% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateX(20px); - -ms-transform: translateX(20px); - transform: translateX(20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-2000px); - -ms-transform: translateX(-2000px); - transform: translateX(-2000px); - } -} - -.bounceOutLeft { - -webkit-animation-name: bounceOutLeft; - animation-name: bounceOutLeft; -} - -@-webkit-keyframes bounceOutRight { - 0% { - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateX(-20px); - transform: translateX(-20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(2000px); - transform: translateX(2000px); - } -} - -@keyframes bounceOutRight { - 0% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateX(-20px); - -ms-transform: translateX(-20px); - transform: translateX(-20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(2000px); - -ms-transform: translateX(2000px); - transform: translateX(2000px); - } -} - -.bounceOutRight { - -webkit-animation-name: bounceOutRight; - animation-name: bounceOutRight; -} - -@-webkit-keyframes bounceOutUp { - 0% { - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateY(20px); - transform: translateY(20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-2000px); - transform: translateY(-2000px); - } -} - -@keyframes bounceOutUp { - 0% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 20% { - opacity: 1; - -webkit-transform: translateY(20px); - -ms-transform: translateY(20px); - transform: translateY(20px); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-2000px); - -ms-transform: translateY(-2000px); - transform: translateY(-2000px); - } -} - -.bounceOutUp { - -webkit-animation-name: bounceOutUp; - animation-name: bounceOutUp; -} - -@-webkit-keyframes fadeIn { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -@keyframes fadeIn { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -.fadeIn { - -webkit-animation-name: fadeIn; - animation-name: fadeIn; -} - -@-webkit-keyframes fadeInDown { - 0% { - opacity: 0; - -webkit-transform: translateY(-20px); - transform: translateY(-20px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes fadeInDown { - 0% { - opacity: 0; - -webkit-transform: translateY(-20px); - -ms-transform: translateY(-20px); - transform: translateY(-20px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } -} - -.fadeInDown { - -webkit-animation-name: fadeInDown; - animation-name: fadeInDown; -} - -@-webkit-keyframes fadeInDownBig { - 0% { - opacity: 0; - -webkit-transform: translateY(-2000px); - transform: translateY(-2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes fadeInDownBig { - 0% { - opacity: 0; - -webkit-transform: translateY(-2000px); - -ms-transform: translateY(-2000px); - transform: translateY(-2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } -} - -.fadeInDownBig { - -webkit-animation-name: fadeInDownBig; - animation-name: fadeInDownBig; -} - -@-webkit-keyframes fadeInLeft { - 0% { - opacity: 0; - -webkit-transform: translateX(-20px); - transform: translateX(-20px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes fadeInLeft { - 0% { - opacity: 0; - -webkit-transform: translateX(-20px); - -ms-transform: translateX(-20px); - transform: translateX(-20px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.fadeInLeft { - -webkit-animation-name: fadeInLeft; - animation-name: fadeInLeft; -} - -@-webkit-keyframes fadeInLeftBig { - 0% { - opacity: 0; - -webkit-transform: translateX(-2000px); - transform: translateX(-2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes fadeInLeftBig { - 0% { - opacity: 0; - -webkit-transform: translateX(-2000px); - -ms-transform: translateX(-2000px); - transform: translateX(-2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.fadeInLeftBig { - -webkit-animation-name: fadeInLeftBig; - animation-name: fadeInLeftBig; -} - -@-webkit-keyframes fadeInRight { - 0% { - opacity: 0; - -webkit-transform: translateX(20px); - transform: translateX(20px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes fadeInRight { - 0% { - opacity: 0; - -webkit-transform: translateX(40px); - -ms-transform: translateX(40px); - transform: translateX(40px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.fadeInRight { - -webkit-animation-name: fadeInRight; - animation-name: fadeInRight; -} - -@-webkit-keyframes fadeInRightBig { - 0% { - opacity: 0; - -webkit-transform: translateX(2000px); - transform: translateX(2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes fadeInRightBig { - 0% { - opacity: 0; - -webkit-transform: translateX(2000px); - -ms-transform: translateX(2000px); - transform: translateX(2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.fadeInRightBig { - -webkit-animation-name: fadeInRightBig; - animation-name: fadeInRightBig; -} - -@-webkit-keyframes fadeInUp { - 0% { - opacity: 0; - -webkit-transform: translateY(20px); - transform: translateY(20px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes fadeInUp { - 0% { - opacity: 0; - -webkit-transform: translateY(20px); - -ms-transform: translateY(20px); - transform: translateY(20px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } -} - -.fadeInUp { - -webkit-animation-name: fadeInUp; - animation-name: fadeInUp; -} - -@-webkit-keyframes fadeInUpBig { - 0% { - opacity: 0; - -webkit-transform: translateY(2000px); - transform: translateY(2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes fadeInUpBig { - 0% { - opacity: 0; - -webkit-transform: translateY(2000px); - -ms-transform: translateY(2000px); - transform: translateY(2000px); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } -} - -.fadeInUpBig { - -webkit-animation-name: fadeInUpBig; - animation-name: fadeInUpBig; -} - -@-webkit-keyframes fadeOut { - 0% { - opacity: 1; - } - - 100% { - opacity: 0; - } -} - -@keyframes fadeOut { - 0% { - opacity: 1; - } - - 100% { - opacity: 0; - } -} - -.fadeOut { - -webkit-animation-name: fadeOut; - animation-name: fadeOut; -} - -@-webkit-keyframes fadeOutDown { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(20px); - transform: translateY(20px); - } -} - -@keyframes fadeOutDown { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(20px); - -ms-transform: translateY(20px); - transform: translateY(20px); - } -} - -.fadeOutDown { - -webkit-animation-name: fadeOutDown; - animation-name: fadeOutDown; -} - -@-webkit-keyframes fadeOutDownBig { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(2000px); - transform: translateY(2000px); - } -} - -@keyframes fadeOutDownBig { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(2000px); - -ms-transform: translateY(2000px); - transform: translateY(2000px); - } -} - -.fadeOutDownBig { - -webkit-animation-name: fadeOutDownBig; - animation-name: fadeOutDownBig; -} - -@-webkit-keyframes fadeOutLeft { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-20px); - transform: translateX(-20px); - } -} - -@keyframes fadeOutLeft { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-20px); - -ms-transform: translateX(-20px); - transform: translateX(-20px); - } -} - -.fadeOutLeft { - -webkit-animation-name: fadeOutLeft; - animation-name: fadeOutLeft; -} - -@-webkit-keyframes fadeOutLeftBig { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-2000px); - transform: translateX(-2000px); - } -} - -@keyframes fadeOutLeftBig { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-2000px); - -ms-transform: translateX(-2000px); - transform: translateX(-2000px); - } -} - -.fadeOutLeftBig { - -webkit-animation-name: fadeOutLeftBig; - animation-name: fadeOutLeftBig; -} - -@-webkit-keyframes fadeOutRight { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(20px); - transform: translateX(20px); - } -} - -@keyframes fadeOutRight { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(20px); - -ms-transform: translateX(20px); - transform: translateX(20px); - } -} - -.fadeOutRight { - -webkit-animation-name: fadeOutRight; - animation-name: fadeOutRight; -} - -@-webkit-keyframes fadeOutRightBig { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(2000px); - transform: translateX(2000px); - } -} - -@keyframes fadeOutRightBig { - 0% { - opacity: 1; - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(2000px); - -ms-transform: translateX(2000px); - transform: translateX(2000px); - } -} - -.fadeOutRightBig { - -webkit-animation-name: fadeOutRightBig; - animation-name: fadeOutRightBig; -} - -@-webkit-keyframes fadeOutUp { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-20px); - transform: translateY(-20px); - } -} - -@keyframes fadeOutUp { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-20px); - -ms-transform: translateY(-20px); - transform: translateY(-20px); - } -} - -.fadeOutUp { - -webkit-animation-name: fadeOutUp; - animation-name: fadeOutUp; -} - -@-webkit-keyframes fadeOutUpBig { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-2000px); - transform: translateY(-2000px); - } -} - -@keyframes fadeOutUpBig { - 0% { - opacity: 1; - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-2000px); - -ms-transform: translateY(-2000px); - transform: translateY(-2000px); - } -} - -.fadeOutUpBig { - -webkit-animation-name: fadeOutUpBig; - animation-name: fadeOutUpBig; -} - -@-webkit-keyframes flip { - 0% { - -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); - transform: perspective(400px) translateZ(0) rotateY(0) scale(1); - -webkit-animation-timing-function: ease-out; - animation-timing-function: ease-out; - } - - 40% { - -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); - transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); - -webkit-animation-timing-function: ease-out; - animation-timing-function: ease-out; - } - - 50% { - -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); - transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - } - - 80% { - -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); - transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - } - - 100% { - -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); - transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - } -} - -@keyframes flip { - 0% { - -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); - -ms-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); - transform: perspective(400px) translateZ(0) rotateY(0) scale(1); - -webkit-animation-timing-function: ease-out; - animation-timing-function: ease-out; - } - - 40% { - -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); - -ms-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); - transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); - -webkit-animation-timing-function: ease-out; - animation-timing-function: ease-out; - } - - 50% { - -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); - -ms-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); - transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - } - - 80% { - -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); - -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); - transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - } - - 100% { - -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); - -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); - transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; - } -} - -.animated.flip { - -webkit-backface-visibility: visible; - -ms-backface-visibility: visible; - backface-visibility: visible; - -webkit-animation-name: flip; - animation-name: flip; -} - -@-webkit-keyframes flipInX { - 0% { - -webkit-transform: perspective(400px) rotateX(90deg); - transform: perspective(400px) rotateX(90deg); - opacity: 0; - } - - 40% { - -webkit-transform: perspective(400px) rotateX(-10deg); - transform: perspective(400px) rotateX(-10deg); - } - - 70% { - -webkit-transform: perspective(400px) rotateX(10deg); - transform: perspective(400px) rotateX(10deg); - } - - 100% { - -webkit-transform: perspective(400px) rotateX(0deg); - transform: perspective(400px) rotateX(0deg); - opacity: 1; - } -} - -@keyframes flipInX { - 0% { - -webkit-transform: perspective(400px) rotateX(90deg); - -ms-transform: perspective(400px) rotateX(90deg); - transform: perspective(400px) rotateX(90deg); - opacity: 0; - } - - 40% { - -webkit-transform: perspective(400px) rotateX(-10deg); - -ms-transform: perspective(400px) rotateX(-10deg); - transform: perspective(400px) rotateX(-10deg); - } - - 70% { - -webkit-transform: perspective(400px) rotateX(10deg); - -ms-transform: perspective(400px) rotateX(10deg); - transform: perspective(400px) rotateX(10deg); - } - - 100% { - -webkit-transform: perspective(400px) rotateX(0deg); - -ms-transform: perspective(400px) rotateX(0deg); - transform: perspective(400px) rotateX(0deg); - opacity: 1; - } -} - -.flipInX { - -webkit-backface-visibility: visible !important; - -ms-backface-visibility: visible !important; - backface-visibility: visible !important; - -webkit-animation-name: flipInX; - animation-name: flipInX; -} - -@-webkit-keyframes flipInY { - 0% { - -webkit-transform: perspective(400px) rotateY(90deg); - transform: perspective(400px) rotateY(90deg); - opacity: 0; - } - - 40% { - -webkit-transform: perspective(400px) rotateY(-10deg); - transform: perspective(400px) rotateY(-10deg); - } - - 70% { - -webkit-transform: perspective(400px) rotateY(10deg); - transform: perspective(400px) rotateY(10deg); - } - - 100% { - -webkit-transform: perspective(400px) rotateY(0deg); - transform: perspective(400px) rotateY(0deg); - opacity: 1; - } -} - -@keyframes flipInY { - 0% { - -webkit-transform: perspective(400px) rotateY(90deg); - -ms-transform: perspective(400px) rotateY(90deg); - transform: perspective(400px) rotateY(90deg); - opacity: 0; - } - - 40% { - -webkit-transform: perspective(400px) rotateY(-10deg); - -ms-transform: perspective(400px) rotateY(-10deg); - transform: perspective(400px) rotateY(-10deg); - } - - 70% { - -webkit-transform: perspective(400px) rotateY(10deg); - -ms-transform: perspective(400px) rotateY(10deg); - transform: perspective(400px) rotateY(10deg); - } - - 100% { - -webkit-transform: perspective(400px) rotateY(0deg); - -ms-transform: perspective(400px) rotateY(0deg); - transform: perspective(400px) rotateY(0deg); - opacity: 1; - } -} - -.flipInY { - -webkit-backface-visibility: visible !important; - -ms-backface-visibility: visible !important; - backface-visibility: visible !important; - -webkit-animation-name: flipInY; - animation-name: flipInY; -} - -@-webkit-keyframes flipOutX { - 0% { - -webkit-transform: perspective(400px) rotateX(0deg); - transform: perspective(400px) rotateX(0deg); - opacity: 1; - } - - 100% { - -webkit-transform: perspective(400px) rotateX(90deg); - transform: perspective(400px) rotateX(90deg); - opacity: 0; - } -} - -@keyframes flipOutX { - 0% { - -webkit-transform: perspective(400px) rotateX(0deg); - -ms-transform: perspective(400px) rotateX(0deg); - transform: perspective(400px) rotateX(0deg); - opacity: 1; - } - - 100% { - -webkit-transform: perspective(400px) rotateX(90deg); - -ms-transform: perspective(400px) rotateX(90deg); - transform: perspective(400px) rotateX(90deg); - opacity: 0; - } -} - -.flipOutX { - -webkit-animation-name: flipOutX; - animation-name: flipOutX; - -webkit-backface-visibility: visible !important; - -ms-backface-visibility: visible !important; - backface-visibility: visible !important; -} - -@-webkit-keyframes flipOutY { - 0% { - -webkit-transform: perspective(400px) rotateY(0deg); - transform: perspective(400px) rotateY(0deg); - opacity: 1; - } - - 100% { - -webkit-transform: perspective(400px) rotateY(90deg); - transform: perspective(400px) rotateY(90deg); - opacity: 0; - } -} - -@keyframes flipOutY { - 0% { - -webkit-transform: perspective(400px) rotateY(0deg); - -ms-transform: perspective(400px) rotateY(0deg); - transform: perspective(400px) rotateY(0deg); - opacity: 1; - } - - 100% { - -webkit-transform: perspective(400px) rotateY(90deg); - -ms-transform: perspective(400px) rotateY(90deg); - transform: perspective(400px) rotateY(90deg); - opacity: 0; - } -} - -.flipOutY { - -webkit-backface-visibility: visible !important; - -ms-backface-visibility: visible !important; - backface-visibility: visible !important; - -webkit-animation-name: flipOutY; - animation-name: flipOutY; -} - -@-webkit-keyframes lightSpeedIn { - 0% { - -webkit-transform: translateX(100%) skewX(-30deg); - transform: translateX(100%) skewX(-30deg); - opacity: 0; - } - - 60% { - -webkit-transform: translateX(-20%) skewX(30deg); - transform: translateX(-20%) skewX(30deg); - opacity: 1; - } - - 80% { - -webkit-transform: translateX(0%) skewX(-15deg); - transform: translateX(0%) skewX(-15deg); - opacity: 1; - } - - 100% { - -webkit-transform: translateX(0%) skewX(0deg); - transform: translateX(0%) skewX(0deg); - opacity: 1; - } -} - -@keyframes lightSpeedIn { - 0% { - -webkit-transform: translateX(100%) skewX(-30deg); - -ms-transform: translateX(100%) skewX(-30deg); - transform: translateX(100%) skewX(-30deg); - opacity: 0; - } - - 60% { - -webkit-transform: translateX(-20%) skewX(30deg); - -ms-transform: translateX(-20%) skewX(30deg); - transform: translateX(-20%) skewX(30deg); - opacity: 1; - } - - 80% { - -webkit-transform: translateX(0%) skewX(-15deg); - -ms-transform: translateX(0%) skewX(-15deg); - transform: translateX(0%) skewX(-15deg); - opacity: 1; - } - - 100% { - -webkit-transform: translateX(0%) skewX(0deg); - -ms-transform: translateX(0%) skewX(0deg); - transform: translateX(0%) skewX(0deg); - opacity: 1; - } -} - -.lightSpeedIn { - -webkit-animation-name: lightSpeedIn; - animation-name: lightSpeedIn; - -webkit-animation-timing-function: ease-out; - animation-timing-function: ease-out; -} - -@-webkit-keyframes lightSpeedOut { - 0% { - -webkit-transform: translateX(0%) skewX(0deg); - transform: translateX(0%) skewX(0deg); - opacity: 1; - } - - 100% { - -webkit-transform: translateX(100%) skewX(-30deg); - transform: translateX(100%) skewX(-30deg); - opacity: 0; - } -} - -@keyframes lightSpeedOut { - 0% { - -webkit-transform: translateX(0%) skewX(0deg); - -ms-transform: translateX(0%) skewX(0deg); - transform: translateX(0%) skewX(0deg); - opacity: 1; - } - - 100% { - -webkit-transform: translateX(100%) skewX(-30deg); - -ms-transform: translateX(100%) skewX(-30deg); - transform: translateX(100%) skewX(-30deg); - opacity: 0; - } -} - -.lightSpeedOut { - -webkit-animation-name: lightSpeedOut; - animation-name: lightSpeedOut; - -webkit-animation-timing-function: ease-in; - animation-timing-function: ease-in; -} - -@-webkit-keyframes rotateIn { - 0% { - -webkit-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(-200deg); - transform: rotate(-200deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -@keyframes rotateIn { - 0% { - -webkit-transform-origin: center center; - -ms-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(-200deg); - -ms-transform: rotate(-200deg); - transform: rotate(-200deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: center center; - -ms-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -.rotateIn { - -webkit-animation-name: rotateIn; - animation-name: rotateIn; -} - -@-webkit-keyframes rotateInDownLeft { - 0% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -@keyframes rotateInDownLeft { - 0% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -.rotateInDownLeft { - -webkit-animation-name: rotateInDownLeft; - animation-name: rotateInDownLeft; -} - -@-webkit-keyframes rotateInDownRight { - 0% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -@keyframes rotateInDownRight { - 0% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -.rotateInDownRight { - -webkit-animation-name: rotateInDownRight; - animation-name: rotateInDownRight; -} - -@-webkit-keyframes rotateInUpLeft { - 0% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -@keyframes rotateInUpLeft { - 0% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -.rotateInUpLeft { - -webkit-animation-name: rotateInUpLeft; - animation-name: rotateInUpLeft; -} - -@-webkit-keyframes rotateInUpRight { - 0% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -@keyframes rotateInUpRight { - 0% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } - - 100% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } -} - -.rotateInUpRight { - -webkit-animation-name: rotateInUpRight; - animation-name: rotateInUpRight; -} - -@-webkit-keyframes rotateOut { - 0% { - -webkit-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(200deg); - transform: rotate(200deg); - opacity: 0; - } -} - -@keyframes rotateOut { - 0% { - -webkit-transform-origin: center center; - -ms-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: center center; - -ms-transform-origin: center center; - transform-origin: center center; - -webkit-transform: rotate(200deg); - -ms-transform: rotate(200deg); - transform: rotate(200deg); - opacity: 0; - } -} - -.rotateOut { - -webkit-animation-name: rotateOut; - animation-name: rotateOut; -} - -@-webkit-keyframes rotateOutDownLeft { - 0% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } -} - -@keyframes rotateOutDownLeft { - 0% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } -} - -.rotateOutDownLeft { - -webkit-animation-name: rotateOutDownLeft; - animation-name: rotateOutDownLeft; -} - -@-webkit-keyframes rotateOutDownRight { - 0% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } -} - -@keyframes rotateOutDownRight { - 0% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } -} - -.rotateOutDownRight { - -webkit-animation-name: rotateOutDownRight; - animation-name: rotateOutDownRight; -} - -@-webkit-keyframes rotateOutUpLeft { - 0% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } -} - -@keyframes rotateOutUpLeft { - 0% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: left bottom; - -ms-transform-origin: left bottom; - transform-origin: left bottom; - -webkit-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - transform: rotate(-90deg); - opacity: 0; - } -} - -.rotateOutUpLeft { - -webkit-animation-name: rotateOutUpLeft; - animation-name: rotateOutUpLeft; -} - -@-webkit-keyframes rotateOutUpRight { - 0% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } -} - -@keyframes rotateOutUpRight { - 0% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - opacity: 1; - } - - 100% { - -webkit-transform-origin: right bottom; - -ms-transform-origin: right bottom; - transform-origin: right bottom; - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - opacity: 0; - } -} - -.rotateOutUpRight { - -webkit-animation-name: rotateOutUpRight; - animation-name: rotateOutUpRight; -} - -@-webkit-keyframes slideInDown { - 0% { - opacity: 0; - -webkit-transform: translateY(-2000px); - transform: translateY(-2000px); - } - - 100% { - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes slideInDown { - 0% { - opacity: 0; - -webkit-transform: translateY(-2000px); - -ms-transform: translateY(-2000px); - transform: translateY(-2000px); - } - - 100% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } -} - -.slideInDown { - -webkit-animation-name: slideInDown; - animation-name: slideInDown; -} - -@-webkit-keyframes slideInLeft { - 0% { - opacity: 0; - -webkit-transform: translateX(-2000px); - transform: translateX(-2000px); - } - - 100% { - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes slideInLeft { - 0% { - opacity: 0; - -webkit-transform: translateX(-2000px); - -ms-transform: translateX(-2000px); - transform: translateX(-2000px); - } - - 100% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.slideInLeft { - -webkit-animation-name: slideInLeft; - animation-name: slideInLeft; -} - -@-webkit-keyframes slideInRight { - 0% { - opacity: 0; - -webkit-transform: translateX(2000px); - transform: translateX(2000px); - } - - 100% { - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@keyframes slideInRight { - 0% { - opacity: 0; - -webkit-transform: translateX(2000px); - -ms-transform: translateX(2000px); - transform: translateX(2000px); - } - - 100% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } -} - -.slideInRight { - -webkit-animation-name: slideInRight; - animation-name: slideInRight; -} - -@-webkit-keyframes slideOutLeft { - 0% { - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-2000px); - transform: translateX(-2000px); - } -} - -@keyframes slideOutLeft { - 0% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(-2000px); - -ms-transform: translateX(-2000px); - transform: translateX(-2000px); - } -} - -.slideOutLeft { - -webkit-animation-name: slideOutLeft; - animation-name: slideOutLeft; -} - -@-webkit-keyframes slideOutRight { - 0% { - -webkit-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(2000px); - transform: translateX(2000px); - } -} - -@keyframes slideOutRight { - 0% { - -webkit-transform: translateX(0); - -ms-transform: translateX(0); - transform: translateX(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(2000px); - -ms-transform: translateX(2000px); - transform: translateX(2000px); - } -} - -.slideOutRight { - -webkit-animation-name: slideOutRight; - animation-name: slideOutRight; -} - -@-webkit-keyframes slideOutUp { - 0% { - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-2000px); - transform: translateY(-2000px); - } -} - -@keyframes slideOutUp { - 0% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(-2000px); - -ms-transform: translateY(-2000px); - transform: translateY(-2000px); - } -} - -.slideOutUp { - -webkit-animation-name: slideOutUp; - animation-name: slideOutUp; -} - -@-webkit-keyframes slideOutDown { - 0% { - -webkit-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(2000px); - transform: translateY(2000px); - } -} - -@keyframes slideOutDown { - 0% { - -webkit-transform: translateY(0); - -ms-transform: translateY(0); - transform: translateY(0); - } - - 100% { - opacity: 0; - -webkit-transform: translateY(2000px); - -ms-transform: translateY(2000px); - transform: translateY(2000px); - } -} - -.slideOutDown { - -webkit-animation-name: slideOutDown; - animation-name: slideOutDown; -} - -@-webkit-keyframes hinge { - 0% { - -webkit-transform: rotate(0); - transform: rotate(0); - -webkit-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - } - - 20%, 60% { - -webkit-transform: rotate(80deg); - transform: rotate(80deg); - -webkit-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - } - - 40% { - -webkit-transform: rotate(60deg); - transform: rotate(60deg); - -webkit-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - } - - 80% { - -webkit-transform: rotate(60deg) translateY(0); - transform: rotate(60deg) translateY(0); - -webkit-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - opacity: 1; - } - - 100% { - -webkit-transform: translateY(700px); - transform: translateY(700px); - opacity: 0; - } -} - -@keyframes hinge { - 0% { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - -webkit-transform-origin: top left; - -ms-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - } - - 20%, 60% { - -webkit-transform: rotate(80deg); - -ms-transform: rotate(80deg); - transform: rotate(80deg); - -webkit-transform-origin: top left; - -ms-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - } - - 40% { - -webkit-transform: rotate(60deg); - -ms-transform: rotate(60deg); - transform: rotate(60deg); - -webkit-transform-origin: top left; - -ms-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - } - - 80% { - -webkit-transform: rotate(60deg) translateY(0); - -ms-transform: rotate(60deg) translateY(0); - transform: rotate(60deg) translateY(0); - -webkit-transform-origin: top left; - -ms-transform-origin: top left; - transform-origin: top left; - -webkit-animation-timing-function: ease-in-out; - animation-timing-function: ease-in-out; - opacity: 1; - } - - 100% { - -webkit-transform: translateY(700px); - -ms-transform: translateY(700px); - transform: translateY(700px); - opacity: 0; - } -} - -.hinge { - -webkit-animation-name: hinge; - animation-name: hinge; -} - -/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ - -@-webkit-keyframes rollIn { - 0% { - opacity: 0; - -webkit-transform: translateX(-100%) rotate(-120deg); - transform: translateX(-100%) rotate(-120deg); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0px) rotate(0deg); - transform: translateX(0px) rotate(0deg); - } -} - -@keyframes rollIn { - 0% { - opacity: 0; - -webkit-transform: translateX(-100%) rotate(-120deg); - -ms-transform: translateX(-100%) rotate(-120deg); - transform: translateX(-100%) rotate(-120deg); - } - - 100% { - opacity: 1; - -webkit-transform: translateX(0px) rotate(0deg); - -ms-transform: translateX(0px) rotate(0deg); - transform: translateX(0px) rotate(0deg); - } -} - -.rollIn { - -webkit-animation-name: rollIn; - animation-name: rollIn; -} - -/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ - -@-webkit-keyframes rollOut { - 0% { - opacity: 1; - -webkit-transform: translateX(0px) rotate(0deg); - transform: translateX(0px) rotate(0deg); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(100%) rotate(120deg); - transform: translateX(100%) rotate(120deg); - } -} - -@keyframes rollOut { - 0% { - opacity: 1; - -webkit-transform: translateX(0px) rotate(0deg); - -ms-transform: translateX(0px) rotate(0deg); - transform: translateX(0px) rotate(0deg); - } - - 100% { - opacity: 0; - -webkit-transform: translateX(100%) rotate(120deg); - -ms-transform: translateX(100%) rotate(120deg); - transform: translateX(100%) rotate(120deg); - } -} - -.rollOut { - -webkit-animation-name: rollOut; - animation-name: rollOut; +@charset "UTF-8"; + +/* +---------------------------------------------------------------------- +// | ThinkAdmin +// +---------------------------------------------------------------------- +// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] +// +---------------------------------------------------------------------- +// | 官方网站: http://think.ctolog.com +// +---------------------------------------------------------------------- +// | 开源协议 ( https://mit-license.org ) +// +---------------------------------------------------------------------- +// | github开源项目:https://github.com/zoujingli/ThinkAdmin +// +--------------------------------------------------------------------*/ + +/*! +Animate.css - http://daneden.me/animate +Licensed under the MIT license + +Copyright (c) 2013 Daniel Eden + +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. +*/ + +.animated { + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + z-index: 100; +} + +.animated.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animated.hinge { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +@-webkit-keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 40% { + -webkit-transform: translateY(-30px); + transform: translateY(-30px); + } + + 60% { + -webkit-transform: translateY(-15px); + transform: translateY(-15px); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 40% { + -webkit-transform: translateY(-30px); + -ms-transform: translateY(-30px); + transform: translateY(-30px); + } + + 60% { + -webkit-transform: translateY(-15px); + -ms-transform: translateY(-15px); + transform: translateY(-15px); + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; +} + +@-webkit-keyframes flash { + 0%, 50%, 100% { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +@keyframes flash { + 0%, 50%, 100% { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +.flash { + -webkit-animation-name: flash; + animation-name: flash; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 50% { + -webkit-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes pulse { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 50% { + -webkit-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} + +.pulse { + -webkit-animation-name: pulse; + animation-name: pulse; +} + +@-webkit-keyframes rubberBand { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 30% { + -webkit-transform: scaleX(1.25) scaleY(0.75); + transform: scaleX(1.25) scaleY(0.75); + } + + 40% { + -webkit-transform: scaleX(0.75) scaleY(1.25); + transform: scaleX(0.75) scaleY(1.25); + } + + 60% { + -webkit-transform: scaleX(1.15) scaleY(0.85); + transform: scaleX(1.15) scaleY(0.85); + } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes rubberBand { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 30% { + -webkit-transform: scaleX(1.25) scaleY(0.75); + -ms-transform: scaleX(1.25) scaleY(0.75); + transform: scaleX(1.25) scaleY(0.75); + } + + 40% { + -webkit-transform: scaleX(0.75) scaleY(1.25); + -ms-transform: scaleX(0.75) scaleY(1.25); + transform: scaleX(0.75) scaleY(1.25); + } + + 60% { + -webkit-transform: scaleX(1.15) scaleY(0.85); + -ms-transform: scaleX(1.15) scaleY(0.85); + transform: scaleX(1.15) scaleY(0.85); + } + + 100% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} + +.rubberBand { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; +} + +@-webkit-keyframes shake { + 0%, 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translateX(-10px); + transform: translateX(-10px); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translateX(10px); + transform: translateX(10px); + } +} + +@keyframes shake { + 0%, 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translateX(-10px); + -ms-transform: translateX(-10px); + transform: translateX(-10px); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translateX(10px); + -ms-transform: translateX(10px); + transform: translateX(10px); + } +} + +.shake { + -webkit-animation-name: shake; + animation-name: shake; +} + +@-webkit-keyframes swing { + 20% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); + } + + 40% { + -webkit-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 60% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); + } + + 80% { + -webkit-transform: rotate(-5deg); + transform: rotate(-5deg); + } + + 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } +} + +@keyframes swing { + 20% { + -webkit-transform: rotate(15deg); + -ms-transform: rotate(15deg); + transform: rotate(15deg); + } + + 40% { + -webkit-transform: rotate(-10deg); + -ms-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 60% { + -webkit-transform: rotate(5deg); + -ms-transform: rotate(5deg); + transform: rotate(5deg); + } + + 80% { + -webkit-transform: rotate(-5deg); + -ms-transform: rotate(-5deg); + transform: rotate(-5deg); + } + + 100% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } +} + +.swing { + -webkit-transform-origin: top center; + -ms-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + animation-name: swing; +} + +@-webkit-keyframes tada { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 10%, 20% { + -webkit-transform: scale(0.9) rotate(-3deg); + transform: scale(0.9) rotate(-3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale(1.1) rotate(3deg); + transform: scale(1.1) rotate(3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale(1.1) rotate(-3deg); + transform: scale(1.1) rotate(-3deg); + } + + 100% { + -webkit-transform: scale(1) rotate(0); + transform: scale(1) rotate(0); + } +} + +@keyframes tada { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 10%, 20% { + -webkit-transform: scale(0.9) rotate(-3deg); + -ms-transform: scale(0.9) rotate(-3deg); + transform: scale(0.9) rotate(-3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale(1.1) rotate(3deg); + -ms-transform: scale(1.1) rotate(3deg); + transform: scale(1.1) rotate(3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale(1.1) rotate(-3deg); + -ms-transform: scale(1.1) rotate(-3deg); + transform: scale(1.1) rotate(-3deg); + } + + 100% { + -webkit-transform: scale(1) rotate(0); + -ms-transform: scale(1) rotate(0); + transform: scale(1) rotate(0); + } +} + +.tada { + -webkit-animation-name: tada; + animation-name: tada; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + 0% { + -webkit-transform: translateX(0%); + transform: translateX(0%); + } + + 15% { + -webkit-transform: translateX(-25%) rotate(-5deg); + transform: translateX(-25%) rotate(-5deg); + } + + 30% { + -webkit-transform: translateX(20%) rotate(3deg); + transform: translateX(20%) rotate(3deg); + } + + 45% { + -webkit-transform: translateX(-15%) rotate(-3deg); + transform: translateX(-15%) rotate(-3deg); + } + + 60% { + -webkit-transform: translateX(10%) rotate(2deg); + transform: translateX(10%) rotate(2deg); + } + + 75% { + -webkit-transform: translateX(-5%) rotate(-1deg); + transform: translateX(-5%) rotate(-1deg); + } + + 100% { + -webkit-transform: translateX(0%); + transform: translateX(0%); + } +} + +@keyframes wobble { + 0% { + -webkit-transform: translateX(0%); + -ms-transform: translateX(0%); + transform: translateX(0%); + } + + 15% { + -webkit-transform: translateX(-25%) rotate(-5deg); + -ms-transform: translateX(-25%) rotate(-5deg); + transform: translateX(-25%) rotate(-5deg); + } + + 30% { + -webkit-transform: translateX(20%) rotate(3deg); + -ms-transform: translateX(20%) rotate(3deg); + transform: translateX(20%) rotate(3deg); + } + + 45% { + -webkit-transform: translateX(-15%) rotate(-3deg); + -ms-transform: translateX(-15%) rotate(-3deg); + transform: translateX(-15%) rotate(-3deg); + } + + 60% { + -webkit-transform: translateX(10%) rotate(2deg); + -ms-transform: translateX(10%) rotate(2deg); + transform: translateX(10%) rotate(2deg); + } + + 75% { + -webkit-transform: translateX(-5%) rotate(-1deg); + -ms-transform: translateX(-5%) rotate(-1deg); + transform: translateX(-5%) rotate(-1deg); + } + + 100% { + -webkit-transform: translateX(0%); + -ms-transform: translateX(0%); + transform: translateX(0%); + } +} + +.wobble { + -webkit-animation-name: wobble; + animation-name: wobble; +} + +@-webkit-keyframes bounceIn { + 0% { + opacity: 0; + -webkit-transform: scale(.3); + transform: scale(.3); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.05); + transform: scale(1.05); + } + + 70% { + -webkit-transform: scale(.9); + transform: scale(.9); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes bounceIn { + 0% { + opacity: 0; + -webkit-transform: scale(.3); + -ms-transform: scale(.3); + transform: scale(.3); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.05); + -ms-transform: scale(1.05); + transform: scale(1.05); + } + + 70% { + -webkit-transform: scale(.9); + -ms-transform: scale(.9); + transform: scale(.9); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} + +.bounceIn { + -webkit-animation-name: bounceIn; + animation-name: bounceIn; +} + +@-webkit-keyframes bounceInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(30px); + transform: translateY(30px); + } + + 80% { + -webkit-transform: translateY(-10px); + transform: translateY(-10px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes bounceInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(30px); + -ms-transform: translateY(30px); + transform: translateY(30px); + } + + 80% { + -webkit-transform: translateY(-10px); + -ms-transform: translateY(-10px); + transform: translateY(-10px); + } + + 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + animation-name: bounceInDown; +} + +@-webkit-keyframes bounceInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(30px); + transform: translateX(30px); + } + + 80% { + -webkit-transform: translateX(-10px); + transform: translateX(-10px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes bounceInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(30px); + -ms-transform: translateX(30px); + transform: translateX(30px); + } + + 80% { + -webkit-transform: translateX(-10px); + -ms-transform: translateX(-10px); + transform: translateX(-10px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} + +@-webkit-keyframes bounceInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(-30px); + transform: translateX(-30px); + } + + 80% { + -webkit-transform: translateX(10px); + transform: translateX(10px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes bounceInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(-30px); + -ms-transform: translateX(-30px); + transform: translateX(-30px); + } + + 80% { + -webkit-transform: translateX(10px); + -ms-transform: translateX(10px); + transform: translateX(10px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + animation-name: bounceInRight; +} + +@-webkit-keyframes bounceInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(-30px); + transform: translateY(-30px); + } + + 80% { + -webkit-transform: translateY(10px); + transform: translateY(10px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes bounceInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(-30px); + -ms-transform: translateY(-30px); + transform: translateY(-30px); + } + + 80% { + -webkit-transform: translateY(10px); + -ms-transform: translateY(10px); + transform: translateY(10px); + } + + 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + animation-name: bounceInUp; +} + +@-webkit-keyframes bounceOut { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 25% { + -webkit-transform: scale(.95); + transform: scale(.95); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.3); + transform: scale(.3); + } +} + +@keyframes bounceOut { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 25% { + -webkit-transform: scale(.95); + -ms-transform: scale(.95); + transform: scale(.95); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.3); + -ms-transform: scale(.3); + transform: scale(.3); + } +} + +.bounceOut { + -webkit-animation-name: bounceOut; + animation-name: bounceOut; +} + +@-webkit-keyframes bounceOutDown { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +@keyframes bounceOutDown { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} + +@-webkit-keyframes bounceOutLeft { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(20px); + transform: translateX(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +@keyframes bounceOutLeft { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(20px); + -ms-transform: translateX(20px); + transform: translateX(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} + +@-webkit-keyframes bounceOutRight { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +@keyframes bounceOutRight { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(-20px); + -ms-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} + +@-webkit-keyframes bounceOutUp { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +@keyframes bounceOutUp { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(20px); + -ms-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} + +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.fadeIn { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; +} + +@-webkit-keyframes fadeInDownBig { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInDownBig { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} + +@-webkit-keyframes fadeInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-20px); + -ms-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} + +@-webkit-keyframes fadeInLeftBig { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInLeftBig { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} + +@-webkit-keyframes fadeInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(20px); + transform: translateX(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(40px); + -ms-transform: translateX(40px); + transform: translateX(40px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + animation-name: fadeInRight; +} + +@-webkit-keyframes fadeInRightBig { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInRightBig { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} + +@-webkit-keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(20px); + -ms-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + animation-name: fadeInUp; +} + +@-webkit-keyframes fadeInUpBig { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInUpBig { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} + +@-webkit-keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +@keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +.fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes fadeOutDown { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(20px); + transform: translateY(20px); + } +} + +@keyframes fadeOutDown { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(20px); + -ms-transform: translateY(20px); + transform: translateY(20px); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} + +@-webkit-keyframes fadeOutDownBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +@keyframes fadeOutDownBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} + +@-webkit-keyframes fadeOutLeft { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-20px); + transform: translateX(-20px); + } +} + +@keyframes fadeOutLeft { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-20px); + -ms-transform: translateX(-20px); + transform: translateX(-20px); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} + +@-webkit-keyframes fadeOutLeftBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +@keyframes fadeOutLeftBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} + +@-webkit-keyframes fadeOutRight { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(20px); + transform: translateX(20px); + } +} + +@keyframes fadeOutRight { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(20px); + -ms-transform: translateX(20px); + transform: translateX(20px); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} + +@-webkit-keyframes fadeOutRightBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +@keyframes fadeOutRightBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} + +@-webkit-keyframes fadeOutUp { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } +} + +@keyframes fadeOutUp { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} + +@-webkit-keyframes fadeOutUpBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +@keyframes fadeOutUpBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} + +@-webkit-keyframes flip { + 0% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); + transform: perspective(400px) translateZ(0) rotateY(0) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); + transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 100% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); + transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +@keyframes flip { + 0% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); + -ms-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); + transform: perspective(400px) translateZ(0) rotateY(0) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); + -ms-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + -ms-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); + -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); + transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 100% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); + -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); + transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +.animated.flip { + -webkit-backface-visibility: visible; + -ms-backface-visibility: visible; + backface-visibility: visible; + -webkit-animation-name: flip; + animation-name: flip; +} + +@-webkit-keyframes flipInX { + 0% { + -webkit-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateX(-10deg); + transform: perspective(400px) rotateX(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateX(10deg); + transform: perspective(400px) rotateX(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} + +@keyframes flipInX { + 0% { + -webkit-transform: perspective(400px) rotateX(90deg); + -ms-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateX(-10deg); + -ms-transform: perspective(400px) rotateX(-10deg); + transform: perspective(400px) rotateX(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateX(10deg); + -ms-transform: perspective(400px) rotateX(10deg); + transform: perspective(400px) rotateX(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateX(0deg); + -ms-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInX; + animation-name: flipInX; +} + +@-webkit-keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateY(-10deg); + transform: perspective(400px) rotateY(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} + +@keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + -ms-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateY(-10deg); + -ms-transform: perspective(400px) rotateY(-10deg); + transform: perspective(400px) rotateY(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateY(10deg); + -ms-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateY(0deg); + -ms-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInY; + animation-name: flipInY; +} + +@-webkit-keyframes flipOutX { + 0% { + -webkit-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + 0% { + -webkit-transform: perspective(400px) rotateX(0deg); + -ms-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateX(90deg); + -ms-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-name: flipOutX; + animation-name: flipOutX; + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; +} + +@-webkit-keyframes flipOutY { + 0% { + -webkit-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} + +@keyframes flipOutY { + 0% { + -webkit-transform: perspective(400px) rotateY(0deg); + -ms-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateY(90deg); + -ms-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + animation-name: flipOutY; +} + +@-webkit-keyframes lightSpeedIn { + 0% { + -webkit-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: translateX(-20%) skewX(30deg); + transform: translateX(-20%) skewX(30deg); + opacity: 1; + } + + 80% { + -webkit-transform: translateX(0%) skewX(-15deg); + transform: translateX(0%) skewX(-15deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } +} + +@keyframes lightSpeedIn { + 0% { + -webkit-transform: translateX(100%) skewX(-30deg); + -ms-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: translateX(-20%) skewX(30deg); + -ms-transform: translateX(-20%) skewX(30deg); + transform: translateX(-20%) skewX(30deg); + opacity: 1; + } + + 80% { + -webkit-transform: translateX(0%) skewX(-15deg); + -ms-transform: translateX(0%) skewX(-15deg); + transform: translateX(0%) skewX(-15deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(0%) skewX(0deg); + -ms-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +@-webkit-keyframes lightSpeedOut { + 0% { + -webkit-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } +} + +@keyframes lightSpeedOut { + 0% { + -webkit-transform: translateX(0%) skewX(0deg); + -ms-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(100%) skewX(-30deg); + -ms-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +@-webkit-keyframes rotateIn { + 0% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(-200deg); + transform: rotate(-200deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateIn { + 0% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(-200deg); + -ms-transform: rotate(-200deg); + transform: rotate(-200deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + animation-name: rotateIn; +} + +@-webkit-keyframes rotateInDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} + +@-webkit-keyframes rotateInDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInDownRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} + +@-webkit-keyframes rotateInUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} + +@-webkit-keyframes rotateInUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInUpRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} + +@-webkit-keyframes rotateOut { + 0% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(200deg); + transform: rotate(200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + 0% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(200deg); + -ms-transform: rotate(200deg); + transform: rotate(200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + animation-name: rotateOut; +} + +@-webkit-keyframes rotateOutDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} + +@-webkit-keyframes rotateOutDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} + +@-webkit-keyframes rotateOutUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} + +@-webkit-keyframes rotateOutUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} + +@-webkit-keyframes slideInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes slideInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.slideInDown { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; +} + +@-webkit-keyframes slideInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes slideInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.slideInLeft { + -webkit-animation-name: slideInLeft; + animation-name: slideInLeft; +} + +@-webkit-keyframes slideInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes slideInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes slideOutLeft { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +@keyframes slideOutLeft { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +.slideOutLeft { + -webkit-animation-name: slideOutLeft; + animation-name: slideOutLeft; +} + +@-webkit-keyframes slideOutRight { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +@keyframes slideOutRight { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; +} + +@-webkit-keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +@keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +.slideOutUp { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; +} + +@-webkit-keyframes slideOutDown { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +@keyframes slideOutDown { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; +} + +@-webkit-keyframes hinge { + 0% { + -webkit-transform: rotate(0); + transform: rotate(0); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate(80deg); + transform: rotate(80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40% { + -webkit-transform: rotate(60deg); + transform: rotate(60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 80% { + -webkit-transform: rotate(60deg) translateY(0); + transform: rotate(60deg) translateY(0); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + 100% { + -webkit-transform: translateY(700px); + transform: translateY(700px); + opacity: 0; + } +} + +@keyframes hinge { + 0% { + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate(80deg); + -ms-transform: rotate(80deg); + transform: rotate(80deg); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40% { + -webkit-transform: rotate(60deg); + -ms-transform: rotate(60deg); + transform: rotate(60deg); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 80% { + -webkit-transform: rotate(60deg) translateY(0); + -ms-transform: rotate(60deg) translateY(0); + transform: rotate(60deg) translateY(0); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + 100% { + -webkit-transform: translateY(700px); + -ms-transform: translateY(700px); + transform: translateY(700px); + opacity: 0; + } +} + +.hinge { + -webkit-animation-name: hinge; + animation-name: hinge; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + 0% { + opacity: 0; + -webkit-transform: translateX(-100%) rotate(-120deg); + transform: translateX(-100%) rotate(-120deg); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } +} + +@keyframes rollIn { + 0% { + opacity: 0; + -webkit-transform: translateX(-100%) rotate(-120deg); + -ms-transform: translateX(-100%) rotate(-120deg); + transform: translateX(-100%) rotate(-120deg); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + -ms-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } +} + +.rollIn { + -webkit-animation-name: rollIn; + animation-name: rollIn; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + 0% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(100%) rotate(120deg); + transform: translateX(100%) rotate(120deg); + } +} + +@keyframes rollOut { + 0% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + -ms-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(100%) rotate(120deg); + -ms-transform: translateX(100%) rotate(120deg); + transform: translateX(100%) rotate(120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + animation-name: rollOut; } \ No newline at end of file diff --git a/static/theme/css/console.css b/static/theme/css/console.css new file mode 100644 index 000000000..2dec1013b --- /dev/null +++ b/static/theme/css/console.css @@ -0,0 +1,163 @@ +@charset "UTF-8"; + +/* +---------------------------------------------------------------------- +// | ThinkAdmin +// +---------------------------------------------------------------------- +// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] +// +---------------------------------------------------------------------- +// | 官方网站:http://think.ctolog.com +// +---------------------------------------------------------------------- +// | 开源协议 ( https://mit-license.org ) +// +---------------------------------------------------------------------- +// | github开源项目:https://github.com/zoujingli/ThinkAdmin +// +--------------------------------------------------------------------*/ + +html{overflow:auto}body{width:100%;font-size:13px;font-family:'微软雅黑','Microsoft YaHei','Helvetica Neue','Luxi Sans','DejaVu Sans','Hiragino Sans GB',serif} +a{color:#06C;cursor:pointer}a:hover{color:#039}input::-ms-clear{display:none}button{border-radius:0!important} +.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}.text-top{vertical-align:top!important} +.color-desc{color:#999!important}.color-text{color:#333!important}.color-blue{color:#2494f2!important}.color-red{color:#ec494e!important}.color-green{color:#090!important} +.nowrap{white-space:nowrap!important}.pointer{cursor:pointer!important}.help-block{margin-bottom:0}.block{display:block!important}.inline-block{display:inline-block!important} +.full-width{width:100%!important}.full-height{height:100%!important}.help-block{font-size:12px}.well{border-radius:0;box-shadow:none} +.transition{transition:all .2s linear;-o-transition:all .2s linear;-moz-transition:all .2s linear;-webkit-transition:all .2s linear} +.notselect{user-select:none;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none} + +[data-tips-image]{cursor:zoom-in!important;} +:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px white inset;-webkit-text-fill-color:#333;} +::-webkit-input-placeholder{color:#aaa}::selection{background-color:#ec494e;color:#fff}::-moz-selection{background-color:#ec494e;color:#fff} +::-webkit-scrollbar{width:3px;height:3px}::-webkit-scrollbar-track{background:#ccc;}::-webkit-scrollbar-thumb{background-color:#666} + +.font-s12{font-size:12px!important}.font-s13{font-size:13px!important}.font-s14{font-size:14px!important;}.font-s16{font-size:16px!important;}.font-s18{font-size:18px!important;}.font-s20{font-size:20px!important;} +.border-none{border:none!important;}.border-radius-none{border-radius:0!important;}.line-height-18{line-height:18px!important;} + +.padding-none{padding:0!important;} +.padding-5{padding:5px!important}.padding-10{padding:10px!important}.padding-15{padding:15px!important}.padding-20{padding:20px!important}.padding-25{padding:25px!important}.padding-30{padding:30px!important} +.padding-top-0{padding-top:0!important;}.padding-top-5{padding-top:5px!important;}.padding-top-10{padding-top:10px!important;}.padding-top-15{padding-top:15px!important;}.padding-top-20{padding-top:20px!important;} +.padding-left-0{padding-left:0!important;}.padding-left-5{padding-left:5px!important;}.padding-left-10{padding-left:10px!important;}.padding-left-15{padding-left:15px!important;}.padding-left-20{padding-left:20px!important;} +.padding-right-0{padding-right:0!important;}.padding-right-5{padding-right:5px!important;}.padding-right-10{padding-right:10px!important;}.padding-right-15{padding-right:15px!important;}.padding-right-20{padding-right:20px!important;} +.padding-bottom-0{padding-bottom:0!important;}.padding-bottom-5{padding-bottom:5px!important;}.padding-bottom-10{padding-bottom:10px!important;}.padding-bottom-15{padding-bottom:15px!important;}.padding-bottom-20{padding-bottom:20px!important;} + +.margin-none{margin:0!important;} +.margin-top-0{margin-top:0!important;}.margin-top-5{margin-top:5px!important;}.margin-top-10{margin-top:10px!important;}.margin-top-15{margin-top:15px!important;}.margin-top-20{margin-top:20px!important;} +.margin-left-0{margin-left:0!important;}.margin-left-5{margin-left:5px!important;}.margin-left-10{margin-left:10px!important;}.margin-left-15{margin-left:15px!important;}.margin-left-20{margin-left:20px!important;} +.margin-right-0{margin-right:0!important;}.margin-right-5{margin-right:5px!important;}.margin-right-10{margin-right:10px!important;}.margin-right-15{margin-right:15px!important;}.margin-right-20{margin-right:20px!important;} +.margin-bottom-0{margin-bottom:0!important;}.margin-bottom-5{margin-bottom:5px!important;}.margin-bottom-10{margin-bottom:10px!important;}.margin-bottom-15{margin-bottom:15px!important;}.margin-bottom-20{margin-bottom:20px!important;} + +/** 页面加载进度 */ +.pace-inactive{display:none} +.pace{-webkit-pointer-events:none;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none} +.pace .pace-progress{background:#22df80;position:fixed;z-index:2000;top:0;right:100%;width:100%;height:2px} +.uploadimage{display:inline-block;width:80px;height:80px;background: url('../img/image.png') no-repeat center center;background-size:cover;cursor:pointer} +.uploadimagemtl{margin-right:8px;} +.uploadimagemtl a{color:#fff;width:20px;height:20px;float:right;display:none;line-height:20px;text-align:center;background:rgba(0, 0, 0, 0.5)} +.uploadimagemtl:hover a{display:inline-block;cursor:pointer} +.uploadimagemtl:hover a:hover{color:#fff;text-decoration:none} +.hr-line-dashed{border-top:1px dashed #e7eaec;color:#fff;background-color:#fff;height:1px;margin:20px 0} +.hr-line-solid{border-bottom:1px solid #e7eaec;background-color:rgba(0,0,0,0);border-style:solid!important;margin-top:15px;margin-bottom:15px} +.pagination-trigger{position:absolute;line-height:31px;margin:20px 0} +.pagination-trigger select{height:20px;text-align:center;border:1px solid #ccc;border-radius:0;font-size:12px} +.label-required:after{content:'*';color:red;position:absolute;margin-left:4px;font-weight:bold;line-height:1.8em;top:6px;right:0} + +/** Layui样式调整 */ +.think-inner-table{border:none!important;margin:0!important} +.think-inner-table tr:first-child td{padding-top:5px!important} +.think-inner-table tr:last-child td{padding-bottom:5px!important} +.think-inner-table tr td{padding:0px 3px!important;border:none!important} +.think-inner-table tr td:first-child{padding-left:10px!important} +.think-inner-table tr td:last-child{padding-right:10px!important} +legend{width:auto!important;border-bottom:none!important} +.layui-form-label{padding:9px 15px 9px 0;width:95px} +.layui-input,.layui-select{line-height:38px} +.layui-table.notevent tbody tr:hover {background-color: inherit;} +.layui-table th,.layui-table td{font-size:13px} +.layui-table td .text-explode:first-child{opacity:0;display:none} +.layui-table td .text-explode{color:#ccc!important;font-weight:normal!important;margin:0px 4px!important} +.layui-table .list-table-image{width:22px;cursor:pointer} +.layui-table .list-table-sort-td{width:60px!important;text-align:center!important} +.layui-table .list-table-sort-td button{width:50px} +.layui-table .list-table-sort-td input{width:50px;text-align:center;line-height:14px;padding:2px;border:1px solid #e6e6e6} +.layui-table .list-table-check-td{width:30px!important;text-align:center;padding:0} +.layui-table .list-table-check-td input{margin:0;vertical-align:middle} + +/** checkbox 优化 */ +label{font-size:100%;font-weight:500;margin-bottom:0} +label.think-radio,label.think-checkbox{cursor:pointer;margin-top:8px;line-height:18px;margin-right:10px} +input[type=checkbox],input[type=radio]{appearance:none;-webkit-appearance:none;display:inline-block!important;margin:0;width:18px;height:18px;cursor:pointer;background:#fff;border-radius:1px;position:relative;vertical-align:bottom;-moz-border-radius:1px;border:1px solid #dcdcdc;-webkit-border-radius:1px;box-sizing:border-box!important;-moz-box-sizing:border-box!important;-webkit-box-sizing:border-box!important} +input[type=checkbox]:active,input[type=radio]:active{background:#ebebeb;border-color:#c6c6c6} +input[type=checkbox]:hover{border-color:#c6c6c6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.1);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.1)} +input[type=radio]{width:18px;height:18px;border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em} +input[type=checkbox]:checked,input[type=radio]:checked{background:#fff} +input[type=radio]:checked::after{top:4px;left:4px;width:8px;height:8px;content:'';display:block;background:#666;position:relative;border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em} +input[type=checkbox]:checked::after{top:-2px;left:-4px;display:block;position:absolute;content:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAAtklEQVQ4y2P4//8/A7Ux1Q0cxoaCADIbCUgCMTvVXAoE5kA8CYidyXYpGrAH4iVAHIXiCwoMDQTimUBcBsRMlBrKCsTpUANzkC0j11BuIK6EGlgKsoAkQ4FgChD7AzELVI8YEDdDDawDYk6YQaQY6gg1oAqILYC4D8oHGcyLbBAphoJAKtQgGO4EYiHk2CLHUJAXm6AG9gCxNHoSIMdQEJCFGqiALaGSayjMxQwUGzq0S6nhZygA2ojsbh6J67kAAAAASUVORK5CYII=')} +input[type=checkbox]:focus,input[type=radio]:focus{outline:none} + +/** 手机盒子及微信菜单 */ +.mobile-preview{position:relative;width:317px;height:580px;background:url('../img/wechat/mobile_head.png') no-repeat 0 0} +.mobile-preview .mobile-header{color:#fff;text-align:center;padding-top:30px;font-size:15px;width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal;margin:0 30px;-webkit-pointer-events:none;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none} +.mobile-body{width:100%;position:absolute;bottom:0;top:60px;border:1px solid #e7e7eb;border-top-style:none;background:#f5f5f5} +.mobile-body iframe{width:100%;height:100%;} +.mobile-footer{list-style-type:none;margin:0;position:absolute;bottom:0;left:0;right:0;border:1px solid #e7e7eb;background:url('../img/wechat/mobile_foot.png') no-repeat 0 0;padding-left:43px} +.mobile-footer li{width:33.33%;line-height:50px;position:relative;float:left;text-align:center} +.mobile-footer li a{display:block;border:1px solid rgba(255,255,255,0);border-left:1px solid #e7e7eb;width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal;color:#616161;text-decoration:none} +.mobile-footer li a.active{background-color:#fff;border:1px solid #44b549!important} +.mobile-footer .icon-add{background:url('../img/wechat/index.png') 0 0 no-repeat;width:14px;height:14px;vertical-align:middle;display:inline-block;margin-top:-2px;border-bottom:none!important} +.mobile-footer .icon-sub{background:url('../img/wechat/index.png') 0 -48px no-repeat;width:7px;height:7px;vertical-align:middle;display:inline-block;margin-right:2px;margin-top:-2px} +.mobile-footer .sub-menu{position:absolute;border-radius:3px;border:1px solid #d0d0d0;display:block;bottom:60px;width:100%;background-color:#fafafa} +.mobile-footer .sub-menu ul, +.mobile-footer .sub-menu ul li{padding:0;display:block;width:100%;float:none;z-index:11} +.mobile-footer .sub-menu ul li a{border:1px solid #fff} +.mobile-footer .sub-menu ul li a.bottom-border{border-bottom:1px solid #e7e7eb} +.mobile-footer .sub-menu ul li:nth-last-child(2) a{border-bottom-color:#fff;} +.mobile-footer .sub-menu ul li:last-child a{border-top-color:#e7e7eb} +.mobile-footer .arrow{position:absolute;left:50%;margin-left:-6px} +.mobile-footer .arrow_in,.mobile-footer .arrow_out{z-index:10;width:0;height:0;display:inline-block;border-width:6px;border-style:dashed;border-color:transparent;border-bottom-width:0;border-top-style:solid} +.mobile-footer .arrow_in{bottom:-5px;border-top-color:#fafafa}.mobile-footer .arrow_out{bottom:-6px;border-top-color:#d0d0d0} + +/* 后台框架 */ +.framework{height:100%;position:relative} +.framework.mini .framework-body{left:0} +.framework.mini .framework-leftbar{display:none} + +/** 顶部菜单 */ +.framework-topbar{font-size:14px;clear:both;height:50px;z-index:2;background:#2494f2} +.framework-topbar a{text-decoration:none}.framework-topbar a:focus{outline:none} +.framework-topbar .topbar-head{z-index:3;height:50px;position:relative;width:220px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1);} +.framework-topbar .topbar-head .topbar-logo{color:#fff;display:block;font-size:18px;line-height:50px;text-align:center;width:100%} +.framework-topbar .topbar-btn{background:none;color:#fff;font-size:14px;height:50px;line-height:50px;z-index:2;display:block;padding:0 20px} +.framework-topbar .topbar-btn:hover,.framework-topbar .topbar-btn.active{background:#36a3ff} +.framework-topbar .dropdown-backdrop{display:none!important} +.framework-topbar .dropdown .dropdown-menu{margin:0;padding:0;width:100%;min-width:0;border:none;border-radius:0;z-index:2;} +.framework-topbar .dropdown .dropdown-menu .topbar-btn{height:auto;padding:0;font-size:14px;color:#333;border-top:1px solid #eaedf1} +.framework-topbar .dropdown .dropdown-menu .topbar-btn a{padding:0;line-height:45px;text-align:center;} +.framework-topbar .dropdown.open .toggle-icon{transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg);-moz-transform:rotate(180deg);-webkit-transform:rotate(180deg)} +.framework-topbar .dropdown.open .topbar-btn{color:#333;background:#fff} + +/* 左则菜单 */ +.framework-leftbar{font-size:13px;top:50px;bottom:0;width:220px;position:fixed;overflow:auto;overflow-x:hidden;background:#293038;box-shadow:1px 0 3px 0 rgba(0,0,0,.08);z-index:3} +.framework-leftbar .leftbar-container .line-top{font-size:10px;color:#ccc;text-align:center;letter-spacing:2px} +.framework-leftbar .leftbar-container .font-icon{min-width:15px;text-align:center;margin-right:5px;display:inline-block;font-size:14px} +.framework-leftbar .leftbar-container a{line-height:40px;display:block;background:#22282e;color:#fff;padding:0 15px;border-bottom:1px solid rgba(0,0,0,.05);} +.framework-leftbar .leftbar-container a:hover{text-decoration:none;background:#414d5c} +.framework-leftbar .leftbar-container div div{display:none} +.framework-leftbar .leftbar-container div.open > a > i{transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg);-moz-transform:rotate(180deg);-webkit-transform:rotate(180deg)} +.framework-leftbar .leftbar-container div.open div{display:block} +.framework-leftbar .leftbar-container div div a{padding-left:30px;background:none} +.framework-leftbar .leftbar-container > a.active, +.framework-leftbar .leftbar-container div div a.active{background:#2494f2} + +/* 右则内容 */ +.framework-body{right:0;bottom:0;top:50px;left:220px;position:fixed;overflow-y:scroll;z-index:1} +.framework-body>.layui-card{box-shadow:none} +.framework-body>.layui-card>.layui-header{height:50px;line-height:50px;font-size:14px;background:#fff;padding-left:15px;box-shadow:0 1px 3px 0 rgba(0,0,0,.08);z-index:2;position:relative} +.framework-body>.layui-card>.layui-header span{color:#333;padding:3px 0;} +.framework-body>.layui-card>.layui-header span:after{content:'/';padding-left:3px;padding-right:3px;} +.framework-body>.layui-card>.layui-header span:last-child:after{content:''} + +/* 右则内容搜索样式 */ +.framework-body .form-search{background:#fff;padding-top:15px} +.framework-body .form-search .layui-btn{height:32px;line-height:30px;font-size:13px;padding:0 10px} +.framework-body .form-search .layui-form-label{width:auto!important;border-radius:0;padding:0 6px;height:32px;line-height:30px} +.framework-body .form-search .layui-input-inline{width:170px} +.framework-body .form-search .layui-input-inline input, +.framework-body .form-search .layui-input-inline select{border-radius:0;padding:0 5px;height:32px;line-height:30px;width:100%} +.framework-body .form-search .layui-form-select{border-radius:0} +.framework-body .form-search .layui-form-select dl{top:31px;padding:0} \ No newline at end of file diff --git a/static/theme/css/login.css b/static/theme/css/login.css new file mode 100644 index 000000000..e15e8f899 --- /dev/null +++ b/static/theme/css/login.css @@ -0,0 +1,63 @@ +@charset "UTF-8"; + +/* +---------------------------------------------------------------------- +// | ThinkAdmin +// +---------------------------------------------------------------------- +// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] +// +---------------------------------------------------------------------- +// | 官方网站: http://think.ctolog.com +// +---------------------------------------------------------------------- +// | 开源协议 ( https://mit-license.org ) +// +---------------------------------------------------------------------- +// | github开源项目:https://github.com/zoujingli/ThinkAdmin +// +--------------------------------------------------------------------*/ + +html,body{height:100%;width:100%;overflow:hidden!important;overflow-y:hidden!important;min-width:0} + +.login-container{height:100%;background-color:#2494F2;overflow:hidden;font-size:9pt} +.login-container ul{display:block;margin:0;padding:0;list-style:none} +.login-container ul li{display:block;margin:0;padding:0;list-style:none} +.login-container .clouds-container{position:absolute;overflow:hidden;height:100%;width:100%} +.login-container .clouds{background:url("../img/login/cloud_two.png") repeat-x left 15%;position:absolute;left:0;top:0;height:100%;width:300%;-webkit-animation:cloudmove 200s linear infinite;-moz-animation:cloudmove 200s linear infinite;-o-animation:cloudmove 200s linear infinite;animation:cloudmove 200s linear infinite;-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} +.login-container .clouds-fast{background:url("../img/login/cloud.png") no-repeat 0px 40%;-webkit-animation:cloudmove 30s linear infinite;-moz-animation:cloudmove 30s linear infinite;-o-animation:cloudmove 30s linear infinite;animation:cloudmove 30s linear infinite} +.login-container .clouds-footer{background:url("../img/login/cloud_one.png") no-repeat left 100%;animation:none} + +@keyframes cloudmove{0%{left:-50%}100%{left:150%}} +@-moz-keyframes cloudmove{0%{left:-50%}100%{left:150%}} +@-webkit-keyframes cloudmove{0%{left:-50%}100%{left:150%}} + +.login-container .header{height:47px;position:absolute;top:0;z-index:100;width:100%} +.login-container .header a, +.login-container .header a:hover, +.login-container .header a:focus{text-decoration:none;color:#eee;outline:none} +.login-container .header a:hover{color:#fff} +.login-container .header span.title{line-height:47px;text-indent:44px;float:left;color:#fff;font-size:16px} +.login-container .header ul{float:right;padding-right:30px} +.login-container .header ul li{float:left;margin-left:20px;line-height:47px;} +.login-container .container{background:url(../img/login/bg.png) no-repeat center center;width:100%;height:585px;overflow:hidden;position:relative;top:50%;margin-top:-300px} + +/* 表单 */ +.login-container .content{width:692px;height:336px;background:url(../img/login/bg_form.png) no-repeat;margin:80px auto auto auto;position:relative;display:block} +.login-container .content ul{margin-left:285px;margin-right:60px} +.login-container .content ul li{margin-bottom:25px;position:relative} +.login-container .content ul li input{position:relative;padding-left:40px;z-index:1;width:100%;height:38px;line-height:38px;border:1px solid #e6e6e6;background-color:#fff;border-radius:2px} +.login-container .content ul li i{position:absolute;background:url(../img/icon.png) #fff no-repeat;background-size:20px;width:34px;height:38px;z-index:2;border:1px solid #e6e6e6;border-right:none;border-radius:2px} +.login-container .content ul li.username i{background-position:8px 8px} +.login-container .content ul li.password i{background-position:8px -20px} +.login-container .content a{color:#666;position:absolute;display:block;} +.login-container .content a:hover{color:#333} +.login-container .content a.forget{right:0} + +/* 头像 */ +.login-container .people{width:165px;height:96px;position:relative;top:8px;left:-70px} +.login-container .people .tou{background:url("../img/login/hand_tou.png") no-repeat;width:97px;height:92px;position:absolute;top:-87px;left:140px} +.login-container .people .left_hand{background:url("../img/login/hand_left.png") no-repeat;width:32px;height:37px;position:absolute;top:-38px;left:150px} +.login-container .people .right_hand{background:url("../img/login/hand_right.png") no-repeat;width:32px;height:37px;position:absolute;top:-38px;right:-64px} +.login-container .people .initial_left_hand{background:url("../img/login/hand.png") no-repeat;width:30px;height:20px;position:absolute;top:-12px;left:100px} +.login-container .people .initial_right_hand{background:url("../img/login/hand.png") no-repeat;width:30px;height:20px;position:absolute;top:-12px;right:-112px} + +/* 底部版权 */ +.login-container .footer{height:50px;line-height:50px;text-align:center;position:absolute;bottom:0;width:100%;color:#fff;;letter-spacing:0.5px} +.login-container .footer span{display:inline-block;height:10px;overflow:hidden;line-height:10px;padding-left:1px;padding-right:1px} +.login-container .footer a{color:#fff} +.login-container .footer a:hover{color:#fff} diff --git a/static/theme/default/css/console.css b/static/theme/default/css/console.css deleted file mode 100644 index fe190b381..000000000 --- a/static/theme/default/css/console.css +++ /dev/null @@ -1,217 +0,0 @@ -@charset "UTF-8"; - -/* +---------------------------------------------------------------------- -// | ThinkAdmin -// +---------------------------------------------------------------------- -// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] -// +---------------------------------------------------------------------- -// | 官方网站: http://think.ctolog.com -// +---------------------------------------------------------------------- -// | 开源协议 ( https://mit-license.org ) -// +---------------------------------------------------------------------- -// | github开源项目:https://github.com/zoujingli/ThinkAdmin -// +--------------------------------------------------------------------*/ - -html{overflow:auto;} -body{position:absolute;width:100%;font-size:12px;line-height:24px;font-family:'微软雅黑','Microsoft YaHei','Helvetica Neue','Luxi Sans','DejaVu Sans','Hiragino Sans GB',serif} -a:hover{color:#039}a{color:#06C;cursor:pointer}input::-ms-clear{display:none}button{border-radius:0!important} -label{font-size:100%;font-weight:500;margin-bottom:0}label.think-radio,label.think-checkbox{line-height:18px;margin-right:10px;margin-top:8px;cursor:pointer} -.color-desc{color:#999!important}.color-text{color:#333!important}.color-blue{color:#2494f2!important}.color-red{color:#ec494e!important}.color-green{color:#090!important} -.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}.text-top{vertical-align:top!important}.nowrap{white-space:nowrap!important} -.pointer{cursor:pointer!important}.help-block{margin-bottom:0}.block{display:block!important}.inline-block{display:inline-block!important}.full-width{width:100%!important}.full-height{height:100%!important} - -/* 设置选择文字及背景颜色 */ -::selection{background-color:#ec494e;color:#fff}::-moz-selection{background-color:#ec494e;color:#fff} -::-webkit-scrollbar{width:3px;height:3px}::-webkit-scrollbar-track{background:#ccc;}::-webkit-scrollbar-thumb{background-color:#666} - -/* 表单样式 */ -.form-control.input-sm{line-height:1em}select.form-control.input-sm{padding:0 0 0 10px}.input-focus{background:none!important;padding:3px 5px!important} -.notselect{-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;} -.transition{-webkit-transition:all .2s linear;-moz-transition:all .2s linear;-o-transition:all .2s linear;transition:all .2s linear} -.uploadimage{display:inline-block;width:80px;height:80px;background-image:url('../img/image.png');background-repeat: no-repeat;background-position:center center;background-size:cover;cursor:pointer} -[data-tips-image]{cursor:pointer!important;cursor:-webkit-zoom-in!important;cursor:-moz-zoom-in!important;cursor:zoom-in!important} - -/** 列表搜索区 */ -.layui-btn{border-radius:0!important} -.layui-box legend{width:auto!important;border-bottom:none!important} -.form-search .layui-input{height:32px} -.form-search .layui-input-inline{width:160px} -.form-search .layui-form-label{height:32px;line-height:30px;padding:0 8px;border-radius:0;width:auto!important} -.form-search .layui-btn{height:32px;line-height:30px;padding:0 10px;border-radius:0} - -/** 表单Input错误提示 */ -.error-input-right{-webkit-animation-duration:.2s;animation-duration:.2s;padding-right:5px} -.label-required:after{content:'*';color:red;position:absolute;margin-left:4px;font-weight:bold;line-height:1.8em} - -/** 表格样式 */ -.think-inner-table{border:none!important;margin:0!important} -.think-inner-table tr:first-child td{padding-top:5px!important} -.think-inner-table tr:last-child td{padding-bottom:5px!important} -.think-inner-table tr td{padding:0px 3px!important;border:none!important} -.think-inner-table tr td:first-child{padding-left:10px!important} -.think-inner-table tr td:last-child{padding-right:10px!important} -table td .text-explode:first-child{opacity:0;display:none} -table td .text-explode{color:#ccc!important;font-weight:normal!important;margin:0px 4px!important} -.table{background:#fff;font-size:12px;border-top:1px solid #e1e6eb;border:1px solid #e1e6eb} -.table-center{text-align:center} -.table-center td,.table-center th{text-align:center!important} -.table-bordered{border:1px solid #EBEBEB} -.table-bordered > thead > tr > td{background-color:#f5f5f6;border-bottom-width:1px} -.table-bordered > thead > tr > th,.table-bordered > tbody > tr > th.table-bordered > thead > tr > td,.table-bordered > tbody > tr > td{border:1px solid #e7e7e7} -.table > thead > tr > th{border-bottom:1px solid #ddd;vertical-align:bottom;font-weight:normal;color:#999;background-color:#f5f6fa} -.table > thead > tr > th,.table > tbody > tr > th,.table > thead > tr > td,.table > tbody > tr > td{border-top:1px solid #e7eaec;line-height:1.42857;padding:8px;vertical-align:middle} - -/** 列表排序样式 */ -.list-table-image{width:22px;cursor:pointer} -.list-table-sort-td{width:60px!important;text-align:center!important} -.list-table-sort-td button{width:50px} -.list-table-sort-td input{width:50px;text-align:center;font-size:12px;line-height:14px;padding:2px;border:1px solid #e6e6e6} -.list-table-check-td{width:30px!important;text-align:center;padding:0} -.list-table-check-td input{margin:0;vertical-align:middle} -.form-control{background-color:#fff;background-image:none;border:1px solid #e5e6e7;border-radius:1px;color:inherit;display:block;padding:6px 12px;transition:border-color 0.15s ease-in-out 0s,box-shadow 0.15s ease-in-out 0s;width:100%;font-size:14px;box-shadow:none} -.form-control:focus{outline:none;box-shadow:none} -.hr-line-dashed{border-top:1px dashed #e7eaec;color:#fff;background-color:#fff;height:1px;margin:20px 0} -.hr-line-solid{border-bottom:1px solid #e7eaec;background-color:rgba(0,0,0,0);border-style:solid!important;margin-top:15px;margin-bottom:15px} - -/** 按钮定义 */ -.btn{outline:none!important} -.btn-primary{background-color:#1ab394;border-color:#1ab394;color:#fff} -.btn-primary:hover,.btn-primary:focus,.btn-primary:active{background-image:none!important;background-color:#18a689!important;border-color:#18a689!important;color:#fff!important;box-shadow:none!important} -.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled:active{background-color:#1dc5a3;border-color:#1dc5a3} -.btn-white{color:inherit;background:white;border:1px solid #e7eaec} -.btn-white:hover,.btn-white:focus,.btn-white:active{color:inherit;border:1px solid #d2d2d2} -.btn-white.disabled,.btn-white.disabled:hover,.btn-white.disabled:focus,.btn-white.disabled.active{color:#cacaca} - -/** 内容盒子 */ -.ibox:after,.ibox:before{display:table} -.ibox{clear:both;margin-bottom:25px;margin-top:0;padding:0} -.ibox-title{border-bottom:1px solid #e7eaec;color:inherit;margin-bottom:0;padding:0px 15px 0px 15px;min-height:48px} -.ibox-content{background-color:#fff;color:inherit;padding:15px} -.ibox-footer{color:inherit;border-top:1px solid #e7eaec;font-size:90%;background:#fff;padding:10px 15px} -.ibox-title h5{display:inline-block;font-size:16px;margin:16px 0px;font-weight:500;text-indent:8px;text-overflow:ellipsis;border-left:2px solid #2494f2} - -/** 页面加载进度 */ -.pace{-webkit-pointer-events:none;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none} -.pace-inactive{display:none} -.pace .pace-progress{background:#22df80;position:fixed;z-index:2000;top:0;right:100%;width:100%;height:2px} - -/** 后台框架 */ -.framework-topbar{position:fixed;width:100%;height:50px;background:#09c;z-index:101} -.framework-body{position:absolute;width:100%;top:50px;bottom:0px;background-color:#000;z-index:100} -.framework-container{width:auto;position:fixed;top:50px;left:0;bottom:0;right:0;background:#fff;overflow:auto} - -/** 顶部菜单 */ -.console-topbar{position:relative;z-index:100;clear:both;height:50px;background:#2494f2;font-size:12px;min-width:990px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} -.console-topbar a{text-decoration:none}.console-topbar a:focus{outline:none} -.console-topbar .icon-arrow-down{display:inline-block;width:18px;text-align:center;vertical-align:middle} -.console-topbar .dropdown .dropdown-menu{z-index:1;font-size:12px;border-radius:0;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)} -.console-topbar .dropdown .dropdown-menu a{padding:0} -.console-topbar .dropdown.open .icon-arrow-down{vertical-align:text-top;transform:rotate(180deg);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg)} -.console-topbar .topbar-wrap,.console-topbar .topbar-logo,.console-topbar .topbar-home-link,.console-topbar .topbar-nav,.console-topbar .topbar-info{height:100%} -.console-topbar .topbar-left{float:left}.console-topbar .topbar-right{float:right} -.console-topbar .topbar-clearfix:before,.console-topbar .topbar-clearfix:after{display:table;content:' '}.console-topbar .topbar-clearfix:after{clear:both} -.console-topbar .topbar-head{height:50px;position:relative;z-index:3} -.console-topbar .topbar-nav{position:relative;z-index:2;background:#09c} -.console-topbar .topbar-logo{display:block;min-width:220px;font-size:18px;color:#fff;text-align:center;line-height:50px} -.console-topbar .topbar-logo span{line-height:50px;padding:auto 20px} -.console-topbar .topbar-home-link{padding:0 20px;margin-right:1px;background:none} -.console-topbar .topbar-btn{color:#fff;font-size:14px;line-height:50px} -.console-topbar .topbar-btn.active{background:#36a3ff} -.console-topbar .topbar-info .topbar-btn{padding:0 15px;height:50px;display:block;z-index:2;} -.console-topbar .topbar-info .topbar-btn:hover, -.console-topbar .topbar-info .topbar-btn.topbar-btn-dark{background:#36a3ff} -.console-topbar .topbar-info .topbar-btn.open{position:relative} -.console-topbar .topbar-info .dropdown{min-width:80px} -.console-topbar .topbar-info .dropdown .dropdown-menu{width:100%;min-width:0;margin:0;border:none;padding:0} -.console-topbar .topbar-info .dropdown.open .topbar-btn{color:#333;background:#fff;border-bottom:1px solid #eaedf1;position:relative} -.console-topbar .topbar-info .topbar-info-btn{height:40px;border-bottom:1px solid #eaedf1} -.console-topbar .topbar-info .topbar-info-btn a{line-height:39px;text-align:center;padding-left:10px;padding-right:10px;margin:0} -.console-topbar .topbar-info-item{display:inline-block;margin-left:1px} -.console-topbar .topbar-info-item.open .glyphicon-menu-up{transform:rotate(180deg);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg)} - -/** 左侧菜单 **/ -.framework-sidebar{width:0px;display:none;position:fixed;top:50px;bottom:0px;background-color:#293038;z-index:102;overflow-x:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} -.framework-sidebar .sidebar-content{width:220px;height:100%;overflow:auto;overflow-x:hidden} -.framework-sidebar .sidebar-fold{height:30px;width:220px;background:#394555;color:#aeb9c2;text-align:center;line-height:30px!important;font-size:12px;user-select:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none} -.framework-sidebar .sidebar-fold:hover{background:#37424f;color:#fff} -.framework-sidebar .sidebar-nav{width:220px} -.framework-sidebar .sidebar-nav ul{margin:0px;padding:0px;list-style:none;overflow:hidden;text-indent:1.2em} -.framework-sidebar .sidebar-nav li a{display:block;width:100%;height:40px;line-height:40px;overflow:hidden} -.framework-sidebar .sidebar-nav li a:hover{background:#37424f} -.framework-sidebar .sidebar-nav li a:hover .nav-icon, -.framework-sidebar .sidebar-nav li a:hover .nav-title{color:#fff} -.framework-sidebar .sidebar-nav .nav-item{position:relative} -.framework-sidebar .sidebar-nav .sidebar-title{height:40px;background:#37424f;border-bottom:1px solid #414d5c;color:#fff;line-height:40px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none} -.framework-sidebar .sidebar-nav .sidebar-title:hover{background:#414d5c} -.framework-sidebar .sidebar-nav .sidebar-title-icon{display:inline-block;margin:0 8px 0 20px;vertical-align:middle} -.framework-sidebar .sidebar-manage{vertical-align:middle;position:absolute;height:40px;width:40px;right:0} -.framework-sidebar .sidebar-manage a{display:block;width:100%;height:100%;text-align:center;line-height:40px;font-size:14px;color:#a0abb3;text-decoration:none} -.framework-sidebar .sidebar-nav.open .sidebar-title{background:#22282e;border-bottom:1px solid #22282e} -.framework-sidebar .sidebar-nav.open .sidebar-title-icon{transform:rotate(90deg);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg)} -.framework-sidebar .sidebar-nav .nav-icon{width:50px;text-align:center;font-size:16px;float:left;color:#aeb9c2} -.framework-sidebar .sidebar-nav .nav-title{float:left;overflow:hidden;color:#fff;white-space:nowrap;text-overflow:ellipsis;display:block;width:130px} -.framework-sidebar .sidebar-nav li.active a{background:#2494f2} -.framework-sidebar .sidebar-nav li.active a .nav-title{color:#fff} -.framework-sidebar .sidebar-nav li.active a .nav-icon{color:#fff} -.framework-sidebar .sidebar-nav .manage-nav{height:30px;overflow:hidden} -.framework-sidebar .sidebar-nav .manage-nav:hover .nav-icon{color:#fff} -.framework-sidebar .sidebar-nav .manage-nav a{display:block;height:100%} -.framework-sidebar .sidebar-nav .manage-nav .nav-icon{height:100%;line-height:30px;font-size:16px} -.framework-sidebar .sidebar-nav .manage-nav .nav-title{margin-top:14px;background:#293038;height:1px;width:120px} -.framework-sidebar-mini .wrapper{min-width:950px} -.framework-sidebar-mini .framework-sidebar{width:50px;display:block} -.framework-sidebar-mini .framework-sidebar .sidebar-content{width:70px} -.framework-sidebar-mini .framework-sidebar .sidebar-fold{width:50px} -.framework-sidebar-mini .framework-sidebar .sidebar-nav{width:50px} -.framework-sidebar-mini .framework-sidebar .sidebar-nav ul{text-indent:0;display:block!important} -.framework-sidebar-mini .framework-sidebar .sidebar-nav .nav-item a:hover + .nav-tooltip{display:block} -.framework-sidebar-mini .framework-sidebar .sidebar-nav .nav-title{display:none} -.framework-sidebar-mini .framework-sidebar .sidebar-nav .sidebar-title-icon{margin:0 8px 0 20px} -.framework-sidebar-mini .framework-sidebar .sidebar-nav .sidebar-title-icon{transform:rotate(90deg);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg)} -.framework-sidebar-mini .framework-sidebar .sidebar-fold span{transform:rotate(-270deg);-webkit-transform:rotate(-270deg);-moz-transform:rotate(-270deg);-ms-transform:rotate(-270deg);-o-transform:rotate(-270deg)} -.framework-sidebar-mini .framework-sidebar .sidebar-title{background:#22282e;border-bottom:1px solid #22282e} -.framework-sidebar-mini .framework-sidebar .sidebar-title .sidebar-title-text{display:none} -.framework-sidebar-mini .framework-sidebar .sidebar-title-inner:hover + .nav-tooltip{display:block} -.framework-sidebar-mini .framework-sidebar .sidebar-manage{display:none} -.framework-sidebar-full .framework-sidebar{width:220px;display:block} -.framework-sidebar-full .framework-sidebar .sidebar-nav .nav-icon{width:50px} -.framework-sidebar-mini .framework-container{left:50px} -.framework-sidebar-full .framework-container{left:220px} - -/** 菜单Tips样式 */ -.console-sidebar-tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible} -.console-sidebar-tooltip .tooltip-inner{max-width:200px;padding:12px 8px;color:#fff;text-align:center;text-decoration:none;border-radius:0 0;background-color:#425160} -.console-sidebar-tooltip .tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid} -.console-sidebar-tooltip.right{padding:0 5px;margin-left:3px} -.console-sidebar-tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#425160;border-width:5px 5px 5px 0} -.console-sidebar-tooltip.in{opacity:0.9;filter:alpha(opacity=90)} - -/** checkbox 优化 */ -input[type=checkbox],input[type=radio]{-webkit-appearance:none;appearance:none;width:18px;height:18px;margin:0;cursor:pointer;vertical-align:bottom;background:#fff;border:1px solid #dcdcdc;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-sizing:border-box!important;-moz-box-sizing:border-box!important;box-sizing:border-box!important;position:relative} -input[type=checkbox]:active,input[type=radio]:active{border-color:#c6c6c6;background:#ebebeb} -input[type=checkbox]:hover{border-color:#c6c6c6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.1);box-shadow:inset 0 1px 1px rgba(0,0,0,0.1)} -input[type=radio]{-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:1em;width:18px;height:18px} -input[type=checkbox]:checked,input[type=radio]:checked{background:#fff} -input[type=radio]:checked::after{content:'';display:block;position:relative;top:2px;left:2px;width:12px;height:12px;background:#666;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:1em} -input[type=checkbox]:checked::after{display:block;position:absolute;top:-2px;left:-4px;content:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAAtklEQVQ4y2P4//8/A7Ux1Q0cxoaCADIbCUgCMTvVXAoE5kA8CYidyXYpGrAH4iVAHIXiCwoMDQTimUBcBsRMlBrKCsTpUANzkC0j11BuIK6EGlgKsoAkQ4FgChD7AzELVI8YEDdDDawDYk6YQaQY6gg1oAqILYC4D8oHGcyLbBAphoJAKtQgGO4EYiHk2CLHUJAXm6AG9gCxNHoSIMdQEJCFGqiALaGSayjMxQwUGzq0S6nhZygA2ojsbh6J67kAAAAASUVORK5CYII=')} -input[type=checkbox]:focus,input[type=radio]:focus{outline:none} - -/** 微信菜单 */ -.mobile-preview{position:relative;width:317px;height:580px;background:url('../img/wechat/mobile_head.png') no-repeat 0 0} -.mobile-preview .mobile-header{color:#fff;text-align:center;padding-top:30px;font-size:15px;width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal;margin:0 30px;-webkit-pointer-events:none;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none} -.mobile-body{width:100%;position:absolute;bottom:0;top:60px} -.mobile-body iframe{width:100%;height:100%;background:#fff} -.mobile-footer{list-style-type:none;margin:0;position:absolute;bottom:0;left:0;right:0;border-top:1px solid #e7e7eb;background:url('../img/wechat/mobile_foot.png') no-repeat 0 0;padding-left:43px} -.mobile-footer li{width:33.33%;line-height:50px;position:relative;float:left;text-align:center} -.mobile-footer li a{display:block;border:1px solid rgba(255,255,255,0);border-left:1px solid #e7e7eb;width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal;color:#616161;text-decoration:none} -.mobile-footer li a.active{background-color:#fff;border:1px solid #44b549!important} -.mobile-footer .icon-add{background:url('../img/wechat/index.png') 0 0 no-repeat;width:14px;height:14px;vertical-align:middle;display:inline-block;margin-top:-2px;border-bottom:none!important} -.mobile-footer .icon-sub{background:url('../img/wechat/index.png') 0 -48px no-repeat;width:7px;height:7px;vertical-align:middle;display:inline-block;margin-right:2px;margin-top:-2px} -.mobile-footer .sub-menu{position:absolute;border-radius:3px;border:1px solid #d0d0d0;display:block;bottom:60px;width:100%;background-color:#fafafa} -.mobile-footer .sub-menu ul, -.mobile-footer .sub-menu ul li{padding:0;display:block;width:100%;float:none;z-index:11} -.mobile-footer .sub-menu ul li a{border:1px solid rgba(255,255,255,0)} -.mobile-footer .sub-menu ul li a.bottom-border{border-bottom:1px solid #e7e7eb} -.mobile-footer .arrow{position:absolute;left:50%;margin-left:-6px} -.mobile-footer .arrow_in,.mobile-footer .arrow_out{z-index:10;width:0;height:0;display:inline-block;border-width:6px;border-style:dashed;border-color:transparent;border-bottom-width:0;border-top-style:solid} -.mobile-footer .arrow_in{bottom:-5px;border-top-color:#fafafa}.mobile-footer .arrow_out{bottom:-6px;border-top-color:#d0d0d0} diff --git a/static/theme/default/css/login.css b/static/theme/default/css/login.css deleted file mode 100644 index ac4f32025..000000000 --- a/static/theme/default/css/login.css +++ /dev/null @@ -1,61 +0,0 @@ -@charset "UTF-8"; - -/* +---------------------------------------------------------------------- -// | ThinkAdmin -// +---------------------------------------------------------------------- -// | 版权所有 2014~2017 广州楚才信息科技有限公司 [ http://www.cuci.cc ] -// +---------------------------------------------------------------------- -// | 官方网站: http://think.ctolog.com -// +---------------------------------------------------------------------- -// | 开源协议 ( https://mit-license.org ) -// +---------------------------------------------------------------------- -// | github开源项目:https://github.com/zoujingli/ThinkAdmin -// +--------------------------------------------------------------------*/ - -html, body {height:100%;width:100%;overflow:hidden!important;overflow-y:hidden !important;min-width:0} - -.login-container {background-color:#2494F2;overflow:hidden;font-family:'微软雅黑', serif;margin:0 auto;min-width:1000px;font-size:9pt} -.login-container ul {display:block;margin:0;padding:0;list-style:none} -.login-container ul li {display:block;margin:0;padding:0;list-style:none} -.login-container img {border:0} -.login-container .clouds-container{position:absolute;overflow:hidden;height:100%;width:100%;} -.login-container .clouds {background:url("../img/login/cloud_three.png") repeat-x left 15%;position:absolute;left:0;top:0;height:100%;width:300%;-webkit-animation:cloudmove 200s linear infinite;-moz-animation:cloudmove 200s linear infinite;-o-animation:cloudmove 200s linear infinite;animation:cloudmove 200s linear infinite;-webkit-transform:translate3d(0, 0, 0);-ms-transform:translate3d(0, 0, 0);-o-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)} -.login-container .clouds-fast {background:url("../img/login/cloud.png") no-repeat 0px 40%;-webkit-animation:cloudmove 30s linear infinite;-moz-animation:cloudmove 30s linear infinite;-o-animation:cloudmove 30s linear infinite;animation:cloudmove 30s linear infinite} -.login-container .clouds-footer {background:url("../img/login/cloud_one.png") no-repeat left 100%;animation:none} - -@-webkit-keyframes cloudmove {0% {left:-50%}100% {left:150%}} -@-moz-keyframes cloudmove {0% {left:-50%}100% {left:150%}} -@keyframes cloudmove {0% {left:-50%}100% {left:150%}} - -.login-container a, -.login-container a:hover {color:#00a4ac;text-decoration:none} -.login-container a:focus {text-decoration:none;color:#000;outline:none;blur:expression(this.onFocus=this.blur())} -.login-container .header {height:47px;position:absolute;position:fixed;top:0;z-index:100;width:100%} -.login-container .header span.title {line-height:47px;text-indent:44px;float:left;font-family:"Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", "\9ED1\4F53", Arial, sans-serif;color:#fff;font-size:16px;letter-spacing:0.5px} -.login-container .header ul {float:right;padding-right:30px} -.login-container .header ul li {float:left;margin-left:20px;line-height:47px;letter-spacing:0.5px} -.login-container .header ul li a {color:#fff} -.login-container .header ul li a:hover {color:#fff;} -.login-container .container {background:url(../img/login/loginbg3.png) no-repeat center center;width:100%;height:585px;overflow:hidden;position:relative;top:47px} - -/* 登录表单 */ -.login-container .content {width:692px;height:336px;background:url(../img/login/logininfo.png) no-repeat;margin:80px auto auto auto;position:relative;display:block} -.login-container .content ul {margin-left:285px;margin-right:60px} -.login-container .content ul li {margin-bottom:25px;position:relative} -.login-container .content ul li input {margin-bottom:25px;position:relative;background:url(../img/icon.png) no-repeat;background-size:20px;padding-left:38px} -.login-container .content ul li input.username {background-position:8px 7px} -.login-container .content ul li input.password {background-position:8px -20px} -.login-container .login-input {display:block;width:100%;padding-left:10px;height:38px;line-height:38px;line-height:36px \9;border:1px solid #e6e6e6;background-color:#fff;border-radius:2px} - -/* 头像 */ -.login-container .people {width:165px;height:96px;position:relative;top:8px;left:-70px} -.login-container .people .tou {background:url("../img/login/tou.png") no-repeat;width:97px;height:92px;position:absolute;top:-87px;left:140px} -.login-container .people .left_hand {background:url("../img/login/left_hand.png") no-repeat;width:32px;height:37px;position:absolute;top:-38px;left:150px} -.login-container .people .right_hand {background:url("../img/login/right_hand.png") no-repeat;width:32px;height:37px;position:absolute;top:-38px;right:-64px} -.login-container .people .initial_left_hand {background:url("../img/login/hand.png") no-repeat;width:30px;height:20px;position:absolute;top:-12px;left:100px} -.login-container .people .initial_right_hand {background:url("../img/login/hand.png") no-repeat;width:30px;height:20px;position:absolute;top:-12px;right:-112px} - -/* 底部版权 */ -.login-container .footer {height:50px;line-height:50px;text-align:center;position:fixed;_position:absolute;*position:absolute;bottom:0;width:100%;color:#fff} -.login-container .footer a {font-weight:bold;color:#0b3a58;letter-spacing:0.5px} -.login-container .footer a:hover {color:#fff} diff --git a/static/theme/default/img/cuci.png b/static/theme/default/img/cuci.png deleted file mode 100644 index 7dcecdda51f86dbfb78a8b587547b870269f1879..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2998 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 J&TWj^{U=!)1!Ng9c-iyG9c< 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 diff --git a/static/theme/default/img/icon.png b/static/theme/default/img/icon.png deleted file mode 100644 index 0b21ce01167fe13ddf488768543468d288c9e1b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4871 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 diff --git a/static/theme/default/img/login/buttonbg.png b/static/theme/default/img/login/buttonbg.png deleted file mode 100644 index 278d93661e3ce7714710e42eba56772f0468301d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3000 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%0A>5eXPe&6f)6flx_KK?yHF}lBHRf!;8Ns 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�%pgfGC+WRsd7oxjCrX_5wU_iS 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

                    SO!=lU;MbdpsUhJKTD@ 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ν5uBpq3i~>QSE#Q@jrHqQ6O(ccdf$5qwz*Mafz3 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

                    wOa2;N5BqS!1q=o4`Ly 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 KS^VV9=l=&@tiV_R diff --git a/static/theme/default/img/login/loginbg1.png b/static/theme/default/img/login/loginbg1.png deleted file mode 100644 index 5e1c810183fe416b2d65c4c952ca10abe56de1b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2834 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 diff --git a/static/theme/default/img/login/loginbg2.png b/static/theme/default/img/login/loginbg2.png deleted file mode 100644 index e4aa3516c7790ed457f702b23a636318e72c1e32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2829 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 diff --git a/static/theme/default/img/login/loginpassword.png b/static/theme/default/img/login/loginpassword.png deleted file mode 100644 index 4e14c4b0f480c6efea677ed4ea212acb653fbefa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3453 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 DK;)A# diff --git a/static/theme/default/img/paper.jpg b/static/theme/default/img/paper.jpg deleted file mode 100644 index b6cdb70f9cefcd930bbf117a2fcf3b04d4a5f076..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 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 literal 0 HcmV?d00001 diff --git a/static/theme/default/img/image.png b/static/theme/img/image.png similarity index 100% rename from static/theme/default/img/image.png rename to static/theme/img/image.png diff --git a/static/theme/default/img/login/loginbg3.png b/static/theme/img/login/bg.png similarity index 100% rename from static/theme/default/img/login/loginbg3.png rename to static/theme/img/login/bg.png diff --git a/static/theme/default/img/login/logininfo.png b/static/theme/img/login/bg_form.png similarity index 100% rename from static/theme/default/img/login/logininfo.png rename to static/theme/img/login/bg_form.png diff --git a/static/theme/default/img/login/cloud.png b/static/theme/img/login/cloud.png similarity index 100% rename from static/theme/default/img/login/cloud.png rename to static/theme/img/login/cloud.png diff --git a/static/theme/default/img/login/cloud_one.png b/static/theme/img/login/cloud_one.png similarity index 100% rename from static/theme/default/img/login/cloud_one.png rename to static/theme/img/login/cloud_one.png diff --git a/static/theme/default/img/login/cloud_three.png b/static/theme/img/login/cloud_two.png similarity index 100% rename from static/theme/default/img/login/cloud_three.png rename to static/theme/img/login/cloud_two.png diff --git a/static/theme/default/img/login/hand.png b/static/theme/img/login/hand.png similarity index 100% rename from static/theme/default/img/login/hand.png rename to static/theme/img/login/hand.png diff --git a/static/theme/default/img/login/left_hand.png b/static/theme/img/login/hand_left.png similarity index 100% rename from static/theme/default/img/login/left_hand.png rename to static/theme/img/login/hand_left.png diff --git a/static/theme/default/img/login/right_hand.png b/static/theme/img/login/hand_right.png similarity index 100% rename from static/theme/default/img/login/right_hand.png rename to static/theme/img/login/hand_right.png diff --git a/static/theme/default/img/login/tou.png b/static/theme/img/login/hand_tou.png similarity index 100% rename from static/theme/default/img/login/tou.png rename to static/theme/img/login/hand_tou.png diff --git a/static/theme/default/img/wechat/index.png b/static/theme/img/wechat/index.png similarity index 100% rename from static/theme/default/img/wechat/index.png rename to static/theme/img/wechat/index.png diff --git a/static/theme/default/img/wechat/mobile_foot.png b/static/theme/img/wechat/mobile_foot.png similarity index 100% rename from static/theme/default/img/wechat/mobile_foot.png rename to static/theme/img/wechat/mobile_foot.png diff --git a/static/theme/default/img/wechat/mobile_head.png b/static/theme/img/wechat/mobile_head.png similarity index 100% rename from static/theme/default/img/wechat/mobile_head.png rename to static/theme/img/wechat/mobile_head.png diff --git a/static/theme/default/img/wechat/qrc_payed.jpg b/static/theme/img/wechat/qrc_payed.jpg similarity index 100% rename from static/theme/default/img/wechat/qrc_payed.jpg rename to static/theme/img/wechat/qrc_payed.jpg diff --git a/think b/think new file mode 100644 index 000000000..a6cdbd0c0 --- /dev/null +++ b/think @@ -0,0 +1,22 @@ +#!/usr/bin/env php +initialize(); +Console::init(); diff --git a/think.admin.sql b/think.admin.sql deleted file mode 100644 index f8c5bde6d..000000000 --- a/think.admin.sql +++ /dev/null @@ -1,570 +0,0 @@ -/* -Navicat MySQL Data Transfer - -Source Server : ctolog.com_3306 -Source Server Version : 50629 -Source Host : ctolog.com:3306 -Source Database : ThinkAdmin - -Target Server Type : MYSQL -Target Server Version : 50629 -File Encoding : 65001 - -Date: 2017-08-23 16:17:24 -*/ - -SET FOREIGN_KEY_CHECKS=0; - --- ---------------------------- --- Table structure for system_auth --- ---------------------------- -DROP TABLE IF EXISTS `system_auth`; -CREATE TABLE `system_auth` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `title` varchar(20) NOT NULL COMMENT '权限名称', - `status` tinyint(1) unsigned DEFAULT '1' COMMENT '状态(1:禁用,2:启用)', - `sort` smallint(6) unsigned DEFAULT '0' COMMENT '排序权重', - `desc` varchar(255) DEFAULT NULL COMMENT '备注说明', - `create_by` bigint(11) unsigned DEFAULT '0' COMMENT '创建人', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`), - UNIQUE KEY `index_system_auth_title` (`title`) USING BTREE, - KEY `index_system_auth_status` (`status`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统权限表'; - --- ---------------------------- --- Records of system_auth --- ---------------------------- - --- ---------------------------- --- Table structure for system_auth_node --- ---------------------------- -DROP TABLE IF EXISTS `system_auth_node`; -CREATE TABLE `system_auth_node` ( - `auth` bigint(20) unsigned DEFAULT NULL COMMENT '角色ID', - `node` varchar(200) DEFAULT NULL COMMENT '节点路径', - KEY `index_system_auth_auth` (`auth`) USING BTREE, - KEY `index_system_auth_node` (`node`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色与节点关系表'; - --- ---------------------------- --- Records of system_auth_node --- ---------------------------- - --- ---------------------------- --- Table structure for system_config --- ---------------------------- -DROP TABLE IF EXISTS `system_config`; -CREATE TABLE `system_config` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(100) DEFAULT NULL COMMENT '配置编码', - `value` varchar(500) DEFAULT NULL COMMENT '配置值', - PRIMARY KEY (`id`), - KEY `index_system_config_name` (`name`) -) ENGINE=InnoDB AUTO_INCREMENT=204 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统参数配置'; - --- ---------------------------- --- Records of system_config --- ---------------------------- -INSERT INTO `system_config` VALUES ('148', 'site_name', 'ThinkAdmin Demo'); -INSERT INTO `system_config` VALUES ('149', 'site_copy', '广州楚才信息科技有限公司 © 2014~2017'); -INSERT INTO `system_config` VALUES ('164', 'storage_type', 'local'); -INSERT INTO `system_config` VALUES ('165', 'storage_qiniu_is_https', '1'); -INSERT INTO `system_config` VALUES ('166', 'storage_qiniu_bucket', 'static'); -INSERT INTO `system_config` VALUES ('167', 'storage_qiniu_domain', 'static.ctolog.com'); -INSERT INTO `system_config` VALUES ('168', 'storage_qiniu_access_key', 'OAFHGzCgZjod2-s4xr-g5ptkXsNbxDO_t2fozIEC'); -INSERT INTO `system_config` VALUES ('169', 'storage_qiniu_secret_key', 'gy0aYdSFMSayQ4kMkgUeEeJRLThVjLpUJoPFxd-Z'); -INSERT INTO `system_config` VALUES ('170', 'storage_qiniu_region', '华东'); -INSERT INTO `system_config` VALUES ('173', 'app_name', 'ThinkAdmin'); -INSERT INTO `system_config` VALUES ('174', 'app_version', '2.00 dev'); -INSERT INTO `system_config` VALUES ('176', 'browser_icon', 'https://think.ctolog.com/static/upload/f47b8fe06e38ae99/08e8398da45583b9.png'); -INSERT INTO `system_config` VALUES ('184', 'wechat_appid', 'wx60a43dd8161666d4'); -INSERT INTO `system_config` VALUES ('185', 'wechat_appsecret', '062938ddcfe0d69786e4e3d9dcbb08aa'); -INSERT INTO `system_config` VALUES ('186', 'wechat_token', 'mytoken'); -INSERT INTO `system_config` VALUES ('187', 'wechat_encodingaeskey', 'KHyoWLoS7oLZYkB4PokMTfA5sm6Hrqc9Tzgn9iXc0YH'); -INSERT INTO `system_config` VALUES ('188', 'wechat_mch_id', '1332187001'); -INSERT INTO `system_config` VALUES ('189', 'wechat_partnerkey', 'A82DC5BD1F3359081049C568D8502BC5'); -INSERT INTO `system_config` VALUES ('194', 'wechat_cert_key', ''); -INSERT INTO `system_config` VALUES ('196', 'wechat_cert_cert', ''); -INSERT INTO `system_config` VALUES ('197', 'tongji_baidu_key', 'aa2f9869e9b578122e4692de2bd9f80f'); -INSERT INTO `system_config` VALUES ('198', 'tongji_cnzz_key', '1261854404'); -INSERT INTO `system_config` VALUES ('199', 'storage_oss_bucket', 'think-oss'); -INSERT INTO `system_config` VALUES ('200', 'storage_oss_keyid', 'WjeX0AYSfgy5VbXQ'); -INSERT INTO `system_config` VALUES ('201', 'storage_oss_secret', 'hQTENHy6MYVUTgwjcgfOCq5gckm2Lp'); -INSERT INTO `system_config` VALUES ('202', 'storage_oss_domain', 'think-oss.oss-cn-shanghai.aliyuncs.com'); -INSERT INTO `system_config` VALUES ('203', 'storage_oss_is_https', '1'); - --- ---------------------------- --- Table structure for system_log --- ---------------------------- -DROP TABLE IF EXISTS `system_log`; -CREATE TABLE `system_log` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `ip` char(15) NOT NULL DEFAULT '' COMMENT '操作者IP地址', - `node` char(200) NOT NULL DEFAULT '' COMMENT '当前操作节点', - `username` varchar(32) NOT NULL DEFAULT '' COMMENT '操作人用户名', - `action` varchar(200) NOT NULL DEFAULT '' COMMENT '操作行为', - `content` text NOT NULL COMMENT '操作内容描述', - `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统操作日志表'; - --- ---------------------------- --- Records of system_log --- ---------------------------- - --- ---------------------------- --- Table structure for system_menu --- ---------------------------- -DROP TABLE IF EXISTS `system_menu`; -CREATE TABLE `system_menu` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `pid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '父id', - `title` varchar(100) NOT NULL DEFAULT '' COMMENT '名称', - `node` varchar(200) NOT NULL DEFAULT '' COMMENT '节点代码', - `icon` varchar(100) NOT NULL DEFAULT '' COMMENT '菜单图标', - `url` varchar(400) NOT NULL DEFAULT '' COMMENT '链接', - `params` varchar(500) DEFAULT '' COMMENT '链接参数', - `target` varchar(20) NOT NULL DEFAULT '_self' COMMENT '链接打开方式', - `sort` int(11) unsigned DEFAULT '0' COMMENT '菜单排序', - `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)', - `create_by` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '创建人', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`), - KEY `index_system_menu_node` (`node`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8 COMMENT='系统菜单表'; - --- ---------------------------- --- Records of system_menu --- ---------------------------- -INSERT INTO `system_menu` VALUES ('2', '0', '系统管理', '', '', '#', '', '_self', '1000', '1', '0', '2015-11-16 19:15:38'); -INSERT INTO `system_menu` VALUES ('4', '2', '系统配置', '', '', '#', '', '_self', '100', '1', '0', '2016-03-14 18:12:55'); -INSERT INTO `system_menu` VALUES ('5', '4', '网站参数', '', 'fa fa-apple', 'admin/config/index', '', '_self', '20', '1', '0', '2016-05-06 14:36:49'); -INSERT INTO `system_menu` VALUES ('6', '4', '文件存储', '', 'fa fa-save', 'admin/config/file', '', '_self', '30', '1', '0', '2016-05-06 14:39:43'); -INSERT INTO `system_menu` VALUES ('9', '20', '操作日志', '', 'glyphicon glyphicon-console', 'admin/log/index', '', '_self', '50', '1', '0', '2017-03-24 15:49:31'); -INSERT INTO `system_menu` VALUES ('19', '20', '权限管理', '', 'fa fa-user-secret', 'admin/auth/index', '', '_self', '10', '1', '0', '2015-11-17 13:18:12'); -INSERT INTO `system_menu` VALUES ('20', '2', '系统权限', '', '', '#', '', '_self', '200', '1', '0', '2016-03-14 18:11:41'); -INSERT INTO `system_menu` VALUES ('21', '20', '系统菜单', '', 'glyphicon glyphicon-menu-hamburger', 'admin/menu/index', '', '_self', '30', '1', '0', '2015-11-16 19:16:16'); -INSERT INTO `system_menu` VALUES ('22', '20', '节点管理', '', 'fa fa-ellipsis-v', 'admin/node/index', '', '_self', '20', '1', '0', '2015-11-16 19:16:16'); -INSERT INTO `system_menu` VALUES ('29', '20', '系统用户', '', 'fa fa-users', 'admin/user/index', '', '_self', '40', '1', '0', '2016-10-31 14:31:40'); -INSERT INTO `system_menu` VALUES ('61', '0', '微信管理', '', '', '#', '', '_self', '2000', '1', '0', '2017-03-29 11:00:21'); -INSERT INTO `system_menu` VALUES ('62', '61', '微信对接配置', '', '', '#', '', '_self', '100', '1', '0', '2017-03-29 11:03:38'); -INSERT INTO `system_menu` VALUES ('63', '62', '微信接口配置\r\n', '', 'fa fa-usb', 'wechat/config/index', '', '_self', '10', '1', '0', '2017-03-29 11:04:44'); -INSERT INTO `system_menu` VALUES ('65', '61', '微信粉丝管理', '', '', '#', '', '_self', '300', '1', '0', '2017-03-29 11:08:32'); -INSERT INTO `system_menu` VALUES ('66', '65', '粉丝标签', '', 'fa fa-tags', 'wechat/tags/index', '', '_self', '10', '1', '0', '2017-03-29 11:09:41'); -INSERT INTO `system_menu` VALUES ('67', '65', '已关注粉丝', '', 'fa fa-wechat', 'wechat/fans/index', '', '_self', '20', '1', '0', '2017-03-29 11:10:07'); -INSERT INTO `system_menu` VALUES ('68', '61', '微信订制', '', '', '#', '', '_self', '200', '1', '0', '2017-03-29 11:10:39'); -INSERT INTO `system_menu` VALUES ('69', '68', '微信菜单定制', '', 'glyphicon glyphicon-phone', 'wechat/menu/index', '', '_self', '40', '1', '0', '2017-03-29 11:11:08'); -INSERT INTO `system_menu` VALUES ('70', '68', '关键字管理', '', 'fa fa-paw', 'wechat/keys/index', '', '_self', '10', '1', '0', '2017-03-29 11:11:49'); -INSERT INTO `system_menu` VALUES ('71', '68', '关注自动回复', '', 'fa fa-commenting-o', 'wechat/keys/subscribe', '', '_self', '20', '1', '0', '2017-03-29 11:12:32'); -INSERT INTO `system_menu` VALUES ('81', '68', '无配置默认回复', '', 'fa fa-commenting-o', 'wechat/keys/defaults', '', '_self', '30', '1', '0', '2017-04-21 14:48:25'); -INSERT INTO `system_menu` VALUES ('82', '61', '素材资源管理', '', '', '#', '', '_self', '300', '1', '0', '2017-04-24 11:23:18'); -INSERT INTO `system_menu` VALUES ('83', '82', '添加图文', '', 'fa fa-folder-open-o', 'wechat/news/add?id=1', '', '_self', '20', '1', '0', '2017-04-24 11:23:40'); -INSERT INTO `system_menu` VALUES ('85', '82', '图文列表', '', 'fa fa-file-pdf-o', 'wechat/news/index', '', '_self', '10', '1', '0', '2017-04-24 11:25:45'); -INSERT INTO `system_menu` VALUES ('86', '65', '粉丝黑名单', '', 'fa fa-reddit-alien', 'wechat/fans/back', '', '_self', '30', '1', '0', '2017-05-05 16:17:03'); -INSERT INTO `system_menu` VALUES ('87', '0', '插件案例', '', '', '#', '', '_self', '3000', '1', '0', '2017-07-10 15:10:16'); -INSERT INTO `system_menu` VALUES ('88', '87', '第三方插件', '', '', '#', '', '_self', '0', '1', '0', '2017-07-10 15:10:37'); -INSERT INTO `system_menu` VALUES ('90', '88', 'PCAS 省市区', '', '', 'demo/plugs/region', '', '_self', '0', '1', '0', '2017-07-10 18:45:47'); -INSERT INTO `system_menu` VALUES ('91', '87', '内置插件', '', '', '#', '', '_self', '0', '1', '0', '2017-07-10 18:56:59'); -INSERT INTO `system_menu` VALUES ('92', '91', '文件上传', '', '', 'demo/plugs/file', '', '_self', '0', '1', '0', '2017-07-10 18:57:22'); -INSERT INTO `system_menu` VALUES ('93', '88', '富文本编辑器', '', '', 'demo/plugs/editor', '', '_self', '0', '1', '0', '2017-07-28 15:19:44'); -INSERT INTO `system_menu` VALUES ('94', '0', '后台首页', '', '', 'admin/index/main', '', '_self', '0', '1', '0', '2017-08-08 11:28:43'); - --- ---------------------------- --- Table structure for system_node --- ---------------------------- -DROP TABLE IF EXISTS `system_node`; -CREATE TABLE `system_node` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `node` varchar(100) DEFAULT NULL COMMENT '节点代码', - `title` varchar(500) DEFAULT NULL COMMENT '节点标题', - `is_menu` tinyint(1) unsigned DEFAULT '0' COMMENT '是否可设置为菜单', - `is_auth` tinyint(1) unsigned DEFAULT '1' COMMENT '是否启动RBAC权限控制', - `is_login` tinyint(1) unsigned DEFAULT '1' COMMENT '是否启动登录控制', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`), - KEY `index_system_node_node` (`node`) -) ENGINE=InnoDB AUTO_INCREMENT=209 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统节点表'; - --- ---------------------------- --- Records of system_node --- ---------------------------- -INSERT INTO `system_node` VALUES ('131', 'admin/auth/index', '权限列表', '1', '1', '1', '2017-08-23 15:45:42'); -INSERT INTO `system_node` VALUES ('132', 'admin', '后台管理', '0', '1', '1', '2017-08-23 15:45:44'); -INSERT INTO `system_node` VALUES ('133', 'admin/auth/apply', '节点授权', '0', '1', '1', '2017-08-23 16:05:18'); -INSERT INTO `system_node` VALUES ('134', 'admin/auth/add', '添加授权', '0', '1', '1', '2017-08-23 16:05:19'); -INSERT INTO `system_node` VALUES ('135', 'admin/auth/edit', '编辑权限', '0', '1', '1', '2017-08-23 16:05:19'); -INSERT INTO `system_node` VALUES ('136', 'admin/auth/forbid', '禁用权限', '0', '1', '1', '2017-08-23 16:05:20'); -INSERT INTO `system_node` VALUES ('137', 'admin/auth/resume', '启用权限', '0', '1', '1', '2017-08-23 16:05:20'); -INSERT INTO `system_node` VALUES ('138', 'admin/auth/del', '删除权限', '0', '1', '1', '2017-08-23 16:05:21'); -INSERT INTO `system_node` VALUES ('139', 'admin/config/index', '参数配置', '1', '1', '1', '2017-08-23 16:05:22'); -INSERT INTO `system_node` VALUES ('140', 'admin/config/file', '文件配置', '1', '1', '1', '2017-08-23 16:05:22'); -INSERT INTO `system_node` VALUES ('141', 'admin/log/index', '日志列表', '1', '1', '1', '2017-08-23 16:05:23'); -INSERT INTO `system_node` VALUES ('142', 'admin/log/del', '删除日志', '0', '1', '1', '2017-08-23 16:05:24'); -INSERT INTO `system_node` VALUES ('143', 'admin/menu/index', '菜单列表', '1', '1', '1', '2017-08-23 16:05:25'); -INSERT INTO `system_node` VALUES ('144', 'admin/menu/add', '添加菜单', '0', '1', '1', '2017-08-23 16:05:25'); -INSERT INTO `system_node` VALUES ('145', 'admin/menu/edit', '编辑菜单', '0', '1', '1', '2017-08-23 16:05:26'); -INSERT INTO `system_node` VALUES ('146', 'admin/menu/del', '删除菜单', '0', '1', '1', '2017-08-23 16:05:26'); -INSERT INTO `system_node` VALUES ('147', 'admin/menu/forbid', '禁用菜单', '0', '1', '1', '2017-08-23 16:05:27'); -INSERT INTO `system_node` VALUES ('148', 'admin/menu/resume', '启用菜单', '0', '1', '1', '2017-08-23 16:05:28'); -INSERT INTO `system_node` VALUES ('149', 'admin/node/index', '节点列表', '1', '1', '1', '2017-08-23 16:05:29'); -INSERT INTO `system_node` VALUES ('150', 'admin/node/save', '节点更新', '0', '1', '1', '2017-08-23 16:05:30'); -INSERT INTO `system_node` VALUES ('151', 'admin/user/index', '用户管理', '1', '1', '1', '2017-08-23 16:05:31'); -INSERT INTO `system_node` VALUES ('152', 'admin/user/auth', '用户授权', '0', '1', '1', '2017-08-23 16:05:32'); -INSERT INTO `system_node` VALUES ('153', 'admin/user/add', '添加用户', '0', '1', '1', '2017-08-23 16:05:33'); -INSERT INTO `system_node` VALUES ('154', 'admin/user/edit', '编辑用户', '0', '1', '1', '2017-08-23 16:05:33'); -INSERT INTO `system_node` VALUES ('155', 'admin/user/pass', '用户密码', '0', '1', '1', '2017-08-23 16:05:34'); -INSERT INTO `system_node` VALUES ('156', 'admin/user/del', '删除用户', '0', '1', '1', '2017-08-23 16:05:34'); -INSERT INTO `system_node` VALUES ('157', 'admin/user/forbid', '禁用用户', '0', '1', '1', '2017-08-23 16:05:34'); -INSERT INTO `system_node` VALUES ('158', 'admin/user/resume', '启用用户', '0', '1', '1', '2017-08-23 16:05:35'); -INSERT INTO `system_node` VALUES ('159', 'demo/plugs/file', '文件上传', '1', '0', '0', '2017-08-23 16:05:36'); -INSERT INTO `system_node` VALUES ('160', 'demo/plugs/region', '区域选择', '1', '0', '0', '2017-08-23 16:05:36'); -INSERT INTO `system_node` VALUES ('161', 'demo/plugs/editor', '富文本器', '1', '0', '0', '2017-08-23 16:05:37'); -INSERT INTO `system_node` VALUES ('162', 'wechat/config/index', '微信参数', '1', '1', '1', '2017-08-23 16:05:37'); -INSERT INTO `system_node` VALUES ('163', 'wechat/config/pay', '微信支付', '0', '1', '1', '2017-08-23 16:05:38'); -INSERT INTO `system_node` VALUES ('164', 'wechat/fans/index', '粉丝列表', '1', '1', '1', '2017-08-23 16:05:39'); -INSERT INTO `system_node` VALUES ('165', 'wechat/fans/back', '粉丝黑名单', '1', '1', '1', '2017-08-23 16:05:39'); -INSERT INTO `system_node` VALUES ('166', 'wechat/fans/backadd', '移入黑名单', '0', '1', '1', '2017-08-23 16:05:40'); -INSERT INTO `system_node` VALUES ('167', 'wechat/fans/tagset', '设置标签', '0', '1', '1', '2017-08-23 16:05:41'); -INSERT INTO `system_node` VALUES ('168', 'wechat/fans/backdel', '移出黑名单', '0', '1', '1', '2017-08-23 16:05:41'); -INSERT INTO `system_node` VALUES ('169', 'wechat/fans/tagadd', '添加标签', '0', '1', '1', '2017-08-23 16:05:41'); -INSERT INTO `system_node` VALUES ('170', 'wechat/fans/tagdel', '删除标签', '0', '1', '1', '2017-08-23 16:05:42'); -INSERT INTO `system_node` VALUES ('171', 'wechat/fans/sync', '同步粉丝', '0', '1', '1', '2017-08-23 16:05:43'); -INSERT INTO `system_node` VALUES ('172', 'wechat/keys/index', '关键字列表', '1', '1', '1', '2017-08-23 16:05:44'); -INSERT INTO `system_node` VALUES ('173', 'wechat/keys/add', '添加关键字', '0', '1', '1', '2017-08-23 16:05:44'); -INSERT INTO `system_node` VALUES ('174', 'wechat/keys/edit', '编辑关键字', '0', '1', '1', '2017-08-23 16:05:45'); -INSERT INTO `system_node` VALUES ('175', 'wechat/keys/del', '删除关键字', '0', '1', '1', '2017-08-23 16:05:45'); -INSERT INTO `system_node` VALUES ('176', 'wechat/keys/forbid', '禁用关键字', '0', '1', '1', '2017-08-23 16:05:46'); -INSERT INTO `system_node` VALUES ('177', 'wechat/keys/resume', '启用关键字', '0', '1', '1', '2017-08-23 16:05:46'); -INSERT INTO `system_node` VALUES ('178', 'wechat/keys/subscribe', '关注默认回复', '0', '1', '1', '2017-08-23 16:05:48'); -INSERT INTO `system_node` VALUES ('179', 'wechat/keys/defaults', '默认响应回复', '0', '1', '1', '2017-08-23 16:05:49'); -INSERT INTO `system_node` VALUES ('180', 'wechat/menu/index', '微信菜单', '1', '1', '1', '2017-08-23 16:05:51'); -INSERT INTO `system_node` VALUES ('181', 'wechat/menu/edit', '发布微信菜单', '0', '1', '1', '2017-08-23 16:05:51'); -INSERT INTO `system_node` VALUES ('182', 'wechat/menu/cancel', '取消微信菜单', '0', '1', '1', '2017-08-23 16:05:52'); -INSERT INTO `system_node` VALUES ('183', 'wechat/news/index', '微信图文列表', '1', '1', '1', '2017-08-23 16:05:52'); -INSERT INTO `system_node` VALUES ('184', 'wechat/news/select', '微信图文选择', '0', '1', '1', '2017-08-23 16:05:53'); -INSERT INTO `system_node` VALUES ('185', 'wechat/news/image', '微信图片选择', '0', '1', '1', '2017-08-23 16:05:53'); -INSERT INTO `system_node` VALUES ('186', 'wechat/news/add', '添加图文', '0', '1', '1', '2017-08-23 16:05:54'); -INSERT INTO `system_node` VALUES ('187', 'wechat/news/edit', '编辑图文', '0', '1', '1', '2017-08-23 16:05:56'); -INSERT INTO `system_node` VALUES ('188', 'wechat/news/del', '删除图文', '0', '1', '1', '2017-08-23 16:05:56'); -INSERT INTO `system_node` VALUES ('189', 'wechat/news/push', '推送图文', '0', '1', '1', '2017-08-23 16:05:56'); -INSERT INTO `system_node` VALUES ('190', 'wechat/tags/index', '微信标签列表', '1', '1', '1', '2017-08-23 16:05:58'); -INSERT INTO `system_node` VALUES ('191', 'wechat/tags/add', '添加微信标签', '0', '1', '1', '2017-08-23 16:05:58'); -INSERT INTO `system_node` VALUES ('192', 'wechat/tags/edit', '编辑微信标签', '0', '1', '1', '2017-08-23 16:05:58'); -INSERT INTO `system_node` VALUES ('193', 'wechat/tags/sync', '同步微信标签', '0', '1', '1', '2017-08-23 16:05:59'); -INSERT INTO `system_node` VALUES ('194', 'admin/auth', '权限管理', '0', '1', '1', '2017-08-23 16:06:58'); -INSERT INTO `system_node` VALUES ('195', 'admin/config', '系统配置', '0', '1', '1', '2017-08-23 16:07:34'); -INSERT INTO `system_node` VALUES ('196', 'admin/log', '系统日志', '0', '1', '1', '2017-08-23 16:07:46'); -INSERT INTO `system_node` VALUES ('197', 'admin/menu', '系统菜单', '0', '1', '1', '2017-08-23 16:08:02'); -INSERT INTO `system_node` VALUES ('198', 'admin/node', '系统节点', '0', '1', '1', '2017-08-23 16:08:44'); -INSERT INTO `system_node` VALUES ('199', 'admin/user', '系统用户', '0', '1', '1', '2017-08-23 16:09:43'); -INSERT INTO `system_node` VALUES ('200', 'demo', '插件案例', '0', '1', '1', '2017-08-23 16:10:43'); -INSERT INTO `system_node` VALUES ('201', 'demo/plugs', '插件案例', '0', '1', '1', '2017-08-23 16:10:51'); -INSERT INTO `system_node` VALUES ('202', 'wechat', '微信管理', '0', '1', '1', '2017-08-23 16:11:13'); -INSERT INTO `system_node` VALUES ('203', 'wechat/config', '微信配置', '0', '1', '1', '2017-08-23 16:11:19'); -INSERT INTO `system_node` VALUES ('204', 'wechat/fans', '粉丝管理', '0', '1', '1', '2017-08-23 16:11:36'); -INSERT INTO `system_node` VALUES ('205', 'wechat/keys', '关键字管理', '0', '1', '1', '2017-08-23 16:13:00'); -INSERT INTO `system_node` VALUES ('206', 'wechat/menu', '微信菜单管理', '0', '1', '1', '2017-08-23 16:14:11'); -INSERT INTO `system_node` VALUES ('207', 'wechat/news', '微信图文管理', '0', '1', '1', '2017-08-23 16:14:40'); -INSERT INTO `system_node` VALUES ('208', 'wechat/tags', '微信标签管理', '0', '1', '1', '2017-08-23 16:15:25'); - --- ---------------------------- --- Table structure for system_sequence --- ---------------------------- -DROP TABLE IF EXISTS `system_sequence`; -CREATE TABLE `system_sequence` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `type` varchar(20) DEFAULT NULL COMMENT '序号类型', - `sequence` char(50) NOT NULL COMMENT '序号值', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `index_system_sequence_unique` (`type`,`sequence`) USING BTREE, - KEY `index_system_sequence_type` (`type`), - KEY `index_system_sequence_number` (`sequence`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统序号表'; - --- ---------------------------- --- Records of system_sequence --- ---------------------------- - --- ---------------------------- --- Table structure for system_user --- ---------------------------- -DROP TABLE IF EXISTS `system_user`; -CREATE TABLE `system_user` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户登录名', - `password` char(32) NOT NULL DEFAULT '' COMMENT '用户登录密码', - `qq` varchar(16) DEFAULT NULL COMMENT '联系QQ', - `mail` varchar(32) DEFAULT NULL COMMENT '联系邮箱', - `phone` varchar(16) DEFAULT NULL COMMENT '联系手机号', - `desc` varchar(255) DEFAULT '' COMMENT '备注说明', - `login_num` bigint(20) unsigned DEFAULT '0' COMMENT '登录次数', - `login_at` datetime DEFAULT NULL, - `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)', - `authorize` varchar(255) DEFAULT NULL, - `is_deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '删除状态(1:删除,0:未删)', - `create_by` bigint(20) unsigned DEFAULT NULL COMMENT '创建人', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`), - UNIQUE KEY `index_system_user_username` (`username`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='系统用户表'; - --- ---------------------------- --- Records of system_user --- ---------------------------- -INSERT INTO `system_user` VALUES ('10000', 'admin', '21232f297a57a5a743894a0e4a801fc3', '22222222', 'sdfsd@Qq.com1', '13888888855', 'dfgsdfgsfd', '27039', '2017-08-23 16:15:57', '1', '301,302,303,304', '0', null, '2015-11-13 15:14:22'); - --- ---------------------------- --- Table structure for wechat_fans --- ---------------------------- -DROP TABLE IF EXISTS `wechat_fans`; -CREATE TABLE `wechat_fans` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '粉丝表ID', - `appid` varchar(50) DEFAULT NULL COMMENT '公众号Appid', - `groupid` bigint(20) unsigned DEFAULT NULL COMMENT '分组ID', - `tagid_list` varchar(100) DEFAULT '' COMMENT '标签id', - `is_back` tinyint(1) unsigned DEFAULT '0' COMMENT '是否为黑名单用户', - `subscribe` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '用户是否订阅该公众号,0:未关注,1:已关注', - `openid` char(100) NOT NULL DEFAULT '' COMMENT '用户的标识,对当前公众号唯一', - `spread_openid` char(100) DEFAULT NULL COMMENT '推荐人OPENID', - `spread_at` datetime DEFAULT NULL, - `nickname` varchar(100) DEFAULT NULL COMMENT '用户的昵称', - `sex` tinyint(1) unsigned DEFAULT NULL COMMENT '用户的性别,值为1时是男性,值为2时是女性,值为0时是未知', - `country` varchar(50) DEFAULT NULL COMMENT '用户所在国家', - `province` varchar(50) DEFAULT NULL COMMENT '用户所在省份', - `city` varchar(50) DEFAULT NULL COMMENT '用户所在城市', - `language` varchar(50) DEFAULT NULL COMMENT '用户的语言,简体中文为zh_CN', - `headimgurl` varchar(500) DEFAULT NULL COMMENT '用户头像', - `subscribe_time` bigint(20) unsigned DEFAULT NULL COMMENT '用户关注时间', - `subscribe_at` datetime DEFAULT NULL COMMENT '关注时间', - `unionid` varchar(100) DEFAULT NULL COMMENT 'unionid', - `remark` varchar(50) DEFAULT NULL COMMENT '备注', - `expires_in` bigint(20) unsigned DEFAULT '0' COMMENT '有效时间', - `refresh_token` varchar(200) DEFAULT NULL COMMENT '刷新token', - `access_token` varchar(200) DEFAULT NULL COMMENT '访问token', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`), - KEY `index_wechat_fans_spread_openid` (`spread_openid`) USING BTREE, - KEY `index_wechat_fans_openid` (`openid`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信粉丝'; - --- ---------------------------- --- Records of wechat_fans --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_fans_tags --- ---------------------------- -DROP TABLE IF EXISTS `wechat_fans_tags`; -CREATE TABLE `wechat_fans_tags` ( - `id` bigint(20) unsigned NOT NULL COMMENT '标签ID', - `appid` char(50) DEFAULT NULL COMMENT '公众号APPID', - `name` varchar(35) DEFAULT NULL COMMENT '标签名称', - `count` int(11) unsigned DEFAULT NULL COMMENT '总数', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期', - KEY `index_wechat_fans_tags_id` (`id`) USING BTREE, - KEY `index_wechat_fans_tags_appid` (`appid`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信会员标签'; - --- ---------------------------- --- Records of wechat_fans_tags --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_keys --- ---------------------------- -DROP TABLE IF EXISTS `wechat_keys`; -CREATE TABLE `wechat_keys` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `appid` char(50) DEFAULT NULL COMMENT '公众号APPID', - `type` varchar(20) DEFAULT NULL COMMENT '类型,text 文件消息,image 图片消息,news 图文消息', - `keys` varchar(100) DEFAULT NULL COMMENT '关键字', - `content` text COMMENT '文本内容', - `image_url` varchar(255) DEFAULT NULL COMMENT '图片链接', - `voice_url` varchar(255) DEFAULT NULL COMMENT '语音链接', - `music_title` varchar(100) DEFAULT NULL COMMENT '音乐标题', - `music_url` varchar(255) DEFAULT NULL COMMENT '音乐链接', - `music_image` varchar(255) DEFAULT NULL COMMENT '音乐缩略图链接', - `music_desc` varchar(255) DEFAULT NULL COMMENT '音乐描述', - `video_title` varchar(100) DEFAULT NULL COMMENT '视频标题', - `video_url` varchar(255) DEFAULT NULL COMMENT '视频URL', - `video_desc` varchar(255) DEFAULT NULL COMMENT '视频描述', - `news_id` bigint(20) unsigned DEFAULT NULL COMMENT '图文ID', - `sort` bigint(20) unsigned DEFAULT '0' COMMENT '排序字段', - `status` tinyint(1) unsigned DEFAULT '1' COMMENT '0 禁用,1 启用', - `create_by` bigint(20) unsigned DEFAULT NULL COMMENT '创建人', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=' 微信关键字'; - --- ---------------------------- --- Records of wechat_keys --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_menu --- ---------------------------- -DROP TABLE IF EXISTS `wechat_menu`; -CREATE TABLE `wechat_menu` ( - `id` bigint(16) unsigned NOT NULL AUTO_INCREMENT, - `index` bigint(20) DEFAULT NULL, - `pindex` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '父id', - `type` varchar(24) NOT NULL DEFAULT '' COMMENT '菜单类型 null主菜单 link链接 keys关键字', - `name` varchar(256) NOT NULL DEFAULT '' COMMENT '菜单名称', - `content` text NOT NULL COMMENT '文字内容', - `sort` bigint(20) unsigned DEFAULT '0' COMMENT '排序', - `status` tinyint(1) unsigned DEFAULT '1' COMMENT '状态(0禁用1启用)', - `create_by` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '创建人', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`), - KEY `index_wechat_menu_pindex` (`pindex`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信菜单配置'; - --- ---------------------------- --- Records of wechat_menu --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_news --- ---------------------------- -DROP TABLE IF EXISTS `wechat_news`; -CREATE TABLE `wechat_news` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `media_id` varchar(100) DEFAULT NULL COMMENT '永久素材MediaID', - `local_url` varchar(300) DEFAULT NULL COMMENT '永久素材显示URL', - `article_id` varchar(60) DEFAULT NULL COMMENT '关联图文ID,用,号做分割', - `is_deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建人', - PRIMARY KEY (`id`), - KEY `index_wechat_new_artcle_id` (`article_id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信图文表'; - --- ---------------------------- --- Records of wechat_news --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_news_article --- ---------------------------- -DROP TABLE IF EXISTS `wechat_news_article`; -CREATE TABLE `wechat_news_article` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `title` varchar(50) DEFAULT NULL COMMENT '素材标题', - `local_url` varchar(300) DEFAULT NULL COMMENT '永久素材显示URL', - `show_cover_pic` tinyint(4) unsigned DEFAULT '0' COMMENT '是否显示封面 0不显示,1 显示', - `author` varchar(20) DEFAULT NULL COMMENT '作者', - `digest` varchar(300) DEFAULT NULL COMMENT '摘要内容', - `content` longtext COMMENT '图文内容', - `content_source_url` varchar(200) DEFAULT NULL COMMENT '图文消息原文地址', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建人', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信素材表'; - --- ---------------------------- --- Records of wechat_news_article --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_news_image --- ---------------------------- -DROP TABLE IF EXISTS `wechat_news_image`; -CREATE TABLE `wechat_news_image` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `md5` varchar(32) DEFAULT NULL COMMENT '文件md5', - `local_url` varchar(300) DEFAULT NULL COMMENT '本地文件链接', - `media_url` varchar(300) DEFAULT NULL COMMENT '远程图片链接', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`), - KEY `index_wechat_news_image_md5` (`md5`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信服务器图片'; - --- ---------------------------- --- Records of wechat_news_image --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_news_media --- ---------------------------- -DROP TABLE IF EXISTS `wechat_news_media`; -CREATE TABLE `wechat_news_media` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `appid` varchar(200) DEFAULT NULL COMMENT '公众号ID', - `md5` varchar(32) DEFAULT NULL COMMENT '文件md5', - `type` varchar(20) DEFAULT NULL COMMENT '媒体类型', - `media_id` varchar(100) DEFAULT NULL COMMENT '永久素材MediaID', - `local_url` varchar(300) DEFAULT NULL COMMENT '本地文件链接', - `media_url` varchar(300) DEFAULT NULL COMMENT '远程图片链接', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信素材表'; - --- ---------------------------- --- Records of wechat_news_media --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_pay_notify --- ---------------------------- -DROP TABLE IF EXISTS `wechat_pay_notify`; -CREATE TABLE `wechat_pay_notify` ( - `id` int(20) NOT NULL AUTO_INCREMENT, - `appid` varchar(50) DEFAULT NULL COMMENT '公众号Appid', - `bank_type` varchar(50) DEFAULT NULL COMMENT '银行类型', - `cash_fee` bigint(20) DEFAULT NULL COMMENT '现金价', - `fee_type` char(20) DEFAULT NULL COMMENT '币种,1人民币', - `is_subscribe` char(1) DEFAULT 'N' COMMENT '是否关注,Y为关注,N为未关注', - `mch_id` varchar(50) DEFAULT NULL COMMENT '商户MCH_ID', - `nonce_str` varchar(32) DEFAULT NULL COMMENT '随机串', - `openid` varchar(50) DEFAULT NULL COMMENT '微信用户openid', - `out_trade_no` varchar(50) DEFAULT NULL COMMENT '支付平台退款交易号', - `sign` varchar(100) DEFAULT NULL COMMENT '签名', - `time_end` datetime DEFAULT NULL COMMENT '结束时间', - `result_code` varchar(10) DEFAULT NULL, - `return_code` varchar(10) DEFAULT NULL, - `total_fee` varchar(11) DEFAULT NULL COMMENT '支付总金额,单位为分', - `trade_type` varchar(20) DEFAULT NULL COMMENT '支付方式', - `transaction_id` varchar(64) DEFAULT NULL COMMENT '订单号', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '本地系统时间', - PRIMARY KEY (`id`), - KEY `index_wechat_pay_notify_openid` (`openid`) USING BTREE, - KEY `index_wechat_pay_notify_out_trade_no` (`out_trade_no`) USING BTREE, - KEY `index_wechat_pay_notify_transaction_id` (`transaction_id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='支付日志表'; - --- ---------------------------- --- Records of wechat_pay_notify --- ---------------------------- - --- ---------------------------- --- Table structure for wechat_pay_prepayid --- ---------------------------- -DROP TABLE IF EXISTS `wechat_pay_prepayid`; -CREATE TABLE `wechat_pay_prepayid` ( - `id` int(20) NOT NULL AUTO_INCREMENT, - `appid` char(50) DEFAULT NULL COMMENT '公众号APPID', - `from` char(32) DEFAULT 'shop' COMMENT '支付来源', - `fee` bigint(20) unsigned DEFAULT NULL COMMENT '支付费用(分)', - `trade_type` varchar(20) DEFAULT NULL COMMENT '订单类型', - `order_no` varchar(50) DEFAULT NULL COMMENT '内部订单号', - `out_trade_no` varchar(50) DEFAULT NULL COMMENT '外部订单号', - `prepayid` varchar(500) DEFAULT NULL COMMENT '预支付码', - `expires_in` bigint(20) unsigned DEFAULT NULL COMMENT '有效时间', - `transaction_id` varchar(64) DEFAULT NULL COMMENT '微信平台订单号', - `is_pay` tinyint(1) unsigned DEFAULT '0' COMMENT '1已支付,0未支退款', - `pay_at` datetime DEFAULT NULL COMMENT '支付时间', - `is_refund` tinyint(1) unsigned DEFAULT '0' COMMENT '是否退款,退款单号(T+原来订单)', - `refund_at` datetime DEFAULT NULL COMMENT '退款时间', - `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '本地系统时间', - PRIMARY KEY (`id`), - KEY `index_wechat_pay_prepayid_outer_no` (`out_trade_no`) USING BTREE, - KEY `index_wechat_pay_prepayid_order_no` (`order_no`) USING BTREE, - KEY `index_wechat_pay_is_pay` (`is_pay`) USING BTREE, - KEY `index_wechat_pay_is_refund` (`is_refund`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='支付订单号对应表'; - --- ---------------------------- --- Records of wechat_pay_prepayid --- ---------------------------- diff --git a/thinkphp/.travis.yml b/thinkphp/.travis.yml deleted file mode 100644 index f74ffca11..000000000 --- a/thinkphp/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -sudo: false - -language: php - -services: - - memcached - - mongodb - - mysql - - postgresql - - redis-server - -matrix: - fast_finish: true - include: - - php: 5.4 - - php: 5.5 - - php: 5.6 - - php: 7.0 - - php: hhvm - allow_failures: - - php: hhvm - -cache: - directories: - - $HOME/.composer/cache - -before_install: - - composer self-update - - mysql -e "create database IF NOT EXISTS test;" -uroot - - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres - - psql -c 'create database test;' -U postgres - -install: - - ./tests/script/install.sh - -script: - ## LINT - - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; - ## PHP Copy/Paste Detector - - vendor/bin/phpcpd --verbose --exclude vendor ./ || true - ## PHPLOC - - vendor/bin/phploc --exclude vendor ./ - ## PHPUNIT - - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/thinkphp/CONTRIBUTING.md b/thinkphp/CONTRIBUTING.md index dc8e91cd5..6cefcb38c 100644 --- a/thinkphp/CONTRIBUTING.md +++ b/thinkphp/CONTRIBUTING.md @@ -7,7 +7,7 @@ ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 -参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 我们希望你贡献的代码符合: @@ -60,7 +60,7 @@ GitHub 提供了 Issue 功能,该功能可以用于: 6. 变基(衍合 `rebase`)你的分支到上游 master 分支; 7. `push` 你的本地仓库到 GitHub; 8. 提交 `pull request`; -9. 等待 CI 验证(若不通过则重复 5~7,不需要重新提交 `pull request`,GitHub 会自动更新你的 `pull request`); +9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`); 10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 *若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* diff --git a/thinkphp/LICENSE.txt b/thinkphp/LICENSE.txt index 2cb9a8a9f..574a39c40 100644 --- a/thinkphp/LICENSE.txt +++ b/thinkphp/LICENSE.txt @@ -1,6 +1,6 @@ ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 -版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn) +版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn) All rights reserved。 ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 diff --git a/thinkphp/README.md b/thinkphp/README.md index 68e5e69dd..5da1eef6a 100644 --- a/thinkphp/README.md +++ b/thinkphp/README.md @@ -1,4 +1,4 @@ -ThinkPHP 5.0 +ThinkPHP 5.1 =============== [![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) @@ -9,95 +9,42 @@ ThinkPHP 5.0 [![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework) [![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) -ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括: +ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: - + 基于命名空间和众多PHP新特性 - + 核心功能组件化 - + 强化路由功能 - + 更灵活的控制器 - + 重构的模型和数据库类 - + 配置文件可分离 - + 重写的自动验证和完成 - + 简化扩展机制 - + API支持完善 - + 改进的Log类 - + 命令行访问支持 - + REST支持 - + 引导文件支持 - + 方便的自动生成定义 - + 真正惰性加载 - + 分布式环境支持 - + 支持Composer - + 支持MongoDb + + 采用容器统一管理对象 + + 支持Facade + + 更易用的路由 + + 注解路由支持 + + 路由跨域请求支持 + + 验证类增强 + + 配置和路由目录独立 + + 取消系统常量 + + 类库别名机制 + + 模型和数据库增强 + + 依赖注入完善 + + 支持PSR-3日志规范 -> ThinkPHP5的运行环境要求PHP5.4以上。 +### 废除的功能: -详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) + + 聚合模型 + + 内置控制器扩展类 + + 模型自动验证 -## 目录结构 +> ThinkPHP5的运行环境要求PHP5.6以上。 -初始的目录结构如下: -~~~ -www WEB部署目录(或者子目录) -├─application 应用目录 -│ ├─common 公共模块目录(可以更改) -│ ├─module_name 模块目录 -│ │ ├─config.php 模块配置文件 -│ │ ├─common.php 模块函数文件 -│ │ ├─controller 控制器目录 -│ │ ├─model 模型目录 -│ │ ├─view 视图目录 -│ │ └─ ... 更多类库目录 -│ │ -│ ├─command.php 命令行工具配置文件 -│ ├─common.php 公共函数文件 -│ ├─config.php 公共配置文件 -│ ├─route.php 路由配置文件 -│ ├─tags.php 应用行为扩展定义文件 -│ └─database.php 数据库配置文件 -│ -├─public WEB目录(对外访问目录) -│ ├─index.php 入口文件 -│ ├─router.php 快速测试文件 -│ └─.htaccess 用于apache的重写 -│ -├─thinkphp 框架系统目录 -│ ├─lang 语言文件目录 -│ ├─library 框架类库目录 -│ │ ├─think Think类库包目录 -│ │ └─traits 系统Trait目录 -│ │ -│ ├─tpl 系统模板目录 -│ ├─base.php 基础定义文件 -│ ├─console.php 控制台入口文件 -│ ├─convention.php 框架惯例配置文件 -│ ├─helper.php 助手函数文件 -│ ├─phpunit.xml phpunit配置文件 -│ └─start.php 框架入口文件 -│ -├─extend 扩展类库目录 -├─runtime 应用的运行时目录(可写,可定制) -├─vendor 第三方类库目录(Composer依赖库) -├─build.php 自动生成定义文件(参考) -├─composer.json composer 定义文件 -├─LICENSE.txt 授权说明文件 -├─README.md README 文件 -├─think 命令行入口文件 -~~~ +## 在线手册 -> router.php用于php自带webserver支持,可用于快速测试 -> 切换到public目录后,启动命令:php -S localhost:8888 router.php -> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 ++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1) ++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) ## 命名规范 -ThinkPHP5的命名规范遵循PSR-2规范以及PSR-4自动加载规范。 +`ThinkPHP5`遵循PSR-2命名规范和PSR-4自动加载规范。 ## 参与开发 -注册并登录 Github 帐号, fork 本项目并进行改动。 -更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md) +请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。 ## 版权信息 diff --git a/thinkphp/base.php b/thinkphp/base.php index f88935d09..fc8b7b130 100644 --- a/thinkphp/base.php +++ b/thinkphp/base.php @@ -8,58 +8,96 @@ // +---------------------------------------------------------------------- // | Author: liu21st // +---------------------------------------------------------------------- - -define('THINK_VERSION', '5.0.15'); -define('THINK_START_TIME', microtime(true)); -define('THINK_START_MEM', memory_get_usage()); -define('EXT', '.php'); -define('DS', DIRECTORY_SEPARATOR); -defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); -define('LIB_PATH', THINK_PATH . 'library' . DS); -define('CORE_PATH', LIB_PATH . 'think' . DS); -define('TRAIT_PATH', LIB_PATH . 'traits' . DS); -defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); -defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); -defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); -defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); -defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); -defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); -defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); -defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); -defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录 -defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀 -defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀 - -// 环境常量 -define('IS_CLI', PHP_SAPI == 'cli' ? true : false); -define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); +namespace think; // 载入Loader类 -require CORE_PATH . 'Loader.php'; - -// 加载环境变量配置文件 -if (is_file(ROOT_PATH . '.env')) { - $env = parse_ini_file(ROOT_PATH . '.env', true); - - foreach ($env as $key => $val) { - $name = ENV_PREFIX . strtoupper($key); - - if (is_array($val)) { - foreach ($val as $k => $v) { - $item = $name . '_' . strtoupper($k); - putenv("$item=$v"); - } - } else { - putenv("$name=$val"); - } - } -} +require __DIR__ . '/library/think/Loader.php'; // 注册自动加载 -\think\Loader::register(); +Loader::register(); // 注册错误和异常处理机制 -\think\Error::register(); +Error::register(); + +// 实现日志接口 +if (interface_exists('Psr\Log\LoggerInterface')) { + interface LoggerInterface extends \Psr\Log\LoggerInterface + {} +} else { + interface LoggerInterface + {} +} + +// 注册核心类到容器 +Container::getInstance()->bind([ + 'app' => App::class, + 'build' => Build::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'cookie' => Cookie::class, + 'debug' => Debug::class, + 'env' => Env::class, + 'hook' => Hook::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'url' => Url::class, + 'validate' => Validate::class, + 'view' => View::class, + 'middlewareDispatcher' => http\middleware\Dispatcher::class, + // 接口依赖注入 + 'think\LoggerInterface' => Log::class, +]); + +// 注册核心类的静态代理 +Facade::bind([ + facade\App::class => App::class, + facade\Build::class => Build::class, + facade\Cache::class => Cache::class, + facade\Config::class => Config::class, + facade\Cookie::class => Cookie::class, + facade\Debug::class => Debug::class, + facade\Env::class => Env::class, + facade\Hook::class => Hook::class, + facade\Lang::class => Lang::class, + facade\Log::class => Log::class, + facade\Request::class => Request::class, + facade\Response::class => Response::class, + facade\Route::class => Route::class, + facade\Session::class => Session::class, + facade\Url::class => Url::class, + facade\Validate::class => Validate::class, + facade\View::class => View::class, +]); + +// 注册类库别名 +Loader::addClassAlias([ + 'App' => facade\App::class, + 'Build' => facade\Build::class, + 'Cache' => facade\Cache::class, + 'Config' => facade\Config::class, + 'Cookie' => facade\Cookie::class, + 'Db' => Db::class, + 'Debug' => facade\Debug::class, + 'Env' => facade\Env::class, + 'Facade' => Facade::class, + 'Hook' => facade\Hook::class, + 'Lang' => facade\Lang::class, + 'Log' => facade\Log::class, + 'Request' => facade\Request::class, + 'Response' => facade\Response::class, + 'Route' => facade\Route::class, + 'Session' => facade\Session::class, + 'Url' => facade\Url::class, + 'Validate' => facade\Validate::class, + 'View' => facade\View::class, +]); // 加载惯例配置文件 -\think\Config::set(include THINK_PATH . 'convention' . EXT); +facade\Config::set(include __DIR__ . '/convention.php'); + +// 加载composer autofile文件 +Loader::loadComposerAutoloadFiles(); diff --git a/thinkphp/codecov.yml b/thinkphp/codecov.yml deleted file mode 100644 index bef9d643a..000000000 --- a/thinkphp/codecov.yml +++ /dev/null @@ -1,12 +0,0 @@ -comment: - layout: header, changes, diff -coverage: - ignore: - - base.php - - helper.php - - convention.php - - lang/zh-cn.php - - start.php - - console.php - status: - patch: false diff --git a/thinkphp/composer.json b/thinkphp/composer.json index c546e1142..3e504960e 100644 --- a/thinkphp/composer.json +++ b/thinkphp/composer.json @@ -13,23 +13,23 @@ { "name": "liu21st", "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" } ], "require": { - "php": ">=5.4.0", + "php": ">=5.6.0", "topthink/think-installer": "~1.0" }, "require-dev": { - "phpunit/phpunit": "4.8.*", + "phpunit/phpunit": "^5.0|^6.0", "johnkary/phpunit-speedtrap": "^1.0", "mikey179/vfsStream": "~1.6", "phploc/phploc": "2.*", "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "2.*", "phpdocumentor/reflection-docblock": "^2.0" - }, - "autoload": { - "psr-4": { - "think\\": "library/think" - } } } diff --git a/thinkphp/convention.php b/thinkphp/convention.php index 4ca03c06e..01046c4f5 100644 --- a/thinkphp/convention.php +++ b/thinkphp/convention.php @@ -4,118 +4,138 @@ return [ // +---------------------------------------------------------------------- // | 应用设置 // +---------------------------------------------------------------------- - // 默认Host地址 - 'app_host' => '', - // 应用调试模式 - 'app_debug' => false, - // 应用Trace - 'app_trace' => false, - // 应用模式状态 - 'app_status' => '', - // 是否支持多模块 - 'app_multi_module' => true, - // 入口自动绑定模块 - 'auto_bind_module' => false, - // 注册的根命名空间 - 'root_namespace' => [], - // 扩展函数文件 - 'extra_file_list' => [THINK_PATH . 'helper' . EXT], - // 默认输出类型 - 'default_return_type' => 'html', - // 默认AJAX 数据返回格式,可选json xml ... - 'default_ajax_return' => 'json', - // 默认JSONP格式返回的处理方法 - 'default_jsonp_handler' => 'jsonpReturn', - // 默认JSONP处理方法 - 'var_jsonp_handler' => 'callback', - // 默认时区 - 'default_timezone' => 'PRC', - // 是否开启多语言 - 'lang_switch_on' => false, - // 默认全局过滤方法 用逗号分隔多个 - 'default_filter' => '', - // 默认语言 - 'default_lang' => 'zh-cn', - // 应用类库后缀 - 'class_suffix' => false, - // 控制器类后缀 - 'controller_suffix' => false, + 'app' => [ + // 应用名称 + 'app_name' => '', + // 应用地址 + 'app_host' => '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, - // +---------------------------------------------------------------------- - // | 模块设置 - // +---------------------------------------------------------------------- + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- - // 默认模块名 - 'default_module' => 'index', - // 禁止访问模块 - 'deny_module_list' => ['common'], - // 默认控制器名 - 'default_controller' => 'Index', - // 默认操作名 - 'default_action' => 'index', - // 默认验证器 - 'default_validate' => '', - // 默认的空控制器名 - 'empty_controller' => 'Error', - // 操作方法前缀 - 'use_action_prefix' => false, - // 操作方法后缀 - 'action_suffix' => '', - // 自动搜索控制器 - 'controller_auto_search' => false, + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空模块名 + 'empty_module' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, - // +---------------------------------------------------------------------- - // | URL设置 - // +---------------------------------------------------------------------- + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- - // PATHINFO变量名 用于兼容模式 - 'var_pathinfo' => 's', - // 兼容PATH_INFO获取 - 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], - // pathinfo分隔符 - 'pathinfo_depr' => '/', - // HTTPS代理标识 - 'https_agent_name' => '', - // URL伪静态后缀 - 'url_html_suffix' => 'html', - // URL普通方式参数 用于自动生成 - 'url_common_param' => false, - // URL参数方式 0 按名称成对解析 1 按顺序解析 - 'url_param_type' => 0, - // 是否开启路由 - 'url_route_on' => true, - // 路由配置文件(支持配置多个) - 'route_config_file' => ['route'], - // 路由使用完整匹配 - 'route_complete_match' => false, - // 是否强制使用路由 - 'url_route_must' => false, - // 域名部署 - 'url_domain_deploy' => false, - // 域名根,如thinkphp.cn - 'url_domain_root' => '', - // 是否自动转换URL中的控制器和操作名 - 'url_convert' => true, - // 默认的访问控制器层 - 'url_controller_layer' => 'controller', - // 表单请求类型伪装变量 - 'var_method' => '_method', - // 表单ajax伪装变量 - 'var_ajax' => '_ajax', - // 表单pjax伪装变量 - 'var_pjax' => '_pjax', - // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 - 'request_cache' => false, - // 请求缓存有效期 - 'request_cache_expire' => null, - // 全局请求缓存排除规则 - 'request_cache_except' => [], + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // HTTPS代理标识 + 'https_agent_name' => '', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 使用注解路由 + 'route_annotation' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 异常页面的模板文件 + 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + ], // +---------------------------------------------------------------------- // | 模板设置 // +---------------------------------------------------------------------- - 'template' => [ + 'template' => [ // 模板引擎类型 支持 php think 支持扩展 'type' => 'Think', // 视图基础目录,配置目录为所有模块的视图起始目录 @@ -125,7 +145,7 @@ return [ // 模板后缀 'view_suffix' => 'html', // 模板文件名分隔符 - 'view_depr' => DS, + 'view_depr' => DIRECTORY_SEPARATOR, // 模板引擎普通标签开始标记 'tpl_begin' => '{', // 模板引擎普通标签结束标记 @@ -136,58 +156,39 @@ return [ 'taglib_end' => '}', ], - // 视图输出字符串内容替换 - 'view_replace_str' => [], - // 默认跳转页面对应的模板文件 - 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', - 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', - - // +---------------------------------------------------------------------- - // | 异常及错误设置 - // +---------------------------------------------------------------------- - - // 异常页面的模板文件 - 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', - - // 错误显示信息,非调试模式有效 - 'error_message' => '页面错误!请稍后再试~', - // 显示错误信息 - 'show_error_msg' => false, - // 异常处理handle类 留空使用 \think\exception\Handle - 'exception_handle' => '', - // 是否记录trace信息到日志 - 'record_trace' => false, - // +---------------------------------------------------------------------- // | 日志设置 // +---------------------------------------------------------------------- - 'log' => [ + 'log' => [ // 日志记录方式,内置 file socket 支持扩展 - 'type' => 'File', + 'type' => 'File', // 日志保存目录 - 'path' => LOG_PATH, + //'path' => LOG_PATH, // 日志记录级别 - 'level' => [], + 'level' => [], + // 是否记录trace信息到日志 + 'record_trace' => false, ], // +---------------------------------------------------------------------- // | Trace设置 开启 app_trace 后 有效 // +---------------------------------------------------------------------- - 'trace' => [ + 'trace' => [ // 内置Html Console 支持扩展 'type' => 'Html', + 'file' => __DIR__ . '/tpl/page_trace.tpl', ], // +---------------------------------------------------------------------- // | 缓存设置 // +---------------------------------------------------------------------- - 'cache' => [ + 'cache' => [ // 驱动方式 'type' => 'File', // 缓存保存目录 - 'path' => CACHE_PATH, + //'path' => CACHE_PATH, // 缓存前缀 'prefix' => '', // 缓存有效期 0表示永久缓存 @@ -198,7 +199,7 @@ return [ // | 会话设置 // +---------------------------------------------------------------------- - 'session' => [ + 'session' => [ 'id' => '', // SESSION_ID的提交变量,解决flash上传跨域 'var_session_id' => '', @@ -215,7 +216,7 @@ return [ // +---------------------------------------------------------------------- // | Cookie设置 // +---------------------------------------------------------------------- - 'cookie' => [ + 'cookie' => [ // cookie 名称前缀 'prefix' => '', // cookie 保存时间 @@ -236,7 +237,7 @@ return [ // | 数据库设置 // +---------------------------------------------------------------------- - 'database' => [ + 'database' => [ // 数据库类型 'type' => 'mysql', // 数据库连接DSN配置 @@ -277,10 +278,12 @@ return [ 'datetime_format' => 'Y-m-d H:i:s', // 是否需要进行SQL性能分析 'sql_explain' => false, + // 查询对象 + 'query' => '\\think\\db\\Query', ], //分页配置 - 'paginate' => [ + 'paginate' => [ 'type' => 'bootstrap', 'var_page' => 'page', 'list_rows' => 15, diff --git a/thinkphp/helper.php b/thinkphp/helper.php index 12683cfd8..461460ea3 100644 --- a/thinkphp/helper.php +++ b/thinkphp/helper.php @@ -13,205 +13,42 @@ // ThinkPHP 助手函数 //------------------------- -use think\Cache; -use think\Config; -use think\Cookie; +use think\Container; use think\Db; -use think\Debug; use think\exception\HttpException; use think\exception\HttpResponseException; -use think\Lang; -use think\Loader; -use think\Log; -use think\Model; -use think\Request; +use think\facade\Cache; +use think\facade\Config; +use think\facade\Cookie; +use think\facade\Debug; +use think\facade\Env; +use think\facade\Hook; +use think\facade\Lang; +use think\facade\Log; +use think\facade\Request; +use think\facade\Route; +use think\facade\Session; +use think\facade\Url; use think\Response; -use think\Session; -use think\Url; -use think\View; +use think\route\RuleItem; -if (!function_exists('load_trait')) { +if (!function_exists('abort')) { /** - * 快速导入Traits PHP5.5以上无需调用 - * @param string $class trait库 - * @param string $ext 类库后缀 - * @return boolean + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 */ - function load_trait($class, $ext = EXT) + function abort($code, $message = null, $header = []) { - return Loader::import($class, TRAIT_PATH, $ext); - } -} - -if (!function_exists('exception')) { - /** - * 抛出异常处理 - * - * @param string $msg 异常消息 - * @param integer $code 异常代码 默认为0 - * @param string $exception 异常类 - * - * @throws Exception - */ - function exception($msg, $code = 0, $exception = '') - { - $e = $exception ?: '\think\Exception'; - throw new $e($msg, $code); - } -} - -if (!function_exists('debug')) { - /** - * 记录时间(微秒)和内存使用情况 - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer|string $dec 小数位 如果是m 表示统计内存占用 - * @return mixed - */ - function debug($start, $end = '', $dec = 6) - { - if ('' == $end) { - Debug::remark($start); + if ($code instanceof Response) { + throw new HttpResponseException($code); } else { - return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + throw new HttpException($code, $message, null, $header); } } } -if (!function_exists('lang')) { - /** - * 获取语言变量值 - * @param string $name 语言变量名 - * @param array $vars 动态变量值 - * @param string $lang 语言 - * @return mixed - */ - function lang($name, $vars = [], $lang = '') - { - return Lang::get($name, $vars, $lang); - } -} - -if (!function_exists('config')) { - /** - * 获取和设置配置参数 - * @param string|array $name 参数名 - * @param mixed $value 参数值 - * @param string $range 作用域 - * @return mixed - */ - function config($name = '', $value = null, $range = '') - { - if (is_null($value) && is_string($name)) { - return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range); - } else { - return Config::set($name, $value, $range); - } - } -} - -if (!function_exists('input')) { - /** - * 获取输入数据 支持默认值和过滤 - * @param string $key 获取的变量名 - * @param mixed $default 默认值 - * @param string $filter 过滤方法 - * @return mixed - */ - function input($key = '', $default = null, $filter = '') - { - if (0 === strpos($key, '?')) { - $key = substr($key, 1); - $has = true; - } - if ($pos = strpos($key, '.')) { - // 指定参数来源 - list($method, $key) = explode('.', $key, 2); - if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { - $key = $method . '.' . $key; - $method = 'param'; - } - } else { - // 默认为自动判断 - $method = 'param'; - } - if (isset($has)) { - return request()->has($key, $method, $default); - } else { - return request()->$method($key, $default, $filter); - } - } -} - -if (!function_exists('widget')) { - /** - * 渲染输出Widget - * @param string $name Widget名称 - * @param array $data 传入的参数 - * @return mixed - */ - function widget($name, $data = []) - { - return Loader::action($name, $data, 'widget'); - } -} - -if (!function_exists('model')) { - /** - * 实例化Model - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Model - */ - function model($name = '', $layer = 'model', $appendSuffix = false) - { - return Loader::model($name, $layer, $appendSuffix); - } -} - -if (!function_exists('validate')) { - /** - * 实例化验证器 - * @param string $name 验证器名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Validate - */ - function validate($name = '', $layer = 'validate', $appendSuffix = false) - { - return Loader::validate($name, $layer, $appendSuffix); - } -} - -if (!function_exists('db')) { - /** - * 实例化数据库类 - * @param string $name 操作的数据表名称(不含前缀) - * @param array|string $config 数据库配置参数 - * @param bool $force 是否强制重新连接 - * @return \think\db\Query - */ - function db($name = '', $config = [], $force = false) - { - return Db::connect($config, $force)->name($name); - } -} - -if (!function_exists('controller')) { - /** - * 实例化控制器 格式:[模块/]控制器 - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Controller - */ - function controller($name, $layer = 'controller', $appendSuffix = false) - { - return Loader::controller($name, $layer, $appendSuffix); - } -} - if (!function_exists('action')) { /** * 调用模块的操作方法 参数格式 [模块/控制器/]操作 @@ -223,92 +60,186 @@ if (!function_exists('action')) { */ function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) { - return Loader::action($url, $vars, $layer, $appendSuffix); + return app()->action($url, $vars, $layer, $appendSuffix); } } -if (!function_exists('import')) { +if (!function_exists('app')) { /** - * 导入所需的类库 同java的Import 本函数有缓存功能 - * @param string $class 类库命名空间字符串 - * @param string $baseUrl 起始路径 - * @param string $ext 导入的文件扩展名 - * @return boolean + * 快速获取容器中的实例 支持依赖注入 + * @param string $name 类名或标识 默认获取当前应用实例 + * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 + * @return object */ - function import($class, $baseUrl = '', $ext = EXT) + function app($name = 'think\App', $args = [], $newInstance = false) { - return Loader::import($class, $baseUrl, $ext); + return Container::get($name, $args, $newInstance); } } -if (!function_exists('vendor')) { +if (!function_exists('behavior')) { /** - * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 - * @param string $class 类库 - * @param string $ext 类库后缀 - * @return boolean - */ - function vendor($class, $ext = EXT) - { - return Loader::import($class, VENDOR_PATH, $ext); - } -} - -if (!function_exists('dump')) { - /** - * 浏览器友好的变量输出 - * @param mixed $var 变量 - * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 - * @param string $label 标签 默认为空 - * @return void|string - */ - function dump($var, $echo = true, $label = null) - { - return Debug::dump($var, $echo, $label); - } -} - -if (!function_exists('url')) { - /** - * Url生成 - * @param string $url 路由地址 - * @param string|array $vars 变量 - * @param bool|string $suffix 生成的URL后缀 - * @param bool|string $domain 域名 - * @return string - */ - function url($url = '', $vars = '', $suffix = true, $domain = false) - { - return Url::build($url, $vars, $suffix, $domain); - } -} - -if (!function_exists('session')) { - /** - * Session管理 - * @param string|array $name session名称,如果为数组表示进行session设置 - * @param mixed $value session值 - * @param string $prefix 前缀 + * 执行某个行为(run方法) 支持依赖注入 + * @param mixed $behavior 行为类名或者别名 + * @param mixed $args 参数 * @return mixed */ - function session($name, $value = '', $prefix = null) + function behavior($behavior, $args = null) { - if (is_array($name)) { - // 初始化 - Session::init($name); - } elseif (is_null($name)) { - // 清除 - Session::clear('' === $value ? null : $value); - } elseif ('' === $value) { - // 判断或获取 - return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); - } elseif (is_null($value)) { - // 删除 - return Session::delete($name, $prefix); - } else { - // 设置 - return Session::set($name, $value, $prefix); + return Hook::exec($behavior, $args); + } +} + +if (!function_exists('bind')) { + /** + * 绑定一个类到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + function bind($abstract, $concrete = null) + { + return Container::getInstance()->bind($abstract, $concrete); + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::rm($name); + } else { + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } + } +} + +if (!function_exists('call')) { + /** + * 调用反射执行callable 支持依赖注入 + * @param mixed $callable 支持闭包等callable写法 + * @param array $args 参数 + * @return mixed + */ + function call($callable, $args = []) + { + return Container::getInstance()->invoke($callable, $args); + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param $class + * @return array + */ + function class_uses_recursive($class) + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + function config($name = '', $value = null) + { + if (is_null($value) && is_string($name)) { + if ('.' == substr($name, -1)) { + return Config::pull(substr($name, 0, -1)); + } + + return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name); + } else { + return Config::set($name, $value); + } + } +} + +if (!function_exists('container')) { + /** + * 获取容器对象实例 + * @return Container + */ + function container() + { + return Container::getInstance(); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return app()->controller($name, $layer, $appendSuffix); } } @@ -330,7 +261,7 @@ if (!function_exists('cookie')) { Cookie::clear($value); } elseif ('' === $value) { // 获取 - return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); } elseif (is_null($value)) { // 删除 return Cookie::delete($name); @@ -341,109 +272,129 @@ if (!function_exists('cookie')) { } } -if (!function_exists('cache')) { +if (!function_exists('db')) { /** - * 缓存管理 - * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 - * @param mixed $value 缓存值 - * @param mixed $options 缓存参数 - * @param string $tag 缓存标签 + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = true) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 * @return mixed */ - function cache($name, $value = '', $options = null, $tag = null) + function debug($start, $end = '', $dec = 6) { - if (is_array($options)) { - // 缓存操作的同时初始化 - $cache = Cache::connect($options); - } elseif (is_array($name)) { - // 缓存初始化 - return Cache::connect($name); + if ('' == $end) { + Debug::remark($start); } else { - $cache = Cache::init(); + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env($name = null, $default = null) + { + return Env::get($name, $default); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = null) + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; } - if (is_null($name)) { - return $cache->clear($value); - } elseif ('' === $value) { - // 获取缓存 - return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); - } elseif (is_null($value)) { - // 删除缓存 - return $cache->rm($name); - } elseif (0 === strpos($name, '?') && '' !== $value) { - $expire = is_numeric($options) ? $options : null; - return $cache->remember(substr($name, 1), $value, $expire); - } else { - // 缓存数据 - if (is_array($options)) { - $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); } else { - $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + $method = 'param'; } - if (is_null($tag)) { - return $cache->set($name, $value, $expire); - } else { - return $cache->tag($tag)->set($name, $value, $expire); - } - } - } -} - -if (!function_exists('trace')) { - /** - * 记录日志信息 - * @param mixed $log log信息 支持字符串和数组 - * @param string $level 日志级别 - * @return void|array - */ - function trace($log = '[think]', $level = 'log') - { - if ('[think]' === $log) { - return Log::getLog(); } else { - Log::record($log, $level); + // 默认为自动判断 + $method = 'param'; } - } -} -if (!function_exists('request')) { - /** - * 获取当前Request对象实例 - * @return Request - */ - function request() - { - return Request::instance(); - } -} - -if (!function_exists('response')) { - /** - * 创建普通 Response 对象实例 - * @param mixed $data 输出数据 - * @param int|string $code 状态码 - * @param array $header 头信息 - * @param string $type - * @return Response - */ - function response($data = [], $code = 200, $header = [], $type = 'html') - { - return Response::create($data, $type, $code, $header); - } -} - -if (!function_exists('view')) { - /** - * 渲染模板输出 - * @param string $template 模板文件 - * @param array $vars 模板变量 - * @param array $replace 模板替换 - * @param integer $code 状态码 - * @return \think\response\View - */ - function view($template = '', $vars = [], $replace = [], $code = 200) - { - return Response::create($template, 'view', $code)->replace($replace)->assign($vars); + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } } } @@ -477,6 +428,253 @@ if (!function_exists('jsonp')) { } } +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return app()->model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + return Response::create($url, 'redirect', $code)->params($params); + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return app('request'); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = [], $code = 200, $header = [], $type = 'html') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('route')) { + /** + * 路由注册 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + function route($rule, $route, $option = [], $pattern = []) + { + return Route::rule($rule, $route, '*', $option, $pattern); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear($value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::token($name, $type); + + return ''; + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return array|void + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait + * @return array + */ + function trait_uses_recursive($trait) + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return app()->validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param integer $code 状态码 + * @param callable $filer 内容过滤 + * @return \think\response\View + */ + function view($template = '', $vars = [], $code = 200, $filter = null) + { + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + return app()->action($name, $data, 'widget'); + } +} + if (!function_exists('xml')) { /** * 获取\think\response\Xml对象实例 @@ -491,99 +689,3 @@ if (!function_exists('xml')) { return Response::create($data, 'xml', $code, $header, $options); } } - -if (!function_exists('redirect')) { - /** - * 获取\think\response\Redirect对象实例 - * @param mixed $url 重定向地址 支持Url::build方法的地址 - * @param array|integer $params 额外参数 - * @param integer $code 状态码 - * @param array $with 隐式传参 - * @return \think\response\Redirect - */ - function redirect($url = [], $params = [], $code = 302, $with = []) - { - if (is_integer($params)) { - $code = $params; - $params = []; - } - return Response::create($url, 'redirect', $code)->params($params)->with($with); - } -} - -if (!function_exists('abort')) { - /** - * 抛出HTTP异常 - * @param integer|Response $code 状态码 或者 Response对象实例 - * @param string $message 错误信息 - * @param array $header 参数 - */ - function abort($code, $message = null, $header = []) - { - if ($code instanceof Response) { - throw new HttpResponseException($code); - } else { - throw new HttpException($code, $message, null, $header); - } - } -} - -if (!function_exists('halt')) { - /** - * 调试变量并且中断输出 - * @param mixed $var 调试变量或者信息 - */ - function halt($var) - { - dump($var); - throw new HttpResponseException(new Response); - } -} - -if (!function_exists('token')) { - /** - * 生成表单令牌 - * @param string $name 令牌名称 - * @param mixed $type 令牌生成方法 - * @return string - */ - function token($name = '__token__', $type = 'md5') - { - $token = Request::instance()->token($name, $type); - return ''; - } -} - -if (!function_exists('load_relation')) { - /** - * 延迟预载入关联查询 - * @param mixed $resultSet 数据集 - * @param mixed $relation 关联 - * @return array - */ - function load_relation($resultSet, $relation) - { - $item = current($resultSet); - if ($item instanceof Model) { - $item->eagerlyResultSet($resultSet, $relation); - } - return $resultSet; - } -} - -if (!function_exists('collection')) { - /** - * 数组转换为数据集对象 - * @param array $resultSet 数据集数组 - * @return \think\model\Collection|\think\Collection - */ - function collection($resultSet) - { - $item = current($resultSet); - if ($item instanceof Model) { - return \think\model\Collection::make($resultSet); - } else { - return \think\Collection::make($resultSet); - } - } -} diff --git a/thinkphp/lang/zh-cn.php b/thinkphp/lang/zh-cn.php index 0c2732f59..dd8084d16 100644 --- a/thinkphp/lang/zh-cn.php +++ b/thinkphp/lang/zh-cn.php @@ -85,6 +85,7 @@ return [ // Validate Error Message ':attribute require' => ':attribute不能为空', + ':attribute must' => ':attribute必须', ':attribute must be numeric' => ':attribute必须是数字', ':attribute must be integer' => ':attribute必须是整数', ':attribute must be float' => ':attribute必须是浮点数', diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php index d34c04cb4..8892585c8 100644 --- a/thinkphp/library/think/App.php +++ b/thinkphp/library/think/App.php @@ -12,650 +12,864 @@ namespace think; use think\exception\ClassNotFoundException; -use think\exception\HttpException; use think\exception\HttpResponseException; -use think\exception\RouteNotFoundException; +use think\route\Dispatch; /** * App 应用管理 - * @author liu21st */ -class App +class App implements \ArrayAccess { - /** - * @var bool 是否初始化过 - */ - protected static $init = false; + const VERSION = '5.1.5'; /** - * @var string 当前模块路径 + * 当前模块路径 + * @var string */ - public static $modulePath; + protected $modulePath; /** - * @var bool 应用调试模式 + * 应用调试模式 + * @var bool */ - public static $debug = true; + protected $debug = true; /** - * @var string 应用类库命名空间 + * 应用开始时间 + * @var float */ - public static $namespace = 'app'; + protected $beginTime; /** - * @var bool 应用类库后缀 + * 应用内存初始占用 + * @var integer */ - public static $suffix = false; + protected $beginMem; /** - * @var bool 应用路由检测 + * 应用类库命名空间 + * @var string */ - protected static $routeCheck; + protected $namespace = 'app'; /** - * @var bool 严格路由检测 + * 应用类库后缀 + * @var bool */ - protected static $routeMust; + protected $suffix = false; /** - * @var array 请求调度分发 + * 严格路由检测 + * @var bool */ - protected static $dispatch; + protected $routeMust; /** - * @var array 额外加载文件 + * 应用类库目录 + * @var string */ - protected static $file = []; + protected $appPath; /** - * 执行应用程序 - * @access public - * @param Request $request 请求对象 - * @return Response - * @throws Exception + * 框架目录 + * @var string */ - public static function run(Request $request = null) + protected $thinkPath; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath; + + /** + * 运行时目录 + * @var string + */ + protected $runtimePath; + + /** + * 配置目录 + * @var string + */ + protected $configPath; + + /** + * 路由目录 + * @var string + */ + protected $routePath; + + /** + * 配置后缀 + * @var string + */ + protected $configExt; + + /** + * 应用调度实例 + * @var Dispatch + */ + protected $dispatch; + + /** + * 容器对象实例 + * @var Container + */ + protected $container; + + /** + * 绑定模块(控制器) + * @var string + */ + protected $bind; + + public function __construct($appPath = '') { - $request = is_null($request) ? Request::instance() : $request; - - try { - $config = self::initCommon(); - - // 模块/控制器绑定 - if (defined('BIND_MODULE')) { - BIND_MODULE && Route::bind(BIND_MODULE); - } elseif ($config['auto_bind_module']) { - // 入口自动绑定 - $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); - if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { - Route::bind($name); - } - } - - $request->filter($config['default_filter']); - - // 默认语言 - Lang::range($config['default_lang']); - // 开启多语言机制 检测当前语言 - $config['lang_switch_on'] && Lang::detect(); - $request->langset(Lang::range()); - - // 加载系统语言包 - Lang::load([ - THINK_PATH . 'lang' . DS . $request->langset() . EXT, - APP_PATH . 'lang' . DS . $request->langset() . EXT, - ]); - - // 监听 app_dispatch - Hook::listen('app_dispatch', self::$dispatch); - // 获取应用调度信息 - $dispatch = self::$dispatch; - - // 未设置调度信息则进行 URL 路由检测 - if (empty($dispatch)) { - $dispatch = self::routeCheck($request, $config); - } - - // 记录当前调度信息 - $request->dispatch($dispatch); - - // 记录路由和请求信息 - if (self::$debug) { - Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); - Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); - Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); - } - - // 监听 app_begin - Hook::listen('app_begin', $dispatch); - - // 请求缓存检查 - $request->cache( - $config['request_cache'], - $config['request_cache_expire'], - $config['request_cache_except'] - ); - - $data = self::exec($dispatch, $config); - } catch (HttpResponseException $exception) { - $data = $exception->getResponse(); - } - - // 清空类的实例化 - Loader::clearInstance(); - - // 输出数据到客户端 - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $type = $request->isAjax() ? - Config::get('default_ajax_return') : - Config::get('default_return_type'); - - $response = Response::create($data, $type); - } else { - $response = Response::create(); - } - - // 监听 app_end - Hook::listen('app_end', $response); - - return $response; + $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; + $this->container = Container::getInstance(); } /** - * 初始化应用,并返回配置信息 + * 绑定模块或者控制器 * @access public - * @return array + * @param string $bind + * @return $this */ - public static function initCommon() + public function bind($bind) { - if (empty(self::$init)) { - if (defined('APP_NAMESPACE')) { - self::$namespace = APP_NAMESPACE; - } + $this->bind = $bind; + return $this; + } - Loader::addNamespace(self::$namespace, APP_PATH); + /** + * 设置应用类库目录 + * @access public + * @param string $path 路径 + * @return $this + */ + public function path($path) + { + $this->appPath = $path; + return $this; + } - // 初始化应用 - $config = self::init(); - self::$suffix = $config['class_suffix']; + /** + * 初始化应用 + * @access public + * @return void + */ + public function initialize() + { + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + $this->thinkPath = dirname(dirname(__DIR__)) . '/'; + $this->rootPath = dirname(realpath($this->appPath)) . '/'; + $this->runtimePath = $this->rootPath . 'runtime/'; + $this->routePath = $this->rootPath . 'route/'; + $this->configPath = $this->rootPath . 'config/'; - // 应用调试模式 - self::$debug = Env::get('app_debug', Config::get('app_debug')); + // 设置路径环境变量 + $this->env->set([ + 'think_path' => $this->thinkPath, + 'root_path' => $this->rootPath, + 'app_path' => $this->appPath, + 'config_path' => $this->configPath, + 'route_path' => $this->routePath, + 'runtime_path' => $this->runtimePath, + 'extend_path' => $this->rootPath . 'extend/', + 'vendor_path' => $this->rootPath . 'vendor/', + ]); - if (!self::$debug) { - ini_set('display_errors', 'Off'); - } elseif (!IS_CLI) { - // 重新申请一块比较大的 buffer - if (ob_get_level() > 0) { - $output = ob_get_clean(); - } - - ob_start(); - - if (!empty($output)) { - echo $output; - } - - } - - if (!empty($config['root_namespace'])) { - Loader::addNamespace($config['root_namespace']); - } - - // 加载额外文件 - if (!empty($config['extra_file_list'])) { - foreach ($config['extra_file_list'] as $file) { - $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; - if (is_file($file) && !isset(self::$file[$file])) { - include $file; - self::$file[$file] = true; - } - } - } - - // 设置系统时区 - date_default_timezone_set($config['default_timezone']); - - // 监听 app_init - Hook::listen('app_init'); - - self::$init = true; + // 加载环境变量配置文件 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); } - return Config::get(); + $this->namespace = $this->env->get('app_namespace', $this->namespace); + $this->env->set('app_namespace', $this->namespace); + + // 注册应用命名空间 + Loader::addNamespace($this->namespace, $this->appPath); + + $this->configExt = $this->env->get('config_ext', '.php'); + + // 初始化应用 + $this->init(); + + // 开启类名后缀 + $this->suffix = $this->config('app.class_suffix'); + + // 应用调试模式 + $this->debug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->debug); + + if (!$this->debug) { + ini_set('display_errors', 'Off'); + } elseif (PHP_SAPI != 'cli') { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + + // 注册根命名空间 + if (!empty($this->config('app.root_namespace'))) { + Loader::addNamespace($this->config('app.root_namespace')); + } + + // 注册类库别名 + Loader::addClassAlias($this->config->pull('alias')); + + // 设置系统时区 + date_default_timezone_set($this->config('app.default_timezone')); + + // 监听app_init + $this->hook->listen('app_init'); } /** * 初始化应用或模块 * @access public - * @param string $module 模块名 - * @return array + * @param string $module 模块名 + * @return void */ - private static function init($module = '') + public function init($module = '') { // 定位模块目录 - $module = $module ? $module . DS : ''; + $module = $module ? $module . DIRECTORY_SEPARATOR : ''; + $path = $this->appPath . $module; // 加载初始化文件 - if (is_file(APP_PATH . $module . 'init' . EXT)) { - include APP_PATH . $module . 'init' . EXT; - } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { - include RUNTIME_PATH . $module . 'init' . EXT; + if (is_file($path . 'init.php')) { + include $path . 'init.php'; + } elseif (is_file($this->runtimePath . $module . 'init.php')) { + include $this->runtimePath . $module . 'init.php'; } else { - // 加载模块配置 - $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); - - // 读取数据库配置文件 - $filename = CONF_PATH . $module . 'database' . CONF_EXT; - Config::load($filename, 'database'); - - // 读取扩展配置文件 - if (is_dir(CONF_PATH . $module . 'extra')) { - $dir = CONF_PATH . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { - $filename = $dir . DS . $file; - Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - - // 加载应用状态配置 - if ($config['app_status']) { - Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); - } - // 加载行为扩展文件 - if (is_file(CONF_PATH . $module . 'tags' . EXT)) { - Hook::import(include CONF_PATH . $module . 'tags' . EXT); + if (is_file($path . 'tags.php')) { + $this->hook->import(include $path . 'tags.php'); } // 加载公共文件 - $path = APP_PATH . $module; - if (is_file($path . 'common' . EXT)) { - include $path . 'common' . EXT; + if (is_file($path . 'common.php')) { + include $path . 'common.php'; } - // 加载当前模块语言包 - if ($module) { - Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); + if ('' == $module) { + // 加载系统助手函数 + include $this->thinkPath . 'helper.php'; + } + + // 注册服务的容器对象实例 + if (is_file($path . 'provider.php')) { + $this->container->bind(include $path . 'provider.php'); + } + + // 自动读取配置文件 + if (is_dir($path . 'config')) { + $dir = $path . 'config'; + } elseif (is_dir($this->configPath . $module)) { + $dir = $this->configPath . $module; + } + + $files = isset($dir) ? scandir($dir) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { + $filename = $dir . DIRECTORY_SEPARATOR . $file; + $this->config->load($filename, pathinfo($file, PATHINFO_FILENAME)); + } } } - return Config::get(); + $this->request->filter($this->config('app.default_filter')); + } + + /** + * 执行应用程序 + * @access public + * @return Response + * @throws Exception + */ + public function run() + { + // 初始化应用 + $this->initialize(); + + try { + if ($this->bind) { + // 模块/控制器绑定 + $this->route->bind($this->bind); + } elseif ($this->config('app.auto_bind_module')) { + // 入口自动绑定 + $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir($this->appPath . $name)) { + $this->route->bind($name); + } + } + + // 读取默认语言 + $this->lang->range($this->config('app.default_lang')); + if ($this->config('app.lang_switch_on')) { + // 开启多语言机制 检测当前语言 + $this->lang->detect(); + } + + $this->request->langset($this->lang->range()); + + // 加载系统语言包 + $this->lang->load([ + $this->thinkPath . 'lang/' . $this->request->langset() . '.php', + $this->appPath . 'lang/' . $this->request->langset() . '.php', + ]); + + // 监听app_dispatch + $this->hook->listen('app_dispatch'); + + // 获取应用调度信息 + $dispatch = $this->dispatch; + if (empty($dispatch)) { + // 进行URL路由检测 + $dispatch = $this->routeCheck(); + } + + // 记录当前调度信息 + $this->request->dispatch($dispatch); + + // 记录路由和请求信息 + if ($this->debug) { + $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); + $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); + $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); + } + + // 监听app_begin + $this->hook->listen('app_begin'); + + // 请求缓存检查 + $this->request->cache( + $this->config('app.request_cache'), + $this->config('app.request_cache_expire'), + $this->config('app.request_cache_except') + ); + + // 执行调度 + $data = $dispatch->run(); + + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } + + $this->middlewareDispatcher->add(function (Request $request, $next) use ($data) { + // 输出数据到客户端 + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $request->isAjax(); + $type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type'); + + $response = Response::create($data, $type); + } else { + $response = Response::create(); + } + return $response; + }); + + $response = $this->middlewareDispatcher->dispatch($this->request); + + // 监听app_end + $this->hook->listen('app_end', $response); + + return $response; } /** * 设置当前请求的调度信息 * @access public - * @param array|string $dispatch 调度信息 - * @param string $type 调度类型 + * @param Dispatch $dispatch 调度信息 + * @return $this + */ + public function dispatch(Dispatch $dispatch) + { + $this->dispatch = $dispatch; + return $this; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 * @return void */ - public static function dispatch($dispatch, $type = 'module') + public function log($log, $type = 'info') { - self::$dispatch = ['type' => $type, $type => $dispatch]; + $this->debug && $this->log->record($log, $type); } /** - * 执行函数或者闭包方法 支持参数调用 + * 获取配置参数 为空则获取所有配置 * @access public - * @param string|array|\Closure $function 函数或者闭包 - * @param array $vars 变量 + * @param string $name 配置参数名(支持二级配置 .号分割) * @return mixed */ - public static function invokeFunction($function, $vars = []) + public function config($name = '') { - $reflect = new \ReflectionFunction($function); - $args = self::bindParams($reflect, $vars); - - // 记录执行信息 - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); - - return $reflect->invokeArgs($args); - } - - /** - * 调用反射执行类的方法 支持参数绑定 - * @access public - * @param string|array $method 方法 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeMethod($method, $vars = []) - { - if (is_array($method)) { - $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); - $reflect = new \ReflectionMethod($class, $method[1]); - } else { - // 静态方法 - $reflect = new \ReflectionMethod($method); - } - - $args = self::bindParams($reflect, $vars); - - self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); - - return $reflect->invokeArgs(isset($class) ? $class : null, $args); - } - - /** - * 调用反射执行类的实例化 支持依赖注入 - * @access public - * @param string $class 类名 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeClass($class, $vars = []) - { - $reflect = new \ReflectionClass($class); - $constructor = $reflect->getConstructor(); - $args = $constructor ? self::bindParams($constructor, $vars) : []; - - return $reflect->newInstanceArgs($args); - } - - /** - * 绑定参数 - * @access private - * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 - * @return array - */ - private static function bindParams($reflect, $vars = []) - { - // 自动获取请求变量 - if (empty($vars)) { - $vars = Config::get('url_param_type') ? - Request::instance()->route() : - Request::instance()->param(); - } - - $args = []; - if ($reflect->getNumberOfParameters() > 0) { - // 判断数组类型 数字数组时按顺序绑定参数 - reset($vars); - $type = key($vars) === 0 ? 1 : 0; - - foreach ($reflect->getParameters() as $param) { - $args[] = self::getParamValue($param, $vars, $type); - } - } - - return $args; - } - - /** - * 获取参数值 - * @access private - * @param \ReflectionParameter $param 参数 - * @param array $vars 变量 - * @param string $type 类别 - * @return array - */ - private static function getParamValue($param, &$vars, $type) - { - $name = $param->getName(); - $class = $param->getClass(); - - if ($class) { - $className = $class->getName(); - $bind = Request::instance()->$name; - - if ($bind instanceof $className) { - $result = $bind; - } else { - if (method_exists($className, 'invoke')) { - $method = new \ReflectionMethod($className, 'invoke'); - - if ($method->isPublic() && $method->isStatic()) { - return $className::invoke(Request::instance()); - } - } - - $result = method_exists($className, 'instance') ? - $className::instance() : - new $className; - } - } elseif (1 == $type && !empty($vars)) { - $result = array_shift($vars); - } elseif (0 == $type && isset($vars[$name])) { - $result = $vars[$name]; - } elseif ($param->isDefaultValueAvailable()) { - $result = $param->getDefaultValue(); - } else { - throw new \InvalidArgumentException('method param miss:' . $name); - } - - return $result; - } - - /** - * 执行调用分发 - * @access protected - * @param array $dispatch 调用信息 - * @param array $config 配置信息 - * @return Response|mixed - * @throws \InvalidArgumentException - */ - protected static function exec($dispatch, $config) - { - switch ($dispatch['type']) { - case 'redirect': // 重定向跳转 - $data = Response::create($dispatch['url'], 'redirect') - ->code($dispatch['status']); - break; - case 'module': // 模块/控制器/操作 - $data = self::module( - $dispatch['module'], - $config, - isset($dispatch['convert']) ? $dispatch['convert'] : null - ); - break; - case 'controller': // 执行控制器操作 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = Loader::action( - $dispatch['controller'], - $vars, - $config['url_controller_layer'], - $config['controller_suffix'] - ); - break; - case 'method': // 回调方法 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = self::invokeMethod($dispatch['method'], $vars); - break; - case 'function': // 闭包 - $data = self::invokeFunction($dispatch['function']); - break; - case 'response': // Response 实例 - $data = $dispatch['response']; - break; - default: - throw new \InvalidArgumentException('dispatch type not support'); - } - - return $data; - } - - /** - * 执行模块 - * @access public - * @param array $result 模块/控制器/操作 - * @param array $config 配置参数 - * @param bool $convert 是否自动转换控制器和操作名 - * @return mixed - * @throws HttpException - */ - public static function module($result, $config, $convert = null) - { - if (is_string($result)) { - $result = explode('/', $result); - } - - $request = Request::instance(); - - if ($config['app_multi_module']) { - // 多模块部署 - $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); - $bind = Route::getBind('module'); - $available = false; - - if ($bind) { - // 绑定模块 - list($bindModule) = explode('/', $bind); - - if (empty($result[0])) { - $module = $bindModule; - $available = true; - } elseif ($module == $bindModule) { - $available = true; - } - } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { - $available = true; - } - - // 模块初始化 - if ($module && $available) { - // 初始化模块 - $request->module($module); - $config = self::init($module); - - // 模块请求缓存检查 - $request->cache( - $config['request_cache'], - $config['request_cache_expire'], - $config['request_cache_except'] - ); - } else { - throw new HttpException(404, 'module not exists:' . $module); - } - } else { - // 单一模块部署 - $module = ''; - $request->module($module); - } - - // 设置默认过滤机制 - $request->filter($config['default_filter']); - - // 当前模块路径 - App::$modulePath = APP_PATH . ($module ? $module . DS : ''); - - // 是否自动转换控制器和操作名 - $convert = is_bool($convert) ? $convert : $config['url_convert']; - - // 获取控制器名 - $controller = strip_tags($result[1] ?: $config['default_controller']); - $controller = $convert ? strtolower($controller) : $controller; - - // 获取操作名 - $actionName = strip_tags($result[2] ?: $config['default_action']); - $actionName = $convert ? strtolower($actionName) : $actionName; - - // 设置当前请求的控制器、操作 - $request->controller(Loader::parseName($controller, 1))->action($actionName); - - // 监听module_init - Hook::listen('module_init', $request); - - try { - $instance = Loader::controller( - $controller, - $config['url_controller_layer'], - $config['controller_suffix'], - $config['empty_controller'] - ); - } catch (ClassNotFoundException $e) { - throw new HttpException(404, 'controller not exists:' . $e->getClass()); - } - - // 获取当前操作名 - $action = $actionName . $config['action_suffix']; - - $vars = []; - if (is_callable([$instance, $action])) { - // 执行操作方法 - $call = [$instance, $action]; - } elseif (is_callable([$instance, '_empty'])) { - // 空操作 - $call = [$instance, '_empty']; - $vars = [$actionName]; - } else { - // 操作不存在 - throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); - } - - Hook::listen('action_begin', $call); - - return self::invokeMethod($call, $vars); + return $this->config->get($name); } /** * URL路由检测(根据PATH_INFO) * @access public - * @param \think\Request $request 请求实例 - * @param array $config 配置信息 - * @return array - * @throws \think\Exception + * @return Dispatch */ - public static function routeCheck($request, array $config) + public function routeCheck() { - $path = $request->path(); - $depr = $config['pathinfo_depr']; - $result = false; + $path = $this->request->path(); + $depr = $this->config('app.pathinfo_depr'); // 路由检测 - $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; - if ($check) { - // 开启路由 - if (is_file(RUNTIME_PATH . 'route.php')) { - // 读取路由缓存 - $rules = include RUNTIME_PATH . 'route.php'; - is_array($rules) && Route::rules($rules); - } else { - $files = $config['route_config_file']; - foreach ($files as $file) { - if (is_file(CONF_PATH . $file . CONF_EXT)) { - // 导入路由配置 - $rules = include CONF_PATH . $file . CONF_EXT; - is_array($rules) && Route::import($rules); - } + $files = scandir($this->routePath); + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $this->routePath . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + $this->route->import($rules); } } + } - // 路由检测(根据路由定义返回不同的URL调度) - $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); - $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; + if ($this->config('app.route_annotation')) { + // 自动生成路由定义 + if ($this->debug) { + $this->build->buildRoute($this->config('app.controller_suffix')); + } - if ($must && false === $result) { - // 路由无效 - throw new RouteNotFoundException(); + $filename = $this->runtimePath . 'build_route.php'; + + if (is_file($filename)) { + include $filename; } } - // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 - if (false === $result) { - $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); - } + // 是否强制路由模式 + $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must'); - return $result; + // 路由检测 返回一个Dispatch对象 + return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match')); } /** * 设置应用的路由检测机制 * @access public - * @param bool $route 是否需要检测路由 * @param bool $must 是否强制检测路由 + * @return $this + */ + public function routeMust($must = false) + { + $this->routeMust = $must; + return $this; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected function parseModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $class = $name; + $module = $this->request->module(); + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = $this->request->module(); + } + + $class = $this->parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 实例化应用类库 + * @access public + * @param string $name 类名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public function create($name, $layer, $appendSuffix = false, $common = 'common') + { + $guid = $name . $layer; + + if ($this->__isset($guid)) { + return $this->__get($guid); + } + + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $object = $this->__get($class); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + if (class_exists($class)) { + $object = $this->__get($class); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + $this->__set($guid, $class); + + return $object; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Model + * @throws ClassNotFoundException + */ + public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return $this->__get($class); + } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { + return $this->__get($emptyClass); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Validate + * @throws ClassNotFoundException + */ + public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: $this->config('default_validate'); + + if (empty($name)) { + return new Validate; + } + + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 数据库初始化 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Query + */ + public function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + * @throws ClassNotFoundException + */ + public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); + $class = $this->controller($module, $layer, $appendSuffix); + + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return $this->container->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix + * @return string + */ + public function parseClass($module, $layer, $name, $appendSuffix = false) + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version() + { + return static::VERSION; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug() + { + return $this->debug; + } + + /** + * 获取模块路径 + * @access public + * @return string + */ + public function getModulePath() + { + return $this->modulePath; + } + + /** + * 设置模块路径 + * @access public + * @param string $path 路径 * @return void */ - public static function route($route, $must = false) + public function setModulePath($path) { - self::$routeCheck = $route; - self::$routeMust = $must; + $this->modulePath = $path; + $this->env->set('module_path', $path); + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath() + { + return $this->rootPath; + } + + /** + * 获取应用类库目录 + * @access public + * @return string + */ + public function getAppPath() + { + return $this->appPath; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath() + { + return $this->runtimePath; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath() + { + return $this->thinkPath; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath() + { + return $this->routePath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath() + { + return $this->configPath; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt() + { + return $this->configExt; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * 设置应用类库命名空间 + * @access public + * @param string $namespace 命名空间名称 + * @return $this + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 是否启用类库后缀 + * @access public + * @return bool + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime() + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem() + { + return $this->beginMem; + } + + /** + * 获取容器实例 + * @access public + * @return Container + */ + public function container() + { + return $this->container; + } + + public function __set($name, $value) + { + $this->container->bind($name, $value); + } + + public function __get($name) + { + return $this->container->make($name); + } + + public function __isset($name) + { + return $this->container->bound($name); + } + + public function __unset($name) + { + $this->container->__unset($name); + } + + public function offsetExists($key) + { + return $this->__isset($key); + } + + public function offsetGet($key) + { + return $this->__get($key); + } + + public function offsetSet($key, $value) + { + $this->__set($key, $value); + } + + public function offsetUnset($key) + { + $this->__unset($key); } } diff --git a/thinkphp/library/think/Build.php b/thinkphp/library/think/Build.php index de7c3275a..ed3ffe43d 100644 --- a/thinkphp/library/think/Build.php +++ b/thinkphp/library/think/Build.php @@ -14,44 +14,57 @@ namespace think; class Build { /** - * 根据传入的 build 资料创建目录和文件 - * @access public - * @param array $build build 列表 - * @param string $namespace 应用类库命名空间 - * @param bool $suffix 类库后缀 - * @return void - * @throws Exception + * 应用对象 + * @var App */ - public static function run(array $build = [], $namespace = 'app', $suffix = false) + protected $app; + + /** + * 应用目录 + * @var string + */ + protected $basePath; + + public function __construct(App $app) + { + $this->app = $app; + $this->basePath = $this->app->getAppPath(); + } + + /** + * 根据传入的build资料创建目录和文件 + * @access public + * @param array $build build列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + */ + public function run(array $build = [], $namespace = 'app', $suffix = false) { // 锁定 - $lock = APP_PATH . 'build.lock'; + $lockfile = $this->basePath . 'build.lock'; - // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了 - if (!is_writable($lock)) { - if (!touch($lock)) { - throw new Exception( - '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!
                    请手动生成项目目录~', - 10006 - ); - } - - foreach ($build as $module => $list) { - if ('__dir__' == $module) { - // 创建目录列表 - self::buildDir($list); - } elseif ('__file__' == $module) { - // 创建文件列表 - self::buildFile($list); - } else { - // 创建模块 - self::module($module, $list, $namespace, $suffix); - } - } - - // 解除锁定 - unlink($lock); + if (is_writable($lockfile)) { + return; + } elseif (!touch($lockfile)) { + throw new Exception('应用目录[' . $this->basePath . ']不可写,目录无法自动生成!
                    请手动生成项目目录~', 10006); } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + $this->buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + $this->buildFile($list); + } else { + // 创建模块 + $this->module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lockfile); } /** @@ -60,11 +73,10 @@ class Build * @param array $list 目录列表 * @return void */ - protected static function buildDir($list) + protected function buildDir($list) { foreach ($list as $dir) { - // 目录不存在则创建目录 - !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true); + $this->checkDirBuild($this->basePath . $dir); } } @@ -74,20 +86,16 @@ class Build * @param array $list 文件列表 * @return void */ - protected static function buildFile($list) + protected function buildFile($list) { foreach ($list as $file) { - // 先创建目录 - if (!is_dir(APP_PATH . dirname($file))) { - mkdir(APP_PATH . dirname($file), 0755, true); + if (!is_dir($this->basePath . dirname($file))) { + // 创建目录 + mkdir($this->basePath . dirname($file), 0755, true); } - // 再创建文件 - if (!is_file(APP_PATH . $file)) { - file_put_contents( - APP_PATH . $file, - 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "basePath . $file)) { + file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "basePath . $module)) { + // 创建模块目录 + mkdir($this->basePath . $module); + } + + if (basename($this->app->getRuntimePath()) != $module) { + // 创建配置文件和公共文件 + $this->buildCommon($module); + // 创建模块的默认页面 + $this->buildHello($module, $namespace, $suffix); } - // 未指定文件和目录,则创建默认的模块目录和文件 if (empty($list)) { + // 创建默认的模块目录和文件 $list = [ - '__file__' => ['config.php', 'common.php'], - '__dir__' => ['controller', 'model', 'view'], + '__file__' => ['common.php'], + '__dir__' => ['controller', 'model', 'view', 'config'], ]; } // 创建子目录和文件 foreach ($list as $path => $file) { - $modulePath = APP_PATH . $module . DS; - + $modulePath = $this->basePath . $module . '/'; if ('__dir__' == $path) { // 生成子目录 foreach ($file as $dir) { - self::checkDirBuild($modulePath . $dir); + $this->checkDirBuild($modulePath . $dir); } } elseif ('__file__' == $path) { // 生成(空白)文件 foreach ($file as $name) { if (!is_file($modulePath . $name)) { - file_put_contents( - $modulePath . $name, - 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "checkDirBuild(dirname($filename)); $content = ''; break; default: @@ -174,30 +180,159 @@ class Build } } + /** + * 根据注释自动生成路由规则 + * @access public + * @param bool $suffix 类库后缀 + * @param string $layer 控制器层目录名 + * @return string + */ + public function buildRoute($alias = false, $layer = '') + { + $namespace = $this->app->getNameSpace(); + $modules = glob($this->basePath . '*', GLOB_ONLYDIR); + $content = 'app->config('app.url_controller_layer'); + } + + foreach ($modules as $module) { + $module = basename($module); + + if (in_array($module, $this->app->config('app.deny_module_list'))) { + continue; + } + + $controllers = glob($this->basePath . $module . '/' . $layer . '/*.php'); + + foreach ($controllers as $controller) { + $content .= $this->getControllerRoute($namespace, $module, basename($controller, '.php'), $alias, $layer); + } + } + + $filename = $this->app->getRuntimePath() . 'build_route.php'; + file_put_contents($filename, $content); + + return $filename; + } + + /** + * 生成控制器类的路由规则 + * @access protected + * @param string $namespace 应用命名空间 + * @param string $module 模块 + * @param string $controller 控制器名 + * @param bool $suffix 类库后缀 + * @param string $layer 控制器层目录名 + * @return string + */ + protected function getControllerRoute($namespace, $module, $controller, $alias = false, $layer = '') + { + $class = new \ReflectionClass($namespace . '\\' . $module . '\\' . $layer . '\\' . $controller); + $content = ''; + $comment = $class->getDocComment(); + + if ($alias) { + $controller = substr($controller, 0, -10); + } + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $route = $module . '/' . $controller; + $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resourece(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } elseif (false !== strpos($comment, '@alias(')) { + $comment = $this->parseRouteComment($comment, '@alias('); + $route = $module . '/' . $controller; + $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } + + $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); + + foreach ($methods as $method) { + $comment = $this->getMethodRouteComment($module, $controller, $method); + if ($comment) { + $content .= PHP_EOL . $comment; + } + } + + return $content; + } + + /** + * 解析路由注释 + * @access protected + * @param string $comment + * @param string $tag + * @return string + */ + protected function parseRouteComment($comment, $tag = '@route(') + { + $comment = substr($comment, 3, -2); + $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1)); + $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment); + + if (count($comment) > 1) { + $key = array_search('', $comment); + $comment = array_slice($comment, 0, false === $key ? 1 : $key); + } + + $comment = implode(PHP_EOL . "\t", $comment) . ';'; + + if (strpos($comment, '{')) { + $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) { + return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0]; + }, $comment); + } + return $comment; + } + + /** + * 获取方法的路由注释 + * @access protected + * @param string $module 模块 + * @param string $controller 控制器名 + * @param \ReflectMethod $reflectMethod + * @return string|void + */ + protected function getMethodRouteComment($module, $controller, $reflectMethod) + { + $comment = $reflectMethod->getDocComment(); + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $action = $reflectMethod->getName(); + + if ($suffix = $this->app->config('app.action_suffix')) { + $action = substr($action, 0, -strlen($suffix)); + } + + $route = $module . '/' . $controller . '/' . $action; + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment); + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment); + + return $comment; + } + } + /** * 创建模块的欢迎页面 * @access protected - * @param string $module 模块名 + * @param string $module 模块名 * @param string $namespace 应用类库命名空间 - * @param bool $suffix 类库后缀 + * @param bool $suffix 类库后缀 * @return void */ - protected static function buildHello($module, $namespace, $suffix = false) + protected function buildHello($module, $namespace, $suffix = false) { - $filename = APP_PATH . ($module ? $module . DS : '') . - 'controller' . DS . 'Index' . - ($suffix ? 'Controller' : '') . EXT; - + $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . ($suffix ? 'Controller' : '') . '.php'; if (!is_file($filename)) { - $module = $module ? $module . '\\' : ''; - $suffix = $suffix ? 'Controller' : ''; - $content = str_replace( - ['{$app}', '{$module}', '{layer}', '{$suffix}'], - [$namespace, $module, 'controller', $suffix], - file_get_contents(THINK_PATH . 'tpl' . DS . 'default_index.tpl') - ); + $content = file_get_contents($this->app->getThinkPath() . 'tpl' . DIRECTORY_SEPARATOR . 'default_index.tpl'); + $content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content); + $this->checkDirBuild(dirname($filename)); - self::checkDirBuild(dirname($filename)); file_put_contents($filename, $content); } } @@ -208,18 +343,20 @@ class Build * @param string $module 模块名 * @return void */ - protected static function buildCommon($module) + protected function buildCommon($module) { - $config = CONF_PATH . ($module ? $module . DS : '') . 'config.php'; + $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php'; + $this->checkDirBuild(dirname($filename)); - self::checkDirBuild(dirname($config)); - - if (!is_file($config)) { - file_put_contents($config, "basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php'; + + if (!is_file($filename)) { + file_put_contents($filename, "app = $app; + } /** - * @var object 操作句柄 - */ - public static $handler; - - /** - * 连接缓存驱动 + * 连接缓存 * @access public - * @param array $options 配置数组 - * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 * @return Driver */ - public static function connect(array $options = [], $name = false) + public function connect(array $options = [], $name = false) { $type = !empty($options['type']) ? $options['type'] : 'File'; @@ -50,45 +53,45 @@ class Cache $name = md5(serialize($options)); } - if (true === $name || !isset(self::$instance[$name])) { - $class = false === strpos($type, '\\') ? - '\\think\\cache\\driver\\' . ucwords($type) : - $type; + if (true === $name || !isset($this->instance[$name])) { + $class = false !== strpos($type, '\\') ? $type : '\\think\\cache\\driver\\' . ucwords($type); // 记录初始化信息 - App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); + $this->app->log('[ CACHE ] INIT ' . $type); if (true === $name) { - return new $class($options); + $name = md5(serialize($options)); } - self::$instance[$name] = new $class($options); + $this->instance[$name] = new $class($options); } - return self::$instance[$name]; + return $this->instance[$name]; } /** * 自动初始化缓存 * @access public - * @param array $options 配置数组 + * @param array $options 配置数组 * @return Driver */ - public static function init(array $options = []) + public function init(array $options = []) { - if (is_null(self::$handler)) { - if (empty($options) && 'complex' == Config::get('cache.type')) { - $default = Config::get('cache.default'); - // 获取默认缓存配置,并连接 - $options = Config::get('cache.' . $default['type']) ?: $default; + if (is_null($this->handler)) { + // 自动初始化缓存 + $config = $this->app['config']; + + if (empty($options) && 'complex' == $config->get('cache.type')) { + $default = $config->get('cache.default'); + $options = $config->get('cache.' . $default['type']) ?: $default; } elseif (empty($options)) { - $options = Config::get('cache'); + $options = $config->pull('cache'); } - self::$handler = self::connect($options); + $this->handler = $this->connect($options); } - return self::$handler; + return $this->handler; } /** @@ -97,151 +100,18 @@ class Cache * @param string $name 缓存标识 * @return Driver */ - public static function store($name = '') + public function store($name = '') { - if ('' !== $name && 'complex' == Config::get('cache.type')) { - return self::connect(Config::get('cache.' . $name), strtolower($name)); + if ('' !== $name && 'complex' == $this->app['config']->get('cache.type')) { + return $this->connect($this->app['config']->get('cache.' . $name), strtolower($name)); } - return self::init(); + return $this->init(); } - /** - * 判断缓存是否存在 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public static function has($name) + public function __call($method, $args) { - self::$readTimes++; - - return self::init()->has($name); - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存标识 - * @param mixed $default 默认值 - * @return mixed - */ - public static function get($name, $default = false) - { - self::$readTimes++; - - return self::init()->get($name, $default); - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存标识 - * @param mixed $value 存储数据 - * @param int|null $expire 有效时间 0为永久 - * @return boolean - */ - public static function set($name, $value, $expire = null) - { - self::$writeTimes++; - - return self::init()->set($name, $value, $expire); - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function inc($name, $step = 1) - { - self::$writeTimes++; - - return self::init()->inc($name, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function dec($name, $step = 1) - { - self::$writeTimes++; - - return self::init()->dec($name, $step); - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存标识 - * @return boolean - */ - public static function rm($name) - { - self::$writeTimes++; - - return self::init()->rm($name); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public static function clear($tag = null) - { - self::$writeTimes++; - - return self::init()->clear($tag); - } - - /** - * 读取缓存并删除 - * @access public - * @param string $name 缓存变量名 - * @return mixed - */ - public static function pull($name) - { - self::$readTimes++; - self::$writeTimes++; - - return self::init()->pull($name); - } - - /** - * 如果不存在则写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 - * @return mixed - */ - public static function remember($name, $value, $expire = null) - { - self::$readTimes++; - - return self::init()->remember($name, $value, $expire); - } - - /** - * 缓存标签 - * @access public - * @param string $name 标签名 - * @param string|array $keys 缓存标识 - * @param bool $overlay 是否覆盖 - * @return Driver - */ - public static function tag($name, $keys = null, $overlay = false) - { - return self::init()->tag($name, $keys, $overlay); + return call_user_func_array([$this->init(), $method], $args); } } diff --git a/thinkphp/library/think/Collection.php b/thinkphp/library/think/Collection.php index 8e132b1bf..929b51939 100644 --- a/thinkphp/library/think/Collection.php +++ b/thinkphp/library/think/Collection.php @@ -20,33 +20,23 @@ use JsonSerializable; class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable { /** - * @var array 数据 + * 数据集数据 + * @var array */ protected $items = []; - /** - * Collection constructor. - * @access public - * @param array $items 数据 - */ public function __construct($items = []) { $this->items = $this->convertToArray($items); } - /** - * 创建 Collection 实例 - * @access public - * @param array $items 数据 - * @return static - */ public static function make($items = []) { return new static($items); } /** - * 判断数据是否为空 + * 是否为空 * @access public * @return bool */ @@ -55,32 +45,45 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return empty($this->items); } - /** - * 将数据转成数组 - * @access public - * @return array - */ public function toArray() { return array_map(function ($value) { - return ($value instanceof Model || $value instanceof self) ? - $value->toArray() : - $value; + return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value; }, $this->items); } - /** - * 获取全部的数据 - * @access public - * @return array - */ public function all() { return $this->items; } + /** + * 合并数组 + * + * @access public + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + /** * 交换数组中的键和值 + * * @access public * @return static */ @@ -90,7 +93,20 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 返回数组中所有的键名组成的新 Collection 实例 + * 比较数组,返回交集 + * + * @access public + * @param mixed $items + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->convertToArray($items))); + } + + /** + * 返回数组中所有的键名 + * * @access public * @return static */ @@ -100,40 +116,8 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 合并数组并返回一个新的 Collection 实例 - * @access public - * @param mixed $items 新的数据 - * @return static - */ - public function merge($items) - { - return new static(array_merge($this->items, $this->convertToArray($items))); - } - - /** - * 比较数组,返回差集生成的新 Collection 实例 - * @access public - * @param mixed $items 做比较的数据 - * @return static - */ - public function diff($items) - { - return new static(array_diff($this->items, $this->convertToArray($items))); - } - - /** - * 比较数组,返回交集组成的 Collection 新实例 - * @access public - * @param mixed $items 比较数据 - * @return static - */ - public function intersect($items) - { - return new static(array_intersect($this->items, $this->convertToArray($items))); - } - - /** - * 返回并删除数据中的的最后一个元素(出栈) + * 删除数组的最后一个元素(出栈) + * * @access public * @return mixed */ @@ -143,7 +127,32 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 返回并删除数据中首个元素 + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * * @access public * @return mixed */ @@ -152,27 +161,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return array_shift($this->items); } - /** - * 在数组开头插入一个元素 - * @access public - * @param mixed $value 值 - * @param mixed $key 键名 - * @return void - */ - public function unshift($value, $key = null) - { - if (is_null($key)) { - array_unshift($this->items, $value); - } else { - $this->items = [$key => $value] + $this->items; - } - } - /** * 在数组结尾插入一个元素 * @access public - * @param mixed $value 值 - * @param mixed $key 键名 + * @param mixed $value + * @param mixed $key * @return void */ public function push($value, $key = null) @@ -185,32 +178,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 通过使用用户自定义函数,以字符串返回数组 + * 把一个数组分割为新的数组块. + * * @access public - * @param callable $callback 回调函数 - * @param mixed $initial 初始值 - * @return mixed - */ - public function reduce(callable $callback, $initial = null) - { - return array_reduce($this->items, $callback, $initial); - } - - /** - * 以相反的顺序创建一个新的 Collection 实例 - * @access public - * @return static - */ - public function reverse() - { - return new static(array_reverse($this->items)); - } - - /** - * 把数据分割为新的数组块 - * @access public - * @param int $size 分隔长度 - * @param bool $preserveKeys 是否保持原数据索引 + * @param int $size + * @param bool $preserveKeys * @return static */ public function chunk($size, $preserveKeys = false) @@ -225,9 +197,26 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 给数据中的每个元素执行回调 + * 在数组开头插入一个元素 * @access public - * @param callable $callback 回调函数 + * @param mixed $value + * @param mixed $key + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback * @return $this */ public function each(callable $callback) @@ -237,9 +226,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria if (false === $result) { break; - } - - if (!is_object($item)) { + } elseif (!is_object($item)) { $this->items[$key] = $result; } } @@ -248,78 +235,56 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 用回调函数过滤数据中的元素 + * 用回调函数过滤数组中的元素 * @access public - * @param callable|null $callback 回调函数 + * @param callable|null $callback * @return static */ public function filter(callable $callback = null) { - return new static(array_filter($this->items, $callback ?: null)); + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); } /** * 返回数据中指定的一列 * @access public * @param mixed $columnKey 键名 - * @param null $indexKey 作为索引值的列 + * @param mixed $indexKey 作为索引值的列 * @return array */ public function column($columnKey, $indexKey = null) { - if (function_exists('array_column')) { - return array_column($this->items, $columnKey, $indexKey); - } - - $result = []; - foreach ($this->items as $row) { - $key = $value = null; - $keySet = $valueSet = false; - - if (null !== $indexKey && array_key_exists($indexKey, $row)) { - $key = (string) $row[$indexKey]; - $keySet = true; - } - - if (null === $columnKey) { - $valueSet = true; - $value = $row; - } elseif (is_array($row) && array_key_exists($columnKey, $row)) { - $valueSet = true; - $value = $row[$columnKey]; - } - - if ($valueSet) { - if ($keySet) { - $result[$key] = $value; - } else { - $result[] = $value; - } - } - } - - return $result; + return array_column($this->items, $columnKey, $indexKey); } /** - * 对数据排序,并返回排序后的数据组成的新 Collection 实例 + * 对数组排序 + * * @access public - * @param callable|null $callback 回调函数 + * @param callable|null $callback * @return static */ public function sort(callable $callback = null) { $items = $this->items; + $callback = $callback ?: function ($a, $b) { return $a == $b ? 0 : (($a < $b) ? -1 : 1); + }; uasort($items, $callback); + return new static($items); } /** - * 将数据打乱后组成新的 Collection 实例 + * 将数组打乱 + * * @access public * @return static */ @@ -328,15 +293,17 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria $items = $this->items; shuffle($items); + return new static($items); } /** - * 截取数据并返回新的 Collection 实例 + * 截取数组 + * * @access public - * @param int $offset 起始位置 - * @param int $length 截取长度 - * @param bool $preserveKeys 是否保持原先的键名 + * @param int $offset + * @param int $length + * @param bool $preserveKeys * @return static */ public function slice($offset, $length = null, $preserveKeys = false) @@ -344,35 +311,17 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return new static(array_slice($this->items, $offset, $length, $preserveKeys)); } - /** - * 指定的键是否存在 - * @access public - * @param mixed $offset 键名 - * @return bool - */ + // ArrayAccess public function offsetExists($offset) { return array_key_exists($offset, $this->items); } - /** - * 获取指定键对应的值 - * @access public - * @param mixed $offset 键名 - * @return mixed - */ public function offsetGet($offset) { return $this->items[$offset]; } - /** - * 设置键值 - * @access public - * @param mixed $offset 键名 - * @param mixed $value 值 - * @return void - */ public function offsetSet($offset, $value) { if (is_null($offset)) { @@ -382,51 +331,33 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } } - /** - * 删除指定键值 - * @access public - * @param mixed $offset 键名 - * @return void - */ public function offsetUnset($offset) { unset($this->items[$offset]); } - /** - * 统计数据的个数 - * @access public - * @return int - */ + //Countable public function count() { return count($this->items); } - /** - * 获取数据的迭代器 - * @access public - * @return ArrayIterator - */ + //IteratorAggregate public function getIterator() { return new ArrayIterator($this->items); } - /** - * 将数据反序列化成数组 - * @access public - * @return array - */ + //JsonSerializable public function jsonSerialize() { return $this->toArray(); } /** - * 转换当前数据集为 JSON 字符串 + * 转换当前数据集为JSON字符串 * @access public - * @param integer $options json 参数 + * @param integer $options json参数 * @return string */ public function toJson($options = JSON_UNESCAPED_UNICODE) @@ -434,24 +365,24 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return json_encode($this->toArray(), $options); } - /** - * 将数据转换成字符串 - * @access public - * @return string - */ public function __toString() { return $this->toJson(); } /** - * 将数据转换成数组 - * @access protected - * @param mixed $items 数据 + * 转换成数组 + * + * @access public + * @param mixed $items * @return array */ protected function convertToArray($items) { - return $items instanceof self ? $items->all() : (array) $items; + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; } } diff --git a/thinkphp/library/think/Config.php b/thinkphp/library/think/Config.php index 8fa668d14..e30471a5e 100644 --- a/thinkphp/library/think/Config.php +++ b/thinkphp/library/think/Config.php @@ -11,204 +11,304 @@ namespace think; -class Config +class Config implements \ArrayAccess { /** - * @var array 配置参数 + * 配置参数 + * @var array */ - private static $config = []; + private $config = []; /** - * @var string 参数作用域 + * 缓存前缀 + * @var string */ - private static $range = '_sys_'; + private $prefix = 'app'; /** - * 设定配置参数的作用域 + * 设置配置参数默认前缀 * @access public - * @param string $range 作用域 + * @param string $prefix 前缀 * @return void */ - public static function range($range) + public function setDefaultPrefix($prefix) { - self::$range = $range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; + $this->prefix = $prefix; } /** * 解析配置文件或内容 * @access public - * @param string $config 配置文件路径或内容 - * @param string $type 配置解析类型 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) * @return mixed */ - public static function parse($config, $type = '', $name = '', $range = '') + public function parse($config, $type = '', $name = '') { - $range = $range ?: self::$range; + if (empty($type)) { + $type = pathinfo($config, PATHINFO_EXTENSION); + } - if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION); + $class = false !== strpos($type, '\\') ? $type : '\\think\\config\\driver\\' . ucwords($type); - $class = false !== strpos($type, '\\') ? - $type : - '\\think\\config\\driver\\' . ucwords($type); - - return self::set((new $class())->parse($config), $name, $range); + return $this->set((new $class())->parse($config), $name); } /** - * 加载配置文件(PHP格式) + * 加载配置文件(多种格式) * @access public - * @param string $file 配置文件名 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 + * @param string $file 配置文件名 + * @param string $name 一级配置名 * @return mixed */ - public static function load($file, $name = '', $range = '') + public function load($file, $name = '') { - $range = $range ?: self::$range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; - if (is_file($file)) { $name = strtolower($name); $type = pathinfo($file, PATHINFO_EXTENSION); if ('php' == $type) { - return self::set(include $file, $name, $range); + return $this->set(include $file, $name); + } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { + return $this->set(yaml_parse_file($file), $name); + } else { + return $this->parse($file, $type, $name); } + } else { + return $this->config; + } + } - if ('yaml' == $type && function_exists('yaml_parse_file')) { - return self::set(yaml_parse_file($file), $name, $range); - } + /** + * 自动加载配置文件(PHP格式) + * @access public + * @param string $name 配置名 + * @return void + */ + protected function autoLoad($name) + { + // 如果尚未载入 则动态加载配置文件 + $module = Container::get('request')->module(); + $module = $module ? $module . '/' : ''; + $app = Container::get('app'); + $path = $app->getAppPath() . $module; - return self::parse($file, $type, $name, $range); + if (is_dir($path . 'config')) { + $file = $path . 'config/' . $name . $app->getConfigExt(); + } elseif (is_dir($app->getConfigPath() . $module)) { + $file = $app->getConfigPath() . $module . $name . $app->getConfigExt(); } - return self::$config[$range]; + if (isset($file) && is_file($file)) { + $this->load($file, $name); + } } /** * 检测配置是否存在 * @access public - * @param string $name 配置参数名(支持二级配置 . 号分割) - * @param string $range 作用域 + * @param string $name 配置参数名(支持多级配置 .号分割) * @return bool */ - public static function has($name, $range = '') + public function has($name) { - $range = $range ?: self::$range; - if (!strpos($name, '.')) { - return isset(self::$config[$range][strtolower($name)]); + $name = $this->prefix . '.' . $name; } - // 二维数组设置和获取支持 - $name = explode('.', $name, 2); - return isset(self::$config[$range][strtolower($name[0])][$name[1]]); + return $this->get($name) ? true : false; + } + + /** + * 获取一级配置 + * @access public + * @param string $name 一级配置名 + * @return array + */ + public function pull($name) + { + $name = strtolower($name); + + if (!isset($this->config[$name])) { + // 如果尚未载入 则动态加载配置文件 + $this->autoLoad($name); + } + + return isset($this->config[$name]) ? $this->config[$name] : []; } /** * 获取配置参数 为空则获取所有配置 * @access public - * @param string $name 配置参数名(支持二级配置 . 号分割) - * @param string $range 作用域 + * @param string $name 配置参数名(支持多级配置 .号分割) * @return mixed */ - public static function get($name = null, $range = '') + public function get($name = null) { - $range = $range ?: self::$range; - // 无参数时获取所有 - if (empty($name) && isset(self::$config[$range])) { - return self::$config[$range]; + if (empty($name)) { + return $this->config; } - // 非二级配置时直接返回 if (!strpos($name, '.')) { - $name = strtolower($name); - return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; + $name = $this->prefix . '.' . $name; + } elseif ('.' == substr($name, -1)) { + return $this->pull(substr($name, 0, -1)); } - // 二维数组设置和获取支持 - $name = explode('.', $name, 2); + $name = explode('.', $name); $name[0] = strtolower($name[0]); + $config = $this->config; - if (!isset(self::$config[$range][$name[0]])) { - // 动态载入额外配置 - $module = Request::instance()->module(); - $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT; - - is_file($file) && self::load($file, $name[0]); + if (!isset($config[$name[0]])) { + // 如果尚未载入 则动态加载配置文件 + $this->autoLoad($name[0]); } - return isset(self::$config[$range][$name[0]][$name[1]]) ? - self::$config[$range][$name[0]][$name[1]] : - null; + // 按.拆分成多维数组进行判断 + foreach ($name as $val) { + if (isset($config[$val])) { + $config = $config[$val]; + } else { + return; + } + } + + return $config; } /** - * 设置配置参数 name 为数组则为批量设置 + * 设置配置参数 name为数组则为批量设置 * @access public - * @param string|array $name 配置参数名(支持二级配置 . 号分割) - * @param mixed $value 配置值 - * @param string $range 作用域 + * @param string|array $name 配置参数名(支持三级配置 .号分割) + * @param mixed $value 配置值 * @return mixed */ - public static function set($name, $value = null, $range = '') + public function set($name, $value = null) { - $range = $range ?: self::$range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; - - // 字符串则表示单个配置设置 if (is_string($name)) { if (!strpos($name, '.')) { - self::$config[$range][strtolower($name)] = $value; + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + $this->config[strtolower($name[0])][$name[1]] = $value; } else { - // 二维数组 - $name = explode('.', $name, 2); - self::$config[$range][strtolower($name[0])][$name[1]] = $value; + $this->config[strtolower($name[0])][$name[1]][$name[2]] = $value; } return $value; - } - - // 数组则表示批量设置 - if (is_array($name)) { + } elseif (is_array($name)) { + // 批量设置 if (!empty($value)) { - self::$config[$range][$value] = isset(self::$config[$range][$value]) ? - array_merge(self::$config[$range][$value], $name) : - $name; + if (isset($this->config[$value])) { + $result = array_merge($this->config[$value], $name); + } else { + $result = $name; + } - return self::$config[$range][$value]; + $this->config[$value] = $result; + } else { + $result = $this->config = array_merge($this->config, $name); } - - return self::$config[$range] = array_merge( - self::$config[$range], array_change_key_case($name) - ); + } else { + // 为空直接返回 已有配置 + $result = $this->config; } - // 为空直接返回已有配置 - return self::$config[$range]; + return $result; + } + + /** + * 移除配置 + * @access public + * @param string $name 配置参数名(支持三级配置 .号分割) + * @return void + */ + public function remove($name) + { + if (!strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + unset($this->config[strtolower($name[0])][$name[1]]); + } else { + unset($this->config[strtolower($name[0])][$name[1]][$name[2]]); + } } /** * 重置配置参数 * @access public - * @param string $range 作用域 + * @param string $prefix 配置前缀名 * @return void */ - public static function reset($range = '') + public function reset($prefix = '') { - $range = $range ?: self::$range; - - if (true === $range) { - self::$config = []; + if ('' === $prefix) { + $this->config = []; } else { - self::$config[$range] = []; + $this->config[$prefix] = []; } } + + /** + * 设置配置 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set($name, $value) + { + return $this->set($name, $value); + } + + /** + * 获取配置参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * 检测是否存在参数 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function __isset($name) + { + return $this->has($name); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->set($name, $value); + } + + public function offsetExists($name) + { + return $this->has($name); + } + + public function offsetUnset($name) + { + $this->remove($name); + } + + public function offsetGet($name) + { + return $this->get($name); + } } diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php index 2ccde8838..5cb1ccde7 100644 --- a/thinkphp/library/think/Console.php +++ b/thinkphp/library/think/Console.php @@ -20,49 +20,20 @@ use think\console\output\driver\Buffer; class Console { - /** - * @var string 命令名称 - */ - private $name; - /** - * @var string 命令版本 - */ + private $name; private $version; - /** - * @var Command[] 命令 - */ + /** @var Command[] */ private $commands = []; - /** - * @var bool 是否需要帮助信息 - */ private $wantHelps = false; - /** - * @var bool 是否捕获异常 - */ private $catchExceptions = true; - - /** - * @var bool 是否自动退出执行 - */ - private $autoExit = true; - - /** - * @var InputDefinition 输入定义 - */ + private $autoExit = true; private $definition; - - /** - * @var string 默认执行的命令 - */ private $defaultCommand; - /** - * @var array 默认提供的命令 - */ private static $defaultCommands = [ "think\\console\\command\\Help", "think\\console\\command\\Lists", @@ -72,16 +43,11 @@ class Console "think\\console\\command\\make\\Model", "think\\console\\command\\optimize\\Autoload", "think\\console\\command\\optimize\\Config", - "think\\console\\command\\optimize\\Route", "think\\console\\command\\optimize\\Schema", + "think\\console\\command\\optimize\\Route", + "think\\console\\command\\RunServer", ]; - /** - * Console constructor. - * @access public - * @param string $name 名称 - * @param string $version 版本 - */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { $this->name = $name; @@ -95,44 +61,45 @@ class Console } } - /** - * 初始化 Console - * @access public - * @param bool $run 是否运行 Console - * @return int|Console - */ public static function init($run = true) { static $console; if (!$console) { - // 实例化 console + // 实例化console $console = new self('Think Console', '0.1'); // 读取指令集 - if (is_file(CONF_PATH . 'command' . EXT)) { - $commands = include CONF_PATH . 'command' . EXT; + $file = Container::get('env')->get('app_path') . 'command.php'; + + if (is_file($file)) { + $commands = include $file; if (is_array($commands)) { foreach ($commands as $command) { - class_exists($command) && - is_subclass_of($command, "\\think\\console\\Command") && - $console->add(new $command()); // 注册指令 + if (class_exists($command) && is_subclass_of($command, "\\think\\console\\Command")) { + // 注册指令 + $console->add(new $command()); + } } } } } - return $run ? $console->run() : $console; + if ($run) { + // 运行 + return $console->run(); + } else { + return $console; + } } /** - * 调用命令 * @access public * @param string $command * @param array $parameters * @param string $driver - * @return Output + * @return Output|Buffer */ public static function call($command, array $parameters = [], $driver = 'buffer') { @@ -154,6 +121,7 @@ class Console * @access public * @return int * @throws \Exception + * @api */ public function run() { @@ -165,21 +133,27 @@ class Console try { $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { - if (!$this->catchExceptions) throw $e; + if (!$this->catchExceptions) { + throw $e; + } $output->renderException($e); $exitCode = $e->getCode(); - if (is_numeric($exitCode)) { - $exitCode = ((int) $exitCode) ?: 1; + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } } else { $exitCode = 1; } } if ($this->autoExit) { - if ($exitCode > 255) $exitCode = 255; + if ($exitCode > 255) { + $exitCode = 255; + } exit($exitCode); } @@ -190,13 +164,12 @@ class Console /** * 执行指令 * @access public - * @param Input $input 输入 - * @param Output $output 输出 + * @param Input $input + * @param Output $output * @return int */ public function doRun(Input $input, Output $output) { - // 获取版本信息 if (true === $input->hasParameterOption(['--version', '-V'])) { $output->writeln($this->getLongVersion()); @@ -205,7 +178,6 @@ class Console $name = $this->getCommandName($input); - // 获取帮助信息 if (true === $input->hasParameterOption(['--help', '-h'])) { if (!$name) { $name = 'help'; @@ -220,26 +192,27 @@ class Console $input = new Input([$this->defaultCommand]); } - return $this->doRunCommand($this->find($name), $input, $output); + $command = $this->find($name); + + $exitCode = $this->doRunCommand($command, $input, $output); + + return $exitCode; } /** * 设置输入参数定义 * @access public - * @param InputDefinition $definition 输入定义 - * @return $this; + * @param InputDefinition $definition */ public function setDefinition(InputDefinition $definition) { $this->definition = $definition; - - return $this; } /** * 获取输入参数定义 * @access public - * @return InputDefinition + * @return InputDefinition The InputDefinition instance */ public function getDefinition() { @@ -247,9 +220,9 @@ class Console } /** - * 获取帮助信息 + * Gets the help message. * @access public - * @return string + * @return string A help message. */ public function getHelp() { @@ -257,29 +230,25 @@ class Console } /** - * 设置是否捕获异常 + * 是否捕获异常 * @access public - * @param bool $boolean 是否捕获 - * @return $this + * @param bool $boolean + * @api */ public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; - - return $this; } /** - * 设置是否自动退出 + * 是否自动退出 * @access public - * @param bool $boolean 是否自动退出 - * @return $this + * @param bool $boolean + * @api */ public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; - - return $this; } /** @@ -295,20 +264,18 @@ class Console /** * 设置名称 * @access public - * @param string $name 名称 - * @return $this + * @param string $name */ public function setName($name) { $this->name = $name; - - return $this; } /** * 获取版本 * @access public * @return string + * @api */ public function getVersion() { @@ -318,14 +285,11 @@ class Console /** * 设置版本 * @access public - * @param string $version 版本信息 - * @return $this + * @param string $version */ public function setVersion($version) { $this->version = $version; - - return $this; } /** @@ -336,11 +300,7 @@ class Console public function getLongVersion() { if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { - return sprintf( - '%s version %s', - $this->getName(), - $this->getVersion() - ); + return sprintf('%s version %s', $this->getName(), $this->getVersion()); } return 'Console Tool'; @@ -349,7 +309,7 @@ class Console /** * 注册一个指令 * @access public - * @param string $name 指令名称 + * @param string $name * @return Command */ public function register($name) @@ -358,37 +318,34 @@ class Console } /** - * 批量添加指令 + * 添加指令 * @access public - * @param Command[] $commands 指令实例 - * @return $this + * @param Command[] $commands */ public function addCommands(array $commands) { - foreach ($commands as $command) $this->add($command); - - return $this; + foreach ($commands as $command) { + $this->add($command); + } } /** * 添加一个指令 * @access public - * @param Command $command 命令实例 - * @return Command|bool + * @param Command $command + * @return Command */ public function add(Command $command) { - if (!$command->isEnabled()) { - $command->setConsole(null); - return false; - } - $command->setConsole($this); + if (!$command->isEnabled()) { + $command->setConsole(null); + return; + } + if (null === $command->getDefinition()) { - throw new \LogicException( - sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)) - ); + throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); } $this->commands[$command->getName()] = $command; @@ -410,9 +367,7 @@ class Console public function get($name) { if (!isset($this->commands[$name])) { - throw new \InvalidArgumentException( - sprintf('The command "%s" does not exist.', $name) - ); + throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); } $command = $this->commands[$name]; @@ -449,16 +404,11 @@ class Console public function getNamespaces() { $namespaces = []; - foreach ($this->commands as $command) { - $namespaces = array_merge( - $namespaces, $this->extractAllNamespaces($command->getName()) - ); + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); foreach ($command->getAliases() as $alias) { - $namespaces = array_merge( - $namespaces, $this->extractAllNamespaces($alias) - ); + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); } } @@ -466,25 +416,22 @@ class Console } /** - * 查找注册命名空间中的名称或缩写 + * 查找注册命名空间中的名称或缩写。 * @access public - * @param string $namespace + * @param string $namespace * @return string * @throws \InvalidArgumentException */ public function findNamespace($namespace) { - $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]) . '[^:]*'; }, $namespace); - - $allNamespaces = $this->getNamespaces(); $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); if (empty($namespaces)) { - $message = sprintf( - 'There are no commands defined in the "%s" namespace.', $namespace - ); + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { if (1 == count($alternatives)) { @@ -500,14 +447,8 @@ class Console } $exact = in_array($namespace, $namespaces, true); - if (count($namespaces) > 1 && !$exact) { - throw new \InvalidArgumentException( - sprintf( - 'The namespace "%s" is ambiguous (%s).', - $namespace, - $this->getAbbreviationSuggestions(array_values($namespaces))) - ); + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); } return $exact ? $namespace : reset($namespaces); @@ -522,15 +463,16 @@ class Console */ public function find($name) { + $allCommands = array_keys($this->commands); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]) . '[^:]*'; }, $name); - $allCommands = array_keys($this->commands); $commands = preg_grep('{^' . $expr . '}', $allCommands); if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { - if (false !== ($pos = strrpos($name, ':'))) { + if (false !== $pos = strrpos($name, ':')) { $this->findNamespace(substr($name, 0, $pos)); } @@ -550,6 +492,7 @@ class Console if (count($commands) > 1) { $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { $commandName = $commandList[$nameOrAlias]->getName(); @@ -561,9 +504,7 @@ class Console if (count($commands) > 1 && !$exact) { $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); - throw new \InvalidArgumentException( - sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions) - ); + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); } return $this->get($exact ? $name : reset($commands)); @@ -574,17 +515,19 @@ class Console * @access public * @param string $namespace 命名空间 * @return Command[] + * @api */ public function all($namespace = null) { - if (null === $namespace) return $this->commands; + if (null === $namespace) { + return $this->commands; + } $commands = []; - foreach ($this->commands as $name => $command) { - $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1); - - if ($ext === $namespace) $commands[$name] = $command; + if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { + $commands[$name] = $command; + } } return $commands; @@ -593,7 +536,7 @@ class Console /** * 获取可能的指令名 * @access public - * @param array $names 指令名 + * @param array $names * @return array */ public static function getAbbreviations($names) @@ -601,7 +544,7 @@ class Console $abbrevs = []; foreach ($names as $name) { for ($len = strlen($name); $len > 0; --$len) { - $abbrev = substr($name, 0, $len); + $abbrev = substr($name, 0, $len); $abbrevs[$abbrev][] = $name; } } @@ -610,11 +553,10 @@ class Console } /** - * 配置基于用户的参数和选项的输入和输出实例 + * 配置基于用户的参数和选项的输入和输出实例。 * @access protected * @param Input $input 输入实例 * @param Output $output 输出实例 - * @return void */ protected function configureIO(Input $input, Output $output) { @@ -656,9 +598,9 @@ class Console } /** - * 获取指令的名称 + * 获取指令的基础名称 * @access protected - * @param Input $input 输入实例 + * @param Input $input * @return string */ protected function getCommandName(Input $input) @@ -686,32 +628,26 @@ class Console } /** - * 获取默认命令 + * 设置默认命令 * @access protected - * @return Command[] + * @return Command[] An array of default Command instances */ protected function getDefaultCommands() { $defaultCommands = []; - foreach (self::$defaultCommands as $class) { - if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) { - $defaultCommands[] = new $class(); + foreach (self::$defaultCommands as $classname) { + if (class_exists($classname) && is_subclass_of($classname, "think\\console\\Command")) { + $defaultCommands[] = new $classname(); } } return $defaultCommands; } - /** - * 添加默认指令 - * @access public - * @param array $classes 指令 - * @return void - */ - public static function addDefaultCommands(array $classes) + public static function addDefaultCommands(array $classnames) { - self::$defaultCommands = array_merge(self::$defaultCommands, $classes); + self::$defaultCommands = array_merge(self::$defaultCommands, $classnames); } /** @@ -722,18 +658,13 @@ class Console */ private function getAbbreviationSuggestions($abbrevs) { - return sprintf( - '%s, %s%s', - $abbrevs[0], - $abbrevs[1], - count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '' - ); + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } /** - * 返回指令的命名空间部分 + * 返回命名空间部分 * @access public - * @param string $name 指令名称 + * @param string $name 指令 * @param string $limit 部分的命名空间的最大数量 * @return string */ @@ -748,16 +679,16 @@ class Console /** * 查找可替代的建议 * @access private - * @param string $name 指令名称 - * @param array|\Traversable $collection 建议集合 + * @param string $name + * @param array|\Traversable $collection * @return array */ private function findAlternatives($name, $collection) { $threshold = 1e3; $alternatives = []; - $collectionParts = []; + $collectionParts = []; foreach ($collection as $item) { $collectionParts[$item] = explode(':', $item); } @@ -765,7 +696,6 @@ class Console foreach (explode(':', $name) as $i => $subname) { foreach ($collectionParts as $collectionName => $parts) { $exists = isset($alternatives[$collectionName]); - if (!isset($parts[$i]) && $exists) { $alternatives[$collectionName] += $threshold; continue; @@ -774,14 +704,8 @@ class Console } $lev = levenshtein($subname, $parts[$i]); - - if ($lev <= strlen($subname) / 3 || - '' !== $subname && - false !== strpos($parts[$i], $subname) - ) { - $alternatives[$collectionName] = $exists ? - $alternatives[$collectionName] + $lev : - $lev; + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; } elseif ($exists) { $alternatives[$collectionName] += $threshold; } @@ -790,18 +714,14 @@ class Console foreach ($collection as $item) { $lev = levenshtein($name, $item); - if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { - $alternatives[$item] = isset($alternatives[$item]) ? - $alternatives[$item] - $lev : - $lev; + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; } } $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); - asort($alternatives); return array_keys($alternatives); @@ -810,27 +730,25 @@ class Console /** * 设置默认的指令 * @access public - * @param string $commandName 指令名称 - * @return $this + * @param string $commandName The Command name */ public function setDefaultCommand($commandName) { $this->defaultCommand = $commandName; - - return $this; } /** * 返回所有的命名空间 * @access private - * @param string $name 指令名称 + * @param string $name * @return array */ private function extractAllNamespaces($name) { + $parts = explode(':', $name, -1); $namespaces = []; - foreach (explode(':', $name, -1) as $part) { + foreach ($parts as $part) { if (count($namespaces)) { $namespaces[] = end($namespaces) . ':' . $part; } else { diff --git a/thinkphp/library/think/Container.php b/thinkphp/library/think/Container.php new file mode 100644 index 000000000..b934cb346 --- /dev/null +++ b/thinkphp/library/think/Container.php @@ -0,0 +1,295 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use Closure; +use InvalidArgumentException; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; + +class Container +{ + /** + * 容器对象实例 + * @var Container + */ + protected static $instance; + + /** + * 容器中的对象实例 + * @var array + */ + protected $instances = []; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = []; + + /** + * 获取当前容器的实例(单例) + * @access public + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + return static::$instance; + } + + /** + * 获取容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function get($abstract, $vars = [], $newInstance = false) + { + return static::getInstance()->make($abstract, $vars, $newInstance); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + public static function set($abstract, $concrete = null) + { + return static::getInstance()->bind($abstract, $concrete); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string|array $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return $this + */ + public function bind($abstract, $concrete = null) + { + if (is_array($abstract)) { + $this->bind = array_merge($this->bind, $abstract); + } elseif ($concrete instanceof Closure) { + $this->bind[$abstract] = $concrete; + } elseif (is_object($concrete)) { + $this->instances[$abstract] = $concrete; + } else { + $this->bind[$abstract] = $concrete; + } + + return $this; + } + + /** + * 绑定一个类实例当容器 + * @access public + * @param string $abstract 类名或者标识 + * @param object $instance 类的实例 + * @return $this + */ + public function instance($abstract, $instance) + { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + + $this->instances[$abstract] = $instance; + + return $this; + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function bound($abstract) + { + return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $name 类名或者标识 + * @return bool + */ + public function has($name) + { + return $this->bound($name); + } + + /** + * 创建类的实例 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public function make($abstract, $vars = [], $newInstance = false) + { + if (true === $vars) { + // 总是创建新的实例化对象 + $newInstance = true; + $vars = []; + } + + if (isset($this->instances[$abstract]) && !$newInstance) { + $object = $this->instances[$abstract]; + } else { + if (isset($this->bind[$abstract])) { + $concrete = $this->bind[$abstract]; + + if ($concrete instanceof Closure) { + $object = $this->invokeFunction($concrete, $vars); + } else { + $object = $this->make($concrete, $vars, $newInstance); + } + } else { + $object = $this->invokeClass($abstract, $vars); + } + + if (!$newInstance) { + $this->instances[$abstract] = $object; + } + } + + return $object; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|array|\Closure $function 函数或者闭包 + * @param array $vars 变量 + * @return mixed + */ + public function invokeFunction($function, $vars = []) + { + $reflect = new ReflectionFunction($function); + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param string|array $method 方法 + * @param array $vars 变量 + * @return mixed + */ + public function invokeMethod($method, $vars = []) + { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]); + $reflect = new ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new ReflectionMethod($method); + } + + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } + + /** + * 调用反射执行callable 支持参数绑定 + * @access public + * @param mixed $callable + * @param array $vars 变量 + * @return mixed + */ + public function invoke($callable, $vars = []) + { + if ($callable instanceof Closure) { + $result = $this->invokeFunction($callable, $vars); + } else { + $result = $this->invokeMethod($callable, $vars); + } + + return $result; + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 变量 + * @return mixed + */ + public function invokeClass($class, $vars = []) + { + $reflect = new ReflectionClass($class); + $constructor = $reflect->getConstructor(); + + if ($constructor) { + $args = $this->bindParams($constructor, $vars); + } else { + $args = []; + } + + return $reflect->newInstanceArgs($args); + } + + /** + * 绑定参数 + * @access protected + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 变量 + * @return array + */ + protected function bindParams($reflect, $vars = []) + { + $args = []; + + if ($reflect->getNumberOfParameters() > 0) { + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); + + foreach ($params as $param) { + $name = $param->getName(); + $class = $param->getClass(); + + if ($class) { + $className = $class->getName(); + $args[] = $this->make($className); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $args[] = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); + } + } + } + + return $args; + } + +} diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php index e446e58df..cc3953d24 100644 --- a/thinkphp/library/think/Controller.php +++ b/thinkphp/library/think/Controller.php @@ -14,49 +14,60 @@ namespace think; use think\exception\ValidateException; use traits\controller\Jump; -Loader::import('controller/Jump', TRAIT_PATH, EXT); - class Controller { use Jump; /** - * @var \think\View 视图类实例 + * 视图类实例 + * @var \think\View */ protected $view; /** - * @var \think\Request Request 实例 + * Request实例 + * @var \think\Request */ protected $request; /** - * @var bool 验证失败是否抛出异常 + * 应用实例 + * @var \think\App + */ + protected $app; + + /** + * 验证失败是否抛出异常 + * @var bool */ protected $failException = false; /** - * @var bool 是否批量验证 + * 是否批量验证 + * @var bool */ protected $batchValidate = false; /** - * @var array 前置操作方法列表 + * 前置操作方法列表 + * @var array $beforeActionList */ protected $beforeActionList = []; /** * 构造方法 * @access public - * @param Request $request Request 对象 */ - public function __construct(Request $request = null) + public function __construct() { - $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); - $this->request = is_null($request) ? Request::instance() : $request; + $this->request = Container::get('request'); + $this->app = Container::get('app'); + $this->view = Container::get('view')->init( + $this->app['config']->pull('template') + ); // 控制器初始化 - $this->_initialize(); + $this->initialize(); // 前置操作方法 if ($this->beforeActionList) { @@ -68,20 +79,15 @@ class Controller } } - /** - * 初始化操作 - * @access protected - */ - protected function _initialize() - { - } + // 初始化 + protected function initialize() + {} /** * 前置操作 * @access protected * @param string $method 前置操作方法名 - * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]] - * @return void + * @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]] */ protected function beforeAction($method, $options = []) { @@ -89,7 +95,6 @@ class Controller if (is_string($options['only'])) { $options['only'] = explode(',', $options['only']); } - if (!in_array($this->request->action(), $options['only'])) { return; } @@ -97,7 +102,6 @@ class Controller if (is_string($options['except'])) { $options['except'] = explode(',', $options['except']); } - if (in_array($this->request->action(), $options['except'])) { return; } @@ -111,13 +115,12 @@ class Controller * @access protected * @param string $template 模板文件名 * @param array $vars 模板输出变量 - * @param array $replace 模板替换 * @param array $config 模板参数 * @return mixed */ - protected function fetch($template = '', $vars = [], $replace = [], $config = []) + protected function fetch($template = '', $vars = [], $config = []) { - return $this->view->fetch($template, $vars, $replace, $config); + return $this->view->fetch($template, $vars, $config); } /** @@ -125,13 +128,12 @@ class Controller * @access protected * @param string $content 模板内容 * @param array $vars 模板输出变量 - * @param array $replace 替换内容 * @param array $config 模板参数 * @return mixed */ - protected function display($content = '', $vars = [], $replace = [], $config = []) + protected function display($content = '', $vars = [], $config = []) { - return $this->view->display($content, $vars, $replace, $config); + return $this->view->display($content, $vars, $config); } /** @@ -148,10 +150,23 @@ class Controller return $this; } + /** + * 视图过滤 + * @access protected + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + protected function filter($filter) + { + $this->view->filter($filter); + + return $this; + } + /** * 初始化模板引擎 * @access protected - * @param array|string $engine 引擎参数 + * @param array|string $engine 引擎参数 * @return $this */ protected function engine($engine) @@ -164,7 +179,7 @@ class Controller /** * 设置验证失败后是否抛出异常 * @access protected - * @param bool $fail 是否抛出异常 + * @param bool $fail 是否抛出异常 * @return $this */ protected function validateFailException($fail = true) @@ -188,24 +203,28 @@ class Controller protected function validate($data, $validate, $message = [], $batch = false, $callback = null) { if (is_array($validate)) { - $v = Loader::validate(); + $v = $this->app->validate(); $v->rule($validate); } else { - // 支持场景 if (strpos($validate, '.')) { + // 支持场景 list($validate, $scene) = explode('.', $validate); } - - $v = Loader::validate($validate); - - !empty($scene) && $v->scene($scene); + $v = $this->app->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } + } + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + if (is_array($message)) { + $v->message($message); } - // 批量验证 - if ($batch || $this->batchValidate) $v->batch(true); - // 设置错误信息 - if (is_array($message)) $v->message($message); - // 使用回调验证 if ($callback && is_callable($callback)) { call_user_func_array($callback, [$v, &$data]); } @@ -213,11 +232,11 @@ class Controller if (!$v->check($data)) { if ($this->failException) { throw new ValidateException($v->getError()); + } else { + return $v->getError(); } - - return $v->getError(); + } else { + return true; } - - return true; } } diff --git a/thinkphp/library/think/Cookie.php b/thinkphp/library/think/Cookie.php index 61b47cced..c645fde69 100644 --- a/thinkphp/library/think/Cookie.php +++ b/thinkphp/library/think/Cookie.php @@ -14,70 +14,80 @@ namespace think; class Cookie { /** - * @var array cookie 设置参数 + * 配置参数 + * @var array */ - protected static $config = [ - 'prefix' => '', // cookie 名称前缀 - 'expire' => 0, // cookie 保存时间 - 'path' => '/', // cookie 保存路径 - 'domain' => '', // cookie 有效域名 - 'secure' => false, // cookie 启用安全传输 - 'httponly' => false, // httponly 设置 - 'setcookie' => true, // 是否使用 setcookie + protected $config = [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, ]; /** - * @var bool 是否完成初始化了 + * 是否初始化 + * @var bool */ - protected static $init; + protected $init; /** * Cookie初始化 * @access public - * @param array $config 配置参数 + * @param array $config * @return void */ - public static function init(array $config = []) + public function init(array $config = []) { if (empty($config)) { - $config = Config::get('cookie'); + $config = Container::get('config')->pull('cookie'); } - self::$config = array_merge(self::$config, array_change_key_case($config)); + $this->config = array_merge($this->config, array_change_key_case($config)); - if (!empty(self::$config['httponly'])) { + if (!empty($this->config['httponly'])) { ini_set('session.cookie_httponly', 1); } - self::$init = true; + $this->init = true; } /** - * 设置或者获取 cookie 作用域(前缀) + * 设置或者获取cookie作用域(前缀) * @access public - * @param string $prefix 前缀 - * @return string| + * @param string $prefix + * @return string|void */ - public static function prefix($prefix = '') + public function prefix($prefix = '') { if (empty($prefix)) { - return self::$config['prefix']; + return $this->config['prefix']; } - return self::$config['prefix'] = $prefix; + $this->config['prefix'] = $prefix; } /** * Cookie 设置、获取、删除 + * * @access public - * @param string $name cookie 名称 - * @param mixed $value cookie 值 + * @param string $name cookie名称 + * @param mixed $value cookie值 * @param mixed $option 可选参数 可能会是 null|integer|string * @return void */ - public static function set($name, $value = '', $option = null) + public function set($name, $value = '', $option = null) { - !isset(self::$init) && self::init(); + !isset($this->init) && $this->init(); // 参数设置(会覆盖黙认设置) if (!is_null($option)) { @@ -87,42 +97,37 @@ class Cookie parse_str($option, $option); } - $config = array_merge(self::$config, array_change_key_case($option)); + $config = array_merge($this->config, array_change_key_case($option)); } else { - $config = self::$config; + $config = $this->config; } $name = $config['prefix'] . $name; - // 设置 cookie + // 设置cookie if (is_array($value)) { - array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'encode'); $value = 'think:' . json_encode($value); } - $expire = !empty($config['expire']) ? - $_SERVER['REQUEST_TIME'] + intval($config['expire']) : - 0; + $expire = !empty($config['expire']) ? $_SERVER['REQUEST_TIME'] + intval($config['expire']) : 0; if ($config['setcookie']) { - setcookie( - $name, $value, $expire, $config['path'], $config['domain'], - $config['secure'], $config['httponly'] - ); + setcookie($name, $value, $expire, $config['path'], $config['domain'], $config['secure'], $config['httponly']); } $_COOKIE[$name] = $value; } /** - * 永久保存 Cookie 数据 + * 永久保存Cookie数据 * @access public - * @param string $name cookie 名称 - * @param mixed $value cookie 值 + * @param string $name cookie名称 + * @param mixed $value cookie值 * @param mixed $option 可选参数 可能会是 null|integer|string * @return void */ - public static function forever($name, $value = '', $option = null) + public function forever($name, $value = '', $option = null) { if (is_null($option) || is_numeric($option)) { $option = []; @@ -130,49 +135,47 @@ class Cookie $option['expire'] = 315360000; - self::set($name, $value, $option); + $this->set($name, $value, $option); } /** - * 判断是否有 Cookie 数据 + * 判断Cookie数据 * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 * @return bool */ - public static function has($name, $prefix = null) + public function has($name, $prefix = null) { - !isset(self::$init) && self::init(); + !isset($this->init) && $this->init(); - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $name = $prefix . $name; - return isset($_COOKIE[$prefix . $name]); + return isset($_COOKIE[$name]); } /** - * 获取 Cookie 的值 + * Cookie获取 * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 + * @param string $name cookie名称 留空获取全部 + * @param string|null $prefix cookie前缀 * @return mixed */ - public static function get($name = '', $prefix = null) + public function get($name = '', $prefix = null) { - !isset(self::$init) && self::init(); + !isset($this->init) && $this->init(); - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; $key = $prefix . $name; if ('' == $name) { - // 获取全部 if ($prefix) { $value = []; - foreach ($_COOKIE as $k => $val) { if (0 === strpos($k, $prefix)) { $value[$k] = $val; } - } } else { $value = $_COOKIE; @@ -181,8 +184,9 @@ class Cookie $value = $_COOKIE[$key]; if (0 === strpos($value, 'think:')) { - $value = json_decode(substr($value, 6), true); - array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); + $value = substr($value, 6); + $value = json_decode($value, true); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'decode'); } } else { $value = null; @@ -192,77 +196,67 @@ class Cookie } /** - * 删除 Cookie + * Cookie删除 * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 * @return void */ - public static function delete($name, $prefix = null) + public function delete($name, $prefix = null) { - !isset(self::$init) && self::init(); + !isset($this->init) && $this->init(); - $config = self::$config; + $config = $this->config; $prefix = !is_null($prefix) ? $prefix : $config['prefix']; $name = $prefix . $name; if ($config['setcookie']) { - setcookie( - $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], - $config['domain'], $config['secure'], $config['httponly'] - ); + setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']); } - // 删除指定 cookie + // 删除指定cookie unset($_COOKIE[$name]); } /** - * 清除指定前缀的所有 cookie + * Cookie清空 * @access public - * @param string|null $prefix cookie 前缀 + * @param string|null $prefix cookie前缀 * @return void */ - public static function clear($prefix = null) + public function clear($prefix = null) { + // 清除指定前缀的所有cookie if (empty($_COOKIE)) { return; } - !isset(self::$init) && self::init(); + !isset($this->init) && $this->init(); - // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀 - $config = self::$config; + // 要删除的cookie前缀,不指定则删除config设置的指定前缀 + $config = $this->config; $prefix = !is_null($prefix) ? $prefix : $config['prefix']; if ($prefix) { + // 如果前缀为空字符串将不作处理直接返回 foreach ($_COOKIE as $key => $val) { if (0 === strpos($key, $prefix)) { if ($config['setcookie']) { - setcookie( - $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], - $config['domain'], $config['secure'], $config['httponly'] - ); + setcookie($key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']); } - unset($_COOKIE[$key]); } } } + + return; } - /** - * json 转换时的格式保护 - * @access protected - * @param mixed $val 要转换的值 - * @param string $key 键名 - * @param string $type 转换类别 - * @return void - */ - protected static function jsonFormatProtect(&$val, $key, $type = 'encode') + private function jsonFormatProtect(&$val, $key, $type = 'encode') { if (!empty($val) && true !== $val) { $val = 'decode' == $type ? urldecode($val) : urlencode($val); } } + } diff --git a/thinkphp/library/think/Db.php b/thinkphp/library/think/Db.php index 80f08d243..21770e081 100644 --- a/thinkphp/library/think/Db.php +++ b/thinkphp/library/think/Db.php @@ -11,23 +11,20 @@ namespace think; -use think\db\Connection; -use think\db\Query; - /** * Class Db * @package think - * @method Query table(string $table) static 指定数据表(含前缀) - * @method Query name(string $name) static 指定数据表(不含前缀) - * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 - * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 - * @method Query union(mixed $union, boolean $all = false) static UNION查询 - * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT - * @method Query order(mixed $field, string $order = null) static 查询ORDER - * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method \think\db\Query table(string $table) static 指定数据表(含前缀) + * @method \think\db\Query name(string $name) static 指定数据表(不含前缀) + * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method \think\db\Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method \think\db\Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method \think\db\Query union(mixed $union, boolean $all = false) static UNION查询 + * @method \think\db\Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method \think\db\Query order(mixed $field, string $order = null) static 查询ORDER + * @method \think\db\Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 * @method mixed value(string $field) static 获取某个字段的值 * @method array column(string $field, string $key = '') static 获取某个列的值 - * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 * @method mixed find(mixed $data = null) static 查询单个记录 * @method mixed select(mixed $data = null) static 查询多个记录 * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 @@ -36,145 +33,37 @@ use think\db\Query; * @method integer update(array $data) static 更新记录 * @method integer delete(mixed $data = null) static 删除记录 * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 + * @method \Generator cursor(mixed $data = null) static 使用游标查找记录 * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 - * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 + * @method \think\Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 * @method mixed transaction(callable $callback) static 执行数据库事务 * @method void startTrans() static 启动事务 * @method void commit() static 用于非自动提交状态下面的查询提交 * @method void rollback() static 事务回滚 * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 - * @method string quote(string $str) static SQL指令安全过滤 * @method string getLastInsID($sequence = null) static 获取最近插入的ID */ class Db { /** - * @var Connection[] 数据库连接实例 - */ - private static $instance = []; - - /** - * @var int 查询次数 + * 查询次数 + * @var integer */ public static $queryTimes = 0; /** - * @var int 执行次数 + * 执行次数 + * @var integer */ public static $executeTimes = 0; - /** - * 数据库初始化,并取得数据库类实例 - * @access public - * @param mixed $config 连接配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return Connection - * @throws Exception - */ - public static function connect($config = [], $name = false) + public static function __callStatic($method, $args) { - if (false === $name) { - $name = md5(serialize($config)); - } + $class = Container::get('config')->get('database.query') ?: '\\think\\db\\Query'; - if (true === $name || !isset(self::$instance[$name])) { - // 解析连接参数 支持数组和字符串 - $options = self::parseConfig($config); + $query = new $class(); - if (empty($options['type'])) { - throw new \InvalidArgumentException('Undefined db type'); - } - - $class = false !== strpos($options['type'], '\\') ? - $options['type'] : - '\\think\\db\\connector\\' . ucwords($options['type']); - - // 记录初始化信息 - if (App::$debug) { - Log::record('[ DB ] INIT ' . $options['type'], 'info'); - } - - if (true === $name) { - $name = md5(serialize($config)); - } - - self::$instance[$name] = new $class($options); - } - - return self::$instance[$name]; - } - - /** - * 清除连接实例 - * @access public - * @return void - */ - public static function clear() - { - self::$instance = []; - } - - /** - * 数据库连接参数解析 - * @access private - * @param mixed $config 连接参数 - * @return array - */ - private static function parseConfig($config) - { - if (empty($config)) { - $config = Config::get('database'); - } elseif (is_string($config) && false === strpos($config, '/')) { - $config = Config::get($config); // 支持读取配置参数 - } - - return is_string($config) ? self::parseDsn($config) : $config; - } - - /** - * DSN 解析 - * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 - * @access private - * @param string $dsnStr 数据库 DSN 字符串解析 - * @return array - */ - private static function parseDsn($dsnStr) - { - $info = parse_url($dsnStr); - - if (!$info) { - return []; - } - - $dsn = [ - 'type' => $info['scheme'], - 'username' => isset($info['user']) ? $info['user'] : '', - 'password' => isset($info['pass']) ? $info['pass'] : '', - 'hostname' => isset($info['host']) ? $info['host'] : '', - 'hostport' => isset($info['port']) ? $info['port'] : '', - 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', - 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', - ]; - - if (isset($info['query'])) { - parse_str($info['query'], $dsn['params']); - } else { - $dsn['params'] = []; - } - - return $dsn; - } - - /** - * 调用驱动类的方法 - * @access public - * @param string $method 方法名 - * @param array $params 参数 - * @return mixed - */ - public static function __callStatic($method, $params) - { - return call_user_func_array([self::connect(), $method], $params); + return call_user_func_array([$query, $method], $args); } } diff --git a/thinkphp/library/think/Debug.php b/thinkphp/library/think/Debug.php index df487485c..8a384e147 100644 --- a/thinkphp/library/think/Debug.php +++ b/thinkphp/library/think/Debug.php @@ -12,63 +12,78 @@ namespace think; use think\exception\ClassNotFoundException; +use think\model\Collection as ModelCollection; use think\response\Redirect; class Debug { /** - * @var array 区间时间信息 + * 区间时间信息 + * @var array */ - protected static $info = []; + protected $info = []; /** - * @var array 区间内存信息 + * 区间内存信息 + * @var array */ - protected static $mem = []; + protected $mem = []; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } /** * 记录时间(微秒)和内存使用情况 * @access public - * @param string $name 标记位置 - * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存) + * @param string $name 标记位置 + * @param mixed $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存 * @return void */ - public static function remark($name, $value = '') + public function remark($name, $value = '') { - self::$info[$name] = is_float($value) ? $value : microtime(true); + // 记录时间和内存使用 + $this->info[$name] = is_float($value) ? $value : microtime(true); if ('time' != $value) { - self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); - self::$mem['peak'][$name] = memory_get_peak_usage(); + $this->mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + $this->mem['peak'][$name] = memory_get_peak_usage(); } } /** - * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 + * 统计某个区间的时间(微秒)使用情况 * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 - * @return string + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return integer */ - public static function getRangeTime($start, $end, $dec = 6) + public function getRangeTime($start, $end, $dec = 6) { - if (!isset(self::$info[$end])) { - self::$info[$end] = microtime(true); + if (!isset($this->info[$end])) { + $this->info[$end] = microtime(true); } - return number_format((self::$info[$end] - self::$info[$start]), $dec); + return number_format(($this->info[$end] - $this->info[$start]), $dec); } /** - * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 + * 统计从开始到统计时的时间(微秒)使用情况 * @access public - * @param integer $dec 小数位 - * @return string + * @param integer|string $dec 小数位 + * @return integer */ - public static function getUseTime($dec = 6) + public function getUseTime($dec = 6) { - return number_format((microtime(true) - THINK_START_TIME), $dec); + return number_format((microtime(true) - $this->app->getBeginTime()), $dec); } /** @@ -76,26 +91,26 @@ class Debug * @access public * @return string */ - public static function getThroughputRate() + public function getThroughputRate() { - return number_format(1 / self::getUseTime(), 2) . 'req/s'; + return number_format(1 / $this->getUseTime(), 2) . 'req/s'; } /** * 记录区间的内存使用情况 * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 * @return string */ - public static function getRangeMem($start, $end, $dec = 2) + public function getRangeMem($start, $end, $dec = 2) { - if (!isset(self::$mem['mem'][$end])) { - self::$mem['mem'][$end] = memory_get_usage(); + if (!isset($this->mem['mem'][$end])) { + $this->mem['mem'][$end] = memory_get_usage(); } - $size = self::$mem['mem'][$end] - self::$mem['mem'][$start]; + $size = $this->mem['mem'][$end] - $this->mem['mem'][$start]; $a = ['B', 'KB', 'MB', 'GB', 'TB']; $pos = 0; @@ -110,12 +125,12 @@ class Debug /** * 统计从开始到统计时的内存使用情况 * @access public - * @param integer $dec 小数位 + * @param integer|string $dec 小数位 * @return string */ - public static function getUseMem($dec = 2) + public function getUseMem($dec = 2) { - $size = memory_get_usage() - THINK_START_MEM; + $size = memory_get_usage() - $this->app->getBeginMem(); $a = ['B', 'KB', 'MB', 'GB', 'TB']; $pos = 0; @@ -130,18 +145,18 @@ class Debug /** * 统计区间的内存峰值情况 * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 * @return string */ - public static function getMemPeak($start, $end, $dec = 2) + public function getMemPeak($start, $end, $dec = 2) { - if (!isset(self::$mem['peak'][$end])) { - self::$mem['peak'][$end] = memory_get_peak_usage(); + if (!isset($this->mem['peak'][$end])) { + $this->mem['peak'][$end] = memory_get_peak_usage(); } - $size = self::$mem['peak'][$end] - self::$mem['peak'][$start]; + $size = $this->mem['peak'][$end] - $this->mem['peak'][$start]; $a = ['B', 'KB', 'MB', 'GB', 'TB']; $pos = 0; @@ -156,90 +171,82 @@ class Debug /** * 获取文件加载信息 * @access public - * @param bool $detail 是否显示详细 + * @param bool $detail 是否显示详细 * @return integer|array */ - public static function getFile($detail = false) + public function getFile($detail = false) { - $files = get_included_files(); - if ($detail) { - $info = []; + $files = get_included_files(); + $info = []; - foreach ($files as $file) { + foreach ($files as $key => $file) { $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; } return $info; } - return count($files); + return count(get_included_files()); } /** * 浏览器友好的变量输出 * @access public - * @param mixed $var 变量 - * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串) - * @param string|null $label 标签(默认为空) - * @param integer $flags htmlspecialchars 的标志 - * @return null|string + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @param integer $flags htmlspecialchars flags + * @return void|string */ - public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + public function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) { $label = (null === $label) ? '' : rtrim($label) . ':'; + if ($var instanceof Model || $var instanceof ModelCollection) { + $var = $var->toArray(); + } ob_start(); var_dump($var); - $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean()); - if (IS_CLI) { + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + + if (PHP_SAPI == 'cli') { $output = PHP_EOL . $label . $output . PHP_EOL; } else { if (!extension_loaded('xdebug')) { $output = htmlspecialchars($output, $flags); } - $output = '

                    ' . $label . $output . '
                    '; } - if ($echo) { echo($output); return; + } else { + return $output; } - - return $output; } - /** - * 调试信息注入到响应中 - * @access public - * @param Response $response 响应实例 - * @param string $content 返回的字符串 - * @return void - */ - public static function inject(Response $response, &$content) + public function inject(Response $response, &$content) { - $config = Config::get('trace'); + $config = $this->app['config']->pull('trace'); $type = isset($config['type']) ? $config['type'] : 'Html'; $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); - unset($config['type']); - if (!class_exists($class)) { + if (class_exists($class)) { + $trace = new $class($config); + } else { throw new ClassNotFoundException('class not exists:' . $class, $class); } - /** @var \think\debug\Console|\think\debug\Html $trace */ - $trace = new $class($config); - if ($response instanceof Redirect) { - // TODO 记录 + //TODO 记录 } else { - $output = $trace->output($response, Log::getLog()); - + $output = $trace->output($response, $this->app['log']->getLog()); if (is_string($output)) { - // trace 调试信息注入 + // trace调试信息注入 $pos = strripos($content, ''); if (false !== $pos) { $content = substr($content, 0, $pos) . $output . substr($content, $pos); diff --git a/thinkphp/library/think/Env.php b/thinkphp/library/think/Env.php index 0a8b25097..2c41794ff 100644 --- a/thinkphp/library/think/Env.php +++ b/thinkphp/library/think/Env.php @@ -13,16 +13,54 @@ namespace think; class Env { + /** + * 环境变量数据 + * @var array + */ + protected $data = []; + + public function __construct() + { + $this->data = $_ENV; + } + + /** + * 读取环境变量定义文件 + * @access public + * @param string $file 环境变量定义文件 + * @return void + */ + public function load($file) + { + $env = parse_ini_file($file, true); + $this->set($env); + } + /** * 获取环境变量值 * @access public - * @param string $name 环境变量名(支持二级 . 号分割) - * @param string $default 默认值 + * @param string $name 环境变量名 + * @param mixed $default 默认值 * @return mixed */ - public static function get($name, $default = null) + public function get($name = null, $default = null) { - $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name))); + if (is_null($name)) { + return $this->data; + } + + $name = strtoupper(str_replace('.', '_', $name)); + + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default); + } + + protected function getEnv($name, $default = null) + { + $result = getenv('PHP_' . $name); if (false !== $result) { if ('false' === $result) { @@ -31,9 +69,41 @@ class Env $result = true; } - return $result; - } + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } - return $default; + return $result; + } else { + return $default; + } + } + + /** + * 设置环境变量值 + * @access public + * @param string|array $env 环境变量 + * @param mixed $value 值 + * @return void + */ + public function set($env, $value = null) + { + if (is_array($env)) { + $env = array_change_key_case($env, CASE_UPPER); + + foreach ($env as $key => $val) { + if (is_array($val)) { + foreach ($val as $k => $v) { + $this->data[$key . '_' . strtoupper($k)] = $v; + } + } else { + $this->data[$key] = $val; + } + } + } else { + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; + } } } diff --git a/thinkphp/library/think/Error.php b/thinkphp/library/think/Error.php index 5f361d589..c00a36a65 100644 --- a/thinkphp/library/think/Error.php +++ b/thinkphp/library/think/Error.php @@ -32,10 +32,9 @@ class Error } /** - * 异常处理 + * Exception Handler * @access public - * @param \Exception|\Throwable $e 异常 - * @return void + * @param \Exception|\Throwable $e */ public static function appException($e) { @@ -43,60 +42,57 @@ class Error $e = new ThrowableError($e); } - $handler = self::getExceptionHandler(); - $handler->report($e); + self::getExceptionHandler()->report($e); - if (IS_CLI) { - $handler->renderForConsole(new ConsoleOutput, $e); + if (PHP_SAPI == 'cli') { + self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); } else { - $handler->render($e)->send(); + self::getExceptionHandler()->render($e)->send(); } } /** - * 错误处理 + * Error Handler * @access public - * @param integer $errno 错误编号 - * @param integer $errstr 详细错误信息 - * @param string $errfile 出错的文件 - * @param integer $errline 出错行号 - * @return void + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 * @throws ErrorException */ public static function appError($errno, $errstr, $errfile = '', $errline = 0) { $exception = new ErrorException($errno, $errstr, $errfile, $errline); - - // 符合异常处理的则将错误信息托管至 think\exception\ErrorException if (error_reporting() & $errno) { + // 将错误信息托管至 think\exception\ErrorException throw $exception; + } else { + self::getExceptionHandler()->report($exception); } - - self::getExceptionHandler()->report($exception); } /** - * 异常中止处理 + * Shutdown Handler * @access public - * @return void */ public static function appShutdown() { - // 将错误信息托管至 think\ErrorException if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { - self::appException(new ErrorException( - $error['type'], $error['message'], $error['file'], $error['line'] - )); + // 将错误信息托管至think\ErrorException + $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']); + + self::appException($exception); } // 写入日志 - Log::save(); + Container::get('log')->save(); } /** * 确定错误类型是否致命 + * * @access protected - * @param int $type 错误类型 + * @param int $type * @return bool */ protected static function isFatal($type) @@ -105,7 +101,8 @@ class Error } /** - * 获取异常处理的实例 + * Get an instance of the exception handler. + * * @access public * @return Handle */ @@ -114,20 +111,15 @@ class Error static $handle; if (!$handle) { - // 异常处理 handle - $class = Config::get('exception_handle'); - - if ($class && is_string($class) && class_exists($class) && - is_subclass_of($class, "\\think\\exception\\Handle") - ) { + // 异常处理handle + $class = Container::get('config')->get('exception_handle'); + if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { $handle = new $class; } else { $handle = new Handle; - if ($class instanceof \Closure) { $handle->setRender($class); } - } } diff --git a/thinkphp/library/think/Exception.php b/thinkphp/library/think/Exception.php index 1ef06bdbd..414a090ad 100644 --- a/thinkphp/library/think/Exception.php +++ b/thinkphp/library/think/Exception.php @@ -13,13 +13,15 @@ namespace think; class Exception extends \Exception { + /** - * @var array 保存异常页面显示的额外 Debug 数据 + * 保存异常页面显示的额外Debug数据 + * @var array */ protected $data = []; /** - * 设置异常额外的 Debug 数据 + * 设置异常额外的Debug数据 * 数据将会显示为下面的格式 * * Exception Data @@ -34,7 +36,6 @@ class Exception extends \Exception * @access protected * @param string $label 数据分类,用于异常页面显示 * @param array $data 需要显示的数据,必须为关联数组 - * @return void */ final protected function setData($label, array $data) { @@ -42,10 +43,10 @@ class Exception extends \Exception } /** - * 获取异常额外 Debug 数据 + * 获取异常额外Debug数据 * 主要用于输出到异常页面便于调试 * @access public - * @return array + * @return array 由setData设置的Debug数据 */ final public function getData() { diff --git a/thinkphp/library/think/Facade.php b/thinkphp/library/think/Facade.php new file mode 100644 index 000000000..2cda381c7 --- /dev/null +++ b/thinkphp/library/think/Facade.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Facade +{ + /** + * 绑定对象 + * @var array + */ + protected static $bind = []; + + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + /** + * 绑定类的静态代理 + * @static + * @access public + * @param string|array $name 类标识 + * @param string $class 类名 + * @return object + */ + public static function bind($name, $class = null) + { + if (__CLASS__ != static::class) { + return self::__callStatic('bind', func_get_args()); + } + + if (is_array($name)) { + self::$bind = array_merge(self::$bind, $name); + } else { + self::$bind[$name] = $class; + } + } + + /** + * 创建Facade实例 + * @static + * @access protected + * @param string $class 类名或标识 + * @param array $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade($class = '', $args = [], $newInstance = false) + { + $class = $class ?: static::class; + $facadeClass = static::getFacadeClass(); + + if ($facadeClass) { + $class = $facadeClass; + } elseif (isset(self::$bind[$class])) { + $class = self::$bind[$class]; + } + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + return Container::getInstance()->make($class, $args, $newInstance); + } + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 带参数实例化当前Facade类 + * @access public + * @return object + */ + public static function instance(...$args) + { + return self::createFacade('', $args); + } + + /** + * 调用类的实例 + * @access public + * @param string $class 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function make($class, $args = [], $newInstance = false) + { + if (__CLASS__ != static::class) { + return self::__callStatic('make', func_get_args()); + } + + if (true === $args) { + // 总是创建新的实例化对象 + $newInstance = true; + $args = []; + } + + return self::createFacade($class, $args, $newInstance); + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php index d2ed22083..00e83a58b 100644 --- a/thinkphp/library/think/File.php +++ b/thinkphp/library/think/File.php @@ -16,61 +16,64 @@ use SplFileObject; class File extends SplFileObject { /** - * @var string 错误信息 + * 错误信息 + * @var string */ private $error = ''; /** - * @var string 当前完整文件名 + * 当前完整文件名 + * @var string */ protected $filename; /** - * @var string 上传文件名 + * 上传文件名 + * @var string */ protected $saveName; /** - * @var string 文件上传命名规则 + * 上传文件命名规则 + * @var string */ protected $rule = 'date'; /** - * @var array 文件上传验证规则 + * 上传文件验证规则 + * @var array */ protected $validate = []; /** - * @var bool 单元测试 + * 是否单元测试 + * @var bool */ protected $isTest; /** - * @var array 上传文件信息 + * 上传文件信息 + * @var array */ - protected $info; + protected $info = []; /** - * @var array 文件 hash 信息 + * 文件hash规则 + * @var array */ protected $hash = []; - /** - * File constructor. - * @access public - * @param string $filename 文件名称 - * @param string $mode 访问模式 - */ public function __construct($filename, $mode = 'r') { parent::__construct($filename, $mode); + $this->filename = $this->getRealPath() ?: $this->getPathname(); } /** - * 设置是否是单元测试 + * 是否测试 * @access public - * @param bool $test 是否是测试 + * @param bool $test 是否测试 * @return $this */ public function isTest($test = false) @@ -83,7 +86,7 @@ class File extends SplFileObject /** * 设置上传信息 * @access public - * @param array $info 上传文件信息 + * @param array $info 上传文件信息 * @return $this */ public function setUploadInfo($info) @@ -96,7 +99,7 @@ class File extends SplFileObject /** * 获取上传文件的信息 * @access public - * @param string $name 信息名称 + * @param string $name * @return array|string */ public function getInfo($name = '') @@ -117,7 +120,7 @@ class File extends SplFileObject /** * 设置上传文件的保存文件名 * @access public - * @param string $saveName 保存名称 + * @param string $saveName * @return $this */ public function setSaveName($saveName) @@ -130,7 +133,7 @@ class File extends SplFileObject /** * 获取文件的哈希散列值 * @access public - * @param string $type 类型 + * @param string $type * @return string */ public function hash($type = 'sha1') @@ -144,19 +147,22 @@ class File extends SplFileObject /** * 检查目录是否可写 - * @access protected - * @param string $path 目录 + * @access public + * @param string $path 目录 * @return boolean */ protected function checkPath($path) { - if (is_dir($path) || mkdir($path, 0755, true)) { + if (is_dir($path)) { return true; } - $this->error = ['directory {:path} creation failed', ['path' => $path]]; - - return false; + if (mkdir($path, 0755, true)) { + return true; + } else { + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + return false; + } } /** @@ -174,7 +180,7 @@ class File extends SplFileObject /** * 设置文件的命名规则 * @access public - * @param string $rule 文件命名规则 + * @param string $rule 文件命名规则 * @return $this */ public function rule($rule) @@ -187,10 +193,10 @@ class File extends SplFileObject /** * 设置上传文件的验证规则 * @access public - * @param array $rule 验证规则 + * @param array $rule 验证规则 * @return $this */ - public function validate(array $rule = []) + public function validate($rule = []) { $this->validate = $rule; @@ -204,13 +210,17 @@ class File extends SplFileObject */ public function isValid() { - return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename); + if ($this->isTest) { + return is_file($this->filename); + } + + return is_uploaded_file($this->filename); } /** * 检测上传文件 * @access public - * @param array $rule 验证规则 + * @param array $rule 验证规则 * @return bool */ public function check($rule = []) @@ -223,7 +233,7 @@ class File extends SplFileObject return false; } - /* 检查文件 Mime 类型 */ + /* 检查文件Mime类型 */ if (isset($rule['type']) && !$this->checkMime($rule['type'])) { $this->error = 'mimetype to upload is not allowed'; return false; @@ -247,7 +257,7 @@ class File extends SplFileObject /** * 检测上传文件后缀 * @access public - * @param array|string $ext 允许后缀 + * @param array|string $ext 允许后缀 * @return bool */ public function checkExt($ext) @@ -258,7 +268,11 @@ class File extends SplFileObject $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); - return in_array($extension, $ext); + if (!in_array($extension, $ext)) { + return false; + } + + return true; } /** @@ -270,61 +284,70 @@ class File extends SplFileObject { $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); - // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true - return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]); + /* 对图像文件进行严格检测 */ + if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) { + return false; + } + + return true; } - /** - * 判断图像类型 - * @access protected - * @param string $image 图片名称 - * @return bool|int - */ + // 判断图像类型 protected function getImageType($image) { if (function_exists('exif_imagetype')) { return exif_imagetype($image); - } - - try { - $info = getimagesize($image); - return $info ? $info[2] : false; - } catch (\Exception $e) { - return false; + } else { + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } } } /** * 检测上传文件大小 * @access public - * @param integer $size 最大大小 + * @param integer $size 最大大小 * @return bool */ public function checkSize($size) { - return $this->getSize() <= $size; + if ($this->getSize() > $size) { + return false; + } + + return true; } /** * 检测上传文件类型 * @access public - * @param array|string $mime 允许类型 + * @param array|string $mime 允许类型 * @return bool */ public function checkMime($mime) { - $mime = is_string($mime) ? explode(',', $mime) : $mime; + if (is_string($mime)) { + $mime = explode(',', $mime); + } - return in_array(strtolower($this->getMime()), $mime); + if (!in_array(strtolower($this->getMime()), $mime)) { + return false; + } + + return true; } /** * 移动文件 * @access public - * @param string $path 保存路径 - * @param string|bool $savename 保存的文件名 默认自动生成 - * @param boolean $replace 同名文件是否覆盖 - * @return false|File + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @return false|File false-失败 否则返回File实例 */ public function move($path, $savename = true, $replace = true) { @@ -345,7 +368,7 @@ class File extends SplFileObject return false; } - $path = rtrim($path, DS) . DS; + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; // 文件保存命名规则 $saveName = $this->buildSaveName($savename); $filename = $path . $saveName; @@ -355,7 +378,7 @@ class File extends SplFileObject return false; } - // 不覆盖同名文件 + /* 不覆盖同名文件 */ if (!$replace && is_file($filename)) { $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; return false; @@ -369,38 +392,39 @@ class File extends SplFileObject return false; } - // 返回 File 对象实例 + // 返回 File对象实例 $file = new self($filename); - $file->setSaveName($saveName)->setUploadInfo($this->info); + $file->setSaveName($saveName); + $file->setUploadInfo($this->info); return $file; } /** * 获取保存文件名 - * @access protected - * @param string|bool $savename 保存的文件名 默认自动生成 + * @access public + * @param string|bool $savename 保存的文件名 默认自动生成 * @return string */ protected function buildSaveName($savename) { - // 自动生成文件名 if (true === $savename) { + // 自动生成文件名 if ($this->rule instanceof \Closure) { $savename = call_user_func_array($this->rule, [$this]); } else { switch ($this->rule) { case 'date': - $savename = date('Ymd') . DS . md5(microtime(true)); + $savename = date('Ymd') . '/' . md5(microtime(true)); break; default: if (in_array($this->rule, hash_algos())) { $hash = $this->hash($this->rule); - $savename = substr($hash, 0, 2) . DS . substr($hash, 2); + $savename = substr($hash, 0, 2) . '/' . substr($hash, 2); } elseif (is_callable($this->rule)) { $savename = call_user_func($this->rule); } else { - $savename = date('Ymd') . DS . md5(microtime(true)); + $savename = date('Ymd') . '/' . md5(microtime(true)); } } } @@ -417,9 +441,8 @@ class File extends SplFileObject /** * 获取错误代码信息 - * @access private - * @param int $errorNo 错误号 - * @return $this + * @access public + * @param int $errorNo 错误号 */ private function error($errorNo) { @@ -443,8 +466,6 @@ class File extends SplFileObject default: $this->error = 'unknown upload error'; } - - return $this; } /** @@ -454,6 +475,8 @@ class File extends SplFileObject */ public function getError() { + $lang = Container::get('lang'); + if (is_array($this->error)) { list($msg, $vars) = $this->error; } else { @@ -461,16 +484,9 @@ class File extends SplFileObject $vars = []; } - return Lang::has($msg) ? Lang::get($msg, $vars) : $msg; + return $lang->has($msg) ? $lang->get($msg, $vars) : $msg; } - /** - * 魔法方法,获取文件的 hash 值 - * @access public - * @param string $method 方法名 - * @param mixed $args 调用参数 - * @return string - */ public function __call($method, $args) { return $this->hash($method); diff --git a/thinkphp/library/think/Hook.php b/thinkphp/library/think/Hook.php index a69ce546d..30b673073 100644 --- a/thinkphp/library/think/Hook.php +++ b/thinkphp/library/think/Hook.php @@ -14,68 +14,111 @@ namespace think; class Hook { /** - * @var array 标签 + * 钩子行为定义 + * @var array */ - private static $tags = []; + private $tags = []; + + /** + * 绑定行为列表 + * @var array + */ + protected $bind = []; + + /** + * 入口方法名称 + * @var string + */ + private static $portal = 'run'; + + /** + * 指定入口方法名称 + * @access public + * @param string $name 方法名 + * @return $this + */ + public function portal($name) + { + self::$portal = $name; + return $this; + } + + /** + * 指定行为标识 便于调用 + * @access public + * @param string|array $name 行为标识 + * @param mixed $behavior 行为 + * @return $this + */ + public function alias($name, $behavior = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $behavior; + } + + return $this; + } /** * 动态添加行为扩展到某个标签 * @access public - * @param string $tag 标签名称 - * @param mixed $behavior 行为名称 - * @param bool $first 是否放到开头执行 + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 * @return void */ - public static function add($tag, $behavior, $first = false) + public function add($tag, $behavior, $first = false) { - isset(self::$tags[$tag]) || self::$tags[$tag] = []; + isset($this->tags[$tag]) || $this->tags[$tag] = []; if (is_array($behavior) && !is_callable($behavior)) { - if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) { - unset($behavior['_overlay']); - self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior); + if (!array_key_exists('_overlay', $behavior)) { + $this->tags[$tag] = array_merge($this->tags[$tag], $behavior); } else { unset($behavior['_overlay']); - self::$tags[$tag] = $behavior; + $this->tags[$tag] = $behavior; } } elseif ($first) { - array_unshift(self::$tags[$tag], $behavior); + array_unshift($this->tags[$tag], $behavior); } else { - self::$tags[$tag][] = $behavior; + $this->tags[$tag][] = $behavior; } } /** * 批量导入插件 * @access public - * @param array $tags 插件信息 - * @param boolean $recursive 是否递归合并 + * @param array $tags 插件信息 + * @param bool $recursive 是否递归合并 * @return void */ - public static function import(array $tags, $recursive = true) + public function import(array $tags, $recursive = true) { if ($recursive) { foreach ($tags as $tag => $behavior) { - self::add($tag, $behavior); + $this->add($tag, $behavior); } } else { - self::$tags = $tags + self::$tags; + $this->tags = $tags + $this->tags; } } /** * 获取插件信息 * @access public - * @param string $tag 插件位置(留空获取全部) + * @param string $tag 插件位置 留空获取全部 * @return array */ - public static function get($tag = '') + public function get($tag = '') { if (empty($tag)) { - return self::$tags; + //获取全部的插件信息 + return $this->tags; + } else { + return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : []; } - - return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; } /** @@ -83,19 +126,21 @@ class Hook * @access public * @param string $tag 标签名称 * @param mixed $params 传入参数 - * @param mixed $extra 额外参数 * @param bool $once 只获取一个有效返回值 * @return mixed */ - public static function listen($tag, &$params = null, $extra = null, $once = false) + public function listen($tag, $params = null, $once = false) { $results = []; + $tags = $this->get($tag); - foreach (static::get($tag) as $key => $name) { - $results[$key] = self::exec($name, $tag, $params, $extra); + foreach ($tags as $key => $name) { + $results[$key] = $this->execTag($name, $tag, $params); - // 如果返回 false,或者仅获取一个有效返回则中断行为执行 - if (false === $results[$key] || (!is_null($results[$key]) && $once)) { + if (false === $results[$key]) { + // 如果返回false 则中断行为执行 + break; + } elseif (!is_null($results[$key]) && $once) { break; } } @@ -104,42 +149,64 @@ class Hook } /** - * 执行某个行为 + * 执行行为 * @access public - * @param mixed $class 要执行的行为 - * @param string $tag 方法名(标签名) - * @param mixed $params 传人的参数 - * @param mixed $extra 额外参数 + * @param mixed $class 行为 + * @param mixed $params 参数 * @return mixed */ - public static function exec($class, $tag = '', &$params = null, $extra = null) + public function exec($class, $params = null) { - App::$debug && Debug::remark('behavior_start', 'time'); + if ($class instanceof \Closure || is_array($class)) { + $method = $class; + } else { + if (isset($this->bind[$class])) { + $class = $this->bind[$class]; + } + $method = [$class, self::$portal]; + } + + return Container::getInstance()->invoke($method, [$params]); + } + + /** + * 执行某个标签的行为 + * @access protected + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 参数 + * @return mixed + */ + protected function execTag($class, $tag = '', $params = null) + { + $app = Container::get('app'); + + $app->isDebug() && $app['debug']->remark('behavior_start', 'time'); $method = Loader::parseName($tag, 1, false); if ($class instanceof \Closure) { - $result = call_user_func_array($class, [ & $params, $extra]); - $class = 'Closure'; - } elseif (is_array($class)) { - list($class, $method) = $class; - - $result = (new $class())->$method($params, $extra); - $class = $class . '->' . $method; - } elseif (is_object($class)) { - $result = $class->$method($params, $extra); - $class = get_class($class); + $call = $class; + $class = 'Closure'; } elseif (strpos($class, '::')) { - $result = call_user_func_array($class, [ & $params, $extra]); + $call = $class; } else { - $obj = new $class(); - $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; - $result = $obj->$method($params, $extra); + $obj = Container::get($class); + + if (!is_callable([$obj, $method])) { + $method = self::$portal; + } + + $call = [$class, $method]; + $class = $class . '->' . $method; } - if (App::$debug) { - Debug::remark('behavior_end', 'time'); - Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); + $result = Container::getInstance()->invoke($call, [$params]); + + if ($app->isDebug()) { + $debug = $app['debug']; + $debug->remark('behavior_end', 'time'); + $app->log('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . $debug->getRangeTime('behavior_start', 'behavior_end') . 's ]'); } return $result; diff --git a/thinkphp/library/think/Lang.php b/thinkphp/library/think/Lang.php index a50d838de..ba4f700ab 100644 --- a/thinkphp/library/think/Lang.php +++ b/thinkphp/library/think/Lang.php @@ -14,92 +14,93 @@ namespace think; class Lang { /** - * @var array 语言数据 + * 多语言信息 + * @var array */ - private static $lang = []; + private $lang = []; /** - * @var string 语言作用域 + * 当前语言 + * @var string */ - private static $range = 'zh-cn'; + private $range = 'zh-cn'; /** - * @var string 语言自动侦测的变量 + * 多语言自动侦测变量名 + * @var string */ - protected static $langDetectVar = 'lang'; + protected $langDetectVar = 'lang'; /** - * @var string 语言 Cookie 变量 + * 多语言cookie变量 + * @var string */ - protected static $langCookieVar = 'think_var'; + protected $langCookieVar = 'think_var'; /** - * @var int 语言 Cookie 的过期时间 + * 允许的多语言列表 + * @var array */ - protected static $langCookieExpire = 3600; + protected $allowLangList = []; /** - * @var array 允许语言列表 + * Accept-Language转义为对应语言包名称 系统默认配置 + * @var string */ - protected static $allowLangList = []; + protected $acceptLanguage = [ + 'zh-hans-cn' => 'zh-cn', + ]; - /** - * @var array Accept-Language 转义为对应语言包名称 系统默认配置 - */ - protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn']; - - /** - * 设定当前的语言 - * @access public - * @param string $range 语言作用域 - * @return string - */ - public static function range($range = '') + // 设定当前的语言 + public function range($range = '') { - if ($range) { - self::$range = $range; + if ('' == $range) { + return $this->range; + } else { + $this->range = $range; } - - return self::$range; } /** * 设置语言定义(不区分大小写) * @access public - * @param string|array $name 语言变量 + * @param string|array $name 语言变量 * @param string $value 语言值 * @param string $range 语言作用域 * @return mixed */ - public static function set($name, $value = null, $range = '') + public function set($name, $value = null, $range = '') { - $range = $range ?: self::$range; - - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; + $range = $range ?: $this->range; + // 批量定义 + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; } if (is_array($name)) { - return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; + return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range]; + } else { + return $this->lang[$range][strtolower($name)] = $value; } - - return self::$lang[$range][strtolower($name)] = $value; } /** * 加载语言定义(不区分大小写) * @access public - * @param array|string $file 语言文件 - * @param string $range 语言作用域 - * @return mixed + * @param string|array $file 语言文件 + * @param string $range 语言作用域 + * @return array */ - public static function load($file, $range = '') + public function load($file, $range = '') { - $range = $range ?: self::$range; - $file = is_string($file) ? [$file] : $file; + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; + // 批量定义 + if (is_string($file)) { + $file = [$file]; } $lang = []; @@ -107,10 +108,8 @@ class Lang foreach ($file as $_file) { if (is_file($_file)) { // 记录加载信息 - App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); - + Container::get('app')->log('[ LANG ] ' . $_file); $_lang = include $_file; - if (is_array($_lang)) { $lang = array_change_key_case($_lang) + $lang; } @@ -118,45 +117,45 @@ class Lang } if (!empty($lang)) { - self::$lang[$range] = $lang + self::$lang[$range]; + $this->lang[$range] = $lang + $this->lang[$range]; } - return self::$lang[$range]; + return $this->lang[$range]; } /** * 获取语言定义(不区分大小写) * @access public - * @param string|null $name 语言变量 - * @param string $range 语言作用域 - * @return mixed + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return bool */ - public static function has($name, $range = '') + public function has($name, $range = '') { - $range = $range ?: self::$range; + $range = $range ?: $this->range; - return isset(self::$lang[$range][strtolower($name)]); + return isset($this->lang[$range][strtolower($name)]); } /** * 获取语言定义(不区分大小写) * @access public - * @param string|null $name 语言变量 - * @param array $vars 变量替换 - * @param string $range 语言作用域 + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 * @return mixed */ - public static function get($name = null, $vars = [], $range = '') + public function get($name = null, $vars = [], $range = '') { - $range = $range ?: self::$range; + $range = $range ?: $this->range; // 空参数返回所有定义 if (empty($name)) { - return self::$lang[$range]; + return $this->lang[$range]; } $key = strtolower($name); - $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $name; + $value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name; // 变量解析 if (!empty($vars) && is_array($vars)) { @@ -177,7 +176,6 @@ class Lang } $value = str_replace($replace, $vars, $value); } - } return $value; @@ -188,35 +186,48 @@ class Lang * @access public * @return string */ - public static function detect() + public function detect() { + // 自动侦测设置获取语言选择 $langSet = ''; - if (isset($_GET[self::$langDetectVar])) { - // url 中设置了语言变量 - $langSet = strtolower($_GET[self::$langDetectVar]); - } elseif (isset($_COOKIE[self::$langCookieVar])) { - // Cookie 中设置了语言变量 - $langSet = strtolower($_COOKIE[self::$langCookieVar]); + if (isset($_GET[$this->langDetectVar])) { + // url中设置了语言变量 + $langSet = strtolower($_GET[$this->langDetectVar]); + } elseif (isset($_COOKIE[$this->langCookieVar])) { + // Cookie中设置了语言变量 + $langSet = strtolower($_COOKIE[$this->langCookieVar]); } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // 自动侦测浏览器语言 preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); $langSet = strtolower($matches[1]); - $acceptLangs = Config::get('header_accept_lang'); - + $acceptLangs = Container::get('config')->get('header_accept_lang'); if (isset($acceptLangs[$langSet])) { $langSet = $acceptLangs[$langSet]; - } elseif (isset(self::$acceptLanguage[$langSet])) { - $langSet = self::$acceptLanguage[$langSet]; + } elseif (isset($this->acceptLanguage[$langSet])) { + $langSet = $this->acceptLanguage[$langSet]; } } - // 合法的语言 - if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { - self::$range = $langSet ?: self::$range; + if (empty($this->allowLangList) || in_array($langSet, $this->allowLangList)) { + // 合法的语言 + $this->range = $langSet ?: $this->range; } - return self::$range; + return $this->range; + } + + /** + * 设置当前语言到Cookie + * @access public + * @param string $lang 语言 + * @return void + */ + public function saveToCookie($lang = null) + { + $range = $lang ?: $this->range; + + $_COOKIE[$this->langCookieVar] = $range; } /** @@ -225,31 +236,20 @@ class Lang * @param string $var 变量名称 * @return void */ - public static function setLangDetectVar($var) + public function setLangDetectVar($var) { - self::$langDetectVar = $var; + $this->langDetectVar = $var; } /** - * 设置语言的 cookie 保存变量 + * 设置语言的cookie保存变量 * @access public * @param string $var 变量名称 * @return void */ - public static function setLangCookieVar($var) + public function setLangCookieVar($var) { - self::$langCookieVar = $var; - } - - /** - * 设置语言的 cookie 的过期时间 - * @access public - * @param string $expire 过期时间 - * @return void - */ - public static function setLangCookieExpire($expire) - { - self::$langCookieExpire = $expire; + $this->langCookieVar = $var; } /** @@ -258,8 +258,8 @@ class Lang * @param array $list 语言列表 * @return void */ - public static function setAllowLangList($list) + public function setAllowLangList($list) { - self::$allowLangList = $list; + $this->allowLangList = $list; } } diff --git a/thinkphp/library/think/Loader.php b/thinkphp/library/think/Loader.php index e4130bdbf..76fbde936 100644 --- a/thinkphp/library/think/Loader.php +++ b/thinkphp/library/think/Loader.php @@ -11,107 +11,123 @@ namespace think; -use think\exception\ClassNotFoundException; - class Loader { /** - * @var array 实例数组 - */ - protected static $instance = []; - - /** - * @var array 类名映射 + * 类名映射信息 + * @var array */ protected static $map = []; /** - * @var array 命名空间别名 + * 类库别名 + * @var array */ - protected static $namespaceAlias = []; + protected static $classAlias = []; /** - * @var array PSR-4 命名空间前缀长度映射 + * PSR-4 + * @var array */ private static $prefixLengthsPsr4 = []; + private static $prefixDirsPsr4 = []; + private static $fallbackDirsPsr4 = []; /** - * @var array PSR-4 的加载目录 - */ - private static $prefixDirsPsr4 = []; - - /** - * @var array PSR-4 加载失败的回退目录 - */ - private static $fallbackDirsPsr4 = []; - - /** - * @var array PSR-0 命名空间前缀映射 - */ - private static $prefixesPsr0 = []; - - /** - * @var array PSR-0 加载失败的回退目录 + * PSR-0 + * @var array */ + private static $prefixesPsr0 = []; private static $fallbackDirsPsr0 = []; /** - * @var array 自动加载的文件 + * 自动加载的文件列表 + * @var array */ private static $autoloadFiles = []; /** - * 自动加载 - * @access public - * @param string $class 类名 - * @return bool + * Composer安装路径 + * @var string */ + private static $composerPath; + + // 注册自动加载机制 + public static function register($autoload = '') + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + // 注册命名空间定义 + self::addNamespace([ + 'think' => __DIR__ . '/', + 'traits' => __DIR__ . '/../traits/', + ]); + + $path = dirname($_SERVER['SCRIPT_FILENAME']); + if (is_file('./think')) { + $rootPath = realpath($path) . '/'; + } else { + $rootPath = realpath($path . '/../') . '/'; + } + + // 加载类库映射文件 + if (is_file($rootPath . 'runtime/classmap.php')) { + self::addClassMap(__include_file($rootPath . 'runtime/classmap.php')); + } + + self::$composerPath = $rootPath . 'vendor/composer/'; + + // Composer自动加载支持 + if (is_dir(self::$composerPath)) { + self::registerComposerLoader(self::$composerPath); + } + + // 自动加载extend目录 + self::addAutoLoadDir($rootPath . 'extend'); + } + + // 自动加载 public static function autoload($class) { - // 检测命名空间别名 - if (!empty(self::$namespaceAlias)) { - $namespace = dirname($class); - if (isset(self::$namespaceAlias[$namespace])) { - $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); - if (class_exists($original)) { - return class_alias($original, $class, false); - } - } + if (isset(self::$classAlias[$class])) { + return class_alias(self::$classAlias[$class], $class); } if ($file = self::findFile($class)) { - // 非 Win 环境不严格区分大小写 - if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) { - __include_file($file); - return true; - } - } - return false; + // Win环境严格区分大小写 + if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) { + return false; + } + + __include_file($file); + return true; + } } /** * 查找文件 * @access private - * @param string $class 类名 - * @return bool|string + * @param string $class + * @return string|false */ private static function findFile($class) { - // 类库映射 if (!empty(self::$map[$class])) { + // 类库映射 return self::$map[$class]; } // 查找 PSR-4 - $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; - $first = $class[0]; + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + $first = $class[0]; if (isset(self::$prefixLengthsPsr4[$first])) { foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { if (0 === strpos($class, $prefix)) { foreach (self::$prefixDirsPsr4[$prefix] as $dir) { - if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } @@ -121,26 +137,26 @@ class Loader // 查找 PSR-4 fallback dirs foreach (self::$fallbackDirsPsr4 as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr4)) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // 查找 PSR-0 if (false !== $pos = strrpos($class, '\\')) { - // namespace class name + // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS); + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DS) . EXT; + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; } if (isset(self::$prefixesPsr0[$first])) { foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr0)) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } @@ -150,22 +166,15 @@ class Loader // 查找 PSR-0 fallback dirs foreach (self::$fallbackDirsPsr0 as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr0)) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } - // 找不到则设置映射为 false 并返回 return self::$map[$class] = false; } - /** - * 注册 classmap - * @access public - * @param string|array $class 类名 - * @param string $map 映射 - * @return void - */ + // 注册classmap public static function addClassMap($class, $map = '') { if (is_array($class)) { @@ -175,167 +184,143 @@ class Loader } } - /** - * 注册命名空间 - * @access public - * @param string|array $namespace 命名空间 - * @param string $path 路径 - * @return void - */ + // 注册命名空间 public static function addNamespace($namespace, $path = '') { if (is_array($namespace)) { foreach ($namespace as $prefix => $paths) { - self::addPsr4($prefix . '\\', rtrim($paths, DS), true); + self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true); } } else { - self::addPsr4($namespace . '\\', rtrim($path, DS), true); + self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true); } } - /** - * 添加 PSR-0 命名空间 - * @access private - * @param array|string $prefix 空间前缀 - * @param array $paths 路径 - * @param bool $prepend 预先设置的优先级更高 - * @return void - */ + // 添加Ps0空间 private static function addPsr0($prefix, $paths, $prepend = false) { if (!$prefix) { - self::$fallbackDirsPsr0 = $prepend ? - array_merge((array) $paths, self::$fallbackDirsPsr0) : - array_merge(self::$fallbackDirsPsr0, (array) $paths); - } else { - $first = $prefix[0]; - - if (!isset(self::$prefixesPsr0[$first][$prefix])) { - self::$prefixesPsr0[$first][$prefix] = (array) $paths; + if ($prepend) { + self::$fallbackDirsPsr0 = array_merge( + (array) $paths, + self::$fallbackDirsPsr0 + ); } else { - self::$prefixesPsr0[$first][$prefix] = $prepend ? - array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) : - array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths); + self::$fallbackDirsPsr0 = array_merge( + self::$fallbackDirsPsr0, + (array) $paths + ); } + + return; + } + + $first = $prefix[0]; + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + + if ($prepend) { + self::$prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + self::$prefixesPsr0[$first][$prefix] + ); + } else { + self::$prefixesPsr0[$first][$prefix] = array_merge( + self::$prefixesPsr0[$first][$prefix], + (array) $paths + ); } } - /** - * 添加 PSR-4 空间 - * @access private - * @param array|string $prefix 空间前缀 - * @param string $paths 路径 - * @param bool $prepend 预先设置的优先级更高 - * @return void - */ + // 添加Psr4空间 private static function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { // Register directories for the root namespace. - self::$fallbackDirsPsr4 = $prepend ? - array_merge((array) $paths, self::$fallbackDirsPsr4) : - array_merge(self::$fallbackDirsPsr4, (array) $paths); - + if ($prepend) { + self::$fallbackDirsPsr4 = array_merge( + (array) $paths, + self::$fallbackDirsPsr4 + ); + } else { + self::$fallbackDirsPsr4 = array_merge( + self::$fallbackDirsPsr4, + (array) $paths + ); + } } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException( - "A non-empty PSR-4 prefix must end with a namespace separator." - ); + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; self::$prefixDirsPsr4[$prefix] = (array) $paths; - - } else { - self::$prefixDirsPsr4[$prefix] = $prepend ? + } elseif ($prepend) { // Prepend directories for an already registered namespace. - array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) : - // Append directories for an already registered namespace. - array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths); - } - } - - /** - * 注册命名空间别名 - * @access public - * @param array|string $namespace 命名空间 - * @param string $original 源文件 - * @return void - */ - public static function addNamespaceAlias($namespace, $original = '') - { - if (is_array($namespace)) { - self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace); + self::$prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + self::$prefixDirsPsr4[$prefix] + ); } else { - self::$namespaceAlias[$namespace] = $original; + // Append directories for an already registered namespace. + self::$prefixDirsPsr4[$prefix] = array_merge( + self::$prefixDirsPsr4[$prefix], + (array) $paths + ); } } - /** - * 注册自动加载机制 - * @access public - * @param callable $autoload 自动加载处理方法 - * @return void - */ - public static function register($autoload = null) + // 注册自动加载类库目录 + public static function addAutoLoadDir($path) { - // 注册系统自动加载 - spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); - - // 注册命名空间定义 - self::addNamespace([ - 'think' => LIB_PATH . 'think' . DS, - 'behavior' => LIB_PATH . 'behavior' . DS, - 'traits' => LIB_PATH . 'traits' . DS, - ]); - - // 加载类库映射文件 - if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { - self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); - } - - // Composer 自动加载支持 - if (is_dir(VENDOR_PATH . 'composer')) { - self::registerComposerLoader(); - } - - // 自动加载 extend 目录 - self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); + self::$fallbackDirsPsr4[] = $path; } - /** - * 注册 composer 自动加载 - * @access private - * @return void - */ - private static function registerComposerLoader() + // 注册类别名 + public static function addClassAlias($alias, $class = null) { - if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) { - $map = require VENDOR_PATH . 'composer/autoload_namespaces.php'; + if (is_array($alias)) { + self::$classAlias = array_merge(self::$classAlias, $alias); + } else { + self::$classAlias[$alias] = $class; + } + } + + // 注册composer自动加载 + public static function registerComposerLoader($composerPath) + { + if (is_file($composerPath . 'autoload_namespaces.php')) { + $map = require $composerPath . 'autoload_namespaces.php'; foreach ($map as $namespace => $path) { self::addPsr0($namespace, $path); } } - if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) { - $map = require VENDOR_PATH . 'composer/autoload_psr4.php'; + if (is_file($composerPath . 'autoload_psr4.php')) { + $map = require $composerPath . 'autoload_psr4.php'; foreach ($map as $namespace => $path) { self::addPsr4($namespace, $path); } } - if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) { - $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php'; + if (is_file($composerPath . 'autoload_classmap.php')) { + $classMap = require $composerPath . 'autoload_classmap.php'; if ($classMap) { self::addClassMap($classMap); } - } + } - if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) { - $includeFiles = require VENDOR_PATH . 'composer/autoload_files.php'; + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + if (is_file(self::$composerPath . 'autoload_files.php')) { + $includeFiles = require self::$composerPath . 'autoload_files.php'; foreach ($includeFiles as $fileIdentifier => $file) { if (empty(self::$autoloadFiles[$fileIdentifier])) { __require_file($file); @@ -345,246 +330,12 @@ class Loader } } - /** - * 导入所需的类库 同 Java 的 Import 本函数有缓存功能 - * @access public - * @param string $class 类库命名空间字符串 - * @param string $baseUrl 起始路径 - * @param string $ext 导入的文件扩展名 - * @return bool - */ - public static function import($class, $baseUrl = '', $ext = EXT) - { - static $_file = []; - $key = $class . $baseUrl; - $class = str_replace(['.', '#'], [DS, '.'], $class); - - if (isset($_file[$key])) { - return true; - } - - if (empty($baseUrl)) { - list($name, $class) = explode(DS, $class, 2); - - if (isset(self::$prefixDirsPsr4[$name . '\\'])) { - // 注册的命名空间 - $baseUrl = self::$prefixDirsPsr4[$name . '\\']; - } elseif ('@' == $name) { - // 加载当前模块应用类库 - $baseUrl = App::$modulePath; - } elseif (is_dir(EXTEND_PATH . $name)) { - $baseUrl = EXTEND_PATH . $name . DS; - } else { - // 加载其它模块的类库 - $baseUrl = APP_PATH . $name . DS; - } - } elseif (substr($baseUrl, -1) != DS) { - $baseUrl .= DS; - } - - // 如果类存在则导入类库文件 - if (is_array($baseUrl)) { - foreach ($baseUrl as $path) { - if (is_file($filename = $path . DS . $class . $ext)) { - break; - } - } - } else { - $filename = $baseUrl . $class . $ext; - } - - if (!empty($filename) && - is_file($filename) && - (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME)) - ) { - __include_file($filename); - $_file[$key] = true; - - return true; - } - - return false; - } - - /** - * 实例化(分层)模型 - * @access public - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object - * @throws ClassNotFoundException - */ - public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') - { - $uid = $name . $layer; - - if (isset(self::$instance[$uid])) { - return self::$instance[$uid]; - } - - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $model = new $class(); - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - - if (class_exists($class)) { - $model = new $class(); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - return self::$instance[$uid] = $model; - } - - /** - * 实例化(分层)控制器 格式:[模块名/]控制器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $empty 空控制器名称 - * @return object - * @throws ClassNotFoundException - */ - public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') - { - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - return App::invokeClass($class); - } - - if ($empty) { - $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix); - - if (class_exists($emptyClass)) { - return new $emptyClass(Request::instance()); - } - } - - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - /** - * 实例化验证类 格式:[模块名/]验证器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object|false - * @throws ClassNotFoundException - */ - public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') - { - $name = $name ?: Config::get('default_validate'); - - if (empty($name)) { - return new Validate; - } - - $uid = $name . $layer; - if (isset(self::$instance[$uid])) { - return self::$instance[$uid]; - } - - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $validate = new $class; - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - - if (class_exists($class)) { - $validate = new $class; - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - return self::$instance[$uid] = $validate; - } - - /** - * 解析模块和类名 - * @access protected - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return array - */ - protected static function getModuleAndClass($name, $layer, $appendSuffix) - { - if (false !== strpos($name, '\\')) { - $module = Request::instance()->module(); - $class = $name; - } else { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); - } else { - $module = Request::instance()->module(); - } - - $class = self::parseClass($module, $layer, $name, $appendSuffix); - } - - return [$module, $class]; - } - - /** - * 数据库初始化 并取得数据库类实例 - * @access public - * @param mixed $config 数据库配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Connection - */ - public static function db($config = [], $name = false) - { - return Db::connect($config, $name); - } - - /** - * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 - * @access public - * @param string $url 调用地址 - * @param string|array $vars 调用参数 支持字符串和数组 - * @param string $layer 要调用的控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return mixed - */ - public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) - { - $info = pathinfo($url); - $action = $info['basename']; - $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller(); - $class = self::controller($module, $layer, $appendSuffix); - - if ($class) { - if (is_scalar($vars)) { - if (strpos($vars, '=')) { - parse_str($vars, $vars); - } else { - $vars = [$vars]; - } - } - - return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars); - } - - return false; - } - /** * 字符串命名风格转换 - * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 * @access public - * @param string $name 字符串 - * @param integer $type 转换类型 + * @param string $name 字符串 + * @param integer $type 转换类型 * @param bool $ucfirst 首字母是否大写(驼峰规则) * @return string */ @@ -594,51 +345,17 @@ class Loader $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { return strtoupper($match[1]); }, $name); - return $ucfirst ? ucfirst($name) : lcfirst($name); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); } - - return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); - } - - /** - * 解析应用类的类名 - * @access public - * @param string $module 模块名 - * @param string $layer 层名 controller model ... - * @param string $name 类名 - * @param bool $appendSuffix 是否添加类名后缀 - * @return string - */ - public static function parseClass($module, $layer, $name, $appendSuffix = false) - { - - $array = explode('\\', str_replace(['/', '.'], '\\', $name)); - $class = self::parseName(array_pop($array), 1); - $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); - $path = $array ? implode('\\', $array) . '\\' : ''; - - return App::$namespace . '\\' . - ($module ? $module . '\\' : '') . - $layer . '\\' . $path . $class; - } - - /** - * 初始化类的实例 - * @access public - * @return void - */ - public static function clearInstance() - { - self::$instance = []; } } -// 作用范围隔离 - /** - * include - * @param string $file 文件路径 + * 作用范围隔离 + * + * @param $file * @return mixed */ function __include_file($file) @@ -646,11 +363,6 @@ function __include_file($file) return include $file; } -/** - * require - * @param string $file 文件路径 - * @return mixed - */ function __require_file($file) { return require $file; diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php index bf6c04f3a..49258141b 100644 --- a/thinkphp/library/think/Log.php +++ b/thinkphp/library/think/Log.php @@ -13,132 +13,158 @@ namespace think; use think\exception\ClassNotFoundException; -/** - * Class Log - * @package think - * - * @method void log($msg) static 记录一般日志 - * @method void error($msg) static 记录错误日志 - * @method void info($msg) static 记录一般信息日志 - * @method void sql($msg) static 记录 SQL 查询日志 - * @method void notice($msg) static 记录提示日志 - * @method void alert($msg) static 记录报警日志 - */ -class Log +class Log implements LoggerInterface { - const LOG = 'log'; - const ERROR = 'error'; - const INFO = 'info'; - const SQL = 'sql'; - const NOTICE = 'notice'; - const ALERT = 'alert'; - const DEBUG = 'debug'; + const EMERGENCY = 'emergency'; + const ALERT = 'alert'; + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const NOTICE = 'notice'; + const INFO = 'info'; + const DEBUG = 'debug'; + const SQL = 'sql'; /** - * @var array 日志信息 + * 日志信息 + * @var array */ - protected static $log = []; + protected $log = []; /** - * @var array 配置参数 + * 配置参数 + * @var array */ - protected static $config = []; + protected $config = []; /** - * @var array 日志类型 + * 日志写入驱动 + * @var object */ - protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; + protected $driver; /** - * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动 + * 日志授权key + * @var string */ - protected static $driver; + protected $key; /** - * @var string 当前日志授权 key + * 应用对象 + * @var App */ - protected static $key; + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } /** * 日志初始化 * @access public - * @param array $config 配置参数 - * @return void + * @param array $config + * @return $this */ - public static function init($config = []) + public function init($config = []) { $type = isset($config['type']) ? $config['type'] : 'File'; $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type); - self::$config = $config; + $this->config = $config; + unset($config['type']); if (class_exists($class)) { - self::$driver = new $class($config); + $this->driver = new $class($config); } else { throw new ClassNotFoundException('class not exists:' . $class, $class); } // 记录初始化信息 - App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); + $this->app->isDebug() && $this->record('[ LOG ] INIT ' . $type); + + return $this; } /** * 获取日志信息 * @access public * @param string $type 信息类型 - * @return array|string + * @return array */ - public static function getLog($type = '') + public function getLog($type = '') { - return $type ? self::$log[$type] : self::$log; + return $type ? $this->log[$type] : $this->log; } /** - * 记录调试信息 + * 记录日志信息 * @access public - * @param mixed $msg 调试信息 - * @param string $type 信息类型 - * @return void + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this */ - public static function record($msg, $type = 'log') + public function record($msg, $type = 'info', array $context = []) { - self::$log[$type][] = $msg; + if (is_string($msg)) { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } - // 命令行下面日志写入改进 - IS_CLI && self::save(); + $msg = strtr($msg, $replace); + } + + $this->log[$type][] = $msg; + + if (PHP_SAPI == 'cli') { + // 命令行日志实时写入 + $this->save(); + } + + return $this; } /** * 清空日志信息 * @access public - * @return void + * @return $this */ - public static function clear() + public function clear() { - self::$log = []; + $this->log = []; + + return $this; } /** - * 设置当前日志记录的授权 key + * 当前日志记录的授权key * @access public - * @param string $key 授权 key - * @return void + * @param string $key 授权key + * @return $this */ - public static function key($key) + public function key($key) { - self::$key = $key; + $this->key = $key; + + return $this; } /** * 检查日志写入权限 * @access public - * @param array $config 当前日志配置参数 + * @param array $config 当前日志配置参数 * @return bool */ - public static function check($config) + public function check($config) { - return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']); + if ($this->key && !empty($config['allow_key']) && !in_array($this->key, $config['allow_key'])) { + return false; + } + + return true; } /** @@ -146,92 +172,201 @@ class Log * @access public * @return bool */ - public static function save() + public function save() { - // 没有需要保存的记录则直接返回 - if (empty(self::$log)) { - return true; - } - - is_null(self::$driver) && self::init(Config::get('log')); - - // 检测日志写入权限 - if (!self::check(self::$config)) { - return false; - } - - if (empty(self::$config['level'])) { - // 获取全部日志 - $log = self::$log; - if (!App::$debug && isset($log['debug'])) { - unset($log['debug']); + if (!empty($this->log)) { + if (is_null($this->driver)) { + $this->init($this->app['config']->pull('log')); } - } else { - // 记录允许级别 - $log = []; - foreach (self::$config['level'] as $level) { - if (isset(self::$log[$level])) { - $log[$level] = self::$log[$level]; + + if (!$this->check($this->config)) { + // 检测日志写入权限 + return false; + } + + if (empty($this->config['level'])) { + // 获取全部日志 + $log = $this->log; + if (!$this->app->isDebug() && isset($log['debug'])) { + unset($log['debug']); + } + } else { + // 记录允许级别 + $log = []; + foreach ($this->config['level'] as $level) { + if (isset($this->log[$level])) { + $log[$level] = $this->log[$level]; + } } } + + $result = $this->driver->save($log); + if ($result) { + $this->log = []; + } + + return $result; } - if ($result = self::$driver->save($log)) { - self::$log = []; - } - - Hook::listen('log_write_done', $log); - - return $result; + return true; } /** * 实时写入日志信息 并支持行为 * @access public * @param mixed $msg 调试信息 - * @param string $type 信息类型 + * @param string $type 日志级别 * @param bool $force 是否强制写入 * @return bool */ - public static function write($msg, $type = 'log', $force = false) + public function write($msg, $type = 'info', $force = false) { - $log = self::$log; + // 封装日志信息 + $log = $this->log; - // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录 - if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) { + if (true === $force || empty($this->config['level'])) { + $log[$type][] = $msg; + } elseif (in_array($type, $this->config['level'])) { + $log[$type][] = $msg; + } else { return false; } - // 封装日志信息 - $log[$type][] = $msg; + // 监听log_write + $this->app['hook']->listen('log_write', $log); - // 监听 log_write - Hook::listen('log_write', $log); - - is_null(self::$driver) && self::init(Config::get('log')); + if (is_null($this->driver)) { + $this->init($this->app['config']->pull('log')); + } // 写入日志 - if ($result = self::$driver->save($log)) { - self::$log = []; + $result = $this->driver->save($log); + + if ($result) { + $this->log = []; } return $result; } /** - * 静态方法调用 + * 记录日志信息 * @access public - * @param string $method 调用方法 - * @param mixed $args 参数 + * @param string $level 日志级别 + * @param mixed $message 日志信息 + * @param array $context 替换内容 * @return void */ - public static function __callStatic($method, $args) + public function log($level, $message, array $context = []) { - if (in_array($method, self::$type)) { - array_push($args, $method); - - call_user_func_array('\\think\\Log::record', $args); - } + $this->record($message, $level, $context); } + /** + * 记录emergency信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function emergency($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录警报信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function alert($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录紧急情况 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function critical($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录错误信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function error($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录warning信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function warning($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录notice信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function notice($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录一般信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function info($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录调试信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function debug($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录sql信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function sql($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } } diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php index a42eafaf2..3b673464e 100644 --- a/thinkphp/library/think/Model.php +++ b/thinkphp/library/think/Model.php @@ -11,20 +11,7 @@ namespace think; -use BadMethodCallException; -use InvalidArgumentException; use think\db\Query; -use think\exception\ValidateException; -use think\model\Collection as ModelCollection; -use think\model\Relation; -use think\model\relation\BelongsTo; -use think\model\relation\BelongsToMany; -use think\model\relation\HasMany; -use think\model\relation\HasManyThrough; -use think\model\relation\HasOne; -use think\model\relation\MorphMany; -use think\model\relation\MorphOne; -use think\model\relation\MorphTo; /** * Class Model @@ -33,93 +20,94 @@ use think\model\relation\MorphTo; */ abstract class Model implements \JsonSerializable, \ArrayAccess { - // 数据库查询对象池 - protected static $links = []; - // 数据库配置 - protected $connection = []; - // 父关联模型对象 - protected $parent; - // 数据库查询对象 - protected $query; - // 当前模型名称 - protected $name; - // 数据表名称 - protected $table; - // 当前类名称 - protected $class; - // 回调事件 - private static $event = []; - // 错误信息 - protected $error; - // 字段验证规则 - protected $validate; - // 数据表主键 复合主键使用数组定义 不设置则自动获取 - protected $pk; - // 数据表字段信息 留空则自动获取 - protected $field = []; - // 数据排除字段 - protected $except = []; - // 数据废弃字段 - protected $disuse = []; - // 只读字段 - protected $readonly = []; - // 显示属性 - protected $visible = []; - // 隐藏属性 - protected $hidden = []; - // 追加属性 - protected $append = []; - // 数据信息 - protected $data = []; - // 原始数据 - protected $origin = []; - // 关联模型 - protected $relation = []; + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; - // 保存自动完成列表 + /** + * 是否更新数据 + * @var bool + */ + private $isUpdate = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置信息 + * @var array|string + */ + protected $connection = []; + + /** + * 数据库查询对象类名 + * @var string + */ + protected $query; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 写入自动完成定义 + * @var array + */ protected $auto = []; - // 新增自动完成列表 + + /** + * 新增自动完成定义 + * @var array + */ protected $insert = []; - // 更新自动完成列表 + + /** + * 更新自动完成定义 + * @var array + */ protected $update = []; - // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 - protected $autoWriteTimestamp; - // 创建时间字段 - protected $createTime = 'create_time'; - // 更新时间字段 - protected $updateTime = 'update_time'; - // 时间字段取出后的默认时间格式 - protected $dateFormat; - // 字段类型或者格式转换 - protected $type = []; - // 是否为更新数据 - protected $isUpdate = false; - // 是否强制更新所有数据 - protected $force = false; - // 更新条件 - protected $updateWhere; - // 验证失败是否抛出异常 - protected $failException = false; - // 全局查询范围 - protected $useGlobalScope = true; - // 是否采用批量验证 - protected $batchValidate = false; - // 查询数据集对象 - protected $resultSetType; - // 关联自动写入 - protected $relationWrite; /** * 初始化过的模型. - * * @var array */ protected static $initialized = []; /** - * 构造方法 + * 查询对象实例 + * @var Query + */ + protected $queryInstance; + + /** + * 错误信息 + * @var mixed + */ + protected $error; + + /** + * 架构函数 * @access public - * @param array|object $data 数据 + * @param array|object $data 数据 */ public function __construct($data = []) { @@ -141,14 +129,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 记录原始数据 $this->origin = $this->data; - // 当前类名 - $this->class = get_called_class(); + $config = Container::get('config'); if (empty($this->name)) { // 当前模型名 - $name = str_replace('\\', '/', $this->class); + $name = str_replace('\\', '/', static::class); $this->name = basename($name); - if (Config::get('class_suffix')) { + if ($config->get('class_suffix')) { $suffix = basename(dirname($name)); $this->name = substr($this->name, 0, -strlen($suffix)); } @@ -156,56 +143,40 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 - $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); + $this->autoWriteTimestamp = $config->get('database.auto_timestamp'); } if (is_null($this->dateFormat)) { // 设置时间戳格式 - $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); + $this->dateFormat = $config->get('database.datetime_format'); } if (is_null($this->resultSetType)) { - $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); + $this->resultSetType = $config->get('database.resultset_type'); } + + if (is_null($this->query)) { + // 设置查询对象 + $this->query = $config->get('database.query'); + } + + if (!empty($this->connection) && is_array($this->connection)) { + // 设置模型的数据库连接 + $this->connection = array_merge($config->pull('database'), $this->connection); + } + // 执行初始化操作 $this->initialize(); } /** - * 创建模型的查询对象 - * @access protected - * @return Query + * 获取当前模型名称 + * @access public + * @return string */ - protected function buildQuery() + public function getName() { - // 合并数据库配置 - if (!empty($this->connection)) { - if (is_array($this->connection)) { - $connection = array_merge(Config::get('database'), $this->connection); - } else { - $connection = $this->connection; - } - } else { - $connection = []; - } - - $con = Db::connect($connection); - // 设置当前模型 确保查询返回模型对象 - $queryClass = $this->query ?: $con->getConfig('query'); - $query = new $queryClass($con, $this); - - // 设置当前数据表和模型名 - if (!empty($this->table)) { - $query->setTable($this->table); - } else { - $query->name($this->name); - } - - if (!empty($this->pk)) { - $query->pk($this->pk); - } - - return $query; + return $this->name; } /** @@ -222,37 +193,69 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 获取当前模型的查询对象 - * @access public - * @param bool $buildNewQuery 创建新的查询对象 + * 创建模型的查询对象 + * @access protected * @return Query */ - public function getQuery($buildNewQuery = false) + protected function buildQuery() { - if ($buildNewQuery) { - return $this->buildQuery(); - } elseif (!isset(self::$links[$this->class])) { - // 创建模型查询对象 - self::$links[$this->class] = $this->buildQuery(); + // 设置当前模型 确保查询返回模型对象 + $class = $this->query; + $query = (new $class())->connect($this->connection)->model($this)->json($this->json); + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->table($this->table); + } else { + $query->name($this->name); } - return self::$links[$this->class]; + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; } /** * 获取当前模型的数据库查询对象 * @access public - * @param bool $useBaseQuery 是否调用全局查询范围 - * @param bool $buildNewQuery 创建新的查询对象 + * @param Query $query 查询对象实例 + * @return $this + */ + public function setQuery($query) + { + $this->queryInstance = $query; + return $this; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool $useBaseQuery 是否调用全局查询范围 * @return Query */ - public function db($useBaseQuery = true, $buildNewQuery = true) + public function db($useBaseQuery = true) { - $query = $this->getQuery($buildNewQuery); + if ($this->queryInstance) { + return $this->queryInstance; + } - // 全局作用域 - if ($useBaseQuery && method_exists($this, 'base')) { - call_user_func_array([$this, 'base'], [ & $query]); + $query = $this->buildQuery(); + + if ($useBaseQuery) { + // 软删除 + if (method_exists($this, 'getDeleteTimeField')) { + $field = $this->getDeleteTimeField(true); + if ($field) { + $query->useSoftDelete($field); + } + } + + // 全局作用域 + if (method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } } // 返回当前模型的数据库查询对象 @@ -266,9 +269,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected function initialize() { - $class = get_class($this); - if (!isset(static::$initialized[$class])) { - static::$initialized[$class] = true; + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; static::init(); } } @@ -279,1140 +281,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @return void */ protected static function init() - { - } - - /** - * 设置父关联对象 - * @access public - * @param Model $model 模型对象 - * @return $this - */ - public function setParent($model) - { - $this->parent = $model; - return $this; - } - - /** - * 获取父关联对象 - * @access public - * @return Model - */ - public function getParent() - { - return $this->parent; - } - - /** - * 设置数据对象值 - * @access public - * @param mixed $data 数据或者属性名 - * @param mixed $value 值 - * @return $this - */ - public function data($data, $value = null) - { - if (is_string($data)) { - $this->data[$data] = $value; - } else { - // 清空数据 - $this->data = []; - if (is_object($data)) { - $data = get_object_vars($data); - } - if (true === $value) { - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - } else { - $this->data = $data; - } - } - return $this; - } - - /** - * 获取对象原始数据 如果不存在指定字段返回false - * @access public - * @param string $name 字段名 留空获取全部 - * @return mixed - * @throws InvalidArgumentException - */ - public function getData($name = null) - { - if (is_null($name)) { - return $this->data; - } elseif (array_key_exists($name, $this->data)) { - return $this->data[$name]; - } elseif (array_key_exists($name, $this->relation)) { - return $this->relation[$name]; - } else { - throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); - } - } - - /** - * 是否需要自动写入时间字段 - * @access public - * @param bool $auto - * @return $this - */ - public function isAutoWriteTimestamp($auto) - { - $this->autoWriteTimestamp = $auto; - return $this; - } - - /** - * 更新是否强制写入数据 而不做比较 - * @access public - * @param bool $force - * @return $this - */ - public function force($force = true) - { - $this->force = $force; - return $this; - } - - /** - * 修改器 设置数据对象值 - * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @param array $data 数据 - * @return $this - */ - public function setAttr($name, $value, $data = []) - { - if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { - // 自动写入的时间戳字段 - $value = $this->autoWriteTimestamp($name); - } else { - // 检测修改器 - $method = 'set' . Loader::parseName($name, 1) . 'Attr'; - if (method_exists($this, $method)) { - $value = $this->$method($value, array_merge($this->data, $data), $this->relation); - } elseif (isset($this->type[$name])) { - // 类型转换 - $value = $this->writeTransform($value, $this->type[$name]); - } - } - - // 设置数据对象属性 - $this->data[$name] = $value; - return $this; - } - - /** - * 获取当前模型的关联模型数据 - * @access public - * @param string $name 关联方法名 - * @return mixed - */ - public function getRelation($name = null) - { - if (is_null($name)) { - return $this->relation; - } elseif (array_key_exists($name, $this->relation)) { - return $this->relation[$name]; - } else { - return; - } - } - - /** - * 设置关联数据对象值 - * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @return $this - */ - public function setRelation($name, $value) - { - $this->relation[$name] = $value; - return $this; - } - - /** - * 自动写入时间戳 - * @access public - * @param string $name 时间戳字段 - * @return mixed - */ - protected function autoWriteTimestamp($name) - { - if (isset($this->type[$name])) { - $type = $this->type[$name]; - if (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'datetime': - case 'date': - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime(time(), $format); - break; - case 'timestamp': - case 'integer': - default: - $value = time(); - break; - } - } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ - 'datetime', - 'date', - 'timestamp', - ]) - ) { - $value = $this->formatDateTime(time(), $this->dateFormat); - } else { - $value = $this->formatDateTime(time(), $this->dateFormat, true); - } - return $value; - } - - /** - * 时间日期字段格式化处理 - * @access public - * @param mixed $time 时间日期表达式 - * @param mixed $format 日期格式 - * @param bool $timestamp 是否进行时间戳转换 - * @return mixed - */ - protected function formatDateTime($time, $format, $timestamp = false) - { - if (false !== strpos($format, '\\')) { - $time = new $format($time); - } elseif (!$timestamp && false !== $format) { - $time = date($format, $time); - } - return $time; - } - - /** - * 数据写入 类型转换 - * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 - * @return mixed - */ - protected function writeTransform($value, $type) - { - if (is_null($value)) { - return; - } - - if (is_array($type)) { - list($type, $param) = $type; - } elseif (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'integer': - $value = (int) $value; - break; - case 'float': - if (empty($param)) { - $value = (float) $value; - } else { - $value = (float) number_format($value, $param, '.', ''); - } - break; - case 'boolean': - $value = (bool) $value; - break; - case 'timestamp': - if (!is_numeric($value)) { - $value = strtotime($value); - } - break; - case 'datetime': - $format = !empty($param) ? $param : $this->dateFormat; - $value = is_numeric($value) ? $value : strtotime($value); - $value = $this->formatDateTime($value, $format); - break; - case 'object': - if (is_object($value)) { - $value = json_encode($value, JSON_FORCE_OBJECT); - } - break; - case 'array': - $value = (array) $value; - case 'json': - $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; - $value = json_encode($value, $option); - break; - case 'serialize': - $value = serialize($value); - break; - - } - return $value; - } - - /** - * 获取器 获取数据对象的值 - * @access public - * @param string $name 名称 - * @return mixed - * @throws InvalidArgumentException - */ - public function getAttr($name) - { - try { - $notFound = false; - $value = $this->getData($name); - } catch (InvalidArgumentException $e) { - $notFound = true; - $value = null; - } - - // 检测属性获取器 - $method = 'get' . Loader::parseName($name, 1) . 'Attr'; - if (method_exists($this, $method)) { - $value = $this->$method($value, $this->data, $this->relation); - } elseif (isset($this->type[$name])) { - // 类型转换 - $value = $this->readTransform($value, $this->type[$name]); - } elseif (in_array($name, [$this->createTime, $this->updateTime])) { - if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ - 'datetime', - 'date', - 'timestamp', - ]) - ) { - $value = $this->formatDateTime(strtotime($value), $this->dateFormat); - } else { - $value = $this->formatDateTime($value, $this->dateFormat); - } - } elseif ($notFound) { - $relation = Loader::parseName($name, 1, false); - if (method_exists($this, $relation)) { - $modelRelation = $this->$relation(); - // 不存在该字段 获取关联数据 - $value = $this->getRelationData($modelRelation); - // 保存关联对象值 - $this->relation[$name] = $value; - } else { - throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); - } - } - return $value; - } - - /** - * 获取关联模型数据 - * @access public - * @param Relation $modelRelation 模型关联对象 - * @return mixed - * @throws BadMethodCallException - */ - protected function getRelationData(Relation $modelRelation) - { - if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { - $value = $this->parent; - } else { - // 首先获取关联数据 - if (method_exists($modelRelation, 'getRelation')) { - $value = $modelRelation->getRelation(); - } else { - throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation'); - } - } - return $value; - } - - /** - * 数据读取 类型转换 - * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 - * @return mixed - */ - protected function readTransform($value, $type) - { - if (is_null($value)) { - return; - } - - if (is_array($type)) { - list($type, $param) = $type; - } elseif (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'integer': - $value = (int) $value; - break; - case 'float': - if (empty($param)) { - $value = (float) $value; - } else { - $value = (float) number_format($value, $param, '.', ''); - } - break; - case 'boolean': - $value = (bool) $value; - break; - case 'timestamp': - if (!is_null($value)) { - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime($value, $format); - } - break; - case 'datetime': - if (!is_null($value)) { - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime(strtotime($value), $format); - } - break; - case 'json': - $value = json_decode($value, true); - break; - case 'array': - $value = empty($value) ? [] : json_decode($value, true); - break; - case 'object': - $value = empty($value) ? new \stdClass() : json_decode($value); - break; - case 'serialize': - $value = unserialize($value); - break; - default: - if (false !== strpos($type, '\\')) { - // 对象类型 - $value = new $type($value); - } - } - return $value; - } - - /** - * 设置需要追加的输出属性 - * @access public - * @param array $append 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function append($append = [], $override = false) - { - $this->append = $override ? $append : array_merge($this->append, $append); - return $this; - } - - /** - * 设置附加关联对象的属性 - * @access public - * @param string $relation 关联方法 - * @param string|array $append 追加属性名 - * @return $this - * @throws Exception - */ - public function appendRelationAttr($relation, $append) - { - if (is_string($append)) { - $append = explode(',', $append); - } - - $relation = Loader::parseName($relation, 1, false); - - // 获取关联数据 - if (isset($this->relation[$relation])) { - $model = $this->relation[$relation]; - } else { - $model = $this->getRelationData($this->$relation()); - } - - if ($model instanceof Model) { - foreach ($append as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($this->data[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $this->data[$key] = $model->getAttr($attr); - } - } - } - return $this; - } - - /** - * 设置需要隐藏的输出属性 - * @access public - * @param array $hidden 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function hidden($hidden = [], $override = false) - { - $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); - return $this; - } - - /** - * 设置需要输出的属性 - * @access public - * @param array $visible - * @param bool $override 是否覆盖 - * @return $this - */ - public function visible($visible = [], $override = false) - { - $this->visible = $override ? $visible : array_merge($this->visible, $visible); - return $this; - } - - /** - * 解析隐藏及显示属性 - * @access protected - * @param array $attrs 属性 - * @param array $result 结果集 - * @param bool $visible - * @return array - */ - protected function parseAttr($attrs, &$result, $visible = true) - { - $array = []; - foreach ($attrs as $key => $val) { - if (is_array($val)) { - if ($visible) { - $array[] = $key; - } - $result[$key] = $val; - } elseif (strpos($val, '.')) { - list($key, $name) = explode('.', $val); - if ($visible) { - $array[] = $key; - } - $result[$key][] = $name; - } else { - $array[] = $val; - } - } - return $array; - } - - /** - * 转换子模型对象 - * @access protected - * @param Model|ModelCollection $model - * @param $visible - * @param $hidden - * @param $key - * @return array - */ - protected function subToArray($model, $visible, $hidden, $key) - { - // 关联模型对象 - if (isset($visible[$key])) { - $model->visible($visible[$key]); - } elseif (isset($hidden[$key])) { - $model->hidden($hidden[$key]); - } - return $model->toArray(); - } - - /** - * 转换当前模型对象为数组 - * @access public - * @return array - */ - public function toArray() - { - $item = []; - $visible = []; - $hidden = []; - - $data = array_merge($this->data, $this->relation); - - // 过滤属性 - if (!empty($this->visible)) { - $array = $this->parseAttr($this->visible, $visible); - $data = array_intersect_key($data, array_flip($array)); - } elseif (!empty($this->hidden)) { - $array = $this->parseAttr($this->hidden, $hidden, false); - $data = array_diff_key($data, array_flip($array)); - } - - foreach ($data as $key => $val) { - if ($val instanceof Model || $val instanceof ModelCollection) { - // 关联模型对象 - $item[$key] = $this->subToArray($val, $visible, $hidden, $key); - } elseif (is_array($val) && reset($val) instanceof Model) { - // 关联模型数据集 - $arr = []; - foreach ($val as $k => $value) { - $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); - } - $item[$key] = $arr; - } else { - // 模型属性 - $item[$key] = $this->getAttr($key); - } - } - // 追加属性(必须定义获取器) - if (!empty($this->append)) { - foreach ($this->append as $key => $name) { - if (is_array($name)) { - // 追加关联对象属性 - $relation = $this->getAttr($key); - $item[$key] = $relation->append($name)->toArray(); - } elseif (strpos($name, '.')) { - list($key, $attr) = explode('.', $name); - // 追加关联对象属性 - $relation = $this->getAttr($key); - $item[$key] = $relation->append([$attr])->toArray(); - } else { - $relation = Loader::parseName($name, 1, false); - if (method_exists($this, $relation)) { - $modelRelation = $this->$relation(); - $value = $this->getRelationData($modelRelation); - - if (method_exists($modelRelation, 'getBindAttr')) { - $bindAttr = $modelRelation->getBindAttr(); - if ($bindAttr) { - foreach ($bindAttr as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($this->data[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $item[$key] = $value ? $value->getAttr($attr) : null; - } - } - continue; - } - } - $item[$name] = $value; - } else { - $item[$name] = $this->getAttr($name); - } - } - } - } - return !empty($item) ? $item : []; - } - - /** - * 转换当前模型对象为JSON字符串 - * @access public - * @param integer $options json参数 - * @return string - */ - public function toJson($options = JSON_UNESCAPED_UNICODE) - { - return json_encode($this->toArray(), $options); - } - - /** - * 移除当前模型的关联属性 - * @access public - * @return $this - */ - public function removeRelation() - { - $this->relation = []; - return $this; - } - - /** - * 转换当前模型数据集为数据集对象 - * @access public - * @param array|\think\Collection $collection 数据集 - * @return \think\Collection - */ - public function toCollection($collection) - { - if ($this->resultSetType) { - if ('collection' == $this->resultSetType) { - $collection = new ModelCollection($collection); - } elseif (false !== strpos($this->resultSetType, '\\')) { - $class = $this->resultSetType; - $collection = new $class($collection); - } - } - return $collection; - } - - /** - * 关联数据一起更新 - * @access public - * @param mixed $relation 关联 - * @return $this - */ - public function together($relation) - { - if (is_string($relation)) { - $relation = explode(',', $relation); - } - $this->relationWrite = $relation; - return $this; - } - - /** - * 获取模型对象的主键 - * @access public - * @param string $name 模型名 - * @return mixed - */ - public function getPk($name = '') - { - if (!empty($name)) { - $table = $this->getQuery()->getTable($name); - return $this->getQuery()->getPk($table); - } elseif (empty($this->pk)) { - $this->pk = $this->getQuery()->getPk(); - } - return $this->pk; - } - - /** - * 判断一个字段名是否为主键字段 - * @access public - * @param string $key 名称 - * @return bool - */ - protected function isPk($key) - { - $pk = $this->getPk(); - if (is_string($pk) && $pk == $key) { - return true; - } elseif (is_array($pk) && in_array($key, $pk)) { - return true; - } - return false; - } - - /** - * 保存当前数据对象 - * @access public - * @param array $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return integer|false - */ - public function save($data = [], $where = [], $sequence = null) - { - if (is_string($data)) { - $sequence = $data; - $data = []; - } - - if (!empty($data)) { - // 数据自动验证 - if (!$this->validateData($data)) { - return false; - } - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - if (!empty($where)) { - $this->isUpdate = true; - $this->updateWhere = $where; - } - } - - // 自动关联写入 - if (!empty($this->relationWrite)) { - $relation = []; - foreach ($this->relationWrite as $key => $name) { - if (is_array($name)) { - if (key($name) === 0) { - $relation[$key] = []; - foreach ($name as $val) { - if (isset($this->data[$val])) { - $relation[$key][$val] = $this->data[$val]; - unset($this->data[$val]); - } - } - } else { - $relation[$key] = $name; - } - } elseif (isset($this->relation[$name])) { - $relation[$name] = $this->relation[$name]; - } elseif (isset($this->data[$name])) { - $relation[$name] = $this->data[$name]; - unset($this->data[$name]); - } - } - } - - // 数据自动完成 - $this->autoCompleteData($this->auto); - - // 事件回调 - if (false === $this->trigger('before_write', $this)) { - return false; - } - $pk = $this->getPk(); - if ($this->isUpdate) { - // 自动更新 - $this->autoCompleteData($this->update); - - // 事件回调 - if (false === $this->trigger('before_update', $this)) { - return false; - } - - // 获取有更新的数据 - $data = $this->getChangedData(); - - if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { - // 关联更新 - if (isset($relation)) { - $this->autoRelationUpdate($relation); - } - return 0; - } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { - // 自动写入更新时间 - $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); - $this->data[$this->updateTime] = $data[$this->updateTime]; - } - - if (empty($where) && !empty($this->updateWhere)) { - $where = $this->updateWhere; - } - - // 保留主键数据 - foreach ($this->data as $key => $val) { - if ($this->isPk($key)) { - $data[$key] = $val; - } - } - - $array = []; - - foreach ((array) $pk as $key) { - if (isset($data[$key])) { - $array[$key] = $data[$key]; - unset($data[$key]); - } - } - - if (!empty($array)) { - $where = $array; - } - - // 检测字段 - $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update)); - - // 模型更新 - if (!empty($allowFields)) { - $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data); - } else { - $result = $this->getQuery()->where($where)->update($data); - } - - // 关联更新 - if (isset($relation)) { - $this->autoRelationUpdate($relation); - } - - // 更新回调 - $this->trigger('after_update', $this); - - } else { - // 自动写入 - $this->autoCompleteData($this->insert); - - // 自动写入创建时间和更新时间 - if ($this->autoWriteTimestamp) { - if ($this->createTime && !isset($this->data[$this->createTime])) { - $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); - } - if ($this->updateTime && !isset($this->data[$this->updateTime])) { - $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); - } - } - - if (false === $this->trigger('before_insert', $this)) { - return false; - } - - // 检测字段 - $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); - if (!empty($allowFields)) { - $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence); - } else { - $result = $this->getQuery()->insert($this->data, false, false, $sequence); - } - - // 获取自动增长主键 - if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) { - foreach ((array) $pk as $key) { - if (!isset($this->data[$key]) || '' == $this->data[$key]) { - $this->data[$key] = $insertId; - } - } - } - - // 关联写入 - if (isset($relation)) { - foreach ($relation as $name => $val) { - $method = Loader::parseName($name, 1, false); - $this->$method()->save($val); - } - } - - // 标记为更新 - $this->isUpdate = true; - - // 新增回调 - $this->trigger('after_insert', $this); - } - // 写入回调 - $this->trigger('after_write', $this); - - // 重新记录原始数据 - $this->origin = $this->data; - - return $result; - } - - protected function checkAllowField($auto = []) - { - if (true === $this->field) { - $this->field = $this->getQuery()->getTableInfo('', 'fields'); - $field = $this->field; - } elseif (!empty($this->field)) { - $field = array_merge($this->field, $auto); - if ($this->autoWriteTimestamp) { - array_push($field, $this->createTime, $this->updateTime); - } - } elseif (!empty($this->except)) { - $fields = $this->getQuery()->getTableInfo('', 'fields'); - $field = array_diff($fields, (array) $this->except); - $this->field = $field; - } else { - $field = []; - } - - if ($this->disuse) { - // 废弃字段 - $field = array_diff($field, (array) $this->disuse); - } - return $field; - } - - protected function autoRelationUpdate($relation) - { - foreach ($relation as $name => $val) { - if ($val instanceof Model) { - $val->save(); - } else { - unset($this->data[$name]); - $model = $this->getAttr($name); - if ($model instanceof Model) { - $model->save($val); - } - } - } - } - - /** - * 获取变化的数据 并排除只读数据 - * @access public - * @return array - */ - public function getChangedData() - { - if ($this->force) { - $data = $this->data; - } else { - $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { - if ((empty($a) || empty($b)) && $a !== $b) { - return 1; - } - return is_object($a) || $a != $b ? 1 : 0; - }); - } - - if (!empty($this->readonly)) { - // 只读字段不允许更新 - foreach ($this->readonly as $key => $field) { - if (isset($data[$field])) { - unset($data[$field]); - } - } - } - - return $data; - } - - /** - * 字段值(延迟)增长 - * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setInc($field, $step = 1, $lazyTime = 0) - { - // 更新条件 - $where = $this->getWhere(); - - $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime); - if (true !== $result) { - $this->data[$field] += $step; - } - - return $result; - } - - /** - * 字段值(延迟)增长 - * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setDec($field, $step = 1, $lazyTime = 0) - { - // 更新条件 - $where = $this->getWhere(); - $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime); - if (true !== $result) { - $this->data[$field] -= $step; - } - - return $result; - } - - /** - * 获取更新条件 - * @access protected - * @return mixed - */ - protected function getWhere() - { - // 删除条件 - $pk = $this->getPk(); - - if (is_string($pk) && isset($this->data[$pk])) { - $where = [$pk => $this->data[$pk]]; - } elseif (!empty($this->updateWhere)) { - $where = $this->updateWhere; - } else { - $where = null; - } - return $where; - } - - /** - * 保存多个数据到当前数据对象 - * @access public - * @param array $dataSet 数据 - * @param boolean $replace 是否自动识别更新和写入 - * @return array|false - * @throws \Exception - */ - public function saveAll($dataSet, $replace = true) - { - if ($this->validate) { - // 数据批量验证 - $validate = $this->validate; - foreach ($dataSet as $data) { - if (!$this->validateData($data, $validate)) { - return false; - } - } - } - - $result = []; - $db = $this->getQuery(); - $db->startTrans(); - try { - $pk = $this->getPk(); - if (is_string($pk) && $replace) { - $auto = true; - } - foreach ($dataSet as $key => $data) { - if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { - $result[$key] = self::update($data, [], $this->field); - } else { - $result[$key] = self::create($data, $this->field); - } - } - $db->commit(); - return $this->toCollection($result); - } catch (\Exception $e) { - $db->rollback(); - throw $e; - } - } - - /** - * 设置允许写入的字段 - * @access public - * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段 - * @return $this - */ - public function allowField($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->field = $field; - return $this; - } - - /** - * 设置排除写入的字段 - * @access public - * @param string|array $field 排除允许写入的字段 - * @return $this - */ - public function except($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->except = $field; - return $this; - } - - /** - * 设置只读字段 - * @access public - * @param mixed $field 只读字段 - * @return $this - */ - public function readonly($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->readonly = $field; - return $this; - } - - /** - * 是否为更新数据 - * @access public - * @param bool $update - * @param mixed $where - * @return $this - */ - public function isUpdate($update = true, $where = null) - { - $this->isUpdate = $update; - if (!empty($where)) { - $this->updateWhere = $where; - } - return $this; - } + {} /** * 数据自动完成 - * @access public - * @param array $auto 要自动更新的字段列表 + * @access protected + * @param array $auto 要自动更新的字段列表 * @return void */ protected function autoCompleteData($auto = []) @@ -1433,6 +307,380 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } } + /** + * 更新是否强制写入数据 而不做比较 + * @access public + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return integer|false + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + if (!$this->checkBeforeSave($data, $where)) { + return false; + } + + $result = $this->isUpdate ? $this->updateData($where) : $this->insertData($sequence); + + if (false === $result) { + return false; + } + + // 写入回调 + $this->trigger('after_write'); + + // 重新记录原始数据 + $this->origin = $this->data; + + return $result; + } + + /** + * 写入之前检查数据 + * @access protected + * @param array $data 数据 + * @param array $where 保存条件 + * @return bool + */ + protected function checkBeforeSave($data, $where) + { + if (!empty($data)) { + + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + + if (!empty($where)) { + $this->isUpdate = true; + $this->updateWhere = $where; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write')) { + return false; + } + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @param array $autoFields 自动完成的字段列表 + * @return array + */ + protected function checkAllowFields(array $append = []) + { + // 检测字段 + if (empty($this->field) || true === $this->field) { + $query = $this->db(false); + $table = $this->table ?: $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + + $field = $this->field; + } else { + $field = array_merge($this->field, $append); + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + return $field; + } + + /** + * 保存写入数据 + * @access protected + * @param array $where 保存条件 + * @return int|false + */ + protected function updateData($where) + { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return 0; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->update)); + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $pk = $this->getPk(); + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[$key] = [$key, '=', $data[$key]]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $name => $val) { + if (is_array($val)) { + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + } + } + + // 模型更新 + $result = $this->db(false)->where($where)->strict(false)->field($allowFields)->update($data); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + // 更新回调 + $this->trigger('after_update'); + + return $result; + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增名 + * @return int|false + */ + protected function insertData($sequence) + { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 时间戳自动写入 + $this->checkTimeStampWrite(); + + if (false === $this->trigger('before_insert')) { + return false; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert)); + + $result = $this->db(false)->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence); + + // 获取自动增长主键 + if ($result && $insertId = $this->db(false)->getLastInsID($sequence)) { + $pk = $this->getPk(); + + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + + // 标记为更新 + $this->isUpdate = true; + + // 新增回调 + $this->trigger('after_insert'); + + return $result; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + $result = $this->db(false)->where($where)->setInc($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] += $step; + } + + return $result; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + $result = $this->db(false)->where($where)->setDec($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] -= $step; + } + + return $result; + } + + /** + * 获取当前的更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->data[$pk])) { + $where[] = [$pk, '=', $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + $result = []; + + $db = $this->db(false); + $db->startTrans(); + + try { + $pk = $this->getPk(); + + if (is_string($pk) && $replace) { + $auto = true; + } + + foreach ($dataSet as $key => $data) { + if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field); + } + } + + $db->commit(); + + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 是否为更新数据 + * @access public + * @param mixed $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + if (is_bool($update)) { + $this->isUpdate = $update; + + if (!empty($where)) { + $this->updateWhere = $where; + } + } else { + $this->isUpdate = true; + $this->updateWhere = $update; + } + + return $this; + } + /** * 删除当前的记录 * @access public @@ -1440,29 +688,25 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function delete() { - if (false === $this->trigger('before_delete', $this)) { + if (false === $this->trigger('before_delete')) { return false; } - // 删除条件 + // 读取更新条件 $where = $this->getWhere(); // 删除当前模型数据 - $result = $this->getQuery()->where($where)->delete(); + $result = $this->db(false)->where($where)->delete(); // 关联删除 if (!empty($this->relationWrite)) { - foreach ($this->relationWrite as $key => $name) { - $name = is_numeric($key) ? $name : $key; - $model = $this->getAttr($name); - if ($model instanceof Model) { - $model->delete(); - } - } + $this->autoRelationDelete(); } - $this->trigger('after_delete', $this); - // 清空原始数据 + $this->trigger('after_delete'); + + // 清空数据 + $this->data = []; $this->origin = []; return $result; @@ -1471,185 +715,68 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置自动完成的字段( 规则通过修改器定义) * @access public - * @param array $fields 需要自动完成的字段 + * @param array $fields 需要自动完成的字段 * @return $this */ public function auto($fields) { $this->auto = $fields; + return $this; } - /** - * 设置字段验证 - * @access public - * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 - * @param array $msg 提示信息 - * @param bool $batch 批量验证 - * @return $this - */ - public function validate($rule = true, $msg = [], $batch = false) - { - if (is_array($rule)) { - $this->validate = [ - 'rule' => $rule, - 'msg' => $msg, - ]; - } else { - $this->validate = true === $rule ? $this->name : $rule; - } - $this->batchValidate = $batch; - return $this; - } - - /** - * 设置验证失败后是否抛出异常 - * @access public - * @param bool $fail 是否抛出异常 - * @return $this - */ - public function validateFailException($fail = true) - { - $this->failException = $fail; - return $this; - } - - /** - * 自动验证数据 - * @access protected - * @param array $data 验证数据 - * @param mixed $rule 验证规则 - * @param bool $batch 批量验证 - * @return bool - */ - protected function validateData($data, $rule = null, $batch = null) - { - $info = is_null($rule) ? $this->validate : $rule; - - if (!empty($info)) { - if (is_array($info)) { - $validate = Loader::validate(); - $validate->rule($info['rule']); - $validate->message($info['msg']); - } else { - $name = is_string($info) ? $info : $this->name; - if (strpos($name, '.')) { - list($name, $scene) = explode('.', $name); - } - $validate = Loader::validate($name); - if (!empty($scene)) { - $validate->scene($scene); - } - } - $batch = is_null($batch) ? $this->batchValidate : $batch; - - if (!$validate->batch($batch)->check($data)) { - $this->error = $validate->getError(); - if ($this->failException) { - throw new ValidateException($this->error); - } else { - return false; - } - } - $this->validate = null; - } - return true; - } - - /** - * 返回模型的错误信息 - * @access public - * @return string|array - */ - public function getError() - { - return $this->error; - } - - /** - * 注册回调方法 - * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 - * @param bool $override 是否覆盖 - * @return void - */ - public static function event($event, $callback, $override = false) - { - $class = get_called_class(); - if ($override) { - self::$event[$class][$event] = []; - } - self::$event[$class][$event][] = $callback; - } - - /** - * 触发事件 - * @access protected - * @param string $event 事件名 - * @param mixed $params 传入参数(引用) - * @return bool - */ - protected function trigger($event, &$params) - { - if (isset(self::$event[$this->class][$event])) { - foreach (self::$event[$this->class][$event] as $callback) { - if (is_callable($callback)) { - $result = call_user_func_array($callback, [ & $params]); - if (false === $result) { - return false; - } - } - } - } - return true; - } - /** * 写入数据 * @access public - * @param array $data 数据数组 - * @param array|true $field 允许字段 - * @return $this + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @return static */ public static function create($data = [], $field = null) { $model = new static(); + if (!empty($field)) { $model->allowField($field); } + $model->isUpdate(false)->save($data, []); + return $model; } /** * 更新数据 * @access public - * @param array $data 数据数组 - * @param array $where 更新条件 - * @param array|true $field 允许字段 - * @return $this + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return static */ public static function update($data = [], $where = [], $field = null) { $model = new static(); + if (!empty($field)) { $model->allowField($field); } - $result = $model->isUpdate(true)->save($data, $where); + + $model->isUpdate(true)->save($data, $where); + return $model; } /** * 查找单条记录 * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @param bool $failException 是否抛出异常 * @return static|null * @throws exception\DbException */ - public static function get($data, $with = [], $cache = false) + public static function get($data, $with = [], $cache = false, $failException = false) { if (is_null($data)) { return; @@ -1659,16 +786,32 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $cache = $with; $with = []; } + $query = static::parseQuery($data, $with, $cache); - return $query->find($data); + + return $query->failException($failException)->find($data); + } + + /** + * 查找单条记录 如果不存在直接抛出异常 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public static function getOrFail($data, $with = [], $cache = false) + { + return self::get($data, $with, $cache, true); } /** * 查找所有记录 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 * @return static[]|false * @throws exception\DbException */ @@ -1678,496 +821,104 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $cache = $with; $with = []; } + $query = static::parseQuery($data, $with, $cache); + return $query->select($data); } /** * 分析查询表达式 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 * @return Query */ protected static function parseQuery(&$data, $with, $cache) { $result = self::with($with)->cache($cache); + if (is_array($data) && key($data) !== 0) { $result = $result->where($data); $data = null; } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $result]); + $data($result); $data = null; } elseif ($data instanceof Query) { $result = $data->with($with)->cache($cache); $data = null; } + return $result; } /** * 删除记录 * @access public - * @param mixed $data 主键列表 支持闭包查询条件 + * @param mixed $data 主键列表 支持闭包查询条件 * @return integer 成功删除的记录数 */ public static function destroy($data) { $model = new static(); + $query = $model->db(); + if (empty($data) && 0 !== $data) { return 0; } elseif (is_array($data) && key($data) !== 0) { $query->where($data); $data = null; } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $query]); + $data($query); $data = null; } + $resultSet = $query->select($data); $count = 0; + if ($resultSet) { foreach ($resultSet as $data) { $result = $data->delete(); $count += $result; } } + return $count; } /** - * 命名范围 + * 获取错误信息 * @access public - * @param string|array|\Closure $name 命名范围名称 逗号分隔 - * @internal mixed ...$params 参数调用 - * @return Query + * @return mixed */ - public static function scope($name) + public function getError() { - $model = new static(); - $query = $model->db(); - $params = func_get_args(); - array_shift($params); - array_unshift($params, $query); - if ($name instanceof \Closure) { - call_user_func_array($name, $params); - } elseif (is_string($name)) { - $name = explode(',', $name); - } - if (is_array($name)) { - foreach ($name as $scope) { - $method = 'scope' . trim($scope); - if (method_exists($model, $method)) { - call_user_func_array([$model, $method], $params); - } - } - } - return $query; + return $this->error; } /** - * 设置是否使用全局查询范围 - * @param bool $use 是否启用全局查询范围 - * @access public - * @return Query + * 解序列化后处理 */ - public static function useGlobalScope($use) + public function __wakeup() { - $model = new static(); - return $model->db($use); + $this->initialize(); } - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $relation 关联方法名 - * @param mixed $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @return Relation|Query - */ - public static function has($relation, $operator = '>=', $count = 1, $id = '*') + public function __debugInfo() { - $relation = (new static())->$relation(); - if (is_array($operator) || $operator instanceof \Closure) { - return $relation->hasWhere($operator); - } - return $relation->has($operator, $count, $id); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $relation 关联方法名 - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Relation|Query - */ - public static function hasWhere($relation, $where = [], $fields = null) - { - return (new static())->$relation()->hasWhere($where, $fields); - } - - /** - * 解析模型的完整命名空间 - * @access public - * @param string $model 模型名(或者完整类名) - * @return string - */ - protected function parseModel($model) - { - if (false === strpos($model, '\\')) { - $path = explode('\\', get_called_class()); - array_pop($path); - array_push($path, Loader::parseName($model, 1)); - $model = implode('\\', $path); - } - return $model; - } - - /** - * 查询当前模型的关联数据 - * @access public - * @param string|array $relations 关联名 - * @return $this - */ - public function relationQuery($relations) - { - if (is_string($relations)) { - $relations = explode(',', $relations); - } - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = null; - if ($relation instanceof \Closure) { - // 支持闭包查询过滤关联条件 - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $method = Loader::parseName($relation, 1, false); - $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); - } - return $this; - } - - /** - * 预载入关联查询 返回数据集 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 - * @return array - */ - public function eagerlyResultSet(&$resultSet, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); - } - } - - /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 - * @return Model - */ - public function eagerlyResult(&$result, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); - } - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param string|array $relation 关联名 - * @return void - */ - public function relationCount(&$result, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } elseif (is_string($key)) { - $name = $relation; - $relation = $key; - } - $relation = Loader::parseName($relation, 1, false); - $count = $this->$relation()->relationCount($result, $closure); - if (!isset($name)) { - $name = Loader::parseName($relation) . '_count'; - } - $result->setAttr($name, $count); - } - } - - /** - * 获取模型的默认外键名 - * @access public - * @param string $name 模型名 - * @return string - */ - protected function getForeignKey($name) - { - if (strpos($name, '\\')) { - $name = basename(str_replace('\\', '/', $name)); - } - return Loader::parseName($name) . '_id'; - } - - /** - * HAS ONE 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @param array $alias 别名定义(已经废弃) - * @param string $joinType JOIN类型 - * @return HasOne - */ - public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - return new HasOne($this, $model, $foreignKey, $localKey, $joinType); - } - - /** - * BELONGS TO 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义(已经废弃) - * @param string $joinType JOIN类型 - * @return BelongsTo - */ - public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: $this->getForeignKey($model); - $localKey = $localKey ?: (new $model)->getPk(); - $trace = debug_backtrace(false, 2); - $relation = Loader::parseName($trace[1]['function']); - return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); - } - - /** - * HAS MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @return HasMany - */ - public function hasMany($model, $foreignKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - return new HasMany($this, $model, $foreignKey, $localKey); - } - - /** - * HAS MANY 远程关联定义 - * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $foreignKey 关联外键 - * @param string $throughKey 关联外键 - * @param string $localKey 当前模型主键 - * @return HasManyThrough - */ - public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $through = $this->parseModel($through); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - $throughKey = $throughKey ?: $this->getForeignKey($through); - return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); - } - - /** - * BELONGS TO MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型关联键 - * @return BelongsToMany - */ - public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $name = Loader::parseName(basename(str_replace('\\', '/', $model))); - $table = $table ?: Loader::parseName($this->name) . '_' . $name; - $foreignKey = $foreignKey ?: $name . '_id'; - $localKey = $localKey ?: $this->getForeignKey($this->name); - return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); - } - - /** - * MORPH MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string|array $morph 多态字段信息 - * @param string $type 多态类型 - * @return MorphMany - */ - public function morphMany($model, $morph = null, $type = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - if (is_null($morph)) { - $trace = debug_backtrace(false, 2); - $morph = Loader::parseName($trace[1]['function']); - } - $type = $type ?: get_class($this); - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphMany($this, $model, $foreignKey, $morphType, $type); - } - - /** - * MORPH One 关联定义 - * @access public - * @param string $model 模型名 - * @param string|array $morph 多态字段信息 - * @param string $type 多态类型 - * @return MorphOne - */ - public function morphOne($model, $morph = null, $type = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - if (is_null($morph)) { - $trace = debug_backtrace(false, 2); - $morph = Loader::parseName($trace[1]['function']); - } - $type = $type ?: get_class($this); - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphOne($this, $model, $foreignKey, $morphType, $type); - } - - /** - * MORPH TO 关联定义 - * @access public - * @param string|array $morph 多态字段信息 - * @param array $alias 多态别名定义 - * @return MorphTo - */ - public function morphTo($morph = null, $alias = []) - { - $trace = debug_backtrace(false, 2); - $relation = Loader::parseName($trace[1]['function']); - - if (is_null($morph)) { - $morph = $relation; - } - // 记录当前关联信息 - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); - } - - public function __call($method, $args) - { - $query = $this->db(true, false); - if (method_exists($this, 'scope' . $method)) { - // 动态调用命名范围 - $method = 'scope' . $method; - array_unshift($args, $query); - call_user_func_array([$this, $method], $args); - return $this; - } else { - return call_user_func_array([$query, $method], $args); - } - } - - public static function __callStatic($method, $args) - { - $model = new static(); - $query = $model->db(); - if (method_exists($model, 'scope' . $method)) { - // 动态调用命名范围 - $method = 'scope' . $method; - array_unshift($args, $query); - - call_user_func_array([$model, $method], $args); - return $query; - } else { - return call_user_func_array([$query, $method], $args); - } + return [ + 'data' => $this->data, + 'relation' => $this->relation, + ]; } /** * 修改器 设置数据对象的值 * @access public - * @param string $name 名称 - * @param mixed $value 值 + * @param string $name 名称 + * @param mixed $value 值 * @return void */ public function __set($name, $value) @@ -2178,7 +929,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 获取器 获取数据对象的值 * @access public - * @param string $name 名称 + * @param string $name 名称 * @return mixed */ public function __get($name) @@ -2189,28 +940,22 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 检测数据对象的值 * @access public - * @param string $name 名称 + * @param string $name 名称 * @return boolean */ public function __isset($name) { - try { - if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { - return true; - } else { - $this->getAttr($name); - return true; - } - } catch (InvalidArgumentException $e) { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { + return true; + } else { return false; } - } /** * 销毁数据对象的值 * @access public - * @param string $name 名称 + * @param string $name 名称 * @return void */ public function __unset($name) @@ -2218,17 +963,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess unset($this->data[$name], $this->relation[$name]); } - public function __toString() - { - return $this->toJson(); - } - - // JsonSerializable - public function jsonSerialize() - { - return $this->toArray(); - } - // ArrayAccess public function offsetSet($name, $value) { @@ -2251,56 +985,27 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 解序列化后处理 + * 设置是否使用全局查询范围 + * @access public + * @param bool $use 是否启用全局查询范围 + * @return Query */ - public function __wakeup() + public static function useGlobalScope($use) { - $this->initialize(); + $model = new static(); + + return $model->db($use); } - /** - * 模型事件快捷方法 - * @param $callback - * @param bool $override - */ - protected static function beforeInsert($callback, $override = false) + public function __call($method, $args) { - self::event('before_insert', $callback, $override); + return call_user_func_array([$this->db(), $method], $args); } - protected static function afterInsert($callback, $override = false) + public static function __callStatic($method, $args) { - self::event('after_insert', $callback, $override); - } + $model = new static(); - protected static function beforeUpdate($callback, $override = false) - { - self::event('before_update', $callback, $override); + return call_user_func_array([$model->db(), $method], $args); } - - protected static function afterUpdate($callback, $override = false) - { - self::event('after_update', $callback, $override); - } - - protected static function beforeWrite($callback, $override = false) - { - self::event('before_write', $callback, $override); - } - - protected static function afterWrite($callback, $override = false) - { - self::event('after_write', $callback, $override); - } - - protected static function beforeDelete($callback, $override = false) - { - self::event('before_delete', $callback, $override); - } - - protected static function afterDelete($callback, $override = false) - { - self::event('after_delete', $callback, $override); - } - } diff --git a/thinkphp/library/think/Paginator.php b/thinkphp/library/think/Paginator.php index 0b42de03b..3fccdf9e6 100644 --- a/thinkphp/library/think/Paginator.php +++ b/thinkphp/library/think/Paginator.php @@ -20,28 +20,52 @@ use Traversable; abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable { - /** @var bool 是否为简洁模式 */ + /** + * 是否简洁模式 + * @var bool + */ protected $simple = false; - /** @var Collection 数据集 */ + /** + * 数据集 + * @var Collection + */ protected $items; - /** @var integer 当前页 */ + /** + * 当前页 + * @var integer + */ protected $currentPage; - /** @var integer 最后一页 */ + /** + * 最后一页 + * @var integer + */ protected $lastPage; - /** @var integer|null 数据总数 */ + /** + * 数据总数 + * @var integer|null + */ protected $total; - /** @var integer 每页的数量 */ + /** + * 每页数量 + * @var integer + */ protected $listRows; - /** @var bool 是否有下一页 */ + /** + * 是否有下一页 + * @var bool + */ protected $hasMore; - /** @var array 一些配置 */ + /** + * 分页配置 + * @var array + */ protected $options = [ 'var_page' => 'page', 'path' => '/', @@ -49,9 +73,6 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J 'fragment' => '', ]; - /** @var mixed simple模式下的下个元素 */ - protected $nextItem; - public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { $this->options = array_merge($this->options, $options); @@ -68,10 +89,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J if ($simple) { $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = count($items) > ($this->listRows); - if ($this->hasMore) { - $this->nextItem = $items->slice($this->listRows, 1); - } - $items = $items->slice(0, $this->listRows); + $items = $items->slice(0, $this->listRows); } else { $this->total = $total; $this->lastPage = (int) ceil($total / $listRows); @@ -82,6 +100,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J } /** + * @access public * @param $items * @param $listRows * @param null $currentPage @@ -107,7 +126,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 获取页码对应的链接 * - * @param $page + * @access protected + * @param $page * @return string */ protected function url($page) @@ -123,27 +143,31 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J $parameters = []; $path = str_replace('[PAGE]', $page, $this->options['path']); } + if (count($this->options['query']) > 0) { $parameters = array_merge($this->options['query'], $parameters); } + $url = $path; if (!empty($parameters)) { $url .= '?' . urldecode(http_build_query($parameters, null, '&')); } + return $url . $this->buildFragment(); } /** * 自动获取当前页码 - * @param string $varPage - * @param int $default + * @access public + * @param string $varPage + * @param int $default * @return int */ public static function getCurrentPage($varPage = 'page', $default = 1) { - $page = (int) Request::instance()->param($varPage); + $page = Container::get('request')->param($varPage); - if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) { + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { return $page; } @@ -152,11 +176,12 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 自动获取当前的path + * @access public * @return string */ public static function getCurrentPath() { - return Request::instance()->baseUrl(); + return Container::get('request')->baseUrl(); } public function total() @@ -164,6 +189,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J if ($this->simple) { throw new \DomainException('not support total'); } + return $this->total; } @@ -182,11 +208,13 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J if ($this->simple) { throw new \DomainException('not support last'); } + return $this->lastPage; } /** * 数据是否足够分页 + * @access public * @return boolean */ public function hasPages() @@ -197,6 +225,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 创建一组分页链接 * + * @access public * @param int $start * @param int $end * @return array @@ -215,18 +244,21 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 设置URL锚点 * + * @access public * @param string|null $fragment * @return $this */ public function fragment($fragment) { $this->options['fragment'] = $fragment; + return $this; } /** * 添加URL参数 * + * @access public * @param array|string $key * @param string|null $value * @return $this @@ -251,6 +283,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 构造锚点字符串 * + * @access public * @return string */ protected function buildFragment() @@ -260,6 +293,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 渲染分页html + * @access public * @return mixed */ abstract public function render(); @@ -282,6 +316,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 给每个元素执行个回调 * + * @access public * @param callable $callback * @return $this */ @@ -289,6 +324,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J { foreach ($this->items as $key => $item) { $result = $callback($item, $key); + if (false === $result) { break; } elseif (!is_object($item)) { @@ -301,6 +337,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Retrieve an external iterator + * @access public * @return Traversable An instance of an object implementing Iterator or * Traversable */ @@ -311,7 +348,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Whether a offset exists - * @param mixed $offset + * @access public + * @param mixed $offset * @return bool */ public function offsetExists($offset) @@ -321,7 +359,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Offset to retrieve - * @param mixed $offset + * @access public + * @param mixed $offset * @return mixed */ public function offsetGet($offset) @@ -331,8 +370,9 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Offset to set - * @param mixed $offset - * @param mixed $value + * @access public + * @param mixed $offset + * @param mixed $value */ public function offsetSet($offset, $value) { @@ -341,9 +381,10 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Offset to unset - * @param mixed $offset + * @access public + * @param mixed $offset * @return void - * @since 5.0.0 + * @since 5.0.0 */ public function offsetUnset($offset) { @@ -365,24 +406,19 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J public function toArray() { - if ($this->simple) { - return [ - 'per_page' => $this->listRows, - 'current_page' => $this->currentPage, - 'has_more' => $this->hasMore, - 'next_item' => $this->nextItem, - 'data' => $this->items->toArray(), - ]; - } else { - return [ - 'total' => $this->total, - 'per_page' => $this->listRows, - 'current_page' => $this->currentPage, - 'last_page' => $this->lastPage, - 'data' => $this->items->toArray(), - ]; + try { + $total = $this->total(); + } catch (\DomainException $e) { + $total = null; } + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; } /** diff --git a/thinkphp/library/think/Process.php b/thinkphp/library/think/Process.php index 6f3faa315..3b574db49 100644 --- a/thinkphp/library/think/Process.php +++ b/thinkphp/library/think/Process.php @@ -114,12 +114,13 @@ class Process /** * 构造方法 - * @param string $commandline 指令 - * @param string|null $cwd 工作目录 - * @param array|null $env 环境变量 - * @param string|null $input 输入 - * @param int|float|null $timeout 超时时间 - * @param array $options proc_open的选项 + * @access public + * @param string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 * @throws \RuntimeException * @api */ @@ -132,7 +133,7 @@ class Process $this->commandline = $commandline; $this->cwd = $cwd; - if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) { + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { $this->cwd = getcwd(); } if (null !== $env) { @@ -141,10 +142,10 @@ class Process $this->input = $input; $this->setTimeout($timeout); - $this->useFileHandles = '\\' === DS; + $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR; $this->pty = false; $this->enhanceWindowsCompatibility = true; - $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); + $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); $this->options = array_replace([ 'suppress_errors' => true, 'binary_pipes' => true, @@ -163,7 +164,8 @@ class Process /** * 运行指令 - * @param callback|null $callback + * @access public + * @param callback|null $callback * @return int */ public function run($callback = null) @@ -175,7 +177,8 @@ class Process /** * 运行指令 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @return self * @throws \RuntimeException * @throws ProcessFailedException @@ -195,7 +198,8 @@ class Process /** * 启动进程并写到 STDIN 输入后返回。 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @throws \RuntimeException * @throws \RuntimeException * @throws \LogicException @@ -216,7 +220,7 @@ class Process $commandline = $this->commandline; - if ('\\' === DS && $this->enhanceWindowsCompatibility) { + if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; foreach ($this->processPipes->getFiles() as $offset => $filename) { $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); @@ -245,7 +249,8 @@ class Process /** * 重启进程 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @return Process * @throws \RuntimeException * @throws \RuntimeException @@ -264,7 +269,8 @@ class Process /** * 等待要终止的进程 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @return int */ public function wait($callback = null) @@ -278,8 +284,8 @@ class Process do { $this->checkTimeout(); - $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen(); - $close = '\\' !== DS || !$running; + $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DIRECTORY_SEPARATOR || !$running; $this->readPipes(true, $close); } while ($running); @@ -296,6 +302,7 @@ class Process /** * 获取PID + * @access public * @return int|null * @throws \RuntimeException */ @@ -312,7 +319,8 @@ class Process /** * 将一个 POSIX 信号发送到进程中 - * @param int $signal + * @access public + * @param int $signal * @return Process */ public function signal($signal) @@ -324,6 +332,7 @@ class Process /** * 禁用从底层过程获取输出和错误输出。 + * @access public * @return Process */ public function disableOutput() @@ -342,6 +351,7 @@ class Process /** * 开启从底层过程获取输出和错误输出。 + * @access public * @return Process * @throws \RuntimeException */ @@ -358,6 +368,7 @@ class Process /** * 输出是否禁用 + * @access public * @return bool */ public function isOutputDisabled() @@ -367,9 +378,9 @@ class Process /** * 获取当前的输出管道 + * @access public * @return string * @throws \LogicException - * @throws \LogicException * @api */ public function getOutput() @@ -380,13 +391,14 @@ class Process $this->requireProcessIsStarted(__FUNCTION__); - $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); return $this->stdout; } /** * 以增量方式返回的输出结果。 + * @access public * @return string */ public function getIncrementalOutput() @@ -408,6 +420,7 @@ class Process /** * 清空输出 + * @access public * @return Process */ public function clearOutput() @@ -420,6 +433,7 @@ class Process /** * 返回当前的错误输出的过程 (STDERR)。 + * @access public * @return string */ public function getErrorOutput() @@ -430,13 +444,14 @@ class Process $this->requireProcessIsStarted(__FUNCTION__); - $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); return $this->stderr; } /** * 以增量方式返回 errorOutput + * @access public * @return string */ public function getIncrementalErrorOutput() @@ -458,6 +473,7 @@ class Process /** * 清空 errorOutput + * @access public * @return Process */ public function clearErrorOutput() @@ -470,6 +486,7 @@ class Process /** * 获取退出码 + * @access public * @return null|int */ public function getExitCode() @@ -485,6 +502,7 @@ class Process /** * 获取退出文本 + * @access public * @return null|string */ public function getExitCodeText() @@ -498,6 +516,7 @@ class Process /** * 检查是否成功 + * @access public * @return bool */ public function isSuccessful() @@ -507,6 +526,7 @@ class Process /** * 是否未捕获的信号已被终止子进程 + * @access public * @return bool */ public function hasBeenSignaled() @@ -524,6 +544,7 @@ class Process /** * 返回导致子进程终止其执行的数。 + * @access public * @return int */ public function getTermSignal() @@ -541,6 +562,7 @@ class Process /** * 检查子进程信号是否已停止 + * @access public * @return bool */ public function hasBeenStopped() @@ -554,6 +576,7 @@ class Process /** * 返回导致子进程停止其执行的数。 + * @access public * @return int */ public function getStopSignal() @@ -567,6 +590,7 @@ class Process /** * 检查是否正在运行 + * @access public * @return bool */ public function isRunning() @@ -582,6 +606,7 @@ class Process /** * 检查是否已开始 + * @access public * @return bool */ public function isStarted() @@ -591,6 +616,7 @@ class Process /** * 检查是否已终止 + * @access public * @return bool */ public function isTerminated() @@ -602,6 +628,7 @@ class Process /** * 获取当前的状态 + * @access public * @return string */ public function getStatus() @@ -613,11 +640,12 @@ class Process /** * 终止进程 + * @access public */ public function stop() { if ($this->isRunning()) { - if ('\\' === DS && !$this->isSigchildEnabled()) { + if ('\\' === DIRECTORY_SEPARATOR && !$this->isSigchildEnabled()) { exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); if ($exitCode > 0) { throw new \RuntimeException('Unable to kill the process'); @@ -642,6 +670,7 @@ class Process /** * 添加一行输出 + * @access public * @param string $line */ public function addOutput($line) @@ -652,6 +681,7 @@ class Process /** * 添加一行错误输出 + * @access public * @param string $line */ public function addErrorOutput($line) @@ -662,6 +692,7 @@ class Process /** * 获取被执行的指令 + * @access public * @return string */ public function getCommandLine() @@ -671,6 +702,7 @@ class Process /** * 设置指令 + * @access public * @param string $commandline * @return self */ @@ -683,6 +715,7 @@ class Process /** * 获取超时时间 + * @access public * @return float|null */ public function getTimeout() @@ -692,6 +725,7 @@ class Process /** * 获取idle超时时间 + * @access public * @return float|null */ public function getIdleTimeout() @@ -701,7 +735,8 @@ class Process /** * 设置超时时间 - * @param int|float|null $timeout + * @access public + * @param int|float|null $timeout * @return self */ public function setTimeout($timeout) @@ -713,7 +748,8 @@ class Process /** * 设置idle超时时间 - * @param int|float|null $timeout + * @access public + * @param int|float|null $timeout * @return self */ public function setIdleTimeout($timeout) @@ -729,12 +765,13 @@ class Process /** * 设置TTY - * @param bool $tty + * @access public + * @param bool $tty * @return self */ public function setTty($tty) { - if ('\\' === DS && $tty) { + if ('\\' === DIRECTORY_SEPARATOR && $tty) { throw new \RuntimeException('TTY mode is not supported on Windows platform.'); } if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { @@ -748,6 +785,7 @@ class Process /** * 检查是否是tty模式 + * @access public * @return bool */ public function isTty() @@ -757,7 +795,8 @@ class Process /** * 设置pty模式 - * @param bool $bool + * @access public + * @param bool $bool * @return self */ public function setPty($bool) @@ -769,6 +808,7 @@ class Process /** * 是否是pty模式 + * @access public * @return bool */ public function isPty() @@ -778,6 +818,7 @@ class Process /** * 获取工作目录 + * @access public * @return string|null */ public function getWorkingDirectory() @@ -791,7 +832,8 @@ class Process /** * 设置工作目录 - * @param string $cwd + * @access public + * @param string $cwd * @return self */ public function setWorkingDirectory($cwd) @@ -803,6 +845,7 @@ class Process /** * 获取环境变量 + * @access public * @return array */ public function getEnv() @@ -812,7 +855,8 @@ class Process /** * 设置环境变量 - * @param array $env + * @access public + * @param array $env * @return self */ public function setEnv(array $env) @@ -831,6 +875,7 @@ class Process /** * 获取输入 + * @access public * @return null|string */ public function getInput() @@ -840,7 +885,8 @@ class Process /** * 设置输入 - * @param mixed $input + * @access public + * @param mixed $input * @return self */ public function setInput($input) @@ -856,6 +902,7 @@ class Process /** * 获取proc_open的选项 + * @access public * @return array */ public function getOptions() @@ -865,7 +912,8 @@ class Process /** * 设置proc_open的选项 - * @param array $options + * @access public + * @param array $options * @return self */ public function setOptions(array $options) @@ -877,6 +925,7 @@ class Process /** * 是否兼容windows + * @access public * @return bool */ public function getEnhanceWindowsCompatibility() @@ -886,7 +935,8 @@ class Process /** * 设置是否兼容windows - * @param bool $enhance + * @access public + * @param bool $enhance * @return self */ public function setEnhanceWindowsCompatibility($enhance) @@ -898,6 +948,7 @@ class Process /** * 返回是否 sigchild 兼容模式激活 + * @access public * @return bool */ public function getEnhanceSigchildCompatibility() @@ -907,7 +958,8 @@ class Process /** * 激活 sigchild 兼容性模式。 - * @param bool $enhance + * @access public + * @param bool $enhance * @return self */ public function setEnhanceSigchildCompatibility($enhance) @@ -941,6 +993,7 @@ class Process /** * 是否支持pty + * @access public * @return bool */ public static function isPtySupported() @@ -951,7 +1004,7 @@ class Process return $result; } - if ('\\' === DS) { + if ('\\' === DIRECTORY_SEPARATOR) { return $result = false; } @@ -967,11 +1020,12 @@ class Process /** * 创建所需的 proc_open 的描述符 + * @access private * @return array */ private function getDescriptors() { - if ('\\' === DS) { + if ('\\' === DIRECTORY_SEPARATOR) { $this->processPipes = WindowsPipes::create($this, $this->input); } else { $this->processPipes = UnixPipes::create($this, $this->input); @@ -990,7 +1044,8 @@ class Process /** * 建立 wait () 使用的回调。 - * @param callable|null $callback + * @access protected + * @param callable|null $callback * @return callable */ protected function buildCallback($callback) @@ -1013,6 +1068,7 @@ class Process /** * 更新状态 + * @access protected * @param bool $blocking */ protected function updateStatus($blocking) @@ -1024,7 +1080,7 @@ class Process $this->processInformation = proc_get_status($this->process); $this->captureExitCode(); - $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true); + $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); if (!$this->processInformation['running']) { $this->close(); @@ -1033,6 +1089,7 @@ class Process /** * 是否开启 '--enable-sigchild' + * @access protected * @return bool */ protected function isSigchildEnabled() @@ -1053,7 +1110,8 @@ class Process /** * 验证是否超时 - * @param int|float|null $timeout + * @access private + * @param int|float|null $timeout * @return float|null */ private function validateTimeout($timeout) @@ -1071,8 +1129,9 @@ class Process /** * 读取pipes - * @param bool $blocking - * @param bool $close + * @access private + * @param bool $blocking + * @param bool $close */ private function readPipes($blocking, $close) { @@ -1100,6 +1159,7 @@ class Process /** * 关闭资源 + * @access private * @return int 退出码 */ private function close() @@ -1146,8 +1206,9 @@ class Process /** * 将一个 POSIX 信号发送到进程中。 - * @param int $signal - * @param bool $throwException + * @access private + * @param int $signal + * @param bool $throwException * @return bool */ private function doSignal($signal, $throwException) @@ -1183,7 +1244,8 @@ class Process /** * 确保进程已经开启 - * @param string $functionName + * @access private + * @param string $functionName */ private function requireProcessIsStarted($functionName) { @@ -1194,7 +1256,8 @@ class Process /** * 确保进程已经终止 - * @param string $functionName + * @access private + * @param string $functionName */ private function requireProcessIsTerminated($functionName) { diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php index 4a40d22c2..1868c5f40 100644 --- a/thinkphp/library/think/Request.php +++ b/thinkphp/library/think/Request.php @@ -11,86 +11,193 @@ namespace think; +use think\exception\HttpResponseException; + class Request { /** - * @var object 对象实例 + * 对象实例 + * @var object */ - protected static $instance; + protected $instance; - protected $method; /** - * @var string 域名(含协议和端口) + * 配置对象 + * @var Config + */ + protected $config; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 域名(含协议及端口) + * @var string */ protected $domain; /** - * @var string URL地址 + * 子域名 + * @var string + */ + protected $subDomain; + + /** + * 泛域名 + * @var string + */ + protected $panDomain; + + /** + * 当前URL地址 + * @var string */ protected $url; /** - * @var string 基础URL + * 基础URL + * @var string */ protected $baseUrl; /** - * @var string 当前执行的文件 + * 当前执行的文件 + * @var string */ protected $baseFile; /** - * @var string 访问的ROOT地址 + * 访问的ROOT地址 + * @var string */ protected $root; /** - * @var string pathinfo + * pathinfo + * @var string */ protected $pathinfo; /** - * @var string pathinfo(不含后缀) + * pathinfo(不含后缀) + * @var string */ protected $path; /** - * @var array 当前路由信息 + * 当前路由信息 + * @var array */ protected $routeInfo = []; /** - * @var array 环境变量 - */ - protected $env; - - /** - * @var array 当前调度信息 + * 当前调度信息 + * @var array */ protected $dispatch = []; + + /** + * 当前模块名 + * @var string + */ protected $module; + + /** + * 当前控制器名 + * @var string + */ protected $controller; + + /** + * 当前操作名 + * @var string + */ protected $action; - // 当前语言集 + + /** + * 当前语言集 + * @var string + */ protected $langset; /** - * @var array 请求参数 + * 当前请求参数 + * @var array */ - protected $param = []; - protected $get = []; - protected $post = []; - protected $request = []; - protected $route = []; - protected $put; - protected $session = []; - protected $file = []; - protected $cookie = []; - protected $server = []; - protected $header = []; + protected $param = []; /** - * @var array 资源类型 + * 当前GET参数 + * @var array + */ + protected $get = []; + + /** + * 当前POST参数 + * @var array + */ + protected $post = []; + + /** + * 当前REQUEST参数 + * @var array + */ + protected $request = []; + + /** + * 当前ROUTE参数 + * @var array + */ + protected $route = []; + + /** + * 当前PUT参数 + * @var array + */ + protected $put; + + /** + * 当前SESSION参数 + * @var array + */ + protected $session = []; + + /** + * 当前FILE参数 + * @var array + */ + protected $file = []; + + /** + * 当前COOKIE参数 + * @var array + */ + protected $cookie = []; + + /** + * 当前SERVER参数 + * @var array + */ + protected $server = []; + + /** + * 当前ENV参数 + * @var array + */ + protected $env = []; + + /** + * 当前HEADER参数 + * @var array + */ + protected $header = []; + + /** + * 资源类型定义 + * @var array */ protected $mimeType = [ 'xml' => 'application/xml,text/xml,application/x-xml', @@ -107,35 +214,60 @@ class Request 'html' => 'text/html,application/xhtml+xml,*/*', ]; + /** + * 当前请求内容 + * @var string + */ protected $content; - // 全局过滤规则 + /** + * 全局过滤规则 + * @var array + */ protected $filter; - // Hook扩展方法 - protected static $hook = []; - // 绑定的属性 - protected $bind = []; + + /** + * 扩展方法 + * @var array + */ + protected $hook = []; + + /** + * php://input内容 + * @var string + */ // php://input protected $input; - // 请求缓存 + + /** + * 请求缓存 + * @var array + */ protected $cache; - // 缓存是否检查 + + /** + * 缓存是否检查 + * @var bool + */ protected $isCheckCache; /** - * 构造函数 - * @access protected - * @param array $options 参数 + * 架构函数 + * @access public + * @param array $options 参数 */ - protected function __construct($options = []) + public function __construct($options = []) { foreach ($options as $name => $item) { if (property_exists($this, $name)) { $this->$name = $item; } } + + $this->config = Container::get('config'); + if (is_null($this->filter)) { - $this->filter = Config::get('default_filter'); + $this->filter = $this->config->get('default_filter'); } // 保存 php://input @@ -144,57 +276,43 @@ class Request public function __call($method, $args) { - if (array_key_exists($method, self::$hook)) { + if (array_key_exists($method, $this->hook)) { array_unshift($args, $this); - return call_user_func_array(self::$hook[$method], $args); + return call_user_func_array($this->hook[$method], $args); } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + throw new Exception('method not exists:' . static::class . '->' . $method); } } /** * Hook 方法注入 * @access public - * @param string|array $method 方法名 - * @param mixed $callback callable + * @param string|array $method 方法名 + * @param mixed $callback callable * @return void */ - public static function hook($method, $callback = null) + public function hook($method, $callback = null) { if (is_array($method)) { - self::$hook = array_merge(self::$hook, $method); + $this->hook = array_merge($this->hook, $method); } else { - self::$hook[$method] = $callback; + $this->hook[$method] = $callback; } } - /** - * 初始化 - * @access public - * @param array $options 参数 - * @return \think\Request - */ - public static function instance($options = []) - { - if (is_null(self::$instance)) { - self::$instance = new static($options); - } - return self::$instance; - } - /** * 创建一个URL请求 * @access public - * @param string $uri URL地址 - * @param string $method 请求类型 - * @param array $params 请求参数 - * @param array $cookie - * @param array $files - * @param array $server - * @param string $content + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content * @return \think\Request */ - public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + public function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) { $server['PATH_INFO'] = ''; $server['REQUEST_METHOD'] = strtoupper($method); @@ -257,15 +375,21 @@ class Request $options['method'] = $server['REQUEST_METHOD']; $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; $options['content'] = $content; - self::$instance = new self($options); - return self::$instance; + + foreach ($options as $name => $item) { + if (property_exists($this, $name)) { + $this->$name = $item; + } + } + + return $this; } /** * 设置或获取当前包含协议的域名 * @access public - * @param string $domain 域名 - * @return string + * @param string $domain 域名 + * @return string|$this */ public function domain($domain = null) { @@ -275,14 +399,55 @@ class Request } elseif (!$this->domain) { $this->domain = $this->scheme() . '://' . $this->host(); } + return $this->domain; } + /** + * 获取当前子域名 + * @access public + * @return string|$this + */ + public function subDomain() + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->config->get('app.url_domain_root'); + + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($this->host(), $rootDomain, true), '.')); + } else { + $domain = explode('.', $this->host(), -2); + } + + $this->subDomain = implode('.', $domain); + } + + return $this->subDomain; + } + + /** + * 设置或获取当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return string|$this + */ + public function panDomain($domain = null) + { + if (is_null($domain)) { + return $this->panDomain; + } else { + $this->panDomain = $domain; + return $this; + } + } + /** * 设置或获取当前完整URL 包括QUERY_STRING * @access public - * @param string|true $url URL地址 true 带域名获取 - * @return string + * @param string|true $url URL地址 true 带域名获取 + * @return string|$this */ public function url($url = null) { @@ -290,7 +455,7 @@ class Request $this->url = $url; return $this; } elseif (!$this->url) { - if (IS_CLI) { + if ($this->isCli()) { $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { $this->url = $_SERVER['HTTP_X_REWRITE_URL']; @@ -302,14 +467,15 @@ class Request $this->url = ''; } } + return true === $url ? $this->domain() . $this->url : $this->url; } /** * 设置或获取当前URL 不含QUERY_STRING * @access public - * @param string $url URL地址 - * @return string + * @param string $url URL地址 + * @return string|$this */ public function baseUrl($url = null) { @@ -320,14 +486,15 @@ class Request $str = $this->url(); $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; } + return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; } /** * 设置或获取当前执行的文件 SCRIPT_NAME * @access public - * @param string $file 当前执行的文件 - * @return string + * @param string $file 当前执行的文件 + * @return string|$this */ public function baseFile($file = null) { @@ -336,7 +503,7 @@ class Request return $this; } elseif (!$this->baseFile) { $url = ''; - if (!IS_CLI) { + if (!$this->isCli()) { $script_name = basename($_SERVER['SCRIPT_FILENAME']); if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { $url = $_SERVER['SCRIPT_NAME']; @@ -352,14 +519,15 @@ class Request } $this->baseFile = $url; } + return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; } /** * 设置或获取URL访问根地址 * @access public - * @param string $url URL地址 - * @return string + * @param string $url URL地址 + * @return string|$this */ public function root($url = null) { @@ -373,9 +541,27 @@ class Request } $this->root = rtrim($file, '/'); } + return true === $url ? $this->domain() . $this->root : $this->root; } + /** + * 获取URL访问根目录 + * @access public + * @return string + */ + public function rootUrl() + { + $base = $this->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base; + + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + + return $root; + } + /** * 获取当前请求URL的pathinfo信息(含URL后缀) * @access public @@ -384,18 +570,20 @@ class Request public function pathinfo() { if (is_null($this->pathinfo)) { - if (isset($_GET[Config::get('var_pathinfo')])) { + if (isset($_GET[$this->config->get('var_pathinfo')])) { // 判断URL里面是否有兼容模式参数 - $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; - unset($_GET[Config::get('var_pathinfo')]); - } elseif (IS_CLI) { + $_SERVER['PATH_INFO'] = $_GET[$this->config->get('var_pathinfo')]; + unset($_GET[$this->config->get('var_pathinfo')]); + } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif ('cli-server' == PHP_SAPI) { + $_SERVER['PATH_INFO'] = strpos($_SERVER['REQUEST_URI'], '?') ? strstr($_SERVER['REQUEST_URI'], '?', true) : $_SERVER['REQUEST_URI']; } // 分析PATHINFO信息 if (!isset($_SERVER['PATH_INFO'])) { - foreach (Config::get('pathinfo_fetch') as $type) { + foreach ($this->config->get('pathinfo_fetch') as $type) { if (!empty($_SERVER[$type])) { $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; @@ -403,8 +591,10 @@ class Request } } } + $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); } + return $this->pathinfo; } @@ -416,7 +606,7 @@ class Request public function path() { if (is_null($this->path)) { - $suffix = Config::get('url_html_suffix'); + $suffix = $this->config->get('url_html_suffix'); $pathinfo = $this->pathinfo(); if (false === $suffix) { // 禁止伪静态访问 @@ -429,6 +619,7 @@ class Request $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); } } + return $this->path; } @@ -445,7 +636,7 @@ class Request /** * 获取当前请求的时间 * @access public - * @param bool $float 是否使用浮点类型 + * @param bool $float 是否使用浮点类型 * @return integer|float */ public function time($float = false) @@ -461,6 +652,7 @@ class Request public function type() { $accept = $this->server('HTTP_ACCEPT'); + if (empty($accept)) { return false; } @@ -473,14 +665,15 @@ class Request } } } + return false; } /** * 设置资源类型 * @access public - * @param string|array $type 资源类型名 - * @param string $val 资源类型 + * @param string|array $type 资源类型名 + * @param string $val 资源类型 * @return void */ public function mimeType($type, $val = '') @@ -495,24 +688,25 @@ class Request /** * 当前的请求类型 * @access public - * @param bool $method true 获取原始请求类型 + * @param bool $method true 获取原始请求类型 * @return string */ public function method($method = false) { if (true === $method) { // 获取原始请求类型 - return IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); + return $this->isCli() ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); } elseif (!$this->method) { - if (isset($_POST[Config::get('var_method')])) { - $this->method = strtoupper($_POST[Config::get('var_method')]); + if (isset($_POST[$this->config->get('var_method')])) { + $this->method = strtoupper($_POST[$this->config->get('var_method')]); $this->{$this->method}($_POST); } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); } else { - $this->method = IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); + $this->method = $this->isCli() ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); } } + return $this->method; } @@ -609,15 +803,16 @@ class Request /** * 获取当前请求的参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function param($name = '', $default = null, $filter = '') { if (empty($this->param)) { $method = $this->method(true); + // 自动获取请求变量 switch ($method) { case 'POST': @@ -631,24 +826,27 @@ class Request default: $vars = []; } + // 当前请求参数和URL地址中的参数合并 $this->param = array_merge($this->get(false), $vars, $this->route(false)); } + if (true === $name) { // 获取包含文件上传信息的数组 $file = $this->file(); $data = is_array($file) ? array_merge($this->param, $file) : $this->param; return $this->input($data, '', $default, $filter); } + return $this->input($this->param, $name, $default, $filter); } /** * 设置获取路由参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function route($name = '', $default = null, $filter = '') @@ -657,15 +855,16 @@ class Request $this->param = []; return $this->route = array_merge($this->route, $name); } + return $this->input($this->route, $name, $default, $filter); } /** * 设置获取GET参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function get($name = '', $default = null, $filter = '') @@ -673,19 +872,21 @@ class Request if (empty($this->get)) { $this->get = $_GET; } + if (is_array($name)) { $this->param = []; return $this->get = array_merge($this->get, $name); } + return $this->input($this->get, $name, $default, $filter); } /** * 设置获取POST参数 * @access public - * @param string $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function post($name = '', $default = null, $filter = '') @@ -698,19 +899,21 @@ class Request $this->post = $_POST; } } + if (is_array($name)) { $this->param = []; return $this->post = array_merge($this->post, $name); } + return $this->input($this->post, $name, $default, $filter); } /** * 设置获取PUT参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function put($name = '', $default = null, $filter = '') @@ -723,6 +926,7 @@ class Request parse_str($content, $this->put); } } + if (is_array($name)) { $this->param = []; return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); @@ -734,9 +938,9 @@ class Request /** * 设置获取DELETE参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function delete($name = '', $default = null, $filter = '') @@ -747,9 +951,9 @@ class Request /** * 设置获取PATCH参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function patch($name = '', $default = null, $filter = '') @@ -759,9 +963,10 @@ class Request /** * 获取request变量 - * @param string $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function request($name = '', $default = null, $filter = '') @@ -769,49 +974,56 @@ class Request if (empty($this->request)) { $this->request = $_REQUEST; } + if (is_array($name)) { $this->param = []; return $this->request = array_merge($this->request, $name); } + return $this->input($this->request, $name, $default, $filter); } /** * 获取session数据 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function session($name = '', $default = null, $filter = '') { if (empty($this->session)) { - $this->session = Session::get(); + $this->session = Container::get('session')->get(); } + if (is_array($name)) { return $this->session = array_merge($this->session, $name); } + return $this->input($this->session, $name, $default, $filter); } /** * 获取cookie参数 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function cookie($name = '', $default = null, $filter = '') { + $cookie = Container::get('cookie'); + if (empty($this->cookie)) { - $this->cookie = Cookie::get(); + $this->cookie = $cookie->get(); } + if (is_array($name)) { return $this->cookie = array_merge($this->cookie, $name); } elseif (!empty($name)) { - $data = Cookie::has($name) ? Cookie::get($name) : $default; + $data = $cookie->has($name) ? $cookie->get($name) : $default; } else { $data = $this->cookie; } @@ -825,15 +1037,16 @@ class Request } else { $this->filterValue($data, $name, $filter); } + return $data; } /** * 获取server参数 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function server($name = '', $default = null, $filter = '') @@ -841,16 +1054,18 @@ class Request if (empty($this->server)) { $this->server = $_SERVER; } + if (is_array($name)) { return $this->server = array_merge($this->server, $name); } + return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); } /** * 获取上传的文件信息 * @access public - * @param string|array $name 名称 + * @param string|array $name 名称 * @return null|array|\think\File */ public function file($name = '') @@ -858,9 +1073,11 @@ class Request if (empty($this->file)) { $this->file = isset($_FILES) ? $_FILES : []; } + if (is_array($name)) { return $this->file = array_merge($this->file, $name); } + $files = $this->file; if (!empty($files)) { // 处理上传文件 @@ -904,32 +1121,36 @@ class Request return $array[$name]; } } + return; } /** * 获取环境变量 - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function env($name = '', $default = null, $filter = '') { if (empty($this->env)) { - $this->env = $_ENV; + $this->env = Container::get('env')->get(); } + if (is_array($name)) { return $this->env = array_merge($this->env, $name); } + return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); } /** * 设置或者获取当前的Header * @access public - * @param string|array $name header名称 - * @param string $default 默认值 + * @param string|array $name header名称 + * @param string $default 默认值 * @return string */ public function header($name = '', $default = null) @@ -955,22 +1176,27 @@ class Request } $this->header = array_change_key_case($header); } + if (is_array($name)) { return $this->header = array_merge($this->header, $name); } + if ('' === $name) { return $this->header; } + $name = str_replace('_', '-', strtolower($name)); + return isset($this->header[$name]) ? $this->header[$name] : $default; } /** * 获取变量 支持过滤和默认值 - * @param array $data 数据源 - * @param string|false $name 字段名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤函数 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 * @return mixed */ public function input($data = [], $name = '', $default = null, $filter = '') @@ -979,6 +1205,7 @@ class Request // 获取原始数据 return $data; } + $name = (string) $name; if ('' != $name) { // 解析name @@ -1015,12 +1242,14 @@ class Request // 强制类型转换 $this->typeCast($data, $type); } + return $data; } /** * 设置或获取当前的过滤规则 - * @param mixed $filter 过滤规则 + * @access public + * @param mixed $filter 过滤规则 * @return mixed */ public function filter($filter = null) @@ -1046,19 +1275,22 @@ class Request } $filter[] = $default; + return $filter; } /** * 递归过滤给定的值 - * @param mixed $value 键值 - * @param mixed $key 键名 - * @param array $filters 过滤方法+默认值 + * @access public + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 * @return mixed */ private function filterValue(&$value, $key, $filters) { $default = array_pop($filters); + foreach ($filters as $filter) { if (is_callable($filter)) { // 调用函数或者方法过滤 @@ -1082,27 +1314,15 @@ class Request } } } - return $this->filterExp($value); - } - /** - * 过滤表单中的表达式 - * @param string $value - * @return void - */ - public function filterExp(&$value) - { - // 过滤查询特殊字符 - if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) { - $value .= ' '; - } - // TODO 其他安全过滤 + return $value; } /** * 强制类型转换 - * @param string $data - * @param string $type + * @access public + * @param string $data + * @param string $type * @return mixed */ private function typeCast(&$data, $type) @@ -1138,9 +1358,9 @@ class Request /** * 是否存在某个请求参数 * @access public - * @param string $name 变量名 - * @param string $type 变量类型 - * @param bool $checkEmpty 是否检测空值 + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 * @return mixed */ public function has($name, $type = 'param', $checkEmpty = false) @@ -1150,6 +1370,7 @@ class Request } else { $param = $this->$type; } + // 按.拆分成多维数组进行判断 foreach (explode('.', $name) as $val) { if (isset($param[$val])) { @@ -1158,36 +1379,50 @@ class Request return false; } } + return ($checkEmpty && '' === $param) ? false : true; } /** * 获取指定的参数 * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 + * @param string|array $name 变量名 + * @param string $type 变量类型 * @return mixed */ public function only($name, $type = 'param') { $param = $this->$type(); + if (is_string($name)) { $name = explode(',', $name); } + $item = []; - foreach ($name as $key) { + foreach ($name as $key => $val) { + + if (is_int($key)) { + $default = null; + $key = $val; + } else { + $default = $val; + } + if (isset($param[$key])) { $item[$key] = $param[$key]; + } elseif (isset($default)) { + $item[$key] = $default; } } + return $item; } /** * 排除指定参数获取 * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 + * @param string|array $name 变量名 + * @param string $type 变量类型 * @return mixed */ public function except($name, $type = 'param') @@ -1196,11 +1431,13 @@ class Request if (is_string($name)) { $name = explode(',', $name); } + foreach ($name as $key) { if (isset($param[$key])) { unset($param[$key]); } } + return $param; } @@ -1212,6 +1449,7 @@ class Request public function isSsl() { $server = array_merge($_SERVER, $this->server); + if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { return true; } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { @@ -1220,55 +1458,60 @@ class Request return true; } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { return true; - } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) { + } elseif ($this->config->get('https_agent_name') && isset($server[$this->config->get('https_agent_name')])) { return true; } + return false; } /** * 当前是否Ajax请求 * @access public - * @param bool $ajax true 获取原始ajax请求 + * @param bool $ajax true 获取原始ajax请求 * @return bool */ public function isAjax($ajax = false) { $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); $result = ('xmlhttprequest' == $value) ? true : false; + if (true === $ajax) { return $result; } else { - return $this->param(Config::get('var_ajax')) ? true : $result; + return $this->param($this->config->get('var_ajax')) ? true : $result; } } /** * 当前是否Pjax请求 * @access public - * @param bool $pjax true 获取原始pjax请求 + * @param bool $pjax true 获取原始pjax请求 * @return bool */ public function isPjax($pjax = false) { $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + if (true === $pjax) { return $result; } else { - return $this->param(Config::get('var_pjax')) ? true : $result; + return $this->param($this->config->get('var_pjax')) ? true : $result; } } /** * 获取客户端IP地址 - * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 - * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @access public + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) * @return mixed */ public function ip($type = 0, $adv = true) { $type = $type ? 1 : 0; static $ip = null; + if (null !== $ip) { return $ip[$type]; } @@ -1289,9 +1532,11 @@ class Request } elseif (isset($_SERVER['REMOTE_ADDR'])) { $ip = $_SERVER['REMOTE_ADDR']; } + // IP地址合法验证 $long = sprintf("%u", ip2long($ip)); $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + return $ip[$type]; } @@ -1345,6 +1590,7 @@ class Request if (isset($_SERVER['HTTP_X_REAL_HOST'])) { return $_SERVER['HTTP_X_REAL_HOST']; } + return $this->server('HTTP_HOST'); } @@ -1386,6 +1632,7 @@ class Request public function contentType() { $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { if (strpos($contentType, ';')) { list($type) = explode(';', $contentType); @@ -1394,13 +1641,14 @@ class Request } return trim($type); } + return ''; } /** * 获取当前请求的路由信息 * @access public - * @param array $route 路由名称 + * @param array $route 路由名称 * @return array */ public function routeInfo($route = []) @@ -1415,7 +1663,7 @@ class Request /** * 设置或者获取当前请求的调度信息 * @access public - * @param array $dispatch 调度信息 + * @param array $dispatch 调度信息 * @return array */ public function dispatch($dispatch = null) @@ -1423,13 +1671,14 @@ class Request if (!is_null($dispatch)) { $this->dispatch = $dispatch; } + return $this->dispatch; } /** * 设置或者获取当前的模块名 * @access public - * @param string $module 模块名 + * @param string $module 模块名 * @return string|Request */ public function module($module = null) @@ -1445,7 +1694,7 @@ class Request /** * 设置或者获取当前的控制器名 * @access public - * @param string $controller 控制器名 + * @param string $controller 控制器名 * @return string|Request */ public function controller($controller = null) @@ -1461,7 +1710,7 @@ class Request /** * 设置或者获取当前的操作名 * @access public - * @param string $action 操作名 + * @param string $action 操作名 * @return string|Request */ public function action($action = null) @@ -1477,7 +1726,7 @@ class Request /** * 设置或者获取当前的语言 * @access public - * @param string $lang 语言名 + * @param string $lang 语言名 * @return string|Request */ public function langset($lang = null) @@ -1500,6 +1749,7 @@ class Request if (is_null($this->content)) { $this->content = $this->input; } + return $this->content; } @@ -1516,28 +1766,31 @@ class Request /** * 生成请求令牌 * @access public - * @param string $name 令牌名称 - * @param mixed $type 令牌生成方法 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 * @return string */ public function token($name = '__token__', $type = 'md5') { $type = is_callable($type) ? $type : 'md5'; $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + if ($this->isAjax()) { header($name . ': ' . $token); } - Session::set($name, $token); + + Container::get('session')->set($name, $token); + return $token; } /** * 设置当前地址的请求缓存 * @access public - * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id - * @param mixed $expire 缓存有效期 - * @param array $except 缓存排除 - * @param string $tag 缓存标签 + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 * @return void */ public function cache($key, $expire = null, $except = [], $tag = null) @@ -1554,22 +1807,25 @@ class Request // 关闭当前缓存 return; } + + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + if ($key instanceof \Closure) { $key = call_user_func_array($key, [$this]); } elseif (true === $key) { - foreach ($except as $rule) { - if (0 === stripos($this->url(), $rule)) { - return; - } - } // 自动缓存功能 $key = '__URL__'; } elseif (strpos($key, '|')) { list($key, $fun) = explode('|', $key); } + // 特殊规则替换 if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); } if (false !== strpos($key, ':')) { @@ -1587,18 +1843,19 @@ class Request return; } } + if (isset($fun)) { $key = $fun($key); } - + $cache = Container::get('cache'); if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { // 读取缓存 $response = Response::create()->code(304); - throw new \think\exception\HttpResponseException($response); - } elseif (Cache::has($key)) { - list($content, $header) = Cache::get($key); + throw new HttpResponseException($response); + } elseif ($cache->has($key)) { + list($content, $header) = $cache->get($key); $response = Response::create($content)->header($header); - throw new \think\exception\HttpResponseException($response); + throw new HttpResponseException($response); } else { $this->cache = [$key, $expire, $tag]; } @@ -1615,34 +1872,4 @@ class Request return $this->cache; } - /** - * 设置当前请求绑定的对象实例 - * @access public - * @param string|array $name 绑定的对象标识 - * @param mixed $obj 绑定的对象实例 - * @return mixed - */ - public function bind($name, $obj = null) - { - if (is_array($name)) { - $this->bind = array_merge($this->bind, $name); - } else { - $this->bind[$name] = $obj; - } - } - - public function __set($name, $value) - { - $this->bind[$name] = $value; - } - - public function __get($name) - { - return isset($this->bind[$name]) ? $this->bind[$name] : null; - } - - public function __isset($name) - { - return isset($this->bind[$name]); - } } diff --git a/thinkphp/library/think/Response.php b/thinkphp/library/think/Response.php index f14bbcfde..e554ab313 100644 --- a/thinkphp/library/think/Response.php +++ b/thinkphp/library/think/Response.php @@ -11,102 +11,130 @@ namespace think; -use think\response\Json as JsonResponse; -use think\response\Jsonp as JsonpResponse; use think\response\Redirect as RedirectResponse; -use think\response\View as ViewResponse; -use think\response\Xml as XmlResponse; class Response { - // 原始数据 + /** + * 原始数据 + * @var mixed + */ protected $data; - // 当前的contentType + /** + * 当前contentType + * @var string + */ protected $contentType = 'text/html'; - // 字符集 + /** + * 字符集 + * @var string + */ protected $charset = 'utf-8'; - //状态 + /** + * 状态码 + * @var integer + */ protected $code = 200; - // 输出参数 + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + + /** + * 输出参数 + * @var array + */ protected $options = []; - // header参数 + + /** + * header参数 + * @var array + */ protected $header = []; + /** + * 输出内容 + * @var string + */ protected $content = null; /** - * 构造函数 - * @access public - * @param mixed $data 输出数据 - * @param int $code - * @param array $header - * @param array $options 输出参数 + * 架构函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 */ public function __construct($data = '', $code = 200, array $header = [], $options = []) { $this->data($data); + if (!empty($options)) { $this->options = array_merge($this->options, $options); } + $this->contentType($this->contentType, $this->charset); - $this->header = array_merge($this->header, $header); + $this->code = $code; + $this->header = array_merge($this->header, $header); } /** * 创建Response对象 * @access public - * @param mixed $data 输出数据 - * @param string $type 输出类型 - * @param int $code - * @param array $header - * @param array $options 输出参数 - * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response */ public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) { $type = empty($type) ? 'null' : strtolower($type); $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst($type); - if (class_exists($class)) { - $response = new $class($data, $code, $header, $options); - } else { - $response = new static($data, $code, $header, $options); - } - return $response; + if (class_exists($class)) { + return new $class($data, $code, $header, $options); + } else { + return new static($data, $code, $header, $options); + } } /** * 发送数据到客户端 * @access public - * @return mixed + * @return void * @throws \InvalidArgumentException */ public function send() { // 监听response_send - Hook::listen('response_send', $this); + Container::get('hook')->listen('response_send', $this); // 处理输出数据 $data = $this->getContent(); // Trace调试注入 - if (Env::get('app_trace', Config::get('app_trace'))) { - Debug::inject($this, $data); + if (Container::get('env')->get('app_trace', Container::get('app')->config('app.app_trace'))) { + Container::get('debug')->inject($this, $data); } - if (200 == $this->code) { - $cache = Request::instance()->getCache(); + if (200 == $this->code && $this->allowCache) { + $cache = Container::get('request')->getCache(); if ($cache) { $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; - Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + + Container::get('cache')->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); } } @@ -115,15 +143,11 @@ class Response http_response_code($this->code); // 发送头部信息 foreach ($this->header as $name => $val) { - if (is_null($val)) { - header($name); - } else { - header($name . ':' . $val); - } + header($name . (!is_null($val) ? ':' . $val : '')); } } - echo $data; + $this->sendData($data); if (function_exists('fastcgi_finish_request')) { // 提高页面响应 @@ -131,18 +155,18 @@ class Response } // 监听response_end - Hook::listen('response_end', $this); + Container::get('hook')->listen('response_end', $this); // 清空当次请求有效的数据 if (!($this instanceof RedirectResponse)) { - Session::flush(); + Container::get('session')->flush(); } } /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) @@ -150,35 +174,61 @@ class Response return $data; } + /** + * 输出数据 + * @access protected + * @param string $data 要处理的数据 + * @return void + */ + protected function sendData($data) + { + echo $data; + } + /** * 输出的参数 * @access public - * @param mixed $options 输出参数 + * @param mixed $options 输出参数 * @return $this */ public function options($options = []) { $this->options = array_merge($this->options, $options); + return $this; } /** * 输出数据设置 * @access public - * @param mixed $data 输出数据 + * @param mixed $data 输出数据 * @return $this */ public function data($data) { $this->data = $data; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @param bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache($cache) + { + $this->allowCache = $cache; + return $this; } /** * 设置响应头 * @access public - * @param string|array $name 参数名 - * @param string $value 参数值 + * @param string|array $name 参数名 + * @param string $value 参数值 * @return $this */ public function header($name, $value = null) @@ -188,12 +238,14 @@ class Response } else { $this->header[$name] = $value; } + return $this; } /** * 设置页面输出内容 - * @param $content + * @access public + * @param mixed $content * @return $this */ public function content($content) @@ -213,74 +265,87 @@ class Response /** * 发送HTTP状态 - * @param integer $code 状态码 + * @access public + * @param integer $code 状态码 * @return $this */ public function code($code) { $this->code = $code; + return $this; } /** * LastModified - * @param string $time + * @access public + * @param string $time * @return $this */ public function lastModified($time) { $this->header['Last-Modified'] = $time; + return $this; } /** * Expires - * @param string $time + * @access public + * @param string $time * @return $this */ public function expires($time) { $this->header['Expires'] = $time; + return $this; } /** * ETag - * @param string $eTag + * @access public + * @param string $eTag * @return $this */ public function eTag($eTag) { $this->header['ETag'] = $eTag; + return $this; } /** * 页面缓存控制 - * @param string $cache 状态码 + * @access public + * @param string $cache 状态码 * @return $this */ public function cacheControl($cache) { $this->header['Cache-control'] = $cache; + return $this; } /** * 页面输出类型 - * @param string $contentType 输出类型 - * @param string $charset 输出编码 + * @access public + * @param string $contentType 输出类型 + * @param string $charset 输出编码 * @return $this */ public function contentType($contentType, $charset = 'utf-8') { $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + return $this; } /** * 获取头部信息 - * @param string $name 头部名称 + * @access public + * @param string $name 头部名称 * @return mixed */ public function getHeader($name = '') @@ -294,6 +359,7 @@ class Response /** * 获取原始数据 + * @access public * @return mixed */ public function getData() @@ -303,6 +369,7 @@ class Response /** * 获取输出数据 + * @access public * @return mixed */ public function getContent() @@ -320,11 +387,13 @@ class Response $this->content = (string) $content; } + return $this->content; } /** * 获取状态码 + * @access public * @return integer */ public function getCode() diff --git a/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php index 2dbd61dd4..ae7919e22 100644 --- a/thinkphp/library/think/Route.php +++ b/thinkphp/library/think/Route.php @@ -11,28 +11,20 @@ namespace think; -use think\exception\HttpException; +use think\exception\RouteNotFoundException; +use think\route\dispatch\Url as UrlDispatch; +use think\route\Domain; +use think\route\Resource; +use think\route\RuleGroup; +use think\route\RuleItem; class Route { - // 路由规则 - private static $rules = [ - 'get' => [], - 'post' => [], - 'put' => [], - 'delete' => [], - 'patch' => [], - 'head' => [], - 'options' => [], - '*' => [], - 'alias' => [], - 'domain' => [], - 'pattern' => [], - 'name' => [], - ]; - - // REST路由操作方法定义 - private static $rest = [ + /** + * REST定义 + * @var array + */ + protected $rest = [ 'index' => ['get', '', 'index'], 'create' => ['get', '/create', 'create'], 'edit' => ['get', '/:id/edit', 'edit'], @@ -42,8 +34,11 @@ class Route 'delete' => ['delete', '/:id', 'delete'], ]; - // 不同请求类型的方法前缀 - private static $methodPrefix = [ + /** + * 请求方法前缀定义 + * @var array + */ + protected $methodPrefix = [ 'get' => 'get', 'post' => 'post', 'put' => 'put', @@ -51,170 +46,411 @@ class Route 'patch' => 'patch', ]; - // 子域名 - private static $subDomain = ''; - // 域名绑定 - private static $bind = []; - // 当前分组信息 - private static $group = []; - // 当前子域名绑定 - private static $domainBind; - private static $domainRule; - // 当前域名 - private static $domain; - // 当前路由执行过程中的参数 - private static $option = []; + /** + * 配置对象 + * @var Config + */ + protected $config; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 当前HOST + * @var string + */ + protected $host; + + /** + * 当前域名 + * @var string + */ + protected $domain; + + /** + * 当前分组 + * @var string + */ + protected $group; + + /** + * 路由标识 + * @var array + */ + protected $name = []; + + /** + * 路由绑定 + * @var array + */ + protected $bind = []; + + /** + * 域名对象 + * @var array + */ + protected $domains = []; + + /** + * 跨域路由规则 + * @var RuleGroup + */ + protected $cross; + + /** + * 当前路由标识 + * @var string + */ + protected $ruleName; + + /** + * 路由别名 + * @var array + */ + protected $alias = []; + + public function __construct(Request $request, Config $config) + { + $this->config = $config; + $this->request = $request; + $this->host = $this->request->host(); + + $this->setDefaultDomain(); + } + + /** + * 初始化默认域名 + * @access protected + * @return void + */ + protected function setDefaultDomain() + { + // 默认域名 + $this->domain = $this->host; + + // 注册默认域名 + $domain = new Domain($this, $this->host); + + $this->domains[$this->host] = $domain; + + // 默认分组 + $this->group = $this->createTopGroup($domain); + } + + /** + * 创建一个域名下的顶级路由分组 + * @access protected + * @param Domain $domain 域名 + * @return RuleGroup + */ + protected function createTopGroup(Domain $domain) + { + $group = new RuleGroup($this); + // 注册分组到当前域名 + $domain->addRule($group); + + return $group; + } + + /** + * 设置当前域名 + * @access public + * @param RuleGroup $group 域名 + * @return void + */ + public function setGroup(RuleGroup $group) + { + $this->group = $group; + } + + /** + * 获取当前分组 + * @access public + * @return RuleGroup + */ + public function getGroup() + { + return $this->group; + } /** * 注册变量规则 * @access public - * @param string|array $name 变量名 - * @param string $rule 变量规则 + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this + */ + public function pattern($name, $rule = '') + { + $this->group->pattern($name, $rule); + + return $this; + } + + /** + * 注册路由参数 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value = '') + { + $this->group->option($name, $value); + + return $this; + } + + /** + * 获取当前根域名 + * @access protected + * @return string + */ + protected function getRootDomain() + { + $root = $this->config->get('app.url_domain_root'); + if (!$root) { + $item = explode('.', $this->host); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + return $root; + } + + /** + * 注册域名路由 + * @access public + * @param string|array $name 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Domain + */ + public function domain($name, $rule = '', $option = [], $pattern = []) + { + // 支持多个域名使用相同路由规则 + $domainName = is_array($name) ? array_shift($name) : $name; + + if ('*' != $domainName && !strpos($domainName, '.')) { + $domainName .= '.' . $this->getRootDomain(); + } + + $route = $this->config->get('url_lazy_route') ? $rule : null; + + $domain = new Domain($this, $domainName, $route, $option, $pattern); + + if (is_null($route)) { + // 获取原始分组 + $originGroup = $this->group; + // 设置当前域名 + $this->domain = $domainName; + $this->group = $this->createTopGroup($domain); + + // 解析域名路由规则 + $this->parseGroupRule($domain, $rule); + + // 还原默认域名 + $this->domain = $this->host; + // 还原默认分组 + $this->group = $originGroup; + } + + $this->domains[$domainName] = $domain; + + if (is_array($name) && !empty($name)) { + $root = $this->getRootDomain(); + foreach ($name as $item) { + if (!strpos($item, '.')) { + $item .= '.' . $root; + } + + $this->domains[$item] = $domainName; + } + } + + // 返回域名对象 + return $domain; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access public + * @param RuleGroup $group 分组路由对象 + * @param mixed $rule 路由规则 * @return void */ - public static function pattern($name = null, $rule = '') + public function parseGroupRule($group, $rule) { - if (is_array($name)) { - self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); - } else { - self::$rules['pattern'][$name] = $rule; + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif ($rule instanceof Response) { + $group->setRule($rule); + } elseif (is_array($rule)) { + $this->rules($rule); + } elseif ($rule) { + if (false !== strpos($rule, '?')) { + list($rule, $query) = explode('?', $rule); + parse_str($query, $vars); + $group->append($vars); + } + + $this->bind($rule); } } /** - * 注册子域名部署规则 + * 获取域名 * @access public - * @param string|array $domain 子域名 - * @param mixed $rule 路由规则 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @return array */ - public static function domain($domain, $rule = '', $option = [], $pattern = []) + public function getDomains() { - if (is_array($domain)) { - foreach ($domain as $key => $item) { - self::domain($key, $item, $option, $pattern); - } - } elseif ($rule instanceof \Closure) { - // 执行闭包 - self::setDomain($domain); - call_user_func_array($rule, []); - self::setDomain(null); - } elseif (is_array($rule)) { - self::setDomain($domain); - self::group('', function () use ($rule) { - // 动态注册域名的路由规则 - self::registerRules($rule); - }, $option, $pattern); - self::setDomain(null); - } else { - self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; - } - } - - private static function setDomain($domain) - { - self::$domain = $domain; + return $this->domains; } /** * 设置路由绑定 * @access public - * @param mixed $bind 绑定信息 - * @param string $type 绑定类型 默认为module 支持 namespace class controller - * @return mixed + * @param string $bind 绑定信息 + * @return $this */ - public static function bind($bind, $type = 'module') + public function bind($bind) { - self::$bind = ['type' => $type, $type => $bind]; - } + $this->bind[$this->domain] = $bind; - /** - * 设置或者获取路由标识 - * @access public - * @param string|array $name 路由命名标识 数组表示批量设置 - * @param array $value 路由地址及变量信息 - * @return array - */ - public static function name($name = '', $value = null) - { - if (is_array($name)) { - return self::$rules['name'] = $name; - } elseif ('' === $name) { - return self::$rules['name']; - } elseif (!is_null($value)) { - self::$rules['name'][strtolower($name)][] = $value; - } else { - $name = strtolower($name); - return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; - } + return $this; } /** * 读取路由绑定 * @access public - * @param string $type 绑定类型 + * @param string $domain 域名 + * @return string|null + */ + public function getBind($domain = null) + { + if (is_null($domain)) { + $domain = $this->domain; + } + + $subDomain = $this->request->subDomain(); + + if (strpos($subDomain, '.')) { + $name = '*' . strstr($subDomain, '.'); + } + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($name) && isset($this->bind[$name])) { + $result = $this->bind[$name]; + } elseif (isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; + } + + /** + * 设置当前路由标识 + * @access public + * @param string $name 路由命名标识 + * @return $this + */ + public function name($name) + { + $this->ruleName = $name; + + return $this; + } + + /** + * 读取路由标识 + * @access public + * @param string $name 路由标识 * @return mixed */ - public static function getBind($type) + public function getName($name = null) { - return isset(self::$bind[$type]) ? self::$bind[$type] : null; + if (is_null($name)) { + return $this->name; + } + + $name = strtolower($name); + + return isset($this->name[$name]) ? $this->name[$name] : null; + } + + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return $this + */ + public function setName($name) + { + $this->name = $name; + return $this; } /** * 导入配置文件的路由规则 * @access public - * @param array $rule 路由规则 - * @param string $type 请求类型 + * @param array $rules 路由规则 + * @param string $type 请求类型 * @return void */ - public static function import(array $rule, $type = '*') + public function import(array $rules, $type = '*') { // 检查域名部署 - if (isset($rule['__domain__'])) { - self::domain($rule['__domain__']); - unset($rule['__domain__']); + if (isset($rules['__domain__'])) { + foreach ($rules['__domain__'] as $key => $rule) { + $this->domain($key, $rule); + } + unset($rules['__domain__']); } // 检查变量规则 - if (isset($rule['__pattern__'])) { - self::pattern($rule['__pattern__']); - unset($rule['__pattern__']); + if (isset($rules['__pattern__'])) { + $this->pattern($rules['__pattern__']); + unset($rules['__pattern__']); } // 检查路由别名 - if (isset($rule['__alias__'])) { - self::alias($rule['__alias__']); - unset($rule['__alias__']); + if (isset($rules['__alias__'])) { + $this->alias($rules['__alias__']); + unset($rules['__alias__']); } // 检查资源路由 - if (isset($rule['__rest__'])) { - self::resource($rule['__rest__']); - unset($rule['__rest__']); + if (isset($rules['__rest__'])) { + foreach ($rules['__rest__'] as $key => $rule) { + $this->resource($key, $rule); + } + unset($rules['__rest__']); } - self::registerRules($rule, strtolower($type)); - } - - // 批量注册路由 - protected static function registerRules($rules, $type = '*') - { + // 检查路由规则(包含分组) foreach ($rules as $key => $val) { if (is_numeric($key)) { $key = array_shift($val); } + if (empty($val)) { continue; } + if (is_string($key) && 0 === strpos($key, '[')) { $key = substr($key, 1, -1); - self::group($key, $val); + $this->group($key, $val); } elseif (is_array($val)) { - self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + $this->rule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); } else { - self::setRule($key, $val, $type); + $this->rule($key, $val, $type); } } } @@ -222,1359 +458,543 @@ class Route /** * 注册路由规则 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) - { - $group = self::getGroup('name'); - - if (!is_null($group)) { - // 路由分组 - $option = array_merge(self::getGroup('option'), $option); - $pattern = array_merge(self::getGroup('pattern'), $pattern); - } - - $type = strtolower($type); - - if (strpos($type, '|')) { - $option['method'] = $type; - $type = '*'; - } - if (is_array($rule) && empty($route)) { - foreach ($rule as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, $val[1]); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $option1 = null; - $pattern1 = null; - $route = $val; - } - self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group); - } - } else { - self::setRule($rule, $route, $type, $option, $pattern, $group); - } - - } - - /** - * 设置路由规则 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @param string $group 所属分组 - * @return void - */ - protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') + public function rule($rule, $route, $method = '*', $option = [], $pattern = []) { + // 读取路由标识 if (is_array($rule)) { $name = $rule[0]; $rule = $rule[1]; + } elseif ($this->ruleName) { + $name = $this->ruleName; + + $this->ruleName = null; } elseif (is_string($route)) { $name = $route; } - if (!isset($option['complete_match'])) { - if (Config::get('route_complete_match')) { - $option['complete_match'] = true; - } elseif ('$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - if ('$' == substr($rule, -1, 1)) { - $rule = substr($rule, 0, -1); - } + $method = strtolower($method); + + // 创建路由规则实例 + $ruleItem = new RuleItem($this, $this->group, $rule, $route, $method, $option, $pattern); - if ('/' != $rule || $group) { - $rule = trim($rule, '/'); - } - $vars = self::parseVar($rule); if (isset($name)) { - $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; - $suffix = isset($option['ext']) ? $option['ext'] : null; - self::name($name, [$key, $vars, self::$domain, $suffix]); + // 上级完整分组名 + $group = $this->group->getFullName(); + + if ($group) { + $rule = $group . '/' . $rule; + } + + // 设置路由标识 用于URL快速生成 + $this->setRuleName($rule, $name, $option); } - if (isset($option['modular'])) { - $route = $option['modular'] . '/' . $route; - } - if ($group) { - if ('*' != $type) { - $option['method'] = $type; - } - if (self::$domain) { - self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - } else { - if ('*' != $type && isset(self::$rules['*'][$rule])) { - unset(self::$rules['*'][$rule]); - } - if (self::$domain) { - self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - if ('*' == $type) { - // 注册路由快捷方式 - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) { - self::$rules['domain'][self::$domain][$method][$rule] = true; - } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) { - self::$rules[$method][$rule] = true; - } - } - } + + // 添加到当前分组 + $this->group->addRule($ruleItem, $method); + + if (!empty($option['cross_domain'])) { + $this->setCrossDomainRule($ruleItem, $method); } + + return $ruleItem; } /** - * 设置当前执行的参数信息 + * 设置路由标识 用于URL反解生成 * @access public - * @param array $options 参数信息 - * @return mixed - */ - protected static function setOption($options = []) - { - self::$option[] = $options; - } - - /** - * 获取当前执行的所有参数信息 - * @access public - * @return array - */ - public static function getOption() - { - return self::$option; - } - - /** - * 获取当前的分组信息 - * @access public - * @param string $type 分组信息名称 name option pattern - * @return mixed - */ - public static function getGroup($type) - { - if (isset(self::$group[$type])) { - return self::$group[$type]; - } else { - return 'name' == $type ? null : []; - } - } - - /** - * 设置当前的路由分组 - * @access public - * @param string $name 分组名称 - * @param array $option 分组路由参数 - * @param array $pattern 分组变量规则 + * @param string $rule 路由规则 + * @param string $name 路由标识 + * @param array $option 路由参数 * @return void */ - public static function setGroup($name, $option = [], $pattern = []) + public function setRuleName($rule, $name, $option = []) { - self::$group['name'] = $name; - self::$group['option'] = $option ?: []; - self::$group['pattern'] = $pattern ?: []; + $vars = $this->parseVar($rule); + + if (isset($option['ext'])) { + $suffix = $option['ext']; + } elseif ($this->group->getOption('ext')) { + $suffix = $this->group->getOption('ext'); + } else { + $suffix = null; + } + + $this->name[strtolower($name)][] = [$rule, $vars, $this->domain, $suffix]; + } + + /** + * 设置跨域有效路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function setCrossDomainRule($rule, $method = '*') + { + if (!isset($this->cross)) { + $this->cross = new RuleGroup($this); + } + + $this->cross->addRule($rule, $method); + + return $this; + } + + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function rules($rules, $method = '*', $option = [], $pattern = []) + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + + if (is_array($val)) { + $route = array_shift($val); + $option = $val ? array_shift($val) : []; + $pattern = $val ? array_shift($val) : []; + } else { + $route = $val; + } + + $this->rule($key, $route, $method, $option, $pattern); + } } /** * 注册路由分组 * @access public - * @param string|array $name 分组名称或者参数 - * @param array|\Closure $routes 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $route 分组路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleGroup */ - public static function group($name, $routes, $option = [], $pattern = []) + public function group($name, $route, $option = [], $pattern = []) { if (is_array($name)) { $option = $name; $name = isset($option['name']) ? $option['name'] : ''; } - // 分组 - $currentGroup = self::getGroup('name'); - if ($currentGroup) { - $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); + + // 创建分组实例 + $rule = $this->config->get('url_lazy_route') ? $route : null; + $group = new RuleGroup($this, $this->group, $name, $rule, $option, $pattern); + + if (is_null($rule)) { + // 解析分组路由 + $parent = $this->getGroup(); + + $this->group = $group; + + // 解析分组路由规则 + $this->parseGroupRule($group, $route); + + $this->group = $parent; } - if (!empty($name)) { - if ($routes instanceof \Closure) { - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - if ($currentGroup != $name) { - self::$rules['*'][$name]['route'] = ''; - self::$rules['*'][$name]['var'] = self::parseVar($name); - self::$rules['*'][$name]['option'] = $option; - self::$rules['*'][$name]['pattern'] = $pattern; - } - } else { - $item = []; - $completeMatch = Config::get('route_complete_match'); - foreach ($routes as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $route = $val; - } - $options = isset($option1) ? $option1 : $option; - $patterns = isset($pattern1) ? $pattern1 : $pattern; - if ('$' == substr($key, -1, 1)) { - // 是否完整匹配 - $options['complete_match'] = true; - $key = substr($key, 0, -1); - } elseif ($completeMatch) { - $options['complete_match'] = true; - } - $key = trim($key, '/'); - $vars = self::parseVar($key); - $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; - // 设置路由标识 - $suffix = isset($options['ext']) ? $options['ext'] : null; - self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); - } - self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; - } + // 注册子分组 + $this->group->addRule($group); - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (!isset(self::$rules[$method][$name])) { - self::$rules[$method][$name] = true; - } elseif (is_array(self::$rules[$method][$name])) { - self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); - } - } - - } elseif ($routes instanceof \Closure) { - // 闭包注册 - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - } else { - // 批量注册路由 - self::rule($routes, '', '*', $option, $pattern); + if (!empty($option['cross_domain'])) { + $this->setCrossDomainRule($group); } + + return $group; } /** * 注册路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function any($rule, $route = '', $option = [], $pattern = []) + public function any($rule, $route = '', $option = [], $pattern = []) { - self::rule($rule, $route, '*', $option, $pattern); + return $this->rule($rule, $route, '*', $option, $pattern); } /** * 注册GET路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function get($rule, $route = '', $option = [], $pattern = []) + public function get($rule, $route = '', $option = [], $pattern = []) { - self::rule($rule, $route, 'GET', $option, $pattern); + return $this->rule($rule, $route, 'GET', $option, $pattern); } /** * 注册POST路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function post($rule, $route = '', $option = [], $pattern = []) + public function post($rule, $route = '', $option = [], $pattern = []) { - self::rule($rule, $route, 'POST', $option, $pattern); + return $this->rule($rule, $route, 'POST', $option, $pattern); } /** * 注册PUT路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function put($rule, $route = '', $option = [], $pattern = []) + public function put($rule, $route = '', $option = [], $pattern = []) { - self::rule($rule, $route, 'PUT', $option, $pattern); + return $this->rule($rule, $route, 'PUT', $option, $pattern); } /** * 注册DELETE路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function delete($rule, $route = '', $option = [], $pattern = []) + public function delete($rule, $route = '', $option = [], $pattern = []) { - self::rule($rule, $route, 'DELETE', $option, $pattern); + return $this->rule($rule, $route, 'DELETE', $option, $pattern); } /** * 注册PATCH路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function patch($rule, $route = '', $option = [], $pattern = []) + public function patch($rule, $route = '', $option = [], $pattern = []) { - self::rule($rule, $route, 'PATCH', $option, $pattern); + return $this->rule($rule, $route, 'PATCH', $option, $pattern); } /** * 注册资源路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Resource */ - public static function resource($rule, $route = '', $option = [], $pattern = []) + public function resource($rule, $route = '', $option = [], $pattern = []) { - if (is_array($rule)) { - foreach ($rule as $key => $val) { - if (is_array($val)) { - list($val, $option, $pattern) = array_pad($val, 3, []); - } - self::resource($key, $val, $option, $pattern); - } - } else { - if (strpos($rule, '.')) { - // 注册嵌套资源路由 - $array = explode('.', $rule); - $last = array_pop($array); - $item = []; - foreach ($array as $val) { - $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); - } - $rule = implode('/', $item) . '/' . $last; - } - // 注册资源路由 - foreach (self::$rest as $key => $val) { - if ((isset($option['only']) && !in_array($key, $option['only'])) - || (isset($option['except']) && in_array($key, $option['except']))) { - continue; - } - if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { - $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); - } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { - $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); - } - $item = ltrim($rule . $val[1], '/'); - $option['rest'] = $key; - self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); - } - } + $resource = new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest); + + // 添加到当前分组 + $this->group->addRule($resource); + + return $resource; } /** * 注册控制器路由 操作方法对应不同的请求后缀 * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return $this */ - public static function controller($rule, $route = '', $option = [], $pattern = []) + public function controller($rule, $route = '', $option = [], $pattern = []) { - foreach (self::$methodPrefix as $type => $val) { - self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + foreach ($this->methodPrefix as $type => $val) { + $this->$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); } + + return $this; + } + + /** + * 注册视图路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function view($rule, $template = '', $vars = [], $option = [], $pattern = []) + { + return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars); + } + + /** + * 注册重定向路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $status 状态码 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function redirect($rule, $route = '', $status = 301, $option = [], $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status); } /** * 注册别名路由 * @access public - * @param string|array $rule 路由别名 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @return void + * @param string|array $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return $this */ - public static function alias($rule = null, $route = '', $option = []) + public function alias($rule = null, $route = '', $option = []) { if (is_array($rule)) { - self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); + $this->alias = array_merge($this->alias, $rule); } else { - self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; + $this->alias[$rule] = $option ? [$route, $option] : $route; } + + return $this; + } + + /** + * 获取别名路由定义 + * @access public + * @param string $name 路由别名 + * @return string|array|null + */ + public function getAlias($name = null) + { + if (is_null($name)) { + return $this->alias; + } + + return isset($this->alias[$name]) ? $this->alias[$name] : null; } /** * 设置不同请求类型下面的方法前缀 * @access public - * @param string $method 请求类型 - * @param string $prefix 类型前缀 - * @return void + * @param string|array $method 请求类型 + * @param string $prefix 类型前缀 + * @return $this */ - public static function setMethodPrefix($method, $prefix = '') + public function setMethodPrefix($method, $prefix = '') { if (is_array($method)) { - self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); + $this->methodPrefix = array_merge($this->methodPrefix, array_change_key_case($method)); } else { - self::$methodPrefix[strtolower($method)] = $prefix; + $this->methodPrefix[strtolower($method)] = $prefix; } + + return $this; + } + + /** + * 获取请求类型的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return string|null + */ + public function getMethodPrefix($method) + { + $method = strtolower($method); + + return isset($this->methodPrefix[$method]) ? $this->methodPrefix[$method] : null; } /** * rest方法定义和修改 * @access public - * @param string|array $name 方法名称 - * @param array|bool $resource 资源 - * @return void + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this */ - public static function rest($name, $resource = []) + public function rest($name, $resource = []) { if (is_array($name)) { - self::$rest = $resource ? $name : array_merge(self::$rest, $name); + $this->rest = $resource ? $name : array_merge($this->rest, $name); } else { - self::$rest[$name] = $resource; + $this->rest[$name] = $resource; } + + return $this; + } + + /** + * 获取rest方法定义的参数 + * @access public + * @param string $name 方法名称 + * @return array|null + */ + public function getRest($name = null) + { + if (is_null($name)) { + return $this->rest; + } + + return isset($this->rest[$name]) ? $this->rest[$name] : null; } /** * 注册未匹配路由规则后的处理 * @access public - * @param string $route 路由地址 - * @param string $method 请求类型 - * @param array $option 路由参数 - * @return void + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem */ - public static function miss($route, $method = '*', $option = []) + public function miss($route, $method = '*', $option = []) { - self::rule('__miss__', $route, $method, $option, []); + return $this->rule('', $route, $method, $option)->isMiss(); } /** * 注册一个自动解析的URL路由 * @access public - * @param string $route 路由地址 - * @return void + * @param string $route 路由地址 + * @return RuleItem */ - public static function auto($route) + public function auto($route) { - self::rule('__auto__', $route, '*', [], []); - } - - /** - * 获取或者批量设置路由定义 - * @access public - * @param mixed $rules 请求类型或者路由定义数组 - * @return array - */ - public static function rules($rules = '') - { - if (is_array($rules)) { - self::$rules = $rules; - } elseif ($rules) { - return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; - } else { - $rules = self::$rules; - unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); - return $rules; - } - } - - /** - * 检测子域名部署 - * @access public - * @param Request $request Request请求对象 - * @param array $currentRules 当前路由规则 - * @param string $method 请求类型 - * @return void - */ - public static function checkDomain($request, &$currentRules, $method = 'get') - { - // 域名规则 - $rules = self::$rules['domain']; - // 开启子域名部署 支持二级和三级域名 - if (!empty($rules)) { - $host = $request->host(); - if (isset($rules[$host])) { - // 完整域名或者IP配置 - $item = $rules[$host]; - } else { - $rootDomain = Config::get('url_domain_root'); - if ($rootDomain) { - // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 - $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); - } else { - $domain = explode('.', $host, -2); - } - // 子域名配置 - if (!empty($domain)) { - // 当前子域名 - $subDomain = implode('.', $domain); - self::$subDomain = $subDomain; - $domain2 = array_pop($domain); - if ($domain) { - // 存在三级域名 - $domain3 = array_pop($domain); - } - if ($subDomain && isset($rules[$subDomain])) { - // 子域名配置 - $item = $rules[$subDomain]; - } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { - // 泛三级域名 - $item = $rules['*.' . $domain2]; - $panDomain = $domain3; - } elseif (isset($rules['*']) && !empty($domain2)) { - // 泛二级域名 - if ('www' != $domain2) { - $item = $rules['*']; - $panDomain = $domain2; - } - } - } - } - if (!empty($item)) { - if (isset($panDomain)) { - // 保存当前泛域名 - $request->route(['__domain__' => $panDomain]); - } - if (isset($item['[bind]'])) { - // 解析子域名部署规则 - list($rule, $option, $pattern) = $item['[bind]']; - if (!empty($option['https']) && !$request->isSsl()) { - // https检测 - throw new HttpException(404, 'must use https request:' . $host); - } - - if (strpos($rule, '?')) { - // 传入其它参数 - $array = parse_url($rule); - $result = $array['path']; - parse_str($array['query'], $params); - if (isset($panDomain)) { - $pos = array_search('*', $params); - if (false !== $pos) { - // 泛域名作为参数 - $params[$pos] = $panDomain; - } - } - $_GET = array_merge($_GET, $params); - } else { - $result = $rule; - } - - if (0 === strpos($result, '\\')) { - // 绑定到命名空间 例如 \app\index\behavior - self::$bind = ['type' => 'namespace', 'namespace' => $result]; - } elseif (0 === strpos($result, '@')) { - // 绑定到类 例如 @app\index\controller\User - self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; - } else { - // 绑定到模块/控制器 例如 index/user - self::$bind = ['type' => 'module', 'module' => $result]; - } - self::$domainBind = true; - } else { - self::$domainRule = $item; - $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; - } - } - } + return $this->rule('', $route)->isAuto(); } /** * 检测URL路由 * @access public - * @param Request $request Request请求对象 - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $checkDomain 是否检测域名规则 - * @return false|array + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $must 是否强制路由 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch + * @throws RouteNotFoundException */ - public static function check($request, $url, $depr = '/', $checkDomain = false) - { - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace($depr, '|', $url); - - if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { - // 检测路由别名 - $result = self::checkRouteAlias($request, $url, $depr); - if (false !== $result) { - return $result; - } - } - $method = strtolower($request->method()); - // 获取当前请求类型的路由规则 - $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; - // 检测域名部署 - if ($checkDomain) { - self::checkDomain($request, $rules, $method); - } - // 检测URL绑定 - $return = self::checkUrlBind($url, $rules, $depr); - if (false !== $return) { - return $return; - } - if ('|' != $url) { - $url = rtrim($url, '|'); - } - $item = str_replace('|', '/', $url); - if (isset($rules[$item])) { - // 静态路由规则检测 - $rule = $rules[$item]; - if (true === $rule) { - $rule = self::getRouteExpress($item); - } - if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { - self::setOption($rule['option']); - return self::parseRule($item, $rule['route'], $url, $rule['option']); - } - } - - // 路由规则检测 - if (!empty($rules)) { - return self::checkRoute($request, $rules, $url, $depr); - } - return false; - } - - private static function getRouteExpress($key) - { - return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; - } - - /** - * 检测路由规则 - * @access private - * @param Request $request - * @param array $rules 路由规则 - * @param string $url URL地址 - * @param string $depr URL分割符 - * @param string $group 路由分组名 - * @param array $options 路由参数(分组) - * @return mixed - */ - private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) - { - foreach ($rules as $key => $item) { - if (true === $item) { - $item = self::getRouteExpress($key); - } - if (!isset($item['rule'])) { - continue; - } - $rule = $item['rule']; - $route = $item['route']; - $vars = $item['var']; - $option = $item['option']; - $pattern = $item['pattern']; - - // 检查参数有效性 - if (!self::checkOption($option, $request)) { - continue; - } - - if (isset($option['ext'])) { - // 路由ext参数 优先于系统配置的URL伪静态后缀参数 - $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); - } - - if (is_array($rule)) { - // 分组路由 - $pos = strpos(str_replace('<', ':', $key), ':'); - if (false !== $pos) { - $str = substr($key, 0, $pos); - } else { - $str = $key; - } - if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { - continue; - } - self::setOption($option); - $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); - if (false !== $result) { - return $result; - } - } elseif ($route) { - if ('__miss__' == $rule || '__auto__' == $rule) { - // 指定特殊路由 - $var = trim($rule, '__'); - ${$var} = $item; - continue; - } - if ($group) { - $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); - } - - self::setOption($option); - if (isset($options['bind_model']) && isset($option['bind_model'])) { - $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); - } - $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); - if (false !== $result) { - return $result; - } - } - } - if (isset($auto)) { - // 自动解析URL地址 - return self::parseUrl($auto['route'] . '/' . $url, $depr); - } elseif (isset($miss)) { - // 未匹配所有路由的路由规则处理 - return self::parseRule('', $miss['route'], $url, $miss['option']); - } - return false; - } - - /** - * 检测路由别名 - * @access private - * @param Request $request - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkRouteAlias($request, $url, $depr) - { - $array = explode('|', $url); - $alias = array_shift($array); - $item = self::$rules['alias'][$alias]; - - if (is_array($item)) { - list($rule, $option) = $item; - $action = $array[0]; - if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { - // 允许操作 - return false; - } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { - // 排除操作 - return false; - } - if (isset($option['method'][$action])) { - $option['method'] = $option['method'][$action]; - } - } else { - $rule = $item; - } - $bind = implode('|', $array); - // 参数有效性检查 - if (isset($option) && !self::checkOption($option, $request)) { - // 路由不匹配 - return false; - } elseif (0 === strpos($rule, '\\')) { - // 路由到类 - return self::bindToClass($bind, substr($rule, 1), $depr); - } elseif (0 === strpos($rule, '@')) { - // 路由到控制器类 - return self::bindToController($bind, substr($rule, 1), $depr); - } else { - // 路由到模块/控制器 - return self::bindToModule($bind, $rule, $depr); - } - } - - /** - * 检测URL绑定 - * @access private - * @param string $url URL地址 - * @param array $rules 路由规则 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkUrlBind(&$url, &$rules, $depr = '/') - { - if (!empty(self::$bind)) { - $type = self::$bind['type']; - $bind = self::$bind[$type]; - // 记录绑定信息 - App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); - // 如果有URL绑定 则进行绑定检测 - switch ($type) { - case 'class': - // 绑定到类 - return self::bindToClass($url, $bind, $depr); - case 'controller': - // 绑定到控制器类 - return self::bindToController($url, $bind, $depr); - case 'namespace': - // 绑定到命名空间 - return self::bindToNamespace($url, $bind, $depr); - } - } - return false; - } - - /** - * 绑定到类 - * @access public - * @param string $url URL地址 - * @param string $class 类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToClass($url, $class, $depr = '/') + public function check($url, $depr = '/', $must = false, $completeMatch = false) { + // 自动检测域名路由 + $domain = $this->checkDomain(); $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; - } - /** - * 绑定到命名空间 - * @access public - * @param string $url URL地址 - * @param string $namespace 命名空间 - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToNamespace($url, $namespace, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 3); - $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); - $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); - if (!empty($array[2])) { - self::parseUrlParams($array[2]); - } - return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; - } + $result = $domain->check($this->request, $url, $depr, $completeMatch); - /** - * 绑定到控制器类 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器名 (支持带模块名 index/user ) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToController($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; - } - - /** - * 绑定到模块/控制器 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToModule($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'module', 'module' => $controller . '/' . $action]; - } - - /** - * 路由参数有效性检查 - * @access private - * @param array $option 路由参数 - * @param Request $request Request对象 - * @return bool - */ - private static function checkOption($option, $request) - { - if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) - || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 - || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 - || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 - || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 - || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 - || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) - || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 - || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 - || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 - || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 - || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 - ) { - return false; - } - return true; - } - - /** - * 检测路由规则 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $url URL地址 - * @param array $pattern 变量规则 - * @param array $option 路由参数 - * @param string $depr URL分隔符(全局) - * @return array|false - */ - private static function checkRule($rule, $route, $url, $pattern, $option, $depr) - { - // 检查完整规则定义 - if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { - return false; - } - // 检查路由的参数分隔符 - if (isset($option['param_depr'])) { - $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); + if (false === $result && !empty($this->cross)) { + // 检测跨越路由 + $result = $this->cross->check($this->request, $url, $depr, $completeMatch); } - $len1 = substr_count($url, '|'); - $len2 = substr_count($rule, '/'); - // 多余参数是否合并 - $merge = !empty($option['merge_extra_vars']); - if ($merge && $len1 > $len2) { - $url = str_replace('|', $depr, $url); - $url = implode('|', explode($depr, $url, $len2 + 1)); - } - - if ($len1 >= $len2 || strpos($rule, '[')) { - if (!empty($option['complete_match'])) { - // 完整匹配 - if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { - return false; - } - } - $pattern = array_merge(self::$rules['pattern'], $pattern); - if (false !== $match = self::match($url, $rule, $pattern)) { - // 匹配到路由规则 - return self::parseRule($rule, $route, $url, $option, $match); - } - } - return false; - } - - /** - * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... - * @access public - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $autoSearch 是否自动深度搜索控制器 - * @return array - */ - public static function parseUrl($url, $depr = '/', $autoSearch = false) - { - - if (isset(self::$bind['module'])) { - $bind = str_replace('/', $depr, self::$bind['module']); - // 如果有模块/控制器绑定 - $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); - } - $url = str_replace($depr, '|', $url); - list($path, $var) = self::parseUrlPath($url); - $route = [null, null, null]; - if (isset($path)) { - // 解析模块 - $module = Config::get('app_multi_module') ? array_shift($path) : null; - if ($autoSearch) { - // 自动搜索控制器 - $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); - $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; - $item = []; - $find = false; - foreach ($path as $val) { - $item[] = $val; - $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; - $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; - if (is_file($file)) { - $find = true; - break; - } else { - $dir .= DS . Loader::parseName($val); - } - } - if ($find) { - $controller = implode('.', $item); - $path = array_slice($path, count($item)); - } else { - $controller = array_shift($path); - } - } else { - // 解析控制器 - $controller = !empty($path) ? array_shift($path) : null; - } - // 解析操作 - $action = !empty($path) ? array_shift($path) : null; - // 解析额外参数 - self::parseUrlParams(empty($path) ? '' : implode('|', $path)); - // 封装路由 - $route = [$module, $controller, $action]; - // 检查地址是否被定义过路由 - $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); - $name2 = ''; - if (empty($module) || isset($bind) && $module == $bind) { - $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); - } - - if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { - throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); - } - } - return ['type' => 'module', 'module' => $route]; - } - - /** - * 解析URL的pathinfo参数和变量 - * @access private - * @param string $url URL地址 - * @return array - */ - private static function parseUrlPath($url) - { - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace('|', '/', $url); - $url = trim($url, '/'); - $var = []; - if (false !== strpos($url, '?')) { - // [模块/控制器/操作?]参数1=值1&参数2=值2... - $info = parse_url($url); - $path = explode('/', $info['path']); - parse_str($info['query'], $var); - } elseif (strpos($url, '/')) { - // [模块/控制器/操作] - $path = explode('/', $url); + if (false !== $result) { + // 路由匹配 + return $result; + } elseif ($must) { + // 强制路由不匹配则抛出异常 + throw new RouteNotFoundException(); } else { - $path = [$url]; + // 默认路由解析 + return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]); } - return [$path, $var]; } /** - * 检测URL和规则路由是否匹配 - * @access private - * @param string $url URL地址 - * @param string $rule 路由规则 - * @param array $pattern 变量规则 - * @return array|false + * 检测域名的路由规则 + * @access protected + * @param string $host 当前主机地址 + * @return Domain */ - private static function match($url, $rule, $pattern) + protected function checkDomain() { - $m2 = explode('/', $rule); - $m1 = explode('|', $url); + // 获取当前子域名 + $subDomain = $this->request->subDomain(); - $var = []; - foreach ($m2 as $key => $val) { - // val中定义了多个变量 - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - $value = []; - $replace = []; - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; - } else { - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; - } - $value[] = $name; - } - $val = str_replace($matches[0], $replace, $val); - if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { - array_shift($match); - foreach ($value as $k => $name) { - if (isset($match[$k])) { - $var[$name] = $match[$k]; - } - } - continue; - } else { - return false; + $item = false; + + if ($subDomain && count($this->domains) > 1) { + $domain = explode('.', $subDomain); + $domain2 = array_pop($domain); + + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + + if ($subDomain && isset($this->domains[$subDomain])) { + // 子域名配置 + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $this->domains['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($this->domains['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $this->domains['*']; + $panDomain = $domain2; } } - if (0 === strpos($val, '[:')) { - // 可选参数 - $val = substr($val, 1, -1); - $optional = true; - } else { - $optional = false; - } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - if (!$optional && !isset($m1[$key])) { - return false; - } - if (isset($m1[$key]) && isset($pattern[$name])) { - // 检查变量规则 - if ($pattern[$name] instanceof \Closure) { - $result = call_user_func_array($pattern[$name], [$m1[$key]]); - if (false === $result) { - return false; - } - } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { - return false; - } - } - $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; - } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { - return false; + if (isset($panDomain)) { + // 保存当前泛域名 + $this->request->panDomain($panDomain); } } - // 成功匹配后返回URL中的动态变量数组 - return $var; + + if (false === $item) { + // 检测当前完整域名 + $item = $this->domains[$this->host]; + } + + if (is_string($item)) { + $item = $this->domains[$item]; + } + + return $item; } /** - * 解析规则路由 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $pathinfo URL地址 - * @param array $option 路由参数 - * @param array $matches 匹配的变量 + * 分析路由规则中的变量 + * @access public + * @param string $rule 路由规则 * @return array */ - private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = []) - { - $request = Request::instance(); - // 解析路由规则 - if ($rule) { - $rule = explode('/', $rule); - // 获取URL地址中的参数 - $paths = explode('|', $pathinfo); - foreach ($rule as $item) { - $fun = ''; - if (0 === strpos($item, '[:')) { - $item = substr($item, 1, -1); - } - if (0 === strpos($item, ':')) { - $var = substr($item, 1); - $matches[$var] = array_shift($paths); - } else { - // 过滤URL中的静态变量 - array_shift($paths); - } - } - } else { - $paths = explode('|', $pathinfo); - } - - // 获取路由地址规则 - if (is_string($route) && isset($option['prefix'])) { - // 路由地址前缀 - $route = $option['prefix'] . $route; - } - // 替换路由地址中的变量 - if (is_string($route) && !empty($matches)) { - foreach ($matches as $key => $val) { - if (false !== strpos($route, ':' . $key)) { - $route = str_replace(':' . $key, $val, $route); - } - } - } - - // 绑定模型数据 - if (isset($option['bind_model'])) { - $bind = []; - foreach ($option['bind_model'] as $key => $val) { - if ($val instanceof \Closure) { - $result = call_user_func_array($val, [$matches]); - } else { - if (is_array($val)) { - $fields = explode('&', $val[1]); - $model = $val[0]; - $exception = isset($val[2]) ? $val[2] : true; - } else { - $fields = ['id']; - $model = $val; - $exception = true; - } - $where = []; - $match = true; - foreach ($fields as $field) { - if (!isset($matches[$field])) { - $match = false; - break; - } else { - $where[$field] = $matches[$field]; - } - } - if ($match) { - $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); - $result = $query->failException($exception)->find(); - } - } - if (!empty($result)) { - $bind[$key] = $result; - } - } - $request->bind($bind); - } - - if (!empty($option['response'])) { - Hook::add('response_send', $option['response']); - } - - // 解析额外参数 - self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); - // 记录匹配的路由信息 - $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); - - // 检测路由after行为 - if (!empty($option['after_behavior'])) { - if ($option['after_behavior'] instanceof \Closure) { - $result = call_user_func_array($option['after_behavior'], []); - } else { - foreach ((array) $option['after_behavior'] as $behavior) { - $result = Hook::exec($behavior, ''); - if (!is_null($result)) { - break; - } - } - } - // 路由规则重定向 - if ($result instanceof Response) { - return ['type' => 'response', 'response' => $result]; - } elseif (is_array($result)) { - return $result; - } - } - - if ($route instanceof \Closure) { - // 执行闭包 - $result = ['type' => 'function', 'function' => $route]; - } elseif (0 === strpos($route, '/') || strpos($route, '://')) { - // 路由到重定向地址 - $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; - } elseif (false !== strpos($route, '\\')) { - // 路由到方法 - list($path, $var) = self::parseUrlPath($route); - $route = str_replace('/', '@', implode('/', $path)); - $method = strpos($route, '@') ? explode('@', $route) : $route; - $result = ['type' => 'method', 'method' => $method, 'var' => $var]; - } elseif (0 === strpos($route, '@')) { - // 路由到控制器 - $route = substr($route, 1); - list($route, $var) = self::parseUrlPath($route); - $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; - $request->action(array_pop($route)); - $request->controller($route ? array_pop($route) : Config::get('default_controller')); - $request->module($route ? array_pop($route) : Config::get('default_module')); - App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); - } else { - // 路由到模块/控制器/操作 - $result = self::parseModule($route); - } - // 开启请求缓存 - if ($request->isGet() && isset($option['cache'])) { - $cache = $option['cache']; - if (is_array($cache)) { - list($key, $expire, $tag) = array_pad($cache, 3, null); - } else { - $key = str_replace('|', '/', $pathinfo); - $expire = $cache; - $tag = null; - } - $request->cache($key, $expire, $tag); - } - return $result; - } - - /** - * 解析URL地址为 模块/控制器/操作 - * @access private - * @param string $url URL地址 - * @return array - */ - private static function parseModule($url) - { - list($path, $var) = self::parseUrlPath($url); - $action = array_pop($path); - $controller = !empty($path) ? array_pop($path) : null; - $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; - $method = Request::instance()->method(); - if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { - // 操作方法前缀支持 - $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; - } - // 设置当前请求的路由变量 - Request::instance()->route($var); - // 路由到模块/控制器/操作 - return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false]; - } - - /** - * 解析URL地址中的参数Request对象 - * @access private - * @param string $url 路由规则 - * @param array $var 变量 - * @return void - */ - private static function parseUrlParams($url, &$var = []) - { - if ($url) { - if (Config::get('url_param_type')) { - $var += explode('|', $url); - } else { - preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { - $var[$match[1]] = strip_tags($match[2]); - }, $url); - } - } - // 设置当前请求的参数 - Request::instance()->route($var); - } - - // 分析路由规则中的变量 - private static function parseVar($rule) + public function parseVar($rule) { // 提取路由规则中的变量 $var = []; + foreach (explode('/', $rule) as $val) { $optional = false; + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { foreach ($matches[1] as $name) { if (strpos($name, '?')) { @@ -1592,12 +1012,30 @@ class Route $optional = true; $val = substr($val, 1, -1); } + if (0 === strpos($val, ':')) { // URL变量 - $name = substr($val, 1); + $name = substr($val, 1); + if ('$' == substr($name, -1)) { + $name = substr($name, 0, -1); + } + $var[$name] = $optional ? 2 : 1; } } + return $var; } + + /** + * 设置全局的路由分组参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup + */ + public function __call($method, $args) + { + return call_user_func_array([$this->group, $method], $args); + } } diff --git a/thinkphp/library/think/Session.php b/thinkphp/library/think/Session.php index 61150bcad..f52bbc623 100644 --- a/thinkphp/library/think/Session.php +++ b/thinkphp/library/think/Session.php @@ -15,37 +15,74 @@ use think\exception\ClassNotFoundException; class Session { - protected static $prefix = ''; - protected static $init = null; + /** + * 前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 是否初始化 + * @var bool + */ + protected $init = null; + + /** + * 锁驱动 + * @var object + */ + protected $lockDriver = null; + + /** + * 锁key + * @var string + */ + protected $sessKey = 'PHPSESSID'; + + /** + * 锁超时时间 + * @var integer + */ + protected $lockTimeout = 3; + + /** + * 是否启用锁机制 + * @var bool + */ + protected $lock = false; /** * 设置或者获取session作用域(前缀) - * @param string $prefix + * @access public + * @param string $prefix * @return string|void */ - public static function prefix($prefix = '') + public function prefix($prefix = '') { - empty(self::$init) && self::boot(); + empty($this->init) && $this->boot(); + if (empty($prefix) && null !== $prefix) { - return self::$prefix; + return $this->prefix; } else { - self::$prefix = $prefix; + $this->prefix = $prefix; } } /** * session初始化 - * @param array $config + * @access public + * @param array $config * @return void * @throws \think\Exception */ - public static function init(array $config = []) + public function init(array $config = []) { if (empty($config)) { - $config = Config::get('session'); + $config = Container::get('config')->pull('session'); } + // 记录初始化信息 - App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); + Container::get('app')->log('[ SESSION ] INIT ' . var_export($config, true)); $isDoStart = false; if (isset($config['use_trans_sid'])) { ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); @@ -57,42 +94,57 @@ class Session $isDoStart = true; } - if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) { - self::$prefix = $config['prefix']; + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; } + + if (isset($config['use_lock'])) { + $this->lock = $config['use_lock']; + } + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { session_id($_REQUEST[$config['var_session_id']]); } elseif (isset($config['id']) && !empty($config['id'])) { session_id($config['id']); } + if (isset($config['name'])) { session_name($config['name']); } + if (isset($config['path'])) { session_save_path($config['path']); } + if (isset($config['domain'])) { ini_set('session.cookie_domain', $config['domain']); } + if (isset($config['expire'])) { ini_set('session.gc_maxlifetime', $config['expire']); ini_set('session.cookie_lifetime', $config['expire']); } + if (isset($config['secure'])) { ini_set('session.cookie_secure', $config['secure']); } + if (isset($config['httponly'])) { ini_set('session.cookie_httponly', $config['httponly']); } + if (isset($config['use_cookies'])) { ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); } + if (isset($config['cache_limiter'])) { session_cache_limiter($config['cache_limiter']); } + if (isset($config['cache_expire'])) { session_cache_expire($config['cache_expire']); } + if (!empty($config['type'])) { // 读取session驱动 $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); @@ -102,42 +154,48 @@ class Session throw new ClassNotFoundException('error session handler:' . $class, $class); } } + if ($isDoStart) { session_start(); - self::$init = true; + $this->init = true; } else { - self::$init = false; + $this->init = false; } } /** * session自动启动或者初始化 + * @access public * @return void */ - public static function boot() + public function boot() { - if (is_null(self::$init)) { - self::init(); - } elseif (false === self::$init) { + if (is_null($this->init)) { + $this->init(); + } elseif (false === $this->init) { if (PHP_SESSION_ACTIVE != session_status()) { session_start(); } - self::$init = true; + $this->init = true; } } /** * session设置 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function set($name, $value = '', $prefix = null) + public function set($name, $value, $prefix = null) { - empty(self::$init) && self::boot(); + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; - $prefix = !is_null($prefix) ? $prefix : self::$prefix; if (strpos($name, '.')) { // 二维数组赋值 list($name1, $name2) = explode('.', $name); @@ -151,51 +209,131 @@ class Session } else { $_SESSION[$name] = $value; } + + $this->unlock(); } /** * session获取 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) * @return mixed */ - public static function get($name = '', $prefix = null) + public function get($name = '', $prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if ('' == $name) { - // 获取全部的session - $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; - } elseif ($prefix) { - // 获取session - if (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null; - } else { - $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null; - } - } else { - if (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; - } else { - $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null; + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + + if ('' != $name) { + $name = explode('.', $name); + + foreach ($name as $val) { + if (isset($value[$val])) { + $value = $value[$val]; + } else { + $value = null; + break; + } } } + + $this->unlock(); + return $value; } + /** + * session 读写锁驱动实例化 + */ + protected function initDriver() + { + // 不在 init 方法中实例化lockDriver,是因为 init 方法不一定先于 set 或 get 方法调用 + $config = Container::get('config')->pull('session'); + + if (!empty($config['type']) && isset($config['use_lock']) && $config['use_lock']) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类及类中是否存在 lock 和 unlock 函数 + if (class_exists($class) && method_exists($class, 'lock') && method_exists($class, 'unlock')) { + $this->lockDriver = new $class($config); + } + } + + // 通过cookie获得session_id + if (isset($config['name']) && $config['name']) { + $this->sessKey = $config['name']; + } + + if (isset($config['lock_timeout']) && $config['lock_timeout'] > 0) { + $this->lockTimeout = $config['lock_timeout']; + } + } + + /** + * session 读写加锁 + * @access protected + * @return void + */ + protected function lock() + { + if (empty($this->lock)) { + return; + } + + $this->initDriver(); + + if (null !== $this->lockDriver && method_exists($this->lockDriver, 'lock')) { + $t = time(); + // 使用 session_id 作为互斥条件,即只对同一 session_id 的会话互斥。第一次请求没有 session_id + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + + do { + if (time() - $t > $this->lockTimeout) { + $this->unlock(); + } + } while (!$this->lockDriver->lock($sessID, $this->lockTimeout)); + } + } + + /** + * session 读写解锁 + * @access protected + * @return void + */ + protected function unlock() + { + if (empty($this->lock)) { + return; + } + + $this->pause(); + + if ($this->lockDriver && method_exists($this->lockDriver, 'unlock')) { + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + $this->lockDriver->unlock($sessID); + } + } + /** * session获取并删除 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) * @return mixed */ - public static function pull($name, $prefix = null) + public function pull($name, $prefix = null) { - $result = self::get($name, $prefix); + $result = $this->get($name, $prefix); + if ($result) { - self::delete($name, $prefix); + $this->delete($name, $prefix); return $result; } else { return; @@ -204,53 +342,63 @@ class Session /** * session设置 下一次请求有效 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function flash($name, $value) + public function flash($name, $value) { - self::set($name, $value); - if (!self::has('__flash__.__time__')) { - self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + $this->set($name, $value); + + if (!$this->has('__flash__.__time__')) { + $this->set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); } - self::push('__flash__', $name); + + $this->push('__flash__', $name); } /** * 清空当前请求的session数据 + * @access public * @return void */ - public static function flush() + public function flush() { - if (self::$init) { - $item = self::get('__flash__'); + if (!$this->init) { + return; + } - if (!empty($item)) { - $time = $item['__time__']; - if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { - unset($item['__time__']); - self::delete($item); - self::set('__flash__', []); - } + $item = $this->get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + $this->delete($item); + $this->set('__flash__', []); } } } /** * 删除session数据 - * @param string|array $name session名称 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function delete($name, $prefix = null) + public function delete($name, $prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if (is_array($name)) { foreach ($name as $key) { - self::delete($key, $prefix); + $this->delete($key, $prefix); } } elseif (strpos($name, '.')) { list($name1, $name2) = explode('.', $name); @@ -270,13 +418,15 @@ class Session /** * 清空session数据 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function clear($prefix = null) + public function clear($prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if ($prefix) { unset($_SESSION[$prefix]); } else { @@ -286,17 +436,20 @@ class Session /** * 判断session数据 - * @param string $name session名称 - * @param string|null $prefix + * @access public + * @param string $name session名称 + * @param string|null $prefix * @return bool */ - public static function has($name, $prefix = null) + public function has($name, $prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if (strpos($name, '.')) { // 支持数组 list($name1, $name2) = explode('.', $name); + return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); } else { return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); @@ -305,62 +458,74 @@ class Session /** * 添加数据到一个session数组 + * @access public * @param string $key * @param mixed $value * @return void */ - public static function push($key, $value) + public function push($key, $value) { - $array = self::get($key); + $array = $this->get($key); + if (is_null($array)) { $array = []; } + $array[] = $value; - self::set($key, $array); + + $this->set($key, $array); } /** * 启动session + * @access public * @return void */ - public static function start() + public function start() { session_start(); - self::$init = true; + + $this->init = true; } /** * 销毁session + * @access public * @return void */ - public static function destroy() + public function destroy() { if (!empty($_SESSION)) { $_SESSION = []; } + session_unset(); session_destroy(); - self::$init = null; + + $this->init = null; + $this->lockDriver = null; } /** * 重新生成session_id - * @param bool $delete 是否删除关联会话文件 + * @access public + * @param bool $delete 是否删除关联会话文件 * @return void */ - public static function regenerate($delete = false) + public function regenerate($delete = false) { session_regenerate_id($delete); } /** * 暂停session + * @access public * @return void */ - public static function pause() + public function pause() { // 暂停session session_write_close(); - self::$init = false; + $this->init = false; } } diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php index 02cd504b8..b97c83904 100644 --- a/thinkphp/library/think/Template.php +++ b/thinkphp/library/think/Template.php @@ -12,7 +12,6 @@ namespace think; use think\exception\TemplateNotFoundException; -use think\template\TagLib; /** * ThinkPHP分离出来的模板引擎 @@ -21,14 +20,21 @@ use think\template\TagLib; */ class Template { - // 模板变量 + /** + * 模板变量 + * @var array + */ protected $data = []; - // 引擎配置 + + /** + * 模板配置参数 + * @var array + */ protected $config = [ 'view_path' => '', // 模板路径 'view_base' => '', 'view_suffix' => 'html', // 默认模板文件后缀 - 'view_depr' => DS, + 'view_depr' => DIRECTORY_SEPARATOR, 'cache_suffix' => 'php', // 默认模板缓存后缀 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 @@ -51,20 +57,35 @@ class Template 'cache_id' => '', // 模板缓存ID 'tpl_replace_string' => [], 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 ]; - private $literal = []; - private $includeFile = []; // 记录所有模板包含的文件路径及更新时间 + /** + * 保留内容信息 + * @var array + */ + private $literal = []; + + /** + * 模板包含信息 + * @var array + */ + private $includeFile = []; + + /** + * 模板存储对象 + * @var object + */ protected $storage; /** - * 构造函数 + * 架构函数 * @access public - * @param array $config + * @param array $config */ public function __construct(array $config = []) { - $this->config['cache_path'] = TEMP_PATH; + $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/'; $this->config = array_merge($this->config, $config); $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; $this->config['taglib_end_origin'] = $this->config['taglib_end']; @@ -82,7 +103,7 @@ class Template /** * 字符串替换 避免正则混淆 * @access private - * @param string $str + * @param string $str * @return string */ private function stripPreg($str) @@ -96,8 +117,8 @@ class Template /** * 模板变量赋值 * @access public - * @param mixed $name - * @param mixed $value + * @param mixed $name + * @param mixed $value * @return void */ public function assign($name, $value = '') @@ -112,8 +133,8 @@ class Template /** * 模板引擎参数赋值 * @access public - * @param mixed $name - * @param mixed $value + * @param mixed $name + * @param mixed $value */ public function __set($name, $value) { @@ -123,8 +144,8 @@ class Template /** * 模板引擎配置项 * @access public - * @param array|string $config - * @return string|void|array + * @param array|string $config + * @return void|array */ public function config($config) { @@ -149,6 +170,7 @@ class Template return $this->data; } else { $data = $this->data; + foreach (explode('.', $name) as $key => $val) { if (isset($data[$val])) { $data = $data[$val]; @@ -157,6 +179,7 @@ class Template break; } } + return $data; } } @@ -164,9 +187,9 @@ class Template /** * 渲染模板文件 * @access public - * @param string $template 模板文件 - * @param array $vars 模板变量 - * @param array $config 模板参数 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 * @return void */ public function fetch($template, $vars = [], $config = []) @@ -174,36 +197,49 @@ class Template if ($vars) { $this->data = $vars; } + if ($config) { $this->config($config); } + + $cache = Container::get('cache'); + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 读取渲染缓存 - $cacheContent = Cache::get($this->config['cache_id']); + $cacheContent = $cache->get($this->config['cache_id']); + if (false !== $cacheContent) { echo $cacheContent; return; } } + $template = $this->parseTemplateFile($template); + if ($template) { $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { // 缓存无效 重新模板编译 $content = file_get_contents($template); $this->compiler($content, $cacheFile); } + // 页面缓存 ob_start(); ob_implicit_flush(0); + // 读取编译存储 $this->storage->read($cacheFile, $this->data); + // 获取并清空缓存 $content = ob_get_clean(); + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 缓存页面输出 - Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); + $cache->set($this->config['cache_id'], $content, $this->config['cache_time']); } + echo $content; } } @@ -211,9 +247,9 @@ class Template /** * 渲染模板内容 * @access public - * @param string $content 模板内容 - * @param array $vars 模板变量 - * @param array $config 模板参数 + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 * @return void */ public function display($content, $vars = [], $config = []) @@ -221,14 +257,18 @@ class Template if ($vars) { $this->data = $vars; } + if ($config) { $this->config($config); } + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { // 缓存无效 模板编译 $this->compiler($content, $cacheFile); } + // 读取编译存储 $this->storage->read($cacheFile, $this->data); } @@ -236,9 +276,9 @@ class Template /** * 设置布局 * @access public - * @param mixed $name 布局模板名称 false 则关闭布局 - * @param string $replace 布局模板内容替换标识 - * @return Template + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return object */ public function layout($name, $replace = '') { @@ -248,14 +288,17 @@ class Template } else { // 开启布局 $this->config['layout_on'] = true; + // 名称必须为字符串 if (is_string($name)) { $this->config['layout_name'] = $name; } + if (!empty($replace)) { $this->config['layout_item'] = $replace; } } + return $this; } @@ -263,7 +306,7 @@ class Template * 检查编译缓存是否有效 * 如果无效则需要重新编译 * @access private - * @param string $cacheFile 缓存文件名 + * @param string $cacheFile 缓存文件名 * @return boolean */ private function checkCache($cacheFile) @@ -272,23 +315,30 @@ class Template if (!$this->config['tpl_cache']) { return false; } + // 缓存文件不存在 if (!is_file($cacheFile)) { return false; } + // 读取缓存文件失败 if (!$handle = @fopen($cacheFile, "r")) { return false; } + // 读取第一行 preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + if (!isset($matches[1])) { return false; } + $includeFile = unserialize($matches[1]); + if (!is_array($includeFile)) { return false; } + // 检查模板文件是否有更新 foreach ($includeFile as $path => $time) { if (is_file($path) && filemtime($path) > $time) { @@ -296,6 +346,7 @@ class Template return false; } } + // 检查编译存储是否有效 return $this->storage->check($cacheFile, $this->config['cache_time']); } @@ -303,23 +354,24 @@ class Template /** * 检查编译缓存是否存在 * @access public - * @param string $cacheId 缓存的id + * @param string $cacheId 缓存的id * @return boolean */ public function isCache($cacheId) { if ($cacheId && $this->config['display_cache']) { // 缓存页面输出 - return Cache::has($cacheId); + return Container::get('cache')->has($cacheId); } + return false; } /** * 编译模板文件内容 * @access private - * @param string $content 模板内容 - * @param string $cacheFile 缓存文件名 + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 * @return void */ private function compiler(&$content, $cacheFile) @@ -332,6 +384,7 @@ class Template } else { // 读取布局模板 $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + if ($layoutFile) { // 替换布局的主体内容 $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); @@ -343,22 +396,28 @@ class Template // 模板解析 $this->parse($content); + if ($this->config['strip_space']) { /* 去除html空格与换行 */ $find = ['~>\s+<~', '~>(\s+\n|\r)~']; $replace = ['><', '>']; $content = preg_replace($find, $replace, $content); } + // 优化生成的php代码 $content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content); + // 模板过滤输出 $replace = $this->config['tpl_replace_string']; $content = str_replace(array_keys($replace), array_values($replace), $content); + // 添加安全代码及模板引用记录 - $content = 'includeFile) . '*/ ?>' . "\n" . $content; + $content = 'includeFile) . '*/ ?>' . "\n" . $content; // 编译存储 $this->storage->write($cacheFile, $content); + $this->includeFile = []; + return; } @@ -366,7 +425,7 @@ class Template * 模板解析入口 * 支持普通标签和TagLib解析 支持自定义标签库 * @access public - * @param string $content 要解析的模板内容 + * @param string $content 要解析的模板内容 * @return void */ public function parse(&$content) @@ -375,16 +434,22 @@ class Template if (empty($content)) { return; } + // 替换literal标签内容 $this->parseLiteral($content); + // 解析继承 $this->parseExtend($content); + // 解析布局 $this->parseLayout($content); + // 检查include语法 $this->parseInclude($content); + // 替换包含文件中literal标签内容 $this->parseLiteral($content); + // 检查PHP语法 $this->parsePhp($content); @@ -395,6 +460,7 @@ class Template // 当TAGLIB_LOAD配置为true时才会进行检测 if ($this->config['taglib_load']) { $tagLibs = $this->getIncludeTagLib($content); + if (!empty($tagLibs)) { // 对导入的TagLib进行解析 foreach ($tagLibs as $tagLibName) { @@ -402,30 +468,36 @@ class Template } } } + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 if ($this->config['taglib_pre_load']) { $tagLibs = explode(',', $this->config['taglib_pre_load']); + foreach ($tagLibs as $tag) { $this->parseTagLib($tag, $content); } } + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 $tagLibs = explode(',', $this->config['taglib_build_in']); + foreach ($tagLibs as $tag) { $this->parseTagLib($tag, $content, true); } + // 解析普通模板标签 {$tagName} $this->parseTag($content); // 还原被替换的Literal标签 $this->parseLiteral($content, true); + return; } /** * 检查PHP语法 * @access private - * @param string $content 要解析的模板内容 + * @param string $content 要解析的模板内容 * @return void * @throws \think\Exception */ @@ -433,17 +505,19 @@ class Template { // 短标签的情况要将' . "\n", $content); + // PHP语法检查 if ($this->config['tpl_deny_php'] && false !== strpos($content, 'parseAttr($matches[0]); + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { // 读取布局模板 $layoutFile = $this->parseTemplateFile($array['name']); + if ($layoutFile) { $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; // 替换布局的主体内容 @@ -466,6 +542,7 @@ class Template } else { $content = str_replace('{__NOLAYOUT__}', '', $content); } + return; } @@ -484,15 +561,19 @@ class Template $array = $this->parseAttr($match[0]); $file = $array['file']; unset($array['file']); + // 分析模板文件名并读取内容 $parseStr = $this->parseTemplateName($file); + foreach ($array as $k => $v) { // 以$开头字符串转换成模板变量 if (0 === strpos($v, '$')) { $v = $this->get(substr($v, 1)); } + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); } + $content = str_replace($match[0], $parseStr, $content); // 再次对包含文件进行模板分析 $func($parseStr); @@ -500,9 +581,9 @@ class Template unset($matches); } }; + // 替换模板中的include标签 $func($content); - return; } /** @@ -516,21 +597,26 @@ class Template $regex = $this->getRegex('extend'); $array = $blocks = $baseBlocks = []; $extend = ''; - $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { if (preg_match($regex, $template, $matches)) { if (!isset($array[$matches['name']])) { $array[$matches['name']] = 1; // 读取继承模板 $extend = $this->parseTemplateName($matches['name']); + // 递归检查继承 $func($extend); + // 取得block标签内容 $blocks = array_merge($blocks, $this->parseBlock($template)); + return; } } else { // 取得顶层模板block标签内容 $baseBlocks = $this->parseBlock($template, true); + if (empty($extend)) { // 无extend标签但有block标签的情况 $extend = $template; @@ -539,28 +625,35 @@ class Template }; $func($content); + if (!empty($extend)) { if ($baseBlocks) { $children = []; foreach ($baseBlocks as $name => $val) { $replace = $val['content']; + if (!empty($children[$name])) { // 如果包含有子block标签 foreach ($children[$name] as $key) { $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); } } + if (isset($blocks[$name])) { // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + if (!empty($val['parent'])) { // 如果不是最顶层的block标签 $parent = $val['parent']; + if (isset($blocks[$parent])) { $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); } + $blocks[$name]['content'] = $replace; $children[$parent][] = $name; + continue; } } elseif (!empty($val['parent'])) { @@ -568,16 +661,17 @@ class Template $children[$val['parent']][] = $name; $blocks[$name] = $val; } + if (!$val['parent']) { // 替换模板中的顶级block标签 $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); } } } + $content = $extend; unset($blocks, $baseBlocks); } - return; } /** @@ -590,9 +684,11 @@ class Template private function parseLiteral(&$content, $restore = false) { $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { if (!$restore) { $count = count($this->literal); + // 替换literal标签 foreach ($matches as $match) { $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); @@ -604,12 +700,13 @@ class Template foreach ($matches as $match) { $content = str_replace($match[0], $this->literal[$match[1]], $content); } + // 清空literal记录 $this->literal = []; } + unset($matches); } - return; } /** @@ -623,20 +720,24 @@ class Template { $regex = $this->getRegex('block'); $result = []; + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { $right = $keys = []; + foreach ($matches as $match) { if (empty($match['name'][0])) { if (count($right) > 0) { - $tag = array_pop($right); - $start = $tag['offset'] + strlen($tag['tag']); - $length = $match[0][1] - $start; + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + $result[$tag['name']] = [ 'begin' => $tag['tag'], 'content' => substr($content, $start, $length), 'end' => $match[0][0], 'parent' => count($right) ? end($right)['name'] : '', ]; + $keys[$tag['name']] = $match[0][1]; } } else { @@ -648,12 +749,15 @@ class Template ]; } } + unset($right, $matches); + if ($sort) { // 按block标签结束符在模板中的位置排序 array_multisort($keys, $result); } } + return $result; } @@ -670,8 +774,10 @@ class Template if (preg_match($this->getRegex('taglib'), $content, $matches)) { // 替换TagLib标签 $content = str_replace($matches[0], '', $content); + return explode(',', $matches['name']); } + return; } @@ -692,9 +798,11 @@ class Template } else { $className = '\\think\\template\\taglib\\' . ucwords($tagLib); } - /** @var Taglib $tLib */ + $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); + return; } @@ -709,12 +817,14 @@ class Template { $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; $array = []; + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $array[$match['name']] = $match['value']; } unset($matches); } + if (!empty($name) && isset($array[$name])) { return $array[$name]; } else { @@ -732,10 +842,12 @@ class Template private function parseTag(&$content) { $regex = $this->getRegex('tag'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $str = stripslashes($match[1]); $flag = substr($str, 0, 1); + switch ($flag) { case '$': // 解析模板变量 格式 {$varName} @@ -743,20 +855,24 @@ class Template if (false !== $pos = strpos($str, '?')) { $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); $name = $array[0]; + $this->parseVar($name); - $this->parseVarFunction($name); + //$this->parseVarFunction($name); $str = trim(substr($str, $pos + 1)); $this->parseVar($str); $first = substr($str, 0, 1); + if (strpos($name, ')')) { // $name为对象或是自动识别,或者含有函数 if (isset($array[1])) { $this->parseVar($array[2]); $name .= $array[1] . $array[2]; } + switch ($first) { case '?': + $this->parseVarFunction($name); $str = ''; break; case '=': @@ -767,27 +883,45 @@ class Template } } else { if (isset($array[1])) { + $express = true; $this->parseVar($array[2]); $express = $name . $array[1] . $array[2]; } else { $express = false; } + + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + // $name为数组 switch ($first) { case '?': // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx - $str = ''; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; case '=': // {$varname?='xxx'} $varname为真时才输出xxx - $str = ''; + $str = ''; break; case ':': // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx - $str = ''; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; default: - $str = ''; + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + } + $str = ''; } } } else { @@ -826,11 +960,12 @@ class Template $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; break; } + $content = str_replace($match[0], $str, $content); } + unset($matches); } - return; } /** @@ -843,10 +978,13 @@ class Template public function parseVar(&$varStr) { $varStr = trim($varStr); + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { static $_varParseList = []; + while ($matches[0]) { $match = array_pop($matches[0]); + //如果已经解析过该变量字串,则直接返回变量值 if (isset($_varParseList[$match[0]])) { $parseStr = $_varParseList[$match[0]]; @@ -854,6 +992,7 @@ class Template if (strpos($match[0], '.')) { $vars = explode('.', $match[0]); $first = array_shift($vars); + if ('$Think' == $first) { // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 $parseStr = $this->parseThinkVar($vars); @@ -868,7 +1007,8 @@ class Template } else { $params = ''; } - $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')'; + + $parseStr = 'app(\'request\')->' . $method . '(' . $params . ')'; } else { switch ($this->config['tpl_var_identify']) { case 'array': // 识别为数组 @@ -884,45 +1024,81 @@ class Template } else { $parseStr = str_replace(':', '->', $match[0]); } + $_varParseList[$match[0]] = $parseStr; } + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); } unset($matches); } - return; } /** * 对模板中使用了函数的变量进行解析 * 格式 {$varname|function1|function2=arg1,arg2} * @access public - * @param string $varStr 变量字符串 + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 * @return void */ - public function parseVarFunction(&$varStr) + public function parseVarFunction(&$varStr, $autoescape = true) { - if (false == strpos($varStr, '|')) { - return; + if (!$autoescape && false === strpos($varStr, '|')) { + return $varStr; + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { + $varStr .= '|' . $this->config['default_filter']; } + static $_varFunctionList = []; - $_key = md5($varStr); + + $_key = md5($varStr); + //如果已经解析过该变量字串,则直接返回变量值 if (isset($_varFunctionList[$_key])) { $varStr = $_varFunctionList[$_key]; } else { $varArray = explode('|', $varStr); + // 取得变量名称 - $name = array_shift($varArray); + $name = trim(array_shift($varArray)); + // 对变量使用函数 $length = count($varArray); + // 取得模板禁止使用函数列表 $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + for ($i = 0; $i < $length; $i++) { $args = explode('=', $varArray[$i], 2); + // 模板函数过滤 $fun = trim($args[0]); - switch ($fun) { + if (in_array($fun, $template_deny_funs)) { + continue; + } + + switch (strtolower($fun)) { + case 'raw': + continue; + case 'date': + $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; case 'default': // 特殊模板函数 if (false === strpos($name, '(')) { $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; @@ -931,26 +1107,25 @@ class Template } break; default: // 通用模板函数 - if (!in_array($fun, $template_deny_funs)) { - if (isset($args[1])) { - if (strstr($args[1], '###')) { - $args[1] = str_replace('###', $name, $args[1]); - $name = "$fun($args[1])"; - } else { - $name = "$fun($name,$args[1])"; - } + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; } else { - if (!empty($args[0])) { - $name = "$fun($name)"; - } + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; } } } } + $_varFunctionList[$_key] = $name; $varStr = $name; } - return; + return $varStr; } /** @@ -964,37 +1139,38 @@ class Template { $type = strtoupper(trim(array_shift($vars))); $param = implode('.', $vars); + if ($vars) { switch ($type) { case 'SERVER': - $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->server(\'' . $param . '\')'; break; case 'GET': - $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->get(\'' . $param . '\')'; break; case 'POST': - $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->post(\'' . $param . '\')'; break; case 'COOKIE': - $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; + $parseStr = 'app(\'cookie\')->get(\'' . $param . '\')'; break; case 'SESSION': - $parseStr = '\\think\\Session::get(\'' . $param . '\')'; + $parseStr = 'app(\'session\')->get(\'' . $param . '\')'; break; case 'ENV': - $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->env(\'' . $param . '\')'; break; case 'REQUEST': - $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->request(\'' . $param . '\')'; break; case 'CONST': $parseStr = strtoupper($param); break; case 'LANG': - $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; + $parseStr = 'app(\'lang\')->get(\'' . $param . '\')'; break; case 'CONFIG': - $parseStr = '\\think\\Config::get(\'' . $param . '\')'; + $parseStr = 'app(\'config\')->get(\'' . $param . '\')'; break; default: $parseStr = '\'\''; @@ -1006,7 +1182,7 @@ class Template $parseStr = "date('Y-m-d g:i a',time())"; break; case 'VERSION': - $parseStr = 'THINK_VERSION'; + $parseStr = 'app()->version()'; break; case 'LDELIM': $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; @@ -1022,6 +1198,7 @@ class Template } } } + return $parseStr; } @@ -1035,20 +1212,25 @@ class Template { $array = explode(',', $templateName); $parseStr = ''; + foreach ($array as $templateName) { if (empty($templateName)) { continue; } + if (0 === strpos($templateName, '$')) { //支持加载变量文件名 $templateName = $this->get(substr($templateName, 1)); } + $template = $this->parseTemplateFile($templateName); + if ($template) { // 获取模板文件内容 $parseStr .= file_get_contents($template); } } + return $parseStr; } @@ -1064,23 +1246,27 @@ class Template if (strpos($template, '@')) { list($module, $template) = explode('@', $template); } + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $this->config['view_depr'], $template); } else { $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); } + if ($this->config['view_base']) { - $module = isset($module) ? $module : Request::instance()->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); + $module = isset($module) ? $module : Container::get('request')->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; + $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; } - $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.')); + + $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); } if (is_file($template)) { // 记录模板文件的更新时间 $this->includeFile[$template] = filemtime($template); + return $template; } else { throw new TemplateNotFoundException('template not exists:' . $template, $template); @@ -1099,6 +1285,7 @@ class Template if ('tag' == $tagName) { $begin = $this->config['tpl_begin']; $end = $this->config['tpl_end']; + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; } else { @@ -1108,6 +1295,7 @@ class Template $begin = $this->config['taglib_begin']; $end = $this->config['taglib_end']; $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + switch ($tagName) { case 'block': if ($single) { @@ -1146,6 +1334,7 @@ class Template break; } } + return '/' . $regex . '/is'; } } diff --git a/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php index 2b5fd7f78..e7cf75d3f 100644 --- a/thinkphp/library/think/Url.php +++ b/thinkphp/library/think/Url.php @@ -13,39 +13,65 @@ namespace think; class Url { - // 生成URL地址的root - protected static $root; - protected static $bindCheck; + /** + * ROOT地址 + * @var string + */ + protected $root; + + /** + * 绑定检查 + * @var bool + */ + protected $bindCheck; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + + if (is_file($app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $app['route']->setName(include $app->getRuntimePath() . 'route.php'); + } + } /** * URL生成 支持路由反射 - * @param string $url 路由地址 - * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] - * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 - * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @access public + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 * @return string */ - public static function build($url = '', $vars = '', $suffix = true, $domain = false) + public function build($url = '', $vars = '', $suffix = true, $domain = false) { - if (false === $domain && Route::rules('domain')) { - $domain = true; - } // 解析URL if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { // [name] 表示使用路由命名标识生成URL $name = substr($url, 1, $pos - 1); $url = 'name' . substr($url, $pos + 1); } + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { $info = parse_url($url); $url = !empty($info['path']) ? $info['path'] : ''; + if (isset($info['fragment'])) { // 解析锚点 $anchor = $info['fragment']; + if (false !== strpos($anchor, '?')) { // 解析参数 list($anchor, $info['query']) = explode('?', $anchor, 2); } + if (false !== strpos($anchor, '@')) { // 解析域名 list($anchor, $domain) = explode('@', $anchor, 2); @@ -63,23 +89,28 @@ class Url } if ($url) { - $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + $rule = $this->app['route']->getName(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + if (is_null($rule) && isset($info['query'])) { - $rule = Route::name($url); + $rule = $this->app['route']->getName($url); // 解析地址里面参数 合并到vars parse_str($info['query'], $params); $vars = array_merge($params, $vars); unset($info['query']); } } - if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars)) { // 匹配路由命名标识 $url = $match[0]; - // 替换可选分隔符 - $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); + if (!empty($match[1])) { - $domain = $match[1]; + $host = $this->app['config']->get('app_host') ?: $this->app['request']->host(); + if ($domain || $match[1] != $host) { + $domain = $match[1]; + } } + if (!is_null($match[2])) { $suffix = $match[2]; } @@ -87,14 +118,16 @@ class Url throw new \InvalidArgumentException('route name not exists:' . $name); } else { // 检查别名路由 - $alias = Route::rules('alias'); + $alias = $this->app['route']->getAlias(); $matchAlias = false; + if ($alias) { // 别名路由解析 foreach ($alias as $key => $val) { if (is_array($val)) { $val = $val[0]; } + if (0 === strpos($url, $val)) { $url = $key . substr($url, strlen($val)); $matchAlias = true; @@ -102,10 +135,12 @@ class Url } } } + if (!$matchAlias) { // 路由标识不存在 直接解析 - $url = self::parseUrl($url, $domain); + $url = $this->parseUrl($url); } + if (isset($info['query'])) { // 解析地址里面参数 合并到vars parse_str($info['query'], $params); @@ -114,31 +149,37 @@ class Url } // 检测URL绑定 - if (!self::$bindCheck) { - $type = Route::getBind('type'); - if ($type) { - $bind = Route::getBind($type); - if ($bind && 0 === strpos($url, $bind)) { - $url = substr($url, strlen($bind) + 1); - } + if (!$this->bindCheck) { + $bind = $this->app['route']->getBind(); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); } + } // 还原URL分隔符 - $depr = Config::get('pathinfo_depr'); + $depr = $this->app['config']->get('pathinfo_depr'); $url = str_replace('/', $depr, $url); // URL后缀 - $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix); + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + // 锚点 $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 if (!empty($vars)) { // 添加参数 - if (Config::get('url_common_param')) { + if ($this->app['config']->get('url_common_param')) { $vars = http_build_query($vars); $url .= $suffix . '?' . $vars . $anchor; } else { - $paramType = Config::get('url_param_type'); + $paramType = $this->app['config']->get('url_param_type'); + foreach ($vars as $var => $val) { if ('' !== trim($val)) { if ($paramType) { @@ -148,24 +189,29 @@ class Url } } } + $url .= $suffix . $anchor; } } else { $url .= $suffix . $anchor; } - // 检测域名 - $domain = self::parseDomain($url, $domain); - // URL组装 - $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); - self::$bindCheck = false; + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + $url = $domain . rtrim($this->root ?: $this->app['request']->root(), '/') . '/' . ltrim($url, '/'); + + $this->bindCheck = false; + return $url; } // 直接解析URL地址 - protected static function parseUrl($url, &$domain) + protected function parseUrl($url) { - $request = Request::instance(); + $request = $this->app['request']; + if (0 === strpos($url, '/')) { // 直接作为路由地址解析 $url = substr($url, 1); @@ -177,42 +223,11 @@ class Url $url = substr($url, 1); } else { // 解析到 模块/控制器/操作 - $module = $request->module(); - $domains = Route::rules('domain'); - if (true === $domain && 2 == substr_count($url, '/')) { - $current = $request->host(); - $match = []; - $pos = []; - foreach ($domains as $key => $item) { - if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) { - $pos[$key] = strlen($item['[bind]'][0]) + 1; - $match[] = $key; - $module = ''; - } - } - if ($match) { - $domain = current($match); - foreach ($match as $item) { - if (0 === strpos($current, $item)) { - $domain = $item; - } - } - self::$bindCheck = true; - $url = substr($url, $pos[$domain]); - } - } elseif ($domain) { - if (isset($domains[$domain]['[bind]'][0])) { - $bindModule = $domains[$domain]['[bind]'][0]; - if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { - $module = ''; - } - } - } - $module = $module ? $module . '/' : ''; - + $module = $request->module(); + $module = $module ? $module . '/' : ''; $controller = $request->controller(); + if ('' == $url) { - // 空字符串输出当前的 模块/控制器/操作 $action = $request->action(); } else { $path = explode('/', $url); @@ -220,28 +235,33 @@ class Url $controller = empty($path) ? $controller : array_pop($path); $module = empty($path) ? $module : array_pop($path) . '/'; } - if (Config::get('url_convert')) { + + if ($this->app['config']->get('url_convert')) { $action = strtolower($action); $controller = Loader::parseName($controller); } + $url = $module . $controller . '/' . $action; } + return $url; } // 检测域名 - protected static function parseDomain(&$url, $domain) + protected function parseDomain(&$url, $domain) { if (!$domain) { return ''; } - $request = Request::instance(); - $rootDomain = Config::get('url_domain_root'); - if (true === $domain) { - // 自动判断域名 - $domain = Config::get('app_host') ?: $request->host(); - $domains = Route::rules('domain'); + if (true === $domain) { + + // 自动判断域名 + $domain = $this->app['config']->get('app_host') ?: $this->app['request']->host(); + $rootDomain = $this->app['config']->get('url_domain_root'); + + $domains = $this->app['route']->getDomains(); + if ($domains) { $route_domain = array_keys($domains); foreach ($route_domain as $domain_prefix) { @@ -251,6 +271,7 @@ class Url if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { $url = ltrim($url, $rule); $domain = $key; + // 生成对应子域名 if (!empty($rootDomain)) { $domain .= $rootDomain; @@ -260,74 +281,76 @@ class Url if (!empty($rootDomain)) { $domain .= $rootDomain; } + break; } } } } } - - } else { - if (empty($rootDomain)) { - $host = Config::get('app_host') ?: $request->host(); - $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; - } - if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { - $domain .= '.' . $rootDomain; - } } + if (false !== strpos($domain, '://')) { $scheme = ''; } else { - $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://'; + $scheme = $this->app['request']->isSsl() || $this->app['config']->get('is_https') ? 'https://' : 'http://'; + } + return $scheme . $domain; } // 解析URL后缀 - protected static function parseSuffix($suffix) + protected function parseSuffix($suffix) { if ($suffix) { - $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix; + $suffix = true === $suffix ? $this->app['config']->get('url_html_suffix') : $suffix; + if ($pos = strpos($suffix, '|')) { $suffix = substr($suffix, 0, $pos); } } + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; } // 匹配路由地址 - public static function getRuleUrl($rule, &$vars = []) + public function getRuleUrl($rule, &$vars = []) { foreach ($rule as $item) { list($url, $pattern, $domain, $suffix) = $item; if (empty($pattern)) { - return [$url, $domain, $suffix]; + return [rtrim($url, '$'), $domain, $suffix]; } - $type = Config::get('url_common_param'); + + $type = $this->app['config']->get('url_common_param'); + foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); unset($vars[$key]); + $result = [$url, $domain, $suffix]; } elseif (2 == $val) { - $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>'], '', $url); $result = [$url, $domain, $suffix]; } else { break; } } + if (isset($result)) { return $result; } } + return false; } // 指定当前生成URL地址的root - public static function root($root) + public function root($root) { - self::$root = $root; - Request::instance()->root($root); + $this->root = $root; + $this->app['request']->root($root); } } diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php index 353561be0..e078c88c1 100644 --- a/thinkphp/library/think/Validate.php +++ b/thinkphp/library/think/Validate.php @@ -12,31 +12,50 @@ namespace think; use think\exception\ClassNotFoundException; +use think\validate\ValidateRule; class Validate { - // 实例 - protected static $instance; - // 自定义的验证类型 + /** + * 自定义验证类型 + * @var array + */ protected static $type = []; - // 验证类型别名 + /** + * 验证类型别名 + * @var array + */ protected $alias = [ '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', ]; - // 当前验证的规则 + /** + * 当前验证规则 + * @var array + */ protected $rule = []; - // 验证提示信息 + /** + * 验证提示信息 + * @var array + */ protected $message = []; - // 验证字段描述 + + /** + * 验证字段描述 + * @var array + */ protected $field = []; - // 验证规则默认提示信息 + /** + * 默认规则提示 + * @var array + */ protected static $typeMsg = [ 'require' => ':attribute require', + 'must' => ':attribute must', 'number' => ':attribute must be numeric', 'integer' => ':attribute must be integer', 'float' => ':attribute must be float', @@ -87,29 +106,86 @@ class Validate 'fileMime' => 'mimetype to upload is not allowed', ]; - // 当前验证场景 + /** + * 当前验证场景 + * @var array + */ protected $currentScene = null; - // 正则表达式 regex = ['zip'=>'\d{6}',...] - protected $regex = []; + /** + * 内置正则验证规则 + * @var array + */ + protected $regex = [ + 'alpha' => '/^[A-Za-z]+$/', + 'alphaNum' => '/^[A-Za-z0-9]+$/', + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3-9][0-9]\d{8}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/', + 'zip' => '/\d{6}/', + ]; - // 验证场景 scene = ['edit'=>'name1,name2,...'] + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; + + /** + * 验证场景定义 + * @var array + */ protected $scene = []; - // 验证失败错误信息 + /** + * 验证失败错误信息 + * @var array + */ protected $error = []; - // 批量验证 + /** + * 是否批量验证 + * @var bool + */ protected $batch = false; /** - * 构造函数 - * @access public - * @param array $rules 验证规则 - * @param array $message 验证提示信息 - * @param array $field 验证字段描述信息 + * 场景需要验证的规则 + * @var array */ - public function __construct(array $rules = [], $message = [], $field = []) + protected $only = []; + + /** + * 场景需要移除的验证规则 + * @var array + */ + protected $remove = []; + + /** + * 场景需要追加的验证规则 + * @var array + */ + protected $append = []; + + /** + * 架构函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], array $message = [], array $field = []) { $this->rule = array_merge($this->rule, $rules); $this->message = array_merge($this->message, $message); @@ -117,43 +193,43 @@ class Validate } /** - * 实例化验证 + * 创建一个验证器类 * @access public - * @param array $rules 验证规则 - * @param array $message 验证提示信息 - * @param array $field 验证字段描述信息 - * @return Validate + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 */ - public static function make($rules = [], $message = [], $field = []) + public static function make(array $rules = [], array $message = [], array $field = []) { - if (is_null(self::$instance)) { - self::$instance = new self($rules, $message, $field); - } - return self::$instance; + return new self($rules, $message, $field); } /** * 添加字段验证规则 * @access protected - * @param string|array $name 字段名称或者规则数组 - * @param mixed $rule 验证规则 - * @return Validate + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则或者字段描述信息 + * @return $this */ public function rule($name, $rule = '') { if (is_array($name)) { $this->rule = array_merge($this->rule, $name); + if (is_array($rule)) { + $this->field = array_merge($this->field, $rule); + } } else { $this->rule[$name] = $rule; } + return $this; } /** - * 注册验证(类型)规则 + * 注册扩展验证(类型)规则 * @access public - * @param string $type 验证规则类型 - * @param mixed $callback callback方法(或闭包) + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) * @return void */ public static function extend($type, $callback = null) @@ -167,9 +243,9 @@ class Validate /** * 设置验证规则的默认提示信息 - * @access protected - * @param string|array $type 验证规则类型名称或者数组 - * @param string $msg 验证提示信息 + * @access public + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 * @return void */ public static function setTypeMsg($type, $msg = null) @@ -184,8 +260,8 @@ class Validate /** * 设置提示信息 * @access public - * @param string|array $name 字段名称 - * @param string $message 提示信息 + * @param string|array $name 字段名称 + * @param string $message 提示信息 * @return Validate */ public function message($name, $message = '') @@ -195,59 +271,119 @@ class Validate } else { $this->message[$name] = $message; } + return $this; } /** * 设置验证场景 * @access public - * @param string|array $name 场景名或者场景设置数组 - * @param mixed $fields 要验证的字段 - * @return Validate + * @param string $name 场景名 + * @return $this */ - public function scene($name, $fields = null) + public function scene($name) { - if (is_array($name)) { - $this->scene = array_merge($this->scene, $name); - }if (is_null($fields)) { - // 设置当前场景 - $this->currentScene = $name; - } else { - // 设置验证场景 - $this->scene[$name] = $fields; - } + // 设置当前场景 + $this->currentScene = $name; + return $this; } /** * 判断是否存在某个验证场景 * @access public - * @param string $name 场景名 + * @param string $name 场景名 * @return bool */ public function hasScene($name) { - return isset($this->scene[$name]); + return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); } /** * 设置批量验证 * @access public - * @param bool $batch 是否批量验证 - * @return Validate + * @param bool $batch 是否批量验证 + * @return $this */ public function batch($batch = true) { $this->batch = $batch; + + return $this; + } + + /** + * 指定需要验证的字段列表 + * @access public + * @param array $fields 字段名 + * @return $this + */ + public function only($fields) + { + $this->only = $fields; + + return $this; + } + + /** + * 移除某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 true 移除所有规则 + * @return $this + */ + public function remove($field, $rule = true) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->remove[$field] = $rule; + } + + return $this; + } + + /** + * 追加某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 + * @return $this + */ + public function append($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + $this->append($key, $rule); + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->append[$field] = $rule; + } + return $this; } /** * 数据自动验证 * @access public - * @param array $data 数据 - * @param mixed $rules 验证规则 - * @param string $scene 验证场景 + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 * @return bool */ public function check($data, $rules = [], $scene = '') @@ -259,37 +395,17 @@ class Validate $rules = $this->rule; } - // 分析验证规则 - $scene = $this->getScene($scene); - if (is_array($scene)) { - // 处理场景验证字段 - $change = []; - $array = []; - foreach ($scene as $k => $val) { - if (is_numeric($k)) { - $array[] = $val; - } else { - $array[] = $k; - $change[$k] = $val; - } + // 获取场景定义 + $this->getScene($scene); + + foreach ($this->append as $key => $rule) { + if (!isset($rules[$key])) { + $rules[$key] = $rule; } } - foreach ($rules as $key => $item) { - // field => rule1|rule2... field=>['rule1','rule2',...] - if (is_numeric($key)) { - // [field,rule1|rule2,msg1|msg2] - $key = $item[0]; - $rule = $item[1]; - if (isset($item[2])) { - $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; - } else { - $msg = []; - } - } else { - $rule = $item; - $msg = []; - } + foreach ($rules as $key => $rule) { + // field => 'rule1|rule2...' field => ['rule1','rule2',...] if (strpos($key, '|')) { // 字段|描述 用于指定属性名称 list($key, $title) = explode('|', $key); @@ -298,17 +414,8 @@ class Validate } // 场景检测 - if (!empty($scene)) { - if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { - continue; - } elseif (is_array($scene)) { - if (!in_array($key, $array)) { - continue; - } elseif (isset($change[$key])) { - // 重载某个验证规则 - $rule = $change[$key]; - } - } + if (!empty($this->only) && !in_array($key, $this->only)) { + continue; } // 获取数据 支持二维数组 @@ -316,10 +423,12 @@ class Validate // 字段验证 if ($rule instanceof \Closure) { - // 匿名函数验证 支持传入当前字段和所有字段两个数据 $result = call_user_func_array($rule, [$value, $data]); + } elseif ($rule instanceof ValidateRule) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); } else { - $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + $result = $this->checkItem($key, $value, $rule, $data, $title); } if (true !== $result) { @@ -337,20 +446,23 @@ class Validate } } } + return !empty($this->error) ? false : true; } /** * 根据验证规则验证数据 - * @access protected + * @access public * @param mixed $value 字段值 * @param mixed $rules 验证规则 * @return bool */ - protected function checkRule($value, $rules) + public function checkRule($value, $rules) { if ($rules instanceof \Closure) { return call_user_func_array($rules, [$value]); + } elseif ($rules instanceof ValidateRule) { + $rules = $rules->getRule(); } elseif (is_string($rules)) { $rules = explode('|', $rules); } @@ -378,20 +490,31 @@ class Validate /** * 验证单个字段规则 * @access protected - * @param string $field 字段名 - * @param mixed $value 字段值 - * @param mixed $rules 验证规则 - * @param array $data 数据 - * @param string $title 字段描述 - * @param array $msg 提示信息 + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 * @return mixed */ protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) { + if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { + // 字段已经移除 无需验证 + return true; + } + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] if (is_string($rules)) { $rules = explode('|', $rules); } + + if (isset($this->append[$field])) { + // 追加额外的验证规则 + $rules = array_merge($rules, $this->append[$field]); + } + $i = 0; foreach ($rules as $key => $rule) { if ($rule instanceof \Closure) { @@ -401,8 +524,15 @@ class Validate // 判断验证类型 list($type, $rule, $info) = $this->getValidateType($key, $rule); - // 如果不是require 有数据才会行验证 - if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { + + } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) { + // 规则已经移除 + $i++; + continue; + } + + if ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { // 验证类型 $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; // 验证数据 @@ -414,7 +544,7 @@ class Validate if (false === $result) { // 验证失败 返回错误信息 - if (isset($msg[$i])) { + if (!empty($msg[$i])) { $message = $msg[$i]; if (is_string($message) && strpos($message, '{%') === 0) { $message = Lang::get(substr($message, 2, -1)); @@ -422,16 +552,22 @@ class Validate } else { $message = $this->getRuleMsg($field, $title, $info, $rule); } + return $message; } elseif (true !== $result) { // 返回自定义错误信息 if (is_string($result) && false !== strpos($result, ':')) { - $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); + $result = str_replace( + [':attribute', ':rule'], + [$title, (string) $rule], + $result); } + return $result; } $i++; } + return $result; } @@ -470,14 +606,14 @@ class Validate /** * 验证是否和某个字段的值一致 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @param string $field 字段名 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 * @return bool */ - protected function confirm($value, $rule, $data, $field = '') + public function confirm($value, $rule, $data = [], $field = '') { if ('' == $rule) { if (strpos($field, '_confirm')) { @@ -486,101 +622,110 @@ class Validate $rule = $field . '_confirm'; } } + return $this->getDataValue($data, $rule) === $value; } /** * 验证是否和某个字段的值是否不同 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function different($value, $rule, $data) + public function different($value, $rule, $data = []) { return $this->getDataValue($data, $rule) != $value; } /** * 验证是否大于等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function egt($value, $rule, $data) + public function egt($value, $rule, $data = []) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value >= $val; + return $value >= $this->getDataValue($data, $rule); } /** * 验证是否大于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function gt($value, $rule, $data) + public function gt($value, $rule, $data) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value > $val; + return $value > $this->getDataValue($data, $rule); } /** * 验证是否小于等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function elt($value, $rule, $data) + public function elt($value, $rule, $data = []) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value <= $val; + return $value <= $this->getDataValue($data, $rule); } /** * 验证是否小于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function lt($value, $rule, $data) + public function lt($value, $rule, $data = []) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value < $val; + return $value < $this->getDataValue($data, $rule); } /** * 验证是否等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function eq($value, $rule) + public function eq($value, $rule) { return $value == $rule; } /** - * 验证字段值是否为有效格式 - * @access protected - * @param mixed $value 字段值 - * @param string $rule 验证规则 - * @param array $data 验证数据 + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function is($value, $rule, $data = []) + public function must($value, $rule = null) { - switch ($rule) { + return !empty($value) || '0' == $value; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + public function is($value, $rule, $data = []) + { + switch (Loader::parseName($rule, 1, false)) { case 'require': // 必须 $result = !empty($value) || '0' == $value; @@ -593,65 +738,18 @@ class Validate // 是否是一个有效日期 $result = false !== strtotime($value); break; - case 'alpha': - // 只允许字母 - $result = $this->regex($value, '/^[A-Za-z]+$/'); - break; - case 'alphaNum': - // 只允许字母和数字 - $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); - break; - case 'alphaDash': - // 只允许字母、数字和下划线 破折号 - $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); - break; - case 'chs': - // 只允许汉字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); - break; - case 'chsAlpha': - // 只允许汉字、字母 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); - break; - case 'chsAlphaNum': - // 只允许汉字、字母和数字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); - break; - case 'chsDash': - // 只允许汉字、字母、数字和下划线_及破折号- - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); - break; case 'activeUrl': // 是否为有效的网址 $result = checkdnsrr($value); break; - case 'ip': - // 是否为IP地址 - $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); - break; - case 'url': - // 是否为一个URL地址 - $result = $this->filter($value, FILTER_VALIDATE_URL); - break; - case 'float': - // 是否为float - $result = $this->filter($value, FILTER_VALIDATE_FLOAT); + case 'boolean': + case 'bool': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; case 'number': $result = is_numeric($value); break; - case 'integer': - // 是否为整型 - $result = $this->filter($value, FILTER_VALIDATE_INT); - break; - case 'email': - // 是否为邮箱地址 - $result = $this->filter($value, FILTER_VALIDATE_EMAIL); - break; - case 'boolean': - // 是否为布尔值 - $result = in_array($value, [true, false, 0, 1, '0', '1'], true); - break; case 'array': // 是否为数组 $result = is_array($value); @@ -669,11 +767,15 @@ class Validate if (isset(self::$type[$rule])) { // 注册的验证规则 $result = call_user_func_array(self::$type[$rule], [$value]); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); } else { // 正则验证 $result = $this->regex($value, $rule); } } + return $result; } @@ -694,42 +796,44 @@ class Validate /** * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function activeUrl($value, $rule) + public function activeUrl($value, $rule = 'MX') { if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { $rule = 'MX'; } + return checkdnsrr($value, $rule); } /** * 验证是否有效IP - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 ipv4 ipv6 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 * @return bool */ - protected function ip($value, $rule) + public function ip($value, $rule = 'ipv4') { if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; } + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); } /** * 验证上传文件后缀 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function fileExt($file, $rule) + public function fileExt($file, $rule) { if (is_array($file)) { foreach ($file as $item) { @@ -747,12 +851,12 @@ class Validate /** * 验证上传文件类型 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function fileMime($file, $rule) + public function fileMime($file, $rule) { if (is_array($file)) { foreach ($file as $item) { @@ -770,12 +874,12 @@ class Validate /** * 验证上传文件大小 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function fileSize($file, $rule) + public function fileSize($file, $rule) { if (is_array($file)) { foreach ($file as $item) { @@ -793,30 +897,36 @@ class Validate /** * 验证图片的宽高及类型 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function image($file, $rule) + public function image($file, $rule) { if (!($file instanceof File)) { return false; } + if ($rule) { - $rule = explode(',', $rule); + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { $imageType = strtolower($rule[2]); + if ('jpeg' == $imageType) { $imageType = 'jpg'; } + if (image_type_to_extension($type, false) != $imageType) { return false; } } list($w, $h) = $rule; + return $w == $width && $h == $height; } else { return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); @@ -825,25 +935,25 @@ class Validate /** * 验证请求类型 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function method($value, $rule) + public function method($value, $rule) { - $method = Request::instance()->method(); + $method = Container::get('request')->method(); return strtoupper($rule) == $method; } /** * 验证时间和日期是否符合指定格式 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function dateFormat($value, $rule) + public function dateFormat($value, $rule) { $info = date_parse_from_format($rule, $value); return 0 == $info['warning_count'] && 0 == $info['error_count']; @@ -851,50 +961,52 @@ class Validate /** * 验证是否唯一 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 - * @param array $data 数据 - * @param string $field 验证字段名 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 * @return bool */ - protected function unique($value, $rule, $data, $field) + public function unique($value, $rule, $data, $field) { if (is_string($rule)) { $rule = explode(',', $rule); } + if (false !== strpos($rule[0], '\\')) { // 指定模型类 $db = new $rule[0]; } else { try { - $db = Loader::model($rule[0]); + $db = Container::get('app')->model($rule[0]); } catch (ClassNotFoundException $e) { $db = Db::name($rule[0]); } } + $key = isset($rule[1]) ? $rule[1] : $field; if (strpos($key, '^')) { // 支持多个字段验证 $fields = explode('^', $key); foreach ($fields as $key) { - $map[$key] = $data[$key]; + $map[] = [$key, '=', $data[$key]]; } - } elseif (strpos($key, '=')) { - parse_str($key, $map); } else { - $map[$key] = $data[$field]; + $map[] = [$key, '=', $data[$field]]; } - $pk = isset($rule[3]) ? $rule[3] : $db->getPk(); + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + if (is_string($pk)) { if (isset($rule[2])) { - $map[$pk] = ['neq', $rule[2]]; + $map[] = [$pk, '<>', $rule[2]]; } elseif (isset($data[$pk])) { - $map[$pk] = ['neq', $data[$pk]]; + $map[] = [$pk, '<>', $data[$pk]]; } } + if ($db->where($map)->field($pk)->find()) { return false; } @@ -903,25 +1015,25 @@ class Validate /** * 使用行为类验证 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return mixed */ - protected function behavior($value, $rule, $data) + public function behavior($value, $rule, $data) { - return Hook::exec($rule, '', $data); + return Container::get('hook')->exec($rule, $data); } /** * 使用filter_var方式验证 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function filter($value, $rule) + public function filter($value, $rule) { if (is_string($rule) && strpos($rule, ',')) { list($rule, $param) = explode(',', $rule); @@ -931,20 +1043,22 @@ class Validate } else { $param = null; } + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); } /** * 验证某个字段等于某个值的时候必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function requireIf($value, $rule, $data) + public function requireIf($value, $rule, $data) { list($field, $val) = explode(',', $rule); + if ($this->getDataValue($data, $field) == $val) { return !empty($value) || '0' == $value; } else { @@ -954,15 +1068,16 @@ class Validate /** * 通过回调方法验证某个字段是否必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function requireCallback($value, $rule, $data) + public function requireCallback($value, $rule, $data) { $result = call_user_func_array($rule, [$value, $data]); + if ($result) { return !empty($value) || '0' == $value; } else { @@ -972,15 +1087,16 @@ class Validate /** * 验证某个字段有值的情况下必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function requireWith($value, $rule, $data) + public function requireWith($value, $rule, $data) { $val = $this->getDataValue($data, $rule); + if (!empty($val)) { return !empty($value) || '0' == $value; } else { @@ -990,68 +1106,70 @@ class Validate /** * 验证是否在范围内 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function in($value, $rule) + public function in($value, $rule) { return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证是否不在某个范围 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function notIn($value, $rule) + public function notIn($value, $rule) { return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * between验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function between($value, $rule) + public function between($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } list($min, $max) = $rule; + return $value >= $min && $value <= $max; } /** * 使用notbetween验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function notBetween($value, $rule) + public function notBetween($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } list($min, $max) = $rule; + return $value < $min || $value > $max; } /** * 验证数据长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function length($value, $rule) + public function length($value, $rule) { if (is_array($value)) { $length = count($value); @@ -1073,12 +1191,12 @@ class Validate /** * 验证数据最大长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function max($value, $rule) + public function max($value, $rule) { if (is_array($value)) { $length = count($value); @@ -1087,17 +1205,18 @@ class Validate } else { $length = mb_strlen((string) $value); } + return $length <= $rule; } /** * 验证数据最小长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function min($value, $rule) + public function min($value, $rule) { if (is_array($value)) { $length = count($value); @@ -1106,46 +1225,49 @@ class Validate } else { $length = mb_strlen((string) $value); } + return $length >= $rule; } /** * 验证日期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function after($value, $rule) + public function after($value, $rule) { return strtotime($value) >= strtotime($rule); } /** * 验证日期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function before($value, $rule) + public function before($value, $rule) { return strtotime($value) <= strtotime($rule); } /** * 验证有效期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function expire($value, $rule) + public function expire($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } + list($start, $end) = $rule; + if (!is_numeric($start)) { $start = strtotime($start); } @@ -1153,76 +1275,83 @@ class Validate if (!is_numeric($end)) { $end = strtotime($end); } + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; } /** * 验证IP许可 - * @access protected - * @param string $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 * @return mixed */ - protected function allowIp($value, $rule) + public function allowIp($value, $rule) { - return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证IP禁用 - * @access protected - * @param string $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 * @return mixed */ - protected function denyIp($value, $rule) + public function denyIp($value, $rule) { - return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 使用正则验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 正则规则或者预定义正则名 - * @return mixed + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return bool */ - protected function regex($value, $rule) + public function regex($value, $rule) { if (isset($this->regex[$rule])) { $rule = $this->regex[$rule]; } + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { // 不是正则表达式则两端补上/ $rule = '/^' . $rule . '$/'; } + return 1 === preg_match($rule, (string) $value); } /** * 验证表单令牌 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function token($value, $rule, $data) + public function token($value, $rule, $data) { - $rule = !empty($rule) ? $rule : '__token__'; - if (!isset($data[$rule]) || !Session::has($rule)) { + $rule = !empty($rule) ? $rule : '__token__'; + $session = Container::get('session'); + + if (!isset($data[$rule]) || !$session->has($rule)) { // 令牌数据无效 return false; } // 令牌验证 - if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { + if (isset($data[$rule]) && $session->get($rule) === $data[$rule]) { // 防止重复提交 - Session::delete($rule); // 验证完成销毁session + $session->delete($rule); // 验证完成销毁session return true; } + // 开启TOKEN重置 - Session::delete($rule); + $session->delete($rule); + return false; } @@ -1235,8 +1364,8 @@ class Validate /** * 获取数据值 * @access protected - * @param array $data 数据 - * @param string $key 数据标识 支持二维 + * @param array $data 数据 + * @param string $key 数据标识 支持二维 * @return mixed */ protected function getDataValue($data, $key) @@ -1250,20 +1379,23 @@ class Validate } else { $value = isset($data[$key]) ? $data[$key] : null; } + return $value; } /** * 获取验证规则的错误提示信息 * @access protected - * @param string $attribute 字段英文名 - * @param string $title 字段描述名 - * @param string $type 验证规则名称 - * @param mixed $rule 验证规则数据 + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 * @return string */ protected function getRuleMsg($attribute, $title, $type, $rule) { + $lang = Container::get('lang'); + if (isset($this->message[$attribute . '.' . $type])) { $msg = $this->message[$attribute . '.' . $type]; } elseif (isset($this->message[$attribute][$type])) { @@ -1275,13 +1407,13 @@ class Validate } elseif (0 === strpos($type, 'require')) { $msg = self::$typeMsg['require']; } else { - $msg = $title . Lang::get('not conform to the rules'); + $msg = $title . $lang->get('not conform to the rules'); } if (is_string($msg) && 0 === strpos($msg, '{%')) { - $msg = Lang::get(substr($msg, 2, -1)); - } elseif (Lang::has($msg)) { - $msg = Lang::get($msg); + $msg = $lang->get(substr($msg, 2, -1)); + } elseif ($lang->has($msg)) { + $msg = $lang->get($msg); } if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { @@ -1296,13 +1428,14 @@ class Validate [$title, (string) $rule, $array[0], $array[1], $array[2]], $msg); } + return $msg; } /** * 获取数据验证的场景 * @access protected - * @param string $scene 验证场景 + * @param string $scene 验证场景 * @return array */ protected function getScene($scene = '') @@ -1312,25 +1445,41 @@ class Validate $scene = $this->currentScene; } - if (!empty($scene) && isset($this->scene[$scene])) { + $this->only = $this->append = $this->remove = []; + + if (empty($scene)) { + return; + } + + if (method_exists($this, 'scene' . $scene)) { + call_user_func([$this, 'scene' . $scene]); + } elseif (isset($this->scene[$scene])) { // 如果设置了验证适用场景 $scene = $this->scene[$scene]; + if (is_string($scene)) { $scene = explode(',', $scene); } - } else { - $scene = []; + + $this->only = $scene; } - return $scene; } - public static function __callStatic($method, $params) + /** + * 动态方法 直接调用is方法进行验证 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) { - $class = self::make(); - if (method_exists($class, $method)) { - return call_user_func_array([$class, $method], $params); - } else { - throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method); + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); } + + array_push($args, lcfirst($method)); + + return call_user_func_array([$this, 'is'], $args); } } diff --git a/thinkphp/library/think/View.php b/thinkphp/library/think/View.php index ca2dadbb6..6127235d9 100644 --- a/thinkphp/library/think/View.php +++ b/thinkphp/library/think/View.php @@ -13,80 +13,67 @@ namespace think; class View { - // 视图实例 - protected static $instance; - // 模板引擎实例 + /** + * 模板引擎实例 + * @var object + */ public $engine; - // 模板变量 - protected $data = []; - // 用于静态赋值的模板变量 - protected static $var = []; - // 视图输出替换 - protected $replace = []; /** - * 构造函数 - * @access public - * @param array $engine 模板引擎参数 - * @param array $replace 字符串替换参数 + * 模板变量 + * @var array */ - public function __construct($engine = [], $replace = []) + protected $data = []; + + /** + * 内容过滤 + * @var mixed + */ + protected $filter; + + /** + * 全局模板变量 + * @var array + */ + protected static $var = []; + + /** + * 初始化 + * @access public + * @param mixed $engine 模板引擎参数 + * @return $this + */ + public function init($engine = []) { // 初始化模板引擎 $this->engine($engine); - // 基础替换字符串 - $request = Request::instance(); - $base = $request->root(); - $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base; - if ('' != $root) { - $root = '/' . ltrim($root, '/'); - } - $baseReplace = [ - '__ROOT__' => $root, - '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()), - '__STATIC__' => $root . '/static', - '__CSS__' => $root . '/static/css', - '__JS__' => $root . '/static/js', - ]; - $this->replace = array_merge($baseReplace, (array) $replace); - } - /** - * 初始化视图 - * @access public - * @param array $engine 模板引擎参数 - * @param array $replace 字符串替换参数 - * @return object - */ - public static function instance($engine = [], $replace = []) - { - if (is_null(self::$instance)) { - self::$instance = new self($engine, $replace); - } - return self::$instance; + return $this; } /** * 模板变量静态赋值 * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 - * @return void + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this */ - public static function share($name, $value = '') + public function share($name, $value = '') { if (is_array($name)) { self::$var = array_merge(self::$var, $name); } else { self::$var[$name] = $value; } + + return $this; } /** * 模板变量赋值 * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 + * @param mixed $name 变量名 + * @param mixed $value 变量值 * @return $this */ public function assign($name, $value = '') @@ -96,13 +83,14 @@ class View } else { $this->data[$name] = $value; } + return $this; } /** * 设置当前模板解析的引擎 * @access public - * @param array|string $options 引擎参数 + * @param array|string $options 引擎参数 * @return $this */ public function engine($options = []) @@ -115,37 +103,64 @@ class View } $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type); + if (isset($options['type'])) { unset($options['type']); } + $this->engine = new $class($options); + return $this; } /** * 配置模板引擎 - * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 参数值 * @return $this */ public function config($name, $value = null) { $this->engine->config($name, $value); + + return $this; + } + + /** + * 检查模板是否存在 + * @access public + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->engine->exists($name); + } + + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter($filter) + { + $this->filter = $filter; return $this; } /** * 解析和获取模板内容 用于输出 - * @param string $template 模板文件名或者内容 - * @param array $vars 模板输出变量 - * @param array $replace 替换内容 - * @param array $config 模板参数 - * @param bool $renderContent 是否渲染内容 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 * @return string - * @throws Exception + * @throws \Exception */ - public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) + public function fetch($template = '', $vars = [], $config = [], $renderContent = false) { // 模板变量 $vars = array_merge(self::$var, $this->data, $vars); @@ -157,9 +172,6 @@ class View // 渲染输出 try { $method = $renderContent ? 'display' : 'fetch'; - // 允许用户自定义模板的字符串替换 - $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string')); - $this->engine->config('tpl_replace_string', $replace); $this->engine->$method($template, $vars, $config); } catch (\Exception $e) { ob_end_clean(); @@ -168,47 +180,32 @@ class View // 获取并清空缓存 $content = ob_get_clean(); - // 内容过滤标签 - Hook::listen('view_filter', $content); - return $content; - } - /** - * 视图内容替换 - * @access public - * @param string|array $content 被替换内容(支持批量替换) - * @param string $replace 替换内容 - * @return $this - */ - public function replace($content, $replace = '') - { - if (is_array($content)) { - $this->replace = array_merge($this->replace, $content); - } else { - $this->replace[$content] = $replace; + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); } - return $this; + + return $content; } /** * 渲染内容输出 * @access public - * @param string $content 内容 - * @param array $vars 模板输出变量 - * @param array $replace 替换内容 - * @param array $config 模板参数 + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 * @return mixed */ - public function display($content, $vars = [], $replace = [], $config = []) + public function display($content, $vars = [], $config = []) { - return $this->fetch($content, $vars, $replace, $config, true); + return $this->fetch($content, $vars, $config, true); } /** * 模板变量赋值 * @access public - * @param string $name 变量名 - * @param mixed $value 变量值 + * @param string $name 变量名 + * @param mixed $value 变量值 */ public function __set($name, $value) { @@ -218,7 +215,7 @@ class View /** * 取得模板显示变量的值 * @access protected - * @param string $name 模板变量 + * @param string $name 模板变量 * @return mixed */ public function __get($name) @@ -229,7 +226,7 @@ class View /** * 检测模板变量是否设置 * @access public - * @param string $name 模板变量名 + * @param string $name 模板变量名 * @return bool */ public function __isset($name) diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php index 07805e485..f0ec7baf4 100644 --- a/thinkphp/library/think/cache/Driver.php +++ b/thinkphp/library/think/cache/Driver.php @@ -11,19 +11,53 @@ namespace think\cache; +use think\Container; + /** * 缓存基础类 */ abstract class Driver { + /** + * 驱动句柄 + * @var object + */ protected $handler = null; + + /** + * 缓存读取次数 + * @var integer + */ + protected $readTimes = 0; + + /** + * 缓存写入次数 + * @var integer + */ + protected $writeTimes = 0; + + /** + * 缓存参数 + * @var array + */ protected $options = []; + + /** + * 缓存标签 + * @var string + */ protected $tag; + /** + * 序列化方法 + * @var array + */ + protected static $serialize = ['serialize', 'unserialize', 'think_serialize:', 16]; + /** * 判断缓存是否存在 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ abstract public function has($name); @@ -31,8 +65,8 @@ abstract class Driver /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ abstract public function get($name, $default = false); @@ -40,9 +74,9 @@ abstract class Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 * @return boolean */ abstract public function set($name, $value, $expire = null); @@ -50,8 +84,8 @@ abstract class Driver /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ abstract public function inc($name, $step = 1); @@ -59,8 +93,8 @@ abstract class Driver /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ abstract public function dec($name, $step = 1); @@ -68,7 +102,7 @@ abstract class Driver /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ abstract public function rm($name); @@ -76,15 +110,30 @@ abstract class Driver /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ abstract public function clear($tag = null); + /** + * 获取有效期 + * @access protected + * @param integer|\DateTime $expire 有效期 + * @return integer + */ + protected function getExpireTime($expire) + { + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + + return $expire; + } + /** * 获取实际的缓存标识 - * @access public - * @param string $name 缓存名 + * @access protected + * @param string $name 缓存名 * @return string */ protected function getCacheKey($name) @@ -95,12 +144,13 @@ abstract class Driver /** * 读取缓存并删除 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return mixed */ public function pull($name) { $result = $this->get($name, false); + if ($result) { $this->rm($name); return $result; @@ -112,9 +162,9 @@ abstract class Driver /** * 如果不存在则写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 * @return mixed */ public function remember($name, $value, $expire = null) @@ -129,14 +179,18 @@ abstract class Driver try { // 锁定 $this->set($name . '_lock', true); + if ($value instanceof \Closure) { - $value = call_user_func($value); + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); } + + // 缓存数据 $this->set($name, $value, $expire); + // 解锁 $this->rm($name . '_lock'); } catch (\Exception $e) { - // 解锁 $this->rm($name . '_lock'); throw $e; } catch (\throwable $e) { @@ -146,15 +200,16 @@ abstract class Driver } else { $value = $this->get($name); } + return $value; } /** * 缓存标签 * @access public - * @param string $name 标签名 - * @param string|array $keys 缓存标识 - * @param bool $overlay 是否覆盖 + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 * @return $this */ public function tag($name, $keys = null, $overlay = false) @@ -165,31 +220,38 @@ abstract class Driver $this->tag = $name; } else { $key = 'tag_' . md5($name); + if (is_string($keys)) { $keys = explode(',', $keys); } + $keys = array_map([$this, 'getCacheKey'], $keys); + if ($overlay) { $value = $keys; } else { $value = array_unique(array_merge($this->getTagItem($name), $keys)); } + $this->set($key, implode(',', $value), 0); } + return $this; } /** * 更新标签 - * @access public - * @param string $name 缓存标识 + * @access protected + * @param string $name 缓存标识 * @return void */ protected function setTagItem($name) { if ($this->tag) { $key = 'tag_' . md5($this->tag); + $prev = $this->tag; $this->tag = null; + if ($this->has($key)) { $value = explode(',', $this->get($key)); $value[] = $name; @@ -197,20 +259,23 @@ abstract class Driver } else { $value = $name; } + $this->set($key, $value, 0); + $this->tag = $prev; } } /** * 获取标签包含的缓存标识 - * @access public - * @param string $tag 缓存标签 + * @access protected + * @param string $tag 缓存标签 * @return array */ protected function getTagItem($tag) { $key = 'tag_' . md5($tag); $value = $this->get($key); + if ($value) { return array_filter(explode(',', $value)); } else { @@ -218,6 +283,53 @@ abstract class Driver } } + /** + * 序列化数据 + * @access protected + * @param mixed $data + * @return string + */ + protected function serialize($data) + { + if (is_scalar($data) || !$this->options['serialize']) { + return $data; + } + + $serialize = self::$serialize[0]; + + return self::$serialize[2] . $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data + * @return mixed + */ + protected function unserialize($data) + { + if ($this->options['serialize'] && 0 === strpos($data, self::$serialize[2])) { + $unserialize = self::$serialize[1]; + + return $unserialize(substr($data, self::$serialize[3])); + } else { + return $data; + } + } + + /** + * 注册序列化机制 + * @access public + * @param callable $serialize 序列化方法 + * @param callable $unserialize 反序列化方法 + * @param string $prefix 序列化前缀标识 + * @return $this + */ + public static function registerSerialize($serialize, $unserialize, $prefix = 'think_serialize:') + { + self::$serialize = [$serialize, $unserialize, $prefix, strlen($prefix)]; + } + /** * 返回句柄对象,可执行其它高级方法 * @@ -228,4 +340,14 @@ abstract class Driver { return $this->handler; } + + public function getReadTimes() + { + return $this->readTimes; + } + + public function getWriteTimes() + { + return $this->writeTimes; + } } diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php index 6b1abd195..d4c520334 100644 --- a/thinkphp/library/think/cache/driver/File.php +++ b/thinkphp/library/think/cache/driver/File.php @@ -12,6 +12,7 @@ namespace think\cache\driver; use think\cache\Driver; +use think\Container; /** * 文件类型缓存类 @@ -23,14 +24,16 @@ class File extends Driver 'expire' => 0, 'cache_subdir' => true, 'prefix' => '', - 'path' => CACHE_PATH, + 'path' => '', + 'hash_type' => 'md5', 'data_compress' => false, + 'serialize' => true, ]; protected $expire; /** - * 构造函数 + * 架构函数 * @param array $options */ public function __construct($options = []) @@ -38,9 +41,13 @@ class File extends Driver if (!empty($options)) { $this->options = array_merge($this->options, $options); } - if (substr($this->options['path'], -1) != DS) { - $this->options['path'] .= DS; + + if (empty($this->options['path'])) { + $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache/'; + } elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; } + $this->init(); } @@ -57,6 +64,7 @@ class File extends Driver return true; } } + return false; } @@ -69,27 +77,31 @@ class File extends Driver */ protected function getCacheKey($name, $auto = false) { - $name = md5($name); + $name = hash($this->options['hash_type'], $name); + if ($this->options['cache_subdir']) { // 使用子目录 - $name = substr($name, 0, 2) . DS . substr($name, 2); + $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); } + if ($this->options['prefix']) { - $name = $this->options['prefix'] . DS . $name; + $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name; } + $filename = $this->options['path'] . $name . '.php'; $dir = dirname($filename); if ($auto && !is_dir($dir)) { mkdir($dir, 0755, true); } + return $filename; } /** * 判断缓存是否存在 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) @@ -100,31 +112,39 @@ class File extends Driver /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $filename = $this->getCacheKey($name); + if (!is_file($filename)) { return $default; } + $content = file_get_contents($filename); $this->expire = null; + if (false !== $content) { $expire = (int) substr($content, 8, 12); if (0 != $expire && time() > filemtime($filename) + $expire) { + //缓存过期删除缓存文件 + $this->unlink($filename); return $default; } + $this->expire = $expire; $content = substr($content, 32); + if ($this->options['data_compress'] && function_exists('gzcompress')) { //启用数据压缩 $content = gzuncompress($content); } - $content = unserialize($content); - return $content; + return $this->unserialize($content); } else { return $default; } @@ -133,30 +153,36 @@ class File extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + + $expire = $this->getExpireTime($expire); $filename = $this->getCacheKey($name, true); + if ($this->tag && !is_file($filename)) { $first = true; } - $data = serialize($value); + + $data = $this->serialize($value); + if ($this->options['data_compress'] && function_exists('gzcompress')) { //数据压缩 $data = gzcompress($data, 3); } + $data = "\n" . $data; $result = file_put_contents($filename, $data); + if ($result) { isset($first) && $this->setTagItem($filename); clearstatcache(); @@ -169,8 +195,8 @@ class File extends Driver /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) @@ -189,8 +215,8 @@ class File extends Driver /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) @@ -209,19 +235,20 @@ class File extends Driver /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { - $filename = $this->getCacheKey($name); - return $this->unlink($filename); + $this->writeTimes++; + + return $this->unlink($this->getCacheKey($name)); } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -235,7 +262,11 @@ class File extends Driver $this->rm('tag_' . md5($tag)); return true; } - $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); + + $this->writeTimes++; + + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*'); + foreach ($files as $path) { if (is_dir($path)) { $matches = glob($path . '/*.php'); @@ -247,12 +278,14 @@ class File extends Driver unlink($path); } } + return true; } /** * 判断文件是否存在后,删除 - * @param $path + * @access private + * @param string $path * @return bool * @author byron sampson * @return boolean diff --git a/thinkphp/library/think/cache/driver/Lite.php b/thinkphp/library/think/cache/driver/Lite.php index 8cbf08f95..544663c05 100644 --- a/thinkphp/library/think/cache/driver/Lite.php +++ b/thinkphp/library/think/cache/driver/Lite.php @@ -26,18 +26,19 @@ class Lite extends Driver ]; /** - * 构造函数 + * 架构函数 * @access public * - * @param array $options + * @param array $options */ public function __construct($options = []) { if (!empty($options)) { $this->options = array_merge($this->options, $options); } - if (substr($this->options['path'], -1) != DS) { - $this->options['path'] .= DS; + + if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; } } @@ -45,7 +46,7 @@ class Lite extends Driver /** * 取得变量的存储文件名 * @access protected - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return string */ protected function getCacheKey($name) @@ -56,7 +57,7 @@ class Lite extends Driver /** * 判断缓存是否存在 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return mixed */ public function has($name) @@ -67,21 +68,26 @@ class Lite extends Driver /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $filename = $this->getCacheKey($name); + if (is_file($filename)) { // 判断是否过期 $mtime = filemtime($filename); + if ($mtime < time()) { // 清除已经过期的文件 unlink($filename); return $default; } + return include $filename; } else { return $default; @@ -90,41 +96,49 @@ class Lite extends Driver /** * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 * @return bool */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { $expire = $expire->getTimestamp(); } else { $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; $expire = time() + $expire; } + $filename = $this->getCacheKey($name); + if ($this->tag && !is_file($filename)) { $first = true; } + $ret = file_put_contents($filename, ("setTagItem($filename); touch($filename, $expire); } + return $ret; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) @@ -134,14 +148,15 @@ class Lite extends Driver } else { $value = $step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) @@ -151,24 +166,27 @@ class Lite extends Driver } else { $value = -$step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + return unlink($this->getCacheKey($name)); } /** * 清除缓存 - * @access public - * @param string $tag 标签名 + * @access public + * @param string $tag 标签名 * @return bool */ public function clear($tag = null) @@ -179,9 +197,13 @@ class Lite extends Driver foreach ($keys as $key) { unlink($key); } + $this->rm('tag_' . md5($tag)); return true; } - array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); + + $this->writeTimes++; + + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*.php')); } } diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/thinkphp/library/think/cache/driver/Memcache.php index 815f60b7f..ea2c0e234 100644 --- a/thinkphp/library/think/cache/driver/Memcache.php +++ b/thinkphp/library/think/cache/driver/Memcache.php @@ -22,12 +22,13 @@ class Memcache extends Driver 'timeout' => 0, // 超时时间(单位:毫秒) 'persistent' => true, 'prefix' => '', + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 * @throws \BadFunctionCallException */ public function __construct($options = []) @@ -35,16 +36,21 @@ class Memcache extends Driver if (!extension_loaded('memcache')) { throw new \BadFunctionCallException('not support: memcache'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } + $this->handler = new \Memcache; + // 支持集群 $hosts = explode(',', $this->options['host']); $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 foreach ((array) $hosts as $i => $host) { $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; @@ -57,99 +63,115 @@ class Memcache extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { $key = $this->getCacheKey($name); + return $this->handler->get($key) ? true : false; } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $result = $this->handler->get($this->getCacheKey($name)); - return false !== $result ? $result : $default; + + return false !== $result ? $this->unserialize($result) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateTime $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } - $key = $this->getCacheKey($name); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if ($this->handler->set($key, $value, 0, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { return $this->handler->increment($key, $step); } + return $this->handler->set($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); $value = $this->handler->get($key) - $step; $res = $this->handler->set($key, $value); - if (!$res) { - return false; - } else { - return $value; - } + + return !$res ? false : $value; } /** * 删除缓存 - * @param string $name 缓存变量名 - * @param bool|false $ttl + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl * @return bool */ public function rm($name, $ttl = false) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return false === $ttl ? $this->handler->delete($key) : $this->handler->delete($key, $ttl); @@ -158,7 +180,7 @@ class Memcache extends Driver /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return bool */ public function clear($tag = null) @@ -169,9 +191,13 @@ class Memcache extends Driver foreach ($keys as $key) { $this->handler->delete($key); } + $this->rm('tag_' . md5($tag)); return true; } + + $this->writeTimes++; + return $this->handler->flush(); } } diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php index 5aab5a306..d04fac087 100644 --- a/thinkphp/library/think/cache/driver/Memcached.php +++ b/thinkphp/library/think/cache/driver/Memcached.php @@ -16,49 +16,58 @@ use think\cache\Driver; class Memcached extends Driver { protected $options = [ - 'host' => '127.0.0.1', - 'port' => 11211, - 'expire' => 0, - 'timeout' => 0, // 超时时间(单位:毫秒) - 'prefix' => '', - 'username' => '', //账号 - 'password' => '', //密码 - 'option' => [], + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 */ public function __construct($options = []) { if (!extension_loaded('memcached')) { throw new \BadFunctionCallException('not support: memcached'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } + $this->handler = new \Memcached; + if (!empty($this->options['option'])) { $this->handler->setOptions($this->options['option']); } + // 设置连接超时时间(单位:毫秒) if ($this->options['timeout'] > 0) { $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); } + // 支持集群 $hosts = explode(',', $this->options['host']); $ports = explode(',', $this->options['port']); if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 $servers = []; foreach ((array) $hosts as $i => $host) { $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; } + $this->handler->addServers($servers); + if ('' != $this->options['username']) { $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); @@ -68,100 +77,115 @@ class Memcached extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { $key = $this->getCacheKey($name); + return $this->handler->get($key) ? true : false; } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $result = $this->handler->get($this->getCacheKey($name)); - return false !== $result ? $result : $default; + + return false !== $result ? $this->unserialize($result) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } + $key = $this->getCacheKey($name); - $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if ($this->handler->set($key, $value, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { return $this->handler->increment($key, $step); } + return $this->handler->set($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); $value = $this->handler->get($key) - $step; $res = $this->handler->set($key, $value); - if (!$res) { - return false; - } else { - return $value; - } + + return !$res ? false : $value; } /** * 删除缓存 - * @param string $name 缓存变量名 - * @param bool|false $ttl + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl * @return bool */ public function rm($name, $ttl = false) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return false === $ttl ? $this->handler->delete($key) : $this->handler->delete($key, $ttl); @@ -170,7 +194,7 @@ class Memcached extends Driver /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return bool */ public function clear($tag = null) @@ -178,10 +202,15 @@ class Memcached extends Driver if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); + $this->handler->deleteMulti($keys); $this->rm('tag_' . md5($tag)); + return true; } + + $this->writeTimes++; + return $this->handler->flush(); } } diff --git a/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php index bb8713a66..ad4de7af6 100644 --- a/thinkphp/library/think/cache/driver/Redis.php +++ b/thinkphp/library/think/cache/driver/Redis.php @@ -31,22 +31,26 @@ class Redis extends Driver 'expire' => 0, 'persistent' => false, 'prefix' => '', + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 */ public function __construct($options = []) { if (!extension_loaded('redis')) { throw new \BadFunctionCallException('not support: redis'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } + $this->handler = new \Redis; + if ($this->options['persistent']) { $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); } else { @@ -65,7 +69,7 @@ class Redis extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) @@ -76,53 +80,56 @@ class Redis extends Driver /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $value = $this->handler->get($this->getCacheKey($name)); + if (is_null($value) || false === $value) { return $default; } - try { - $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value; - } catch (\Exception $e) { - $result = $default; - } - - return $result; + return $this->unserialize($value); } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } - $key = $this->getCacheKey($name); - $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + + $value = $this->serialize($value); + if ($expire) { $result = $this->handler->setex($key, $expire, $value); } else { $result = $this->handler->set($key, $value); } + isset($first) && $this->setTagItem($key); + return $result; } @@ -135,6 +142,8 @@ class Redis extends Driver */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); return $this->handler->incrby($key, $step); @@ -149,6 +158,8 @@ class Redis extends Driver */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); return $this->handler->decrby($key, $step); @@ -157,18 +168,20 @@ class Redis extends Driver /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + return $this->handler->delete($this->getCacheKey($name)); } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -176,12 +189,17 @@ class Redis extends Driver if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); + foreach ($keys as $key) { $this->handler->delete($key); } + $this->rm('tag_' . md5($tag)); return true; } + + $this->writeTimes++; + return $this->handler->flushDB(); } diff --git a/thinkphp/library/think/cache/driver/Sqlite.php b/thinkphp/library/think/cache/driver/Sqlite.php index dc2ee0559..7e78ec12d 100644 --- a/thinkphp/library/think/cache/driver/Sqlite.php +++ b/thinkphp/library/think/cache/driver/Sqlite.php @@ -25,30 +25,34 @@ class Sqlite extends Driver 'prefix' => '', 'expire' => 0, 'persistent' => false, + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 - * @throws \BadFunctionCallException + * 架构函数 * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException */ public function __construct($options = []) { if (!extension_loaded('sqlite')) { throw new \BadFunctionCallException('not support: sqlite'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } - $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); } /** * 获取实际的缓存标识 * @access public - * @param string $name 缓存名 + * @param string $name 缓存名 * @return string */ protected function getCacheKey($name) @@ -59,82 +63,101 @@ class Sqlite extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { - $name = $this->getCacheKey($name); - $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; $result = sqlite_query($this->handler, $sql); + return sqlite_num_rows($result); } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { - $name = $this->getCacheKey($name); - $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $this->readTimes++; + + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { $content = sqlite_fetch_single($result); if (function_exists('gzcompress')) { //启用数据压缩 $content = gzuncompress($content); } - return unserialize($content); + + return $this->unserialize($content); } + return $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { - $name = $this->getCacheKey($name); - $value = sqlite_escape_string(serialize($value)); + $this->writeTimes++; + + $name = $this->getCacheKey($name); + + $value = sqlite_escape_string($this->serialize($value)); + if (is_null($expire)) { $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { $expire = $expire->getTimestamp(); } else { $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 } + if (function_exists('gzcompress')) { //数据压缩 $value = gzcompress($value, 3); } + if ($this->tag) { $tag = $this->tag; $this->tag = null; } else { $tag = ''; } + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + if (sqlite_query($this->handler, $sql)) { return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) @@ -144,14 +167,15 @@ class Sqlite extends Driver } else { $value = $step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) @@ -161,27 +185,32 @@ class Sqlite extends Driver } else { $value = -$step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + $name = $this->getCacheKey($name); - $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; sqlite_query($this->handler, $sql); + return true; } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -192,8 +221,13 @@ class Sqlite extends Driver sqlite_query($this->handler, $sql); return true; } + + $this->writeTimes++; + $sql = 'DELETE FROM ' . $this->options['table']; + sqlite_query($this->handler, $sql); + return true; } } diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/thinkphp/library/think/cache/driver/Wincache.php index 03f8d3577..10966e786 100644 --- a/thinkphp/library/think/cache/driver/Wincache.php +++ b/thinkphp/library/think/cache/driver/Wincache.php @@ -20,21 +20,23 @@ use think\cache\Driver; class Wincache extends Driver { protected $options = [ - 'prefix' => '', - 'expire' => 0, + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 - * @throws \BadFunctionCallException + * 架构函数 * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException */ public function __construct($options = []) { if (!function_exists('wincache_ucache_info')) { throw new \BadFunctionCallException('not support: WinCache'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } @@ -43,96 +45,115 @@ class Wincache extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { + $this->readTimes++; + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key); } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $key = $this->getCacheKey($name); - return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; + + return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - $key = $this->getCacheKey($name); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if ($this->tag && !$this->has($name)) { $first = true; } + if (wincache_ucache_set($key, $value, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return wincache_ucache_inc($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return wincache_ucache_dec($key, $step); } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + return wincache_ucache_delete($this->getCacheKey($name)); } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -145,6 +166,7 @@ class Wincache extends Driver $this->rm('tag_' . md5($tag)); return true; } else { + $this->writeTimes++; return wincache_ucache_clear(); } } diff --git a/thinkphp/library/think/cache/driver/Xcache.php b/thinkphp/library/think/cache/driver/Xcache.php index 4d94c033c..6d1bf3f6c 100644 --- a/thinkphp/library/think/cache/driver/Xcache.php +++ b/thinkphp/library/think/cache/driver/Xcache.php @@ -20,14 +20,15 @@ use think\cache\Driver; class Xcache extends Driver { protected $options = [ - 'prefix' => '', - 'expire' => 0, + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 * @throws \BadFunctionCallException */ public function __construct($options = []) @@ -35,6 +36,7 @@ class Xcache extends Driver if (!function_exists('xcache_info')) { throw new \BadFunctionCallException('not support: Xcache'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } @@ -43,96 +45,113 @@ class Xcache extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { $key = $this->getCacheKey($name); + return xcache_isset($key); } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $key = $this->getCacheKey($name); - return xcache_isset($key) ? xcache_get($key) : $default; + + return xcache_isset($key) ? $this->unserialize(xcache_get($key)) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } - $key = $this->getCacheKey($name); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if (xcache_set($key, $value, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return xcache_inc($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return xcache_dec($key, $step); } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + return xcache_unset($this->getCacheKey($name)); } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -146,6 +165,9 @@ class Xcache extends Driver $this->rm('tag_' . md5($tag)); return true; } + + $this->writeTimes++; + if (function_exists('xcache_unset_by_prefix')) { return xcache_unset_by_prefix($this->options['prefix']); } else { diff --git a/thinkphp/library/think/config/driver/Json.php b/thinkphp/library/think/config/driver/Json.php index 479dcc892..808bfff80 100644 --- a/thinkphp/library/think/config/driver/Json.php +++ b/thinkphp/library/think/config/driver/Json.php @@ -18,7 +18,9 @@ class Json if (is_file($config)) { $config = file_get_contents($config); } + $result = json_decode($config, true); + return $result; } } diff --git a/thinkphp/library/think/config/driver/Xml.php b/thinkphp/library/think/config/driver/Xml.php index 1158519fe..b149b57fa 100644 --- a/thinkphp/library/think/config/driver/Xml.php +++ b/thinkphp/library/think/config/driver/Xml.php @@ -20,12 +20,14 @@ class Xml } else { $content = simplexml_load_string($config); } + $result = (array) $content; foreach ($result as $key => $val) { if (is_object($val)) { $result[$key] = (array) $val; } } + return $result; } } diff --git a/thinkphp/library/think/console/command/Build.php b/thinkphp/library/think/console/command/Build.php index 39806c3f4..2b83199b1 100644 --- a/thinkphp/library/think/console/command/Build.php +++ b/thinkphp/library/think/console/command/Build.php @@ -15,6 +15,8 @@ use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; +use think\facade\App; +use think\facade\Build as AppBuild; class Build extends Command { @@ -35,7 +37,7 @@ class Build extends Command protected function execute(Input $input, Output $output) { if ($input->hasOption('module')) { - \think\Build::module($input->getOption('module')); + AppBuild::module($input->getOption('module')); $output->writeln("Successed"); return; } @@ -43,13 +45,15 @@ class Build extends Command if ($input->hasOption('config')) { $build = include $input->getOption('config'); } else { - $build = include APP_PATH . 'build.php'; + $build = include App::getAppPath() . 'build.php'; } + if (empty($build)) { $output->writeln("Build Config Is Empty"); return; } - \think\Build::run($build); + + AppBuild::run($build); $output->writeln("Successed"); } diff --git a/thinkphp/library/think/console/command/Clear.php b/thinkphp/library/think/console/command/Clear.php index 41019ceaf..4ca0c4ffa 100644 --- a/thinkphp/library/think/console/command/Clear.php +++ b/thinkphp/library/think/console/command/Clear.php @@ -14,6 +14,7 @@ use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; +use think\facade\App; class Clear extends Command { @@ -28,27 +29,17 @@ class Clear extends Command protected function execute(Input $input, Output $output) { - $path = $input->getOption('path') ?: RUNTIME_PATH; - - if (is_dir($path)) { - $this->clearPath($path); - } - - $output->writeln("Clear Successed"); - } - - protected function clearPath($path) - { - $path = realpath($path) . DS; + $path = $input->getOption('path') ?: App::getRuntimePath(); $files = scandir($path); if ($files) { foreach ($files as $file) { if ('.' != $file && '..' != $file && is_dir($path . $file)) { - $this->clearPath($path . $file); + array_map('unlink', glob($path . $file . '/*.*')); } elseif ('.gitignore' != $file && is_file($path . $file)) { unlink($path . $file); } } } + $output->writeln("Clear Successed"); } } diff --git a/thinkphp/library/think/console/command/Make.php b/thinkphp/library/think/console/command/Make.php index d1daf34fa..3bdfc6b9d 100644 --- a/thinkphp/library/think/console/command/Make.php +++ b/thinkphp/library/think/console/command/Make.php @@ -11,12 +11,13 @@ namespace think\console\command; -use think\App; -use think\Config; use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\Output; +use think\facade\App; +use think\facade\Config; +use think\facade\Env; abstract class Make extends Command { @@ -65,23 +66,23 @@ abstract class Make extends Command return str_replace(['{%className%}', '{%namespace%}', '{%app_namespace%}'], [ $class, $namespace, - App::$namespace, + App::getNamespace(), ], $stub); } protected function getPathName($name) { - $name = str_replace(App::$namespace . '\\', '', $name); + $name = str_replace(App::getNamespace() . '\\', '', $name); - return APP_PATH . str_replace('\\', '/', $name) . '.php'; + return Env::get('app_path') . ltrim(str_replace('\\', '/', $name), '/') . '.php'; } protected function getClassName($name) { - $appNamespace = App::$namespace; + $appNamespace = App::getNamespace(); - if (strpos($name, $appNamespace . '\\') === 0) { + if (strpos($name, $appNamespace . '\\') !== false) { return $name; } diff --git a/thinkphp/library/think/console/command/RunServer.php b/thinkphp/library/think/console/command/RunServer.php new file mode 100644 index 000000000..60edfd98b --- /dev/null +++ b/thinkphp/library/think/console/command/RunServer.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; + +class RunServer extends Command +{ + + public function configure() + { + $this->setName('run') + ->addOption('host', 'H', Option::VALUE_OPTIONAL, + 'The host to server the application on', '127.0.0.1') + ->addOption('port', 'p', Option::VALUE_OPTIONAL, + 'The port to server the application on', 8000) + ->addOption('root', 'r', Option::VALUE_OPTIONAL, + 'The document root of the application', App::getRootPath() . 'public') + ->setDescription('PHP Built-in Server for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + $host = $input->getOption('host'); + $port = $input->getOption('port'); + $root = $input->getOption('root'); + + $command = sprintf( + 'php -S %s:%d -t %s %s', + $host, + $port, + escapeshellarg($root), + escapeshellarg($root . '/router.php') + ); + + $output->writeln(sprintf('ThinkPHP Development server is started On ', $host, $port)); + $output->writeln(sprintf('You can exit with `CTRL-C`')); + $output->writeln(sprintf('Document root is: %s', $root)); + passthru($command); + } + +} diff --git a/thinkphp/library/think/console/command/make/Controller.php b/thinkphp/library/think/console/command/make/Controller.php index afa7be905..d702bd1af 100644 --- a/thinkphp/library/think/console/command/make/Controller.php +++ b/thinkphp/library/think/console/command/make/Controller.php @@ -11,9 +11,9 @@ namespace think\console\command\make; -use think\Config; use think\console\command\Make; use think\console\input\Option; +use think\facade\Config; class Controller extends Make { diff --git a/thinkphp/library/think/console/command/optimize/Autoload.php b/thinkphp/library/think/console/command/optimize/Autoload.php index 6a77c2cba..c5eab03ce 100644 --- a/thinkphp/library/think/console/command/optimize/Autoload.php +++ b/thinkphp/library/think/console/command/optimize/Autoload.php @@ -10,11 +10,10 @@ // +---------------------------------------------------------------------- namespace think\console\command\optimize; -use think\App; -use think\Config; use think\console\Command; use think\console\Input; use think\console\Output; +use think\Container; class Autoload extends Command { @@ -37,20 +36,14 @@ class Autoload extends Command return [ EOF; - + $app = Container::get('app'); $namespacesToScan = [ - App::$namespace . '\\' => realpath(rtrim(APP_PATH)), - 'think\\' => LIB_PATH . 'think', - 'behavior\\' => LIB_PATH . 'behavior', - 'traits\\' => LIB_PATH . 'traits', - '' => realpath(rtrim(EXTEND_PATH)), + $app->getNamespace() . '\\' => realpath(rtrim($app->getAppPath())), + 'think\\' => $app->getAppPath() . 'library/think', + 'traits\\' => $app->getAppPath() . 'library/traits', + '' => realpath(rtrim($app->getRootPath() . 'extend')), ]; - $root_namespace = Config::get('root_namespace'); - foreach ($root_namespace as $namespace => $dir) { - $namespacesToScan[$namespace . '\\'] = realpath($dir); - } - krsort($namespacesToScan); $classMap = []; foreach ($namespacesToScan as $namespace => $dir) { @@ -59,7 +52,7 @@ EOF; continue; } - $namespaceFilter = $namespace === '' ? null : $namespace; + $namespaceFilter = '' === $namespace ? null : $namespace; $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); } @@ -68,12 +61,12 @@ EOF; $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; } $classmapFile .= "];\n"; - - if (!is_dir(RUNTIME_PATH)) { - @mkdir(RUNTIME_PATH, 0755, true); + $runtimePath = $app->getRuntimePath(); + if (!is_dir($runtimePath)) { + @mkdir($runtimePath, 0755, true); } - file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); + file_put_contents($runtimePath . 'classmap.php', $classmapFile); $output->writeln('Succeed!'); } @@ -100,40 +93,33 @@ EOF; protected function getPathCode($path) { - $baseDir = ''; - $libPath = $this->normalizePath(realpath(LIB_PATH)); - $appPath = $this->normalizePath(realpath(APP_PATH)); - $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); - $rootPath = $this->normalizePath(realpath(ROOT_PATH)); + $app = Container::get('app'); + $appPath = $this->normalizePath(realpath($app->getAppPath())); + $libPath = $this->normalizePath(realpath($app->getThinkPath() . 'library')); + $extendPath = $this->normalizePath(realpath($app->getRootPath() . 'extend')); $path = $this->normalizePath($path); - if ($libPath !== null && strpos($path, $libPath . '/') === 0) { - $path = substr($path, strlen(LIB_PATH)); - $baseDir = 'LIB_PATH'; - } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { + if (strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen($app->getThinkPath() . 'library')); + $baseDir = "'" . $libPath . "/'"; + } elseif (strpos($path, $appPath . '/') === 0) { $path = substr($path, strlen($appPath) + 1); - $baseDir = 'APP_PATH'; - } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { + $baseDir = "'" . $appPath . "/'"; + } elseif (strpos($path, $extendPath . '/') === 0) { $path = substr($path, strlen($extendPath) + 1); - $baseDir = 'EXTEND_PATH'; - } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { - $path = substr($path, strlen($rootPath) + 1); - $baseDir = 'ROOT_PATH'; + $baseDir = "'" . $extendPath . "/'"; } - if ($path !== false) { + if (false !== $path) { $baseDir .= " . "; } - return $baseDir . (($path !== false) ? var_export($path, true) : ""); + return $baseDir . ((false !== $path) ? var_export($path, true) : ""); } protected function normalizePath($path) { - if ($path === false) { - return; - } $parts = []; $path = strtr($path, '\\', '/'); $prefix = ''; @@ -252,7 +238,7 @@ EOF; // strip leading non-php code if needed if (substr($contents, 0, 2) !== 'getArgument('module')) { - $module = $input->getArgument('module') . DS; + $module = $input->getArgument('module') . DIRECTORY_SEPARATOR; } else { $module = ''; } - $content = 'buildCacheContent($module); - - if (!is_dir(RUNTIME_PATH . $module)) { - @mkdir(RUNTIME_PATH . $module, 0755, true); + $content = 'buildCacheContent($module); + $runtimePath = App::getRuntimePath(); + if (!is_dir($runtimePath . $module)) { + @mkdir($runtimePath . $module, 0755, true); } - file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); + file_put_contents($runtimePath . $module . 'init.php', $content); $output->writeln('Succeed!'); } protected function buildCacheContent($module) { - $content = ''; - $path = realpath(APP_PATH . $module) . DS; - + $content = '// This cache file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL; + $path = realpath(App::getAppPath() . $module) . DIRECTORY_SEPARATOR; if ($module) { - // 加载模块配置 - $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); + $configPath = is_dir($path . 'config') ? $path . 'config' : App::getConfigPath() . $module; + } else { + $configPath = App::getConfigPath(); + } + $ext = App::getConfigExt(); + $config = Container::get('config'); - // 读取数据库配置文件 - $filename = CONF_PATH . $module . 'database' . CONF_EXT; - ThinkConfig::load($filename, 'database'); + $files = is_dir($configPath) ? scandir($configPath) : []; - // 加载应用状态配置 - if ($config['app_status']) { - $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); - } - // 读取扩展配置文件 - if (is_dir(CONF_PATH . $module . 'extra')) { - $dir = CONF_PATH . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if (strpos($file, CONF_EXT)) { - $filename = $dir . DS . $file; - ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $ext) { + $filename = $configPath . DIRECTORY_SEPARATOR . $file; + $config->load($filename, pathinfo($file, PATHINFO_FILENAME)); } } // 加载行为扩展文件 - if (is_file(CONF_PATH . $module . 'tags' . EXT)) { - $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; + if (is_file($path . 'tags.php')) { + $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export(include $path . 'tags.php' ?: [], true)) . ');' . PHP_EOL; } // 加载公共文件 - if (is_file($path . 'common' . EXT)) { - $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; + if (is_file($path . 'common.php')) { + $common = substr(php_strip_whitespace($path . 'common.php'), 6); + if ($common) { + $content .= PHP_EOL . $common . PHP_EOL; + } } - $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; + if ('' == $module) { + $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL; + } + + if (is_file($path . 'provider.php')) { + $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export(include $path . 'provider.php' ?: [], true) . ');' . PHP_EOL; + } + + $content .= PHP_EOL . '\think\facade\Config::set(' . var_export($config->get(), true) . ');' . PHP_EOL; + return $content; } } diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/thinkphp/library/think/console/command/optimize/Route.php index 6da1d9a62..0b1885833 100644 --- a/thinkphp/library/think/console/command/optimize/Route.php +++ b/thinkphp/library/think/console/command/optimize/Route.php @@ -6,13 +6,14 @@ // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- -// | Author: liu21st +// | Author: yunwuxin <448901948@qq.com> // +---------------------------------------------------------------------- namespace think\console\command\optimize; use think\console\Command; use think\console\Input; use think\console\Output; +use think\Container; class Route extends Command { @@ -27,49 +28,37 @@ class Route extends Command protected function execute(Input $input, Output $output) { - - if (!is_dir(RUNTIME_PATH)) { - @mkdir(RUNTIME_PATH, 0755, true); - } - - file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); + file_put_contents(Container::get('app')->getRuntimePath() . 'route.php', $this->buildRouteCache()); $output->writeln('Succeed!'); } protected function buildRouteCache() { - $files = \think\Config::get('route_config_file'); + Container::get('route')->setName([]); + Container::get('config')->set('url_lazy_route', false); + // 路由检测 + $path = Container::get('app')->getRoutePath(); + + $files = is_dir($path) ? scandir($path) : []; + foreach ($files as $file) { - if (is_file(CONF_PATH . $file . CONF_EXT)) { - $config = include CONF_PATH . $file . CONF_EXT; - if (is_array($config)) { - \think\Route::import($config); + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + Container::get('route')->import($rules); } } } - $rules = \think\Route::rules(true); - array_walk_recursive($rules, [$this, 'buildClosure']); + + if (Container::get('config')->get('route_annotation')) { + include Container::get('build')->buildRoute(); + } + $content = 'getName(), true) . ';'; return $content; } - protected function buildClosure(&$value) - { - if ($value instanceof \Closure) { - $reflection = new \ReflectionFunction($value); - $startLine = $reflection->getStartLine(); - $endLine = $reflection->getEndLine(); - $file = $reflection->getFileName(); - $item = file($file); - $content = ''; - for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { - $content .= $item[$i]; - } - $start = strpos($content, 'function'); - $end = strrpos($content, '}'); - $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; - } - } } diff --git a/thinkphp/library/think/console/command/optimize/Schema.php b/thinkphp/library/think/console/command/optimize/Schema.php index 33534240e..179970137 100644 --- a/thinkphp/library/think/console/command/optimize/Schema.php +++ b/thinkphp/library/think/console/command/optimize/Schema.php @@ -10,12 +10,12 @@ // +---------------------------------------------------------------------- namespace think\console\command\optimize; -use think\App; use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; use think\Db; +use think\facade\App; class Schema extends Command { @@ -25,7 +25,6 @@ class Schema extends Command protected function configure() { $this->setName('optimize:schema') - ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .') ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') @@ -34,56 +33,58 @@ class Schema extends Command protected function execute(Input $input, Output $output) { - if (!is_dir(RUNTIME_PATH . 'schema')) { - @mkdir(RUNTIME_PATH . 'schema', 0755, true); - } - $config = []; - if ($input->hasOption('config')) { - $config = $input->getOption('config'); + if (!is_dir(App::getRuntimePath() . 'schema')) { + @mkdir(App::getRuntimePath() . 'schema', 0755, true); } + if ($input->hasOption('module')) { $module = $input->getOption('module'); // 读取模型 - $path = APP_PATH . $module . DS . 'model'; - $list = is_dir($path) ? scandir($path) : []; - $app = App::$namespace; + $path = App::getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $namespace = App::getNamespace(); + foreach ($list as $file) { if (0 === strpos($file, '.')) { continue; } - $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $class = '\\' . $namespace . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); $this->buildModelSchema($class); } + $output->writeln('Succeed!'); return; } elseif ($input->hasOption('table')) { $table = $input->getOption('table'); if (!strpos($table, '.')) { - $dbName = Db::connect($config)->getConfig('database'); + $dbName = Db::getConfig('database'); } + $tables[] = $table; } elseif ($input->hasOption('db')) { $dbName = $input->getOption('db'); - $tables = Db::connect($config)->getTables($dbName); - } elseif (!\think\Config::get('app_multi_module')) { - $app = App::$namespace; - $path = APP_PATH . 'model'; - $list = is_dir($path) ? scandir($path) : []; + $tables = Db::getConnection()->getTables($dbName); + } elseif (!\think\facade\Config::get('app_multi_module')) { + $namespace = App::getNamespace(); + $path = App::getAppPath() . 'model'; + $list = is_dir($path) ? scandir($path) : []; + foreach ($list as $file) { if (0 === strpos($file, '.')) { continue; } - $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); $this->buildModelSchema($class); } + $output->writeln('Succeed!'); return; } else { - $tables = Db::connect($config)->getTables(); + $tables = Db::getConnection()->getTables(); } $db = isset($dbName) ? $dbName . '.' : ''; - $this->buildDataBaseSchema($tables, $db, $config); + $this->buildDataBaseSchema($tables, $db); $output->writeln('Succeed!'); } @@ -97,22 +98,24 @@ class Schema extends Command $content = 'getFields($table); $content .= var_export($info, true) . ';'; - file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); + + file_put_contents(App::getRuntimePath() . 'schema/' . $dbName . '.' . $table . '.php', $content); } } - protected function buildDataBaseSchema($tables, $db, $config) + protected function buildDataBaseSchema($tables, $db) { if ('' == $db) { - $dbName = Db::connect($config)->getConfig('database') . '.'; + $dbName = Db::getConfig('database') . '.'; } else { $dbName = $db; } + foreach ($tables as $table) { $content = 'getFields($db . $table); + $info = Db::getConnection()->getFields($db . $table); $content .= var_export($info, true) . ';'; - file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . $table . '.php', $content); } } } diff --git a/thinkphp/library/think/console/output/Descriptor.php b/thinkphp/library/think/console/output/Descriptor.php index 23dc64812..6d98d53c7 100644 --- a/thinkphp/library/think/console/output/Descriptor.php +++ b/thinkphp/library/think/console/output/Descriptor.php @@ -69,7 +69,7 @@ class Descriptor * 描述参数 * @param InputArgument $argument * @param array $options - * @return void + * @return string|mixed */ protected function describeInputArgument(InputArgument $argument, array $options = []) { @@ -93,7 +93,7 @@ class Descriptor * 描述选项 * @param InputOption $option * @param array $options - * @return void + * @return string|mixed */ protected function describeInputOption(InputOption $option, array $options = []) { @@ -128,7 +128,7 @@ class Descriptor * 描述输入 * @param InputDefinition $definition * @param array $options - * @return void + * @return string|mixed */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { @@ -173,7 +173,7 @@ class Descriptor * 描述指令 * @param Command $command * @param array $options - * @return void + * @return string|mixed */ protected function describeCommand(Command $command, array $options = []) { @@ -208,7 +208,7 @@ class Descriptor * 描述控制台 * @param Console $console * @param array $options - * @return void + * @return string|mixed */ protected function describeConsole(Console $console, array $options = []) { diff --git a/thinkphp/library/think/console/output/driver/Console.php b/thinkphp/library/think/console/output/driver/Console.php index 8f29fd020..e041b5250 100644 --- a/thinkphp/library/think/console/output/driver/Console.php +++ b/thinkphp/library/think/console/output/driver/Console.php @@ -37,11 +37,6 @@ class Console $this->formatter->setDecorated($decorated); } - public function getFormatter() - { - return $this->formatter; - } - public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); @@ -173,7 +168,7 @@ class Console return $this->terminalDimensions; } - if ('\\' === DS) { + if ('\\' === DIRECTORY_SEPARATOR) { if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { return [(int) $matches[1], (int) $matches[2]]; } diff --git a/thinkphp/library/think/controller/Rest.php b/thinkphp/library/think/controller/Rest.php deleted file mode 100644 index 43ab2f629..000000000 --- a/thinkphp/library/think/controller/Rest.php +++ /dev/null @@ -1,99 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\controller; - -use think\App; -use think\Request; -use think\Response; - -abstract class Rest -{ - - protected $method; // 当前请求类型 - protected $type; // 当前资源类型 - // 输出类型 - protected $restMethodList = 'get|post|put|delete'; - protected $restDefaultMethod = 'get'; - protected $restTypeList = 'html|xml|json|rss'; - protected $restDefaultType = 'html'; - protected $restOutputType = [ // REST允许输出的资源类型列表 - 'xml' => 'application/xml', - 'json' => 'application/json', - 'html' => 'text/html', - ]; - - /** - * 构造函数 取得模板对象实例 - * @access public - */ - public function __construct() - { - // 资源类型检测 - $request = Request::instance(); - $ext = $request->ext(); - if ('' == $ext) { - // 自动检测资源类型 - $this->type = $request->type(); - } elseif (!preg_match('/(' . $this->restTypeList . ')$/i', $ext)) { - // 资源类型非法 则用默认资源类型访问 - $this->type = $this->restDefaultType; - } else { - $this->type = $ext; - } - // 请求方式检测 - $method = strtolower($request->method()); - if (!preg_match('/(' . $this->restMethodList . ')$/i', $method)) { - // 请求方式非法 则用默认请求方法 - $method = $this->restDefaultMethod; - } - $this->method = $method; - } - - /** - * REST 调用 - * @access public - * @param string $method 方法名 - * @return mixed - * @throws \Exception - */ - public function _empty($method) - { - if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) { - // RESTFul方法支持 - $fun = $method . '_' . $this->method . '_' . $this->type; - } elseif ($this->method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) { - $fun = $method . '_' . $this->type; - } elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) { - $fun = $method . '_' . $this->method; - } - if (isset($fun)) { - return App::invokeMethod([$this, $fun]); - } else { - // 抛出异常 - throw new \Exception('error action :' . $method); - } - } - - /** - * 输出返回数据 - * @access protected - * @param mixed $data 要返回的数据 - * @param String $type 返回类型 JSON XML - * @param integer $code HTTP状态码 - * @return Response - */ - protected function response($data, $type = 'json', $code = 200) - { - return Response::create($data, $type)->code($code); - } - -} diff --git a/thinkphp/library/think/controller/Yar.php b/thinkphp/library/think/controller/Yar.php deleted file mode 100644 index af4e9a1a8..000000000 --- a/thinkphp/library/think/controller/Yar.php +++ /dev/null @@ -1,51 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\controller; - -/** - * ThinkPHP Yar控制器类 - */ -abstract class Yar -{ - - /** - * 构造函数 - * @access public - */ - public function __construct() - { - //控制器初始化 - if (method_exists($this, '_initialize')) { - $this->_initialize(); - } - - //判断扩展是否存在 - if (!extension_loaded('yar')) { - throw new \Exception('not support yar'); - } - - //实例化Yar_Server - $server = new \Yar_Server($this); - // 启动server - $server->handle(); - } - - /** - * 魔术方法 有不存在的操作的时候执行 - * @access public - * @param string $method 方法名 - * @param array $args 参数 - * @return mixed - */ - public function __call($method, $args) - {} -} diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php index bbc20374e..4f98ac952 100644 --- a/thinkphp/library/think/db/Builder.php +++ b/thinkphp/library/think/db/Builder.php @@ -11,7 +11,6 @@ namespace think\db; -use BadMethodCallException; use PDO; use think\Exception; @@ -19,29 +18,42 @@ abstract class Builder { // connection对象实例 protected $connection; - // 查询对象实例 - protected $query; - // 数据库表达式 - protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; + // 查询表达式映射 + protected $exp = ['EQ' => '=', 'NEQ' => '<>', 'GT' => '>', 'EGT' => '>=', 'LT' => '<', 'ELT' => '<=', 'NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + ]; // SQL表达式 - protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT%%LOCK%%COMMENT%'; - protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; - protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; - protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $updateSql = 'UPDATE %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $deleteSql = 'DELETE FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; /** - * 构造函数 + * 架构函数 * @access public - * @param Connection $connection 数据库连接对象实例 - * @param Query $query 数据库查询对象实例 + * @param Connection $connection 数据库连接对象实例 */ - public function __construct(Connection $connection, Query $query) + public function __construct(Connection $connection) { $this->connection = $connection; - $this->query = $query; } /** @@ -55,56 +67,66 @@ abstract class Builder } /** - * 获取当前的Query对象实例 + * 注册查询表达式解析 * @access public - * @return Query + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this */ - public function getQuery() + public function bindParser($name, $parser) { - return $this->query; - } - - /** - * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) - * @access protected - * @param string $sql sql语句 - * @return string - */ - protected function parseSqlTable($sql) - { - return $this->query->parseSqlTable($sql); + $this->parser[$name] = $parser; + return $this; } /** * 数据分析 * @access protected - * @param array $data 数据 - * @param array $options 查询参数 + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 + * @param string $suffix 参数绑定后缀 * @return array - * @throws Exception */ - protected function parseData($data, $options) + protected function parseData(Query $query, $data = [], $fields = [], $bind = [], $suffix = '') { if (empty($data)) { return []; } + $options = $query->getOptions(); + // 获取绑定信息 - $bind = $this->query->getFieldsBind($options['table']); - if ('*' == $options['field']) { - $fields = array_keys($bind); - } else { - $fields = $options['field']; + if (empty($bind)) { + $bind = $this->connection->getFieldsBind($options['table']); + } + + if (empty($fields)) { + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } } $result = []; + foreach ($data as $key => $val) { - $item = $this->parseKey($key, $options); - if (is_object($val) && method_exists($val, '__toString')) { + $item = $this->parseKey($query, $key); + + if (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) { + $val = json_encode($val); + } elseif (is_object($val) && method_exists($val, '__toString')) { // 对象数据写入 $val = $val->__toString(); } - if (false === strpos($key, '.') && !in_array($key, $fields, true)) { + + if (false !== strpos($key, '->')) { + list($key, $name) = explode('->', $key); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind, $suffix) . ')'; + } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) { if ($options['strict']) { throw new Exception('fields not exists:[' . $key . ']'); } @@ -116,229 +138,257 @@ abstract class Builder $result[$item] = $val[1]; break; case 'inc': - $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]); + $result[$item] = $this->parseKey($query, $val[1]) . ' + ' . floatval($val[2]); break; case 'dec': - $result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]); + $result[$item] = $this->parseKey($query, $val[1]) . ' - ' . floatval($val[2]); break; } } elseif (is_scalar($val)) { // 过滤非标量数据 - if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { - $result[$item] = $val; - } else { - $key = str_replace('.', '_', $key); - $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':data__' . $key; - } + $result[$item] = $this->parseDataBind($query, $key, $val, $bind, $suffix); } } + return $result; } /** - * 字段名分析 + * 数据绑定处理 * @access protected - * @param string $key - * @param array $options + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @param string $suffix 绑定后缀 * @return string */ - protected function parseKey($key, $options = []) + protected function parseDataBind(Query $query, $key, $data, $bind = [], $suffix = '') + { + // 过滤非标量数据 + if (0 === strpos($data, ':') && $query->isBind(substr($data, 1))) { + return $data; + } else { + $key = str_replace(['.', '->'], '_', $key); + $name = 'data__' . $key . $suffix; + $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + return ':' . $name; + } + } + + /** + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 + * @return string + */ + public function parseKey(Query $query, $key) { return $key; } - /** - * value分析 - * @access protected - * @param mixed $value - * @param string $field - * @return string|array - */ - protected function parseValue($value, $field = '') - { - if (is_string($value)) { - $value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); - } elseif (is_array($value)) { - $value = array_map([$this, 'parseValue'], $value); - } elseif (is_bool($value)) { - $value = $value ? '1' : '0'; - } elseif (is_null($value)) { - $value = 'null'; - } - return $value; - } - /** * field分析 * @access protected - * @param mixed $fields - * @param array $options + * @param Query $query 查询对象 + * @param mixed $fields 字段名 * @return string */ - protected function parseField($fields, $options = []) + protected function parseField(Query $query, $fields) { if ('*' == $fields || empty($fields)) { $fieldsStr = '*'; } elseif (is_array($fields)) { // 支持 'field1'=>'field2' 这样的字段别名定义 $array = []; + foreach ($fields as $key => $field) { if (!is_numeric($key)) { - $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options); + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field); } else { - $array[] = $this->parseKey($field, $options); + $array[] = $this->parseKey($query, $field); } } + $fieldsStr = implode(',', $array); } + return $fieldsStr; } /** * table分析 * @access protected - * @param mixed $tables - * @param array $options + * @param Query $query 查询对象 + * @param mixed $tables 表名 * @return string */ - protected function parseTable($tables, $options = []) + protected function parseTable(Query $query, $tables) { - $item = []; + $item = []; + $options = $query->getOptions(); foreach ((array) $tables as $key => $table) { if (!is_numeric($key)) { - $key = $this->parseSqlTable($key); - $item[] = $this->parseKey($key) . ' ' . (isset($options['alias'][$table]) ? $this->parseKey($options['alias'][$table]) : $this->parseKey($table)); + $key = $this->connection->parseSqlTable($key); + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); } else { - $table = $this->parseSqlTable($table); + $table = $this->connection->parseSqlTable($table); + if (isset($options['alias'][$table])) { - $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); } else { - $item[] = $this->parseKey($table); + $item[] = $this->parseKey($query, $table); } } } + return implode(',', $item); } /** * where分析 * @access protected - * @param mixed $where 查询条件 - * @param array $options 查询参数 + * @param Query $query 查询对象 + * @param mixed $where 查询条件 * @return string */ - protected function parseWhere($where, $options) + protected function parseWhere(Query $query, $where) { - $whereStr = $this->buildWhere($where, $options); + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + if (!empty($options['soft_delete'])) { // 附加软删除条件 list($field, $condition) = $options['soft_delete']; - $binds = $this->query->getFieldsBind($options['table']); + $binds = $this->connection->getFieldsBind($options['table']); $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; - $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, '', $binds); } + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; } /** * 生成查询条件SQL * @access public - * @param mixed $where - * @param array $options + * @param Query $query 查询对象 + * @param mixed $where 查询条件 * @return string */ - public function buildWhere($where, $options) + public function buildWhere(Query $query, $where) { if (empty($where)) { $where = []; } - if ($where instanceof Query) { - return $this->buildWhere($where->getOptions('where'), $options); - } - $whereStr = ''; - $binds = $this->query->getFieldsBind($options['table']); - foreach ($where as $key => $val) { + $binds = $this->connection->getFieldsBind($query->getOptions('table')); + + foreach ($where as $logic => $val) { $str = []; - foreach ($val as $field => $value) { + + foreach ($val as $value) { + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + if ($value instanceof \Closure) { // 使用闭包查询 - $query = new Query($this->connection); - call_user_func_array($value, [ & $query]); - $whereClause = $this->buildWhere($query->getOptions('where'), $options); + $newQuery = $query->newQuery()->setConnection($this->connection); + $value($newQuery); + $whereClause = $this->buildWhere($query, $newQuery->getOptions('where')); + if (!empty($whereClause)) { - $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )'; } + } elseif (is_array($field)) { + array_unshift($value, $field); + $str2 = []; + foreach ($value as $item) { + $str2[] = $this->parseWhereItem($query, array_shift($item), $item, $logic, $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $str2) . ' )'; } elseif (strpos($field, '|')) { // 不同字段使用相同查询条件(OR) $array = explode('|', $field); $item = []; + foreach ($array as $k) { - $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); } - $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; + + $str[] = ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; } elseif (strpos($field, '&')) { // 不同字段使用相同查询条件(AND) $array = explode('&', $field); $item = []; + foreach ($array as $k) { - $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); } - $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; } else { // 对字段使用表达式查询 $field = is_string($field) ? $field : ''; - $str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); + $str[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $logic, $binds); } } - $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); } return $whereStr; } // where子单元分析 - protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) + protected function parseWhereItem(Query $query, $field, $val, $rule = '', $binds = [], $bindName = null) { // 字段分析 - $key = $field ? $this->parseKey($field, $options) : ''; + $key = $field ? $this->parseKey($query, $field) : ''; // 查询规则和条件 if (!is_array($val)) { $val = is_null($val) ? ['null', ''] : ['=', $val]; } + list($exp, $value) = $val; // 对一个字段使用多个查询条件 if (is_array($exp)) { $item = array_pop($val); + // 传入 or 或者 and if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { $rule = $item; } else { array_push($val, $item); } + foreach ($val as $k => $item) { $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; - $str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); + $str[] = $this->parseWhereItem($query, $field, $item, $rule, $binds, $bindName); } + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; } // 检测操作符 - if (!in_array($exp, $this->exp)) { - $exp = strtolower($exp); - if (isset($this->exp[$exp])) { - $exp = $this->exp[$exp]; - } else { - throw new Exception('where express error:' . $exp); - } + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; } + $bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field); + if (preg_match('/\W/', $bindName)) { // 处理带非单词字符的字段名 $bindName = md5($bindName); @@ -350,141 +400,321 @@ abstract class Builder } $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; - if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { - if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { - if ($this->query->isBind($bindName)) { + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (strpos($value, ':') !== 0 || !$query->isBind(substr($value, 1))) { + if ($query->isBind($bindName)) { $bindName .= '_' . str_replace('.', '_', uniqid('', true)); } - $this->query->bind($bindName, $value, $bindType); + + $query->bind($bindName, $value, $bindType); $value = ':' . $bindName; } } - $whereStr = ''; - if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { - // 比较运算 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindName, $bindType, isset($val[2]) ? $val[2] : 'AND'); + break; } - } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { - // 模糊匹配 - if (is_array($value)) { - foreach ($value as $item) { - $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); - } - $logic = isset($val[2]) ? $val[2] : 'AND'; - $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); - } - } elseif ('EXP' == $exp) { - // 表达式查询 - $whereStr .= '( ' . $key . ' ' . $value . ' )'; - } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { - // NULL 查询 - $whereStr .= $key . ' IS ' . $exp; - } elseif (in_array($exp, ['NOT IN', 'IN'])) { - // IN 查询 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); - } else { - $value = array_unique(is_array($value) ? $value : explode(',', $value)); - if (array_key_exists($field, $binds)) { - $bind = []; - $array = []; - $i = 0; - foreach ($value as $v) { - $i++; - if ($this->query->isBind($bindName . '_in_' . $i)) { - $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; - } else { - $bindKey = $bindName . '_in_' . $i; - } - $bind[$bindKey] = [$v, $bindType]; - $array[] = ':' . $bindKey; - } - $this->query->bind($bind); - $zone = implode(',', $array); - } else { - $zone = implode(',', $this->parseValue($value, $field)); - } - $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; - } - } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { - // BETWEEN 查询 - $data = is_array($value) ? $value : explode(',', $value); - if (array_key_exists($field, $binds)) { - if ($this->query->isBind($bindName . '_between_1')) { - $bindKey1 = $bindName . '_between_1' . uniqid(); - $bindKey2 = $bindName . '_between_2' . uniqid(); - } else { - $bindKey1 = $bindName . '_between_1'; - $bindKey2 = $bindName . '_between_2'; - } - $bind = [ - $bindKey1 => [$data[0], $bindType], - $bindKey2 => [$data[1], $bindType], - ]; - $this->query->bind($bind); - $between = ':' . $bindKey1 . ' AND :' . $bindKey2; - } else { - $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); - } - $whereStr .= $key . ' ' . $exp . ' ' . $between; - } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { - // EXISTS 查询 - if ($value instanceof \Closure) { - $whereStr .= $exp . ' ' . $this->parseClosure($value); - } else { - $whereStr .= $exp . ' (' . $value . ')'; - } - } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { - $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); - } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { - if (is_string($value)) { - $value = explode(',', $value); - } - - $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); } + + if (!isset($whereStr)) { + throw new Exception('where express error:' . $exp); + } + return $whereStr; } - // 执行闭包子查询 - protected function parseClosure($call, $show = true) + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, $key, $exp, $value, $field, $bindName, $bindType, $logic) { - $query = new Query($this->connection); - call_user_func_array($call, [ & $query]); - return $query->buildSql($show); + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $k => $item) { + $bindKey = $bindName . '_' . $k; + $bind[$bindKey] = [$item, $bindType]; + $array[] = $key . ' ' . $exp . ' :' . $bindKey; + } + + $query->bind($bind); + + $whereStr = '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // 表达式查询 + return '( ' . $key . ' ' . $value . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + if ($query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } + + $bind = [ + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], + ]; + + $query->bind($bind); + + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; + + return $key . ' ' . $exp . ' ' . $between; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // EXISTS 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } + + return $exp . ' (' . $value . ')'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindName, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value); + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindName . '_between_1', $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindName . '_between_2', $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // IN 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + + $bind = []; + $array = []; + $i = 0; + + foreach ($value as $k => $v) { + $i++; + if ($query->isBind($bindName . '_in_' . $i)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; + } else { + $bindKey = $bindName . '_in_' . $i; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; + } + + $zone = implode(',', $array); + $query->bind($bind); + + $value = empty($zone) ? "''" : $zone; + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, $call, $show = true) + { + $newQuery = $query->newQuery()->setConnection($this->connection); + $call($newQuery); + + return $newQuery->buildSql($show); } /** * 日期时间条件解析 * @access protected - * @param string $value - * @param string $key - * @param array $options - * @param string $bindName - * @param integer $bindType + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @param array $options + * @param string $bindName + * @param integer $bindType * @return string */ - protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) + protected function parseDateTime(Query $query, $value, $key, $bindName = null, $bindType = null) { + $options = $query->getOptions(); + // 获取时间字段类型 if (strpos($key, '.')) { list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { $table = $pos; } } else { $table = $options['table']; } - $type = $this->query->getTableInfo($table, 'type'); + + $type = $this->connection->getTableInfo($table, 'type'); + if (isset($type[$key])) { $info = $type[$key]; } + if (isset($info)) { if (is_string($value)) { $value = strtotime($value) ?: $value; @@ -498,18 +728,22 @@ abstract class Builder $value = date('Y-m-d', $value); } } + $bindName = $bindName ?: $key; - $this->query->bind($bindName, $value, $bindType); + + $query->bind($bindName, $value, $bindType); + return ':' . $bindName; } /** * limit分析 * @access protected - * @param mixed $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - protected function parseLimit($limit) + protected function parseLimit(Query $query, $limit) { return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; } @@ -517,85 +751,111 @@ abstract class Builder /** * join分析 * @access protected - * @param array $join - * @param array $options 查询条件 + * @param Query $query 查询对象 + * @param array $join * @return string */ - protected function parseJoin($join, $options = []) + protected function parseJoin(Query $query, $join) { $joinStr = ''; + if (!empty($join)) { foreach ($join as $item) { list($table, $type, $on) = $item; - $condition = []; + + $condition = []; + foreach ((array) $on as $val) { if (strpos($val, '=')) { list($val1, $val2) = explode('=', $val, 2); - $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + $condition[] = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); } else { $condition[] = $val; } } - $table = $this->parseTable($table, $options); + $table = $this->parseTable($query, $table); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); } } + return $joinStr; } /** * order分析 * @access protected - * @param mixed $order - * @param array $options 查询条件 + * @param Query $query 查询对象 + * @param mixed $order * @return string */ - protected function parseOrder($order, $options = []) + protected function parseOrder(Query $query, $order) { + if (empty($order)) { + return ''; + } + if (is_array($order)) { $array = []; + foreach ($order as $key => $val) { - if (is_numeric($key)) { + if (is_array($val)) { + if (isset($val['sort'])) { + $sort = ' ' . $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $options = $query->getOptions(); + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); + } + + $array[] = 'field(' . $this->parseKey($query, $key) . ',' . implode(',', $val) . ')' . $sort; + } elseif (is_numeric($key)) { if ('[rand]' == $val) { - if (method_exists($this, 'parseRand')) { - $array[] = $this->parseRand(); - } else { - throw new BadMethodCallException('method not exists:' . get_class($this) . '-> parseRand'); - } + $array[] = $this->parseRand($query); } elseif (false === strpos($val, '(')) { - $array[] = $this->parseKey($val, $options); + $array[] = $this->parseKey($query, $val); } else { $array[] = $val; } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($key, $options) . ' ' . $sort; + $array[] = $this->parseKey($query, $key) . ' ' . $sort; } } + $order = implode(',', $array); } - return !empty($order) ? ' ORDER BY ' . $order : ''; + + return ' ORDER BY ' . $order; } /** * group分析 * @access protected - * @param mixed $group + * @param Query $query 查询对象 + * @param mixed $group * @return string */ - protected function parseGroup($group) + protected function parseGroup(Query $query, $group) { - return !empty($group) ? ' GROUP BY ' . $this->parseKey($group) : ''; + return !empty($group) ? ' GROUP BY ' . $this->parseKey($query, $group) : ''; } /** * having分析 * @access protected - * @param string $having + * @param Query $query 查询对象 + * @param string $having * @return string */ - protected function parseHaving($having) + protected function parseHaving(Query $query, $having) { return !empty($having) ? ' HAVING ' . $having : ''; } @@ -603,10 +863,11 @@ abstract class Builder /** * comment分析 * @access protected - * @param string $comment + * @param Query $query 查询对象 + * @param string $comment * @return string */ - protected function parseComment($comment) + protected function parseComment(Query $query, $comment) { return !empty($comment) ? ' /* ' . $comment . ' */' : ''; } @@ -614,10 +875,11 @@ abstract class Builder /** * distinct分析 * @access protected - * @param mixed $distinct + * @param Query $query 查询对象 + * @param mixed $distinct * @return string */ - protected function parseDistinct($distinct) + protected function parseDistinct(Query $query, $distinct) { return !empty($distinct) ? ' DISTINCT ' : ''; } @@ -625,33 +887,38 @@ abstract class Builder /** * union分析 * @access protected - * @param mixed $union + * @param Query $query 查询对象 + * @param mixed $union * @return string */ - protected function parseUnion($union) + protected function parseUnion(Query $query, $union) { if (empty($union)) { return ''; } + $type = $union['type']; unset($union['type']); + foreach ($union as $u) { if ($u instanceof \Closure) { - $sql[] = $type . ' ' . $this->parseClosure($u); + $sql[] = $type . ' ' . $this->parseClosure($query, $u); } elseif (is_string($u)) { - $sql[] = $type . ' ( ' . $this->parseSqlTable($u) . ' )'; + $sql[] = $type . ' ( ' . $this->connection->parseSqlTable($u) . ' )'; } } + return ' ' . implode(' ', $sql); } /** * index分析,可在操作链中指定需要强制使用的索引 * @access protected - * @param mixed $index + * @param Query $query 查询对象 + * @param mixed $index * @return string */ - protected function parseForce($index) + protected function parseForce(Query $query, $index) { if (empty($index)) { return ''; @@ -667,14 +934,15 @@ abstract class Builder /** * 设置锁机制 * @access protected - * @param bool|string $lock + * @param Query $query 查询对象 + * @param bool|string $lock * @return string */ - protected function parseLock($lock = false) + protected function parseLock(Query $query, $lock = false) { if (is_bool($lock)) { return $lock ? ' FOR UPDATE ' : ''; - } elseif (is_string($lock)) { + } elseif (is_string($lock) && !empty($lock)) { return ' ' . trim($lock) . ' '; } } @@ -682,192 +950,196 @@ abstract class Builder /** * 生成查询SQL * @access public - * @param array $options 表达式 + * @param Query $query 查询对象 * @return string */ - public function select($options = []) + public function select(Query $query) { - $sql = str_replace( + $options = $query->getOptions(); + + return str_replace( ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], [ - $this->parseTable($options['table'], $options), - $this->parseDistinct($options['distinct']), - $this->parseField($options['field'], $options), - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseGroup($options['group']), - $this->parseHaving($options['having']), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseUnion($options['union']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - $this->parseForce($options['force']), - ], $this->selectSql); - return $sql; + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); } /** - * 生成insert SQL + * 生成Insert SQL * @access public - * @param array $data 数据 - * @param array $options 表达式 - * @param bool $replace 是否replace + * @param Query $query 查询对象 + * @param bool $replace 是否replace * @return string */ - public function insert(array $data, $options = [], $replace = false) + public function insert(Query $query, $replace = false) { + $options = $query->getOptions(); + // 分析并处理数据 - $data = $this->parseData($data, $options); + $data = $this->parseData($query, $options['data']); if (empty($data)) { return 0; } + $fields = array_keys($data); $values = array_values($data); - $sql = str_replace( + return str_replace( ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), + $this->parseTable($query, $options['table']), implode(' , ', $fields), implode(' , ', $values), - $this->parseComment($options['comment']), - ], $this->insertSql); - - return $sql; + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); } /** * 生成insertall SQL * @access public - * @param array $dataSet 数据集 - * @param array $options 表达式 - * @param bool $replace 是否replace + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace * @return string - * @throws Exception */ - public function insertAll($dataSet, $options = [], $replace = false) + public function insertAll(Query $query, $dataSet, $replace = false) { + $options = $query->getOptions(); + // 获取合法的字段 if ('*' == $options['field']) { - $fields = array_keys($this->query->getFieldsType($options['table'])); + $allowFields = $this->connection->getTableFields($options['table']); } else { - $fields = $options['field']; + $allowFields = $options['field']; } - foreach ($dataSet as $data) { - foreach ($data as $key => $val) { - if (!in_array($key, $fields, true)) { - if ($options['strict']) { - throw new Exception('fields not exists:[' . $key . ']'); - } - unset($data[$key]); - } elseif (is_null($val)) { - $data[$key] = 'NULL'; - } elseif (is_scalar($val)) { - $data[$key] = $this->parseValue($val, $key); - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $data[$key] = $val->__toString(); - } else { - // 过滤掉非标量数据 - unset($data[$key]); - } - } - $value = array_values($data); - $values[] = 'SELECT ' . implode(',', $value); + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k); + + $values[] = 'SELECT ' . implode(',', array_values($data)); if (!isset($insertFields)) { - $insertFields = array_map([$this, 'parseKey'], array_keys($data)); + $insertFields = array_keys($data); } } + $fields = []; + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + return str_replace( ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), - implode(' , ', $insertFields), + $this->parseTable($query, $options['table']), + implode(' , ', $fields), implode(' UNION ALL ', $values), - $this->parseComment($options['comment']), - ], $this->insertAllSql); + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); } /** - * 生成select insert SQL + * 生成slect insert SQL * @access public - * @param array $fields 数据 - * @param string $table 数据表 - * @param array $options 表达式 + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 * @return string */ - public function selectInsert($fields, $table, $options) + public function selectInsert(Query $query, $fields, $table) { + $options = $query->getOptions(); + if (is_string($fields)) { $fields = explode(',', $fields); } - $fields = array_map([$this, 'parseKey'], $fields); - $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); - return $sql; + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); } /** * 生成update SQL * @access public - * @param array $data 数据 - * @param array $options 表达式 + * @param Query $query 查询对象 * @return string */ - public function update($data, $options) + public function update(Query $query) { - $table = $this->parseTable($options['table'], $options); - $data = $this->parseData($data, $options); + $options = $query->getOptions(); + + $table = $this->parseTable($query, $options['table']); + $data = $this->parseData($query, $options['data']); + if (empty($data)) { return ''; } + foreach ($data as $key => $val) { - $set[] = $key . '=' . $val; + $set[] = $key . ' = ' . $val; } - $sql = str_replace( + return str_replace( ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table'], $options), - implode(',', $set), - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - ], $this->updateSql); - - return $sql; + $this->parseTable($query, $options['table']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); } /** * 生成delete SQL * @access public - * @param array $options 表达式 + * @param Query $query 查询对象 * @return string */ - public function delete($options) + public function delete(Query $query) { - $sql = str_replace( + $options = $query->getOptions(); + + return str_replace( ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table'], $options), - !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - ], $this->deleteSql); - - return $sql; + $this->parseTable($query, $options['table']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); } } diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php index e77abf8ab..c7d3cd452 100644 --- a/thinkphp/library/think/db/Connection.php +++ b/thinkphp/library/think/db/Connection.php @@ -11,25 +11,19 @@ namespace think\db; +use InvalidArgumentException; use PDO; use PDOStatement; +use think\Container; use think\Db; use think\db\exception\BindParamException; use think\Debug; use think\Exception; use think\exception\PDOException; -use think\Log; -/** - * Class Connection - * @package think - * @method Query table(string $table) 指定数据表(含前缀) - * @method Query name(string $name) 指定数据表(不含前缀) - * - */ abstract class Connection { - + protected static $instance = []; /** @var PDOStatement PDO操作实例 */ protected $PDOStatement; @@ -56,7 +50,13 @@ abstract class Connection protected $attrCase = PDO::CASE_LOWER; // 监听回调 protected static $event = []; + + // 数据表信息 + protected static $info = []; + // 使用Builder类 + protected $builderClassName; + // Builder对象 protected $builder; // 数据库连接参数配置 protected $config = [ @@ -92,10 +92,8 @@ abstract class Connection 'slave_no' => '', // 是否严格检查字段是否存在 'fields_strict' => true, - // 数据返回类型 - 'result_type' => PDO::FETCH_ASSOC, // 数据集返回类型 - 'resultset_type' => 'array', + 'resultset_type' => '', // 自动写入时间戳字段 'auto_timestamp' => false, // 时间字段取出后的默认时间格式 @@ -123,26 +121,67 @@ abstract class Connection protected $bind = []; /** - * 构造函数 读取数据库配置信息 + * 架构函数 读取数据库配置信息 * @access public - * @param array $config 数据库配置数组 + * @param array $config 数据库配置数组 */ public function __construct(array $config = []) { if (!empty($config)) { $this->config = array_merge($this->config, $config); } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + + // 执行初始化操作 + $this->initialize(); } /** - * 获取新的查询对象 + * 初始化 * @access protected - * @return Query + * @return void */ - protected function getQuery() + protected function initialize() + {} + + /** + * 取得数据库连接类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function instance($config = [], $name = false) { - $class = $this->config['query']; - return new $class($this); + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + // 解析连接参数 支持数组和字符串 + $options = self::parseConfig($config); + + if (empty($options['type'])) { + throw new InvalidArgumentException('Undefined db type'); + } + + $class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']); + // 记录初始化信息 + Container::get('app')->log('[ DB ] INIT ' . $options['type']); + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; } /** @@ -150,31 +189,42 @@ abstract class Connection * @access public * @return string */ - public function getBuilder() + public function getBuilderClass() { - if (!empty($this->builder)) { - return $this->builder; + if (!empty($this->builderClassName)) { + return $this->builderClassName; } else { return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); } } /** - * 调用Query类的查询方法 - * @access public - * @param string $method 方法名称 - * @param array $args 调用参数 - * @return mixed + * 设置当前的数据库Builder对象 + * @access protected + * @param Builder $builder + * @return void */ - public function __call($method, $args) + protected function setBuilder(Builder $builder) { - return call_user_func_array([$this->getQuery(), $method], $args); + $this->builder = $builder; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; } /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ abstract protected function parseDsn($config); @@ -182,7 +232,7 @@ abstract class Connection /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ abstract public function getFields($tableName); @@ -198,7 +248,7 @@ abstract class Connection /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ abstract protected function getExplain($sql); @@ -206,7 +256,7 @@ abstract class Connection /** * 对返数据表字段信息进行大小写转换出来 * @access public - * @param array $info 字段信息 + * @param array $info 字段信息 * @return array */ public function fieldCase($info) @@ -223,13 +273,171 @@ abstract class Connection default: // 不做转换 } + return $info; } + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + + return $bind; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $prefix = $this->getConfig('prefix'); + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { + return $prefix . strtolower($match[1]); + }, $sql); + } + + return $sql; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($tableName) = explode(' ', $tableName); + + if (!strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset(self::$info[$schema])) { + // 读取缓存 + $cacheFile = Container::get('app')->getRuntimePath() . 'schema/' . $schema . '.php'; + if (!$this->config['debug'] && is_file($cacheFile)) { + $info = include $cacheFile; + } else { + $info = $this->getFields($tableName); + } + + $fields = array_keys($info); + $bind = $type = []; + + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + + self::$info[$schema] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + + return $fetch ? self::$info[$schema][$fetch] : self::$info[$schema]; + } + + /** + * 获取数据表的主键 + * @access public + * @param string $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName) + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName) + { + return $this->getTableInfo($tableName, 'bind'); + } + /** * 获取数据库的配置参数 * @access public - * @param string $config 配置名称 + * @param string $config 配置名称 * @return mixed */ public function getConfig($config = '') @@ -240,8 +448,8 @@ abstract class Connection /** * 设置数据库的配置参数 * @access public - * @param string|array $config 配置名称 - * @param mixed $value 配置值 + * @param string|array $config 配置名称 + * @param mixed $value 配置值 * @return void */ public function setConfig($config, $value = '') @@ -256,9 +464,9 @@ abstract class Connection /** * 连接数据库方法 * @access public - * @param array $config 连接参数 - * @param integer $linkNum 连接序号 - * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) * @return PDO * @throws Exception */ @@ -270,40 +478,42 @@ abstract class Connection } else { $config = array_merge($this->config, $config); } + // 连接参数 if (isset($config['params']) && is_array($config['params'])) { $params = $config['params'] + $this->params; } else { $params = $this->params; } + // 记录当前字段属性大小写设置 $this->attrCase = $params[PDO::ATTR_CASE]; - // 数据返回类型 - if (isset($config['result_type'])) { - $this->fetchType = $config['result_type']; - } try { if (empty($config['dsn'])) { $config['dsn'] = $this->parseDsn($config); } + if ($config['debug']) { $startTime = microtime(true); } + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + if ($config['debug']) { // 记录数据库连接信息 - Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); + $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); } } catch (\PDOException $e) { if ($autoConnection) { - Log::record($e->getMessage(), 'error'); + $this->log($e->getMessage(), 'error'); return $this->connect($autoConnection, $linkNum); } else { throw $e; } } } + return $this->links[$linkNum]; } @@ -330,31 +540,101 @@ abstract class Connection } } + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param Model $model 模型对象实例 + * @param array $condition 查询条件 + * @param mixed $relation 关联查询 + * @return \Generator + */ + public function getCursor($sql, $bind = [], $master = false, $model = null, $condition = null, $relation = null) + { + $this->initConnect($master); + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + // 释放前次的查询结果 + if (!empty($this->PDOStatement)) { + $this->free(); + } + + Db::$queryTimes++; + + // 调试开始 + $this->debug(true); + + // 预处理 + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + $instance = $model->newInstance($result, $condition); + + if ($relation) { + $instance->relationQuery($relation); + } + + yield $instance; + } else { + yield $result; + } + } + } + /** * 执行查询 返回数据集 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param bool $master 是否在主服务器读操作 - * @param bool $pdo 是否返回PDO对象 - * @return mixed - * @throws PDOException + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return array + * @throws BindParamException + * @throws \PDOException * @throws \Exception + * @throws \Throwable */ public function query($sql, $bind = [], $master = false, $pdo = false) { $this->initConnect($master); + if (!$this->linkID) { return false; } // 记录SQL语句 $this->queryStr = $sql; - if ($bind) { - $this->bind = $bind; - } + + $this->bind = $bind; Db::$queryTimes++; + try { // 调试开始 $this->debug(true); @@ -363,38 +643,47 @@ abstract class Connection if (!empty($this->PDOStatement)) { $this->free(); } + // 预处理 if (empty($this->PDOStatement)) { $this->PDOStatement = $this->linkID->prepare($sql); } + // 是否为存储过程调用 $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 if ($procedure) { $this->bindParam($bind); } else { $this->bindValue($bind); } + // 执行查询 $this->PDOStatement->execute(); + // 调试结束 $this->debug(false); + // 返回结果集 return $this->getResult($pdo, $procedure); } catch (\PDOException $e) { if ($this->isBreak($e)) { return $this->close()->query($sql, $bind, $master, $pdo); } + throw new PDOException($e, $this->config, $this->getLastsql()); } catch (\Throwable $e) { if ($this->isBreak($e)) { return $this->close()->query($sql, $bind, $master, $pdo); } + throw $e; } catch (\Exception $e) { if ($this->isBreak($e)) { return $this->close()->query($sql, $bind, $master, $pdo); } + throw $e; } } @@ -402,24 +691,26 @@ abstract class Connection /** * 执行语句 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 + * @param string $sql sql指令 + * @param array $bind 参数绑定 * @return int - * @throws PDOException + * @throws BindParamException + * @throws \PDOException * @throws \Exception + * @throws \Throwable */ public function execute($sql, $bind = []) { $this->initConnect(true); + if (!$this->linkID) { return false; } // 记录SQL语句 $this->queryStr = $sql; - if ($bind) { - $this->bind = $bind; - } + + $this->bind = $bind; Db::$executeTimes++; try { @@ -430,48 +721,710 @@ abstract class Connection if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { $this->free(); } + // 预处理 if (empty($this->PDOStatement)) { $this->PDOStatement = $this->linkID->prepare($sql); } + // 是否为存储过程调用 $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 if ($procedure) { $this->bindParam($bind); } else { $this->bindValue($bind); } + // 执行语句 $this->PDOStatement->execute(); + // 调试结束 $this->debug(false); $this->numRows = $this->PDOStatement->rowCount(); + return $this->numRows; } catch (\PDOException $e) { if ($this->isBreak($e)) { return $this->close()->execute($sql, $bind); } + throw new PDOException($e, $this->config, $this->getLastsql()); } catch (\Throwable $e) { if ($this->isBreak($e)) { return $this->close()->execute($sql, $bind); } + throw $e; } catch (\Exception $e) { if ($this->isBreak($e)) { return $this->close()->execute($sql, $bind); } + throw $e; } } + /** + * 查找单条记录 + * @access public + * @param Query $query 查询对象 + * @return array|null|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + + if (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + } + + $data = $options['data']; + $result = false; + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + if (is_string($cache['key'])) { + $key = $cache['key']; + } elseif (!isset($key)) { + $key = $this->getCacheKey($data, $options, $query->getBind(false)); + } + + $result = Container::get('cache')->get($key); + } + + if (false === $result) { + if (is_string($pk)) { + if (!is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + } + $query->setOption('data', $data); + $query->setOption('limit', 1); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 事件回调 + if ($result = $query->trigger('before_find')) { + } else { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } + + return $result; + } + + /** + * 使用游标查询记录 + * @access public + * @param Query $query 查询对象 + * @return \Generator + */ + public function cursor(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + $condition = isset($options['where']['AND']) ? $options['where']['AND'] : null; + $relation = isset($options['relaltion']) ? $options['relation'] : null; + + // 执行查询操作 + return $this->getCursor($sql, $bind, $options['master'], $query->getModel(), $condition, $relation); + } + + /** + * 查找记录 + * @access public + * @param Query $query 查询对象 + * @return array|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $resultSet = false; + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options) . serialize($query->getBind(false))); + $resultSet = Container::get('cache')->get($key); + } + + if (false === $resultSet) { + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + if ($resultSet = $query->trigger('before_select')) { + } else { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (isset($cache) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $cache); + } + } + + return $resultSet; + } + + /** + * 插入记录 + * @access public + * @param Query $query 查询对象 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(Query $query, $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query, $replace); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行操作 + $result = $this->execute($sql, $bind); + + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $query->trigger('after_insert'); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param Query $query 查询对象 + * @param mixed $dataSet 数据集 + * @param bool $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(Query $query, $dataSet = [], $replace = false, $limit = null) + { + if (!is_array(reset($dataSet))) { + return false; + } + + $options = $query->getOptions(); + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item, $replace); + $bind = $query->getBind(); + if (!empty($options['fetch_sql'])) { + $fetchSql[] = $this->getRealSql($sql, $bind); + } else { + $count += $this->execute($sql, $bind); + } + } + + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + + return isset($fetchSql) ? implode(';', $fetchSql) : $count; + } + + $sql = $this->builder->insertAll($query, $dataSet, $replace); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } else { + // 执行操作 + return $this->execute($sql, $bind); + } + } + + /** + * 通过Select方式插入记录 + * @access public + * @param Query $query 查询对象 + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert(Query $query, $fields, $table) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成SQL语句 + $table = $this->parseSqlTable($table); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } else { + // 执行操作 + return $this->execute($sql, $bind); + } + } + + /** + * 更新记录 + * @access public + * @param Query $query 查询对象 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(Query $query) + { + $options = $query->getOptions(); + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + $pk = $query->getPk($options); + $data = $options['data']; + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = [$pk, '=', $data[$pk]]; + if (!isset($key)) { + $key = $this->getCacheKey($data[$pk], $options); + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = [$field, '=', $data[$field]]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + $query->setOption('where', ['AND' => $where]); + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + } + + // 更新数据 + $query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } else { + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind); + + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + + $query->setOption('data', $data); + $query->trigger('after_update'); + } + + return $result; + } + } + + /** + * 删除记录 + * @access public + * @param Query $query 查询对象 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + $data = $options['data']; + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } elseif (!is_null($data) && true !== $data && !is_array($data)) { + $key = $this->getCacheKey($data, $options); + } elseif (is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = $this->execute($sql, $bind); + + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + + $options['data'] = $data; + + $query->trigger('after_delete'); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @param bool $default 默认值 + * @return mixed + */ + public function value(Query $query, $field, $default = null) + { + $options = $query->getOptions(); + + $result = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false))); + $result = Container::get('cache')->get($key); + } + + if (false === $result) { + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + $query->setOption('limit', 1); + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + + $result = $pdo->fetchColumn(); + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @return mixed + */ + public function aggregate(Query $query, $aggregate, $field) + { + $field = $aggregate . '(' . $this->builder->parseKey($query, $field) . ') AS tp_' . strtolower($aggregate); + + return $this->value($query, $field, 0); + } + + /** + * 得到某个列的数组 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(Query $query, $field, $key = '') + { + $options = $query->getOptions(); + + $result = false; + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false))); + $result = Container::get('cache')->get($guid); + } + + if (false === $result) { + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if ('*' == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + + if (2 == $count) { + $column = $key2; + } elseif (1 == $count) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; + } + } + + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + } + + return $result; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function pdo(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + /** * 根据参数绑定组装最终的SQL语句 便于调试 * @access public - * @param string $sql 带参数绑定的sql语句 - * @param array $bind 参数绑定列表 + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 * @return string */ public function getRealSql($sql, array $bind = []) @@ -483,11 +1436,13 @@ abstract class Connection foreach ($bind as $key => $val) { $value = is_array($val) ? $val[0] : $val; $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + if (PDO::PARAM_STR == $type) { - $value = $this->quote($value); + $value = '\'' . addslashes($value) . '\''; } elseif (PDO::PARAM_INT == $type) { $value = (float) $value; } + // 判断占位符 $sql = is_numeric($key) ? substr_replace($sql, $value, strpos($sql, '?'), 1) : @@ -496,6 +1451,7 @@ abstract class Connection [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], $sql . ' '); } + return rtrim($sql); } @@ -504,7 +1460,7 @@ abstract class Connection * 支持 ['name'=>'value','id'=>123] 对应命名占位符 * 或者 ['value',123] 对应问号占位符 * @access public - * @param array $bind 要绑定的参数列表 + * @param array $bind 要绑定的参数列表 * @return void * @throws BindParamException */ @@ -513,6 +1469,7 @@ abstract class Connection foreach ($bind as $key => $val) { // 占位符 $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { $val[0] = 0; @@ -521,6 +1478,7 @@ abstract class Connection } else { $result = $this->PDOStatement->bindValue($param, $val); } + if (!$result) { throw new BindParamException( "Error occurred when binding parameters '{$param}'", @@ -535,7 +1493,7 @@ abstract class Connection /** * 存储过程的输入输出参数绑定 * @access public - * @param array $bind 要绑定的参数列表 + * @param array $bind 要绑定的参数列表 * @return void * @throws BindParamException */ @@ -543,14 +1501,17 @@ abstract class Connection { foreach ($bind as $key => $val) { $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { array_unshift($val, $param); $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); } else { $result = $this->PDOStatement->bindValue($param, $val); } + if (!$result) { $param = array_shift($val); + throw new BindParamException( "Error occurred when binding parameters '{$param}'", $this->config, @@ -564,9 +1525,9 @@ abstract class Connection /** * 获得数据集数组 * @access protected - * @param bool $pdo 是否返回PDOStatement - * @param bool $procedure 是否存储过程 - * @return PDOStatement|array + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return array */ protected function getResult($pdo = false, $procedure = false) { @@ -574,12 +1535,16 @@ abstract class Connection // 返回PDOStatement对象处理 return $this->PDOStatement; } + if ($procedure) { // 存储过程返回结果 return $this->procedure(); } - $result = $this->PDOStatement->fetchAll($this->fetchType); + + $result = $this->PDOStatement->fetchAll($this->fetchType); + $this->numRows = count($result); + return $result; } @@ -591,20 +1556,23 @@ abstract class Connection protected function procedure() { $item = []; + do { $result = $this->getResult(); if ($result) { $item[] = $result; } } while ($this->PDOStatement->nextRowset()); + $this->numRows = count($item); + return $item; } /** * 执行数据库事务 * @access public - * @param callable $callback 数据操作方法回调 + * @param callable $callback 数据操作方法回调 * @return mixed * @throws PDOException * @throws \Exception @@ -613,11 +1581,13 @@ abstract class Connection public function transaction($callback) { $this->startTrans(); + try { $result = null; if (is_callable($callback)) { $result = call_user_func_array($callback, [$this]); } + $this->commit(); return $result; } catch (\Exception $e) { @@ -632,7 +1602,8 @@ abstract class Connection /** * 启动事务 * @access public - * @return bool|mixed + * @return void + * @throws \PDOException * @throws \Exception */ public function startTrans() @@ -643,6 +1614,7 @@ abstract class Connection } ++$this->transTimes; + try { if (1 == $this->transTimes) { $this->linkID->beginTransaction(); @@ -651,7 +1623,6 @@ abstract class Connection $this->parseSavepoint('trans' . $this->transTimes) ); } - } catch (\PDOException $e) { if ($this->isBreak($e)) { return $this->close()->startTrans(); @@ -662,11 +1633,6 @@ abstract class Connection return $this->close()->startTrans(); } throw $e; - } catch (\Error $e) { - if ($this->isBreak($e)) { - return $this->close()->startTrans(); - } - throw $e; } } @@ -719,7 +1685,8 @@ abstract class Connection /** * 生成定义保存点的SQL - * @param $name + * @access protected + * @param $name * @return string */ protected function parseSavepoint($name) @@ -729,7 +1696,8 @@ abstract class Connection /** * 生成回滚到保存点的SQL - * @param $name + * @access protected + * @param $name * @return string */ protected function parseSavepointRollBack($name) @@ -741,7 +1709,8 @@ abstract class Connection * 批处理执行SQL语句 * 批处理的指令都认为是execute操作 * @access public - * @param array $sqlArray SQL批处理指令 + * @param array $sqlArray SQL批处理指令 + * @param array $bind 参数绑定 * @return boolean */ public function batchQuery($sqlArray = [], $bind = []) @@ -749,8 +1718,10 @@ abstract class Connection if (!is_array($sqlArray)) { return false; } + // 自动启动事务支持 $this->startTrans(); + try { foreach ($sqlArray as $sql) { $this->execute($sql, $bind); @@ -768,7 +1739,7 @@ abstract class Connection /** * 获得查询次数 * @access public - * @param boolean $execute 是否包含所有查询 + * @param boolean $execute 是否包含所有查询 * @return integer */ public function getQueryTimes($execute = false) @@ -797,13 +1768,14 @@ abstract class Connection $this->linkWrite = null; $this->linkRead = null; $this->links = []; + return $this; } /** * 是否断线 * @access protected - * @param \PDOException|\Exception $e 异常对象 + * @param \PDOException|\Exception $e 异常对象 * @return bool */ protected function isBreak($e) @@ -848,7 +1820,7 @@ abstract class Connection /** * 获取最近插入的ID * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return string */ public function getLastInsID($sequence = null) @@ -879,50 +1851,43 @@ abstract class Connection } else { $error = ''; } + if ('' != $this->queryStr) { $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); } - return $error; - } - /** - * SQL指令安全过滤 - * @access public - * @param string $str SQL字符串 - * @param bool $master 是否主库查询 - * @return string - */ - public function quote($str, $master = true) - { - $this->initConnect($master); - return $this->linkID ? $this->linkID->quote($str) : $str; + return $error; } /** * 数据库调试 记录当前SQL及分析性能 * @access protected - * @param boolean $start 调试开始标记 true 开始 false 结束 - * @param string $sql 执行的SQL语句 留空自动获取 + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 * @return void */ protected function debug($start, $sql = '') { if (!empty($this->config['debug'])) { // 开启数据库调试模式 + $debug = Container::get('debug'); + if ($start) { - Debug::remark('queryStartTime', 'time'); + $debug->remark('queryStartTime', 'time'); } else { // 记录操作结束时间 - Debug::remark('queryEndTime', 'time'); - $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); + $debug->remark('queryEndTime', 'time'); + $runtime = $debug->getRangeTime('queryStartTime', 'queryEndTime'); $sql = $sql ?: $this->getLastsql(); $result = []; + // SQL性能分析 if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { $result = $this->getExplain($sql); } + // SQL监听 - $this->trigger($sql, $runtime, $result); + $this->triggerSql($sql, $runtime, $result); } } } @@ -930,7 +1895,7 @@ abstract class Connection /** * 监听SQL执行 * @access public - * @param callable $callback 回调方法 + * @param callable $callback 回调方法 * @return void */ public function listen($callback) @@ -941,12 +1906,12 @@ abstract class Connection /** * 触发SQL事件 * @access protected - * @param string $sql SQL语句 - * @param float $runtime SQL运行时间 - * @param mixed $explain SQL分析 + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 * @return bool */ - protected function trigger($sql, $runtime, $explain = []) + protected function triggerSql($sql, $runtime, $explain = []) { if (!empty(self::$event)) { foreach (self::$event as $callback) { @@ -956,17 +1921,23 @@ abstract class Connection } } else { // 未注册监听则记录到日志中 - Log::record('[ SQL ] ' . $sql . ' [ RunTime:' . $runtime . 's ]', 'sql'); + $this->log('[ SQL ] ' . $sql . ' [ RunTime:' . $runtime . 's ]'); + if (!empty($explain)) { - Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql'); + $this->log('[ EXPLAIN : ' . var_export($explain, true) . ' ]'); } } } + public function log($log, $type = 'sql') + { + $this->config['debug'] && Container::get('log')->record($log, $type); + } + /** * 初始化数据库连接 * @access protected - * @param boolean $master 是否主服务器 + * @param boolean $master 是否主服务器 * @return void */ protected function initConnect($master = true) @@ -977,11 +1948,13 @@ abstract class Connection if (!$this->linkWrite) { $this->linkWrite = $this->multiConnect(true); } + $this->linkID = $this->linkWrite; } else { if (!$this->linkRead) { $this->linkRead = $this->multiConnect(false); } + $this->linkID = $this->linkRead; } } elseif (!$this->linkID) { @@ -993,12 +1966,13 @@ abstract class Connection /** * 连接分布式服务器 * @access protected - * @param boolean $master 主服务器 + * @param boolean $master 主服务器 * @return PDO */ protected function multiConnect($master = false) { $_config = []; + // 分布式数据库配置解析 foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { $_config[$name] = explode(',', $this->config[$name]); @@ -1024,16 +1998,20 @@ abstract class Connection $r = floor(mt_rand(0, count($_config['hostname']) - 1)); } $dbMaster = false; + if ($m != $r) { $dbMaster = []; foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; } } + $dbConfig = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; } + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); } @@ -1044,10 +2022,111 @@ abstract class Connection public function __destruct() { // 释放查询 - if ($this->PDOStatement) { - $this->free(); - } + $this->free(); + // 关闭连接 $this->close(); } + + /** + * 缓存数据 + * @access protected + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + $cache = Container::get('cache'); + + if (isset($config['tag'])) { + $cache->tag($config['tag'])->set($key, $data, $config['expire']); + } else { + $cache->set($key, $data, $config['expire']); + } + } + + /** + * 生成缓存标识 + * @access protected + * @param mixed $value 缓存数据 + * @param array $options 缓存参数 + * @param array $bind 绑定参数 + * @return string + */ + protected function getCacheKey($value, $options, $bind = []) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'])) { + $data = $value[2]; + } + + if (isset($data)) { + return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } else { + try { + return md5(serialize($options) . serialize($bind)); + } catch (\Exception $e) { + return; + } + } + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config + * @return array + */ + private static function parseConfig($config) + { + if (empty($config)) { + $config = Container::get('config')->pull('database'); + } elseif (is_string($config) && false === strpos($config, '/')) { + // 支持读取配置参数 + $config = Container::get('config')->get('database.' . $config); + } + + if (is_string($config)) { + return self::parseDsnConfig($config); + } else { + return $config; + } + } + + /** + * DSN解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr + * @return array + */ + private static function parseDsnConfig($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + } diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php index d425d78a1..99093acd2 100644 --- a/thinkphp/library/think/db/Query.php +++ b/thinkphp/library/think/db/Query.php @@ -12,11 +12,8 @@ namespace think\db; use PDO; -use think\App; -use think\Cache; use think\Collection; -use think\Config; -use think\Db; +use think\Container; use think\db\exception\BindParamException; use think\db\exception\DataNotFoundException; use think\db\exception\ModelNotFoundException; @@ -31,70 +28,188 @@ use think\Paginator; class Query { - // 数据库Connection对象实例 + /** + * 数据库连接对象列表 + * @var array + */ + protected static $connections = []; + + /** + * 当前数据库连接对象 + * @var Connection + */ protected $connection; - // 数据库Builder对象实例 - protected $builder; - // 当前模型类名称 + + /** + * 当前模型对象 + * @var Model + */ protected $model; - // 当前数据表名称(含前缀) - protected $table = ''; - // 当前数据表名称(不含前缀) + + /** + * 当前数据表名称(不含前缀) + * @var string + */ protected $name = ''; - // 当前数据表主键 + + /** + * 当前数据表主键 + * @var string|array + */ protected $pk; - // 当前数据表前缀 + + /** + * 当前数据表前缀 + * @var string + */ protected $prefix = ''; - // 查询参数 + + /** + * 当前查询参数 + * @var array + */ protected $options = []; - // 参数绑定 + + /** + * 当前参数绑定 + * @var array + */ protected $bind = []; - // 数据表信息 - protected static $info = []; - // 回调事件 + + /** + * 事件回调 + * @var array + */ private static $event = []; /** - * 构造函数 - * @access public - * @param Connection $connection 数据库对象实例 - * @param Model $model 模型对象 + * 扩展查询方法 + * @var array */ - public function __construct(Connection $connection = null, $model = null) + private static $extend = []; + + /** + * 日期查询表达式 + * @var array + */ + protected $timeRule = [ + 'today' => ['today', 'tomorrow'], + 'yesterday' => ['yesterday', 'today'], + 'week' => ['this week 00:00:00', 'next week 00:00:00'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'], + 'year' => ['this year 1/1', 'next year 1/1'], + 'last year' => ['last year 1/1', 'this year 1/1'], + ]; + + /** + * 日期查询快捷定义 + * @var array + */ + protected $timeExp = ['d' => 'today', 'w' => 'week', 'm' => 'month', 'y' => 'year']; + + /** + * 架构函数 + * @access public + */ + public function __construct(Connection $connection = null) { - $this->connection = $connection ?: Db::connect([], true); - $this->prefix = $this->connection->getConfig('prefix'); - $this->model = $model; - // 设置当前连接的Builder对象 - $this->setBuilder(); + if (is_null($connection)) { + $this->connection = Connection::instance(); + } else { + $this->connection = $connection; + } + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 创建一个新的查询对象 + * @access public + * @return Query + */ + public function newQuery() + { + return new static($this->connection); } /** * 利用__call方法实现一些特殊的Model方法 * @access public - * @param string $method 方法名称 - * @param array $args 调用参数 + * @param string $method 方法名称 + * @param array $args 调用参数 * @return mixed * @throws DbException * @throws Exception */ public function __call($method, $args) { - if (strtolower(substr($method, 0, 5)) == 'getby') { + if (isset(self::$extend[strtolower($method)])) { + // 调用扩展查询方法 + array_unshift($args, $this); + + return Container::getInstance()->invoke(self::$extend[strtolower($method)], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'getby') { // 根据某个字段获取记录 - $field = Loader::parseName(substr($method, 5)); - $where[$field] = $args[0]; - return $this->where($where)->find(); + $field = Loader::parseName(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { // 根据某个字段获取记录的某个值 - $name = Loader::parseName(substr($method, 10)); - $where[$name] = $args[0]; - return $this->where($where)->value($args[1]); + $name = Loader::parseName(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Loader::parseName(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Loader::parseName(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; } else { - throw new Exception('method not exist:' . __CLASS__ . '->' . $method); + throw new Exception('method not exist:' . static::class . '->' . $method); } } + /** + * 扩展查询方法 + * @access public + * @param string|array $method 查询方法名 + * @param callable $callback + * @return void + */ + public static function extend($method, $callback = null) + { + if (is_array($method)) { + foreach ($method as $key => $val) { + self::$extend[strtolower($key)] = $val; + } + } else { + self::$extend[strtolower($method)] = $callback; + } + } + + /** + * 设置当前的数据库Connection对象 + * @access public + * @param Connection $connection + * @return $this + */ + public function setConnection(Connection $connection) + { + $this->connection = $connection; + $this->prefix = $this->connection->getConfig('prefix'); + + return $this; + } + /** * 获取当前的数据库Connection对象 * @access public @@ -106,54 +221,31 @@ class Query } /** - * 切换当前的数据库连接 + * 指定模型 * @access public - * @param mixed $config + * @param Model $model 模型对象实例 * @return $this */ - public function connect($config) + public function model(Model $model) { - $this->connection = Db::connect($config); - $this->setBuilder(); - $this->prefix = $this->connection->getConfig('prefix'); + $this->model = $model; return $this; } /** - * 设置当前的数据库Builder对象 - * @access protected - * @return void - */ - protected function setBuilder() - { - $class = $this->connection->getBuilder(); - $this->builder = new $class($this->connection, $this); - } - - /** - * 获取当前的模型对象名 + * 获取当前的模型对象 * @access public * @return Model|null */ public function getModel() { - return $this->model; + return $this->model ? $this->model->setQuery($this) : null; } /** - * 获取当前的builder实例对象 + * 指定当前数据表名(不含前缀) * @access public - * @return Builder - */ - public function getBuilder() - { - return $this->builder; - } - - /** - * 指定默认的数据表名(不含前缀) - * @access public - * @param string $name + * @param string $name * @return $this */ public function name($name) @@ -163,61 +255,61 @@ class Query } /** - * 指定默认数据表名(含前缀) + * 获取当前的数据表名称 * @access public - * @param string $table 表名 - * @return $this + * @return string */ - public function setTable($table) + public function getName() { - $this->table = $table; - return $this; + return $this->name ?: $this->model->getName(); } /** * 得到当前或者指定名称的数据表 * @access public - * @param string $name + * @param string $name * @return string */ public function getTable($name = '') { - if ($name || empty($this->table)) { - $name = $name ?: $this->name; - $tableName = $this->prefix; - if ($name) { - $tableName .= Loader::parseName($name); - } - } else { - $tableName = $this->table; + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; } - return $tableName; + + $name = $name ?: $this->name; + + return $this->prefix . Loader::parseName($name); } /** - * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * 切换数据库连接 * @access public - * @param string $sql sql语句 - * @return string + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return $this|object + * @throws Exception */ - public function parseSqlTable($sql) + public function connect($config = [], $name = false) { - if (false !== strpos($sql, '__')) { - $prefix = $this->prefix; - $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { - return $prefix . strtolower($match[1]); - }, $sql); + $this->connection = Connection::instance($config, $name); + $query = $this->connection->getConfig('query'); + + if (__CLASS__ != trim($query, '\\')) { + return new $query($this->connection); } - return $sql; + + $this->prefix = $this->connection->getConfig('prefix'); + + return $this; } /** * 执行查询 返回数据集 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param boolean $master 是否在主服务器读操作 - * @param bool|string $class 指定返回的数据集对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool|string $class 指定返回的数据集对象 * @return mixed * @throws BindParamException * @throws PDOException @@ -230,8 +322,8 @@ class Query /** * 执行语句 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 + * @param string $sql sql指令 + * @param array $bind 参数绑定 * @return int * @throws BindParamException * @throws PDOException @@ -241,10 +333,21 @@ class Query return $this->connection->execute($sql, $bind); } + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + $this->connection->listen($callback); + } + /** * 获取最近插入的ID * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return string */ public function getLastInsID($sequence = null) @@ -265,7 +368,7 @@ class Query /** * 执行数据库事务 * @access public - * @param callable $callback 数据操作方法回调 + * @param callable $callback 数据操作方法回调 * @return mixed */ public function transaction($callback) @@ -309,18 +412,18 @@ class Query * 批处理执行SQL语句 * 批处理的指令都认为是execute操作 * @access public - * @param array $sql SQL批处理指令 + * @param array $sql SQL批处理指令 * @return boolean */ - public function batchQuery($sql = [], $bind = []) + public function batchQuery($sql = []) { - return $this->connection->batchQuery($sql, $bind); + return $this->connection->batchQuery($sql); } /** * 获取数据库的配置参数 * @access public - * @param string $name 参数名称 + * @param string $name 参数名称 * @return boolean */ public function getConfig($name = '') @@ -328,12 +431,43 @@ class Query return $this->connection->getConfig($name); } + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = '') + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName = '', $field = null) + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getFieldsType($tableName, $field); + } + /** * 得到分表的的数据表名 * @access public - * @param array $data 操作的数据 - * @param string $field 分表依据的字段 - * @param array $rule 分表规则 + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 * @return string */ public function getPartitionTableName($data, $field, $rule = []) @@ -382,6 +516,7 @@ class Query } $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; + return $tableName; } } @@ -389,122 +524,64 @@ class Query /** * 得到某个字段的值 * @access public - * @param string $field 字段名 - * @param mixed $default 默认值 - * @param bool $force 强制转为数字类型 + * @param string $field 字段名 + * @param mixed $default 默认值 * @return mixed */ - public function value($field, $default = null, $force = false) + public function value($field, $default = null) { - $result = false; - if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { - // 判断查询缓存 - $cache = $this->options['cache']; - if (empty($this->options['table'])) { - $this->options['table'] = $this->getTable(); - } - $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options) . serialize($this->bind)); - $result = Cache::get($key); - } - if (false === $result) { - if (isset($this->options['field'])) { - unset($this->options['field']); - } - $pdo = $this->field($field)->limit(1)->getPdo(); - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } - $result = $pdo->fetchColumn(); - if ($force) { - $result += 0; - } + $this->parseOptions(); - if (isset($cache)) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } - } else { - // 清空查询条件 - $this->options = []; + $result = $this->connection->value($this, $field, $default); + + if (!empty($this->options['fetch_sql'])) { + return $result; } - return false !== $result ? $result : $default; + + return $result; } /** * 得到某个列的数组 * @access public - * @param string $field 字段名 多个字段用逗号分隔 - * @param string $key 索引 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 * @return array */ public function column($field, $key = '') { - $result = false; - if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { - // 判断查询缓存 - $cache = $this->options['cache']; - if (empty($this->options['table'])) { - $this->options['table'] = $this->getTable(); - } - $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options) . serialize($this->bind)); - $result = Cache::get($guid); - } - if (false === $result) { - if (isset($this->options['field'])) { - unset($this->options['field']); - } - if (is_null($field)) { - $field = '*'; - } elseif ($key && '*' != $field) { - $field = $key . ',' . $field; - } - $pdo = $this->field($field)->getPdo(); - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } - if (1 == $pdo->columnCount()) { - $result = $pdo->fetchAll(PDO::FETCH_COLUMN); - } else { - $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); - if ($resultSet) { - $fields = array_keys($resultSet[0]); - $count = count($fields); - $key1 = array_shift($fields); - $key2 = $fields ? array_shift($fields) : ''; - $key = $key ?: $key1; - if (strpos($key, '.')) { - list($alias, $key) = explode('.', $key); - } - foreach ($resultSet as $val) { - if ($count > 2) { - $result[$val[$key]] = $val; - } elseif (2 == $count) { - $result[$val[$key]] = $val[$key2]; - } elseif (1 == $count) { - $result[$val[$key]] = $val[$key1]; - } - } - } else { - $result = []; - } - } - if (isset($cache) && isset($guid)) { - // 缓存数据 - $this->cacheData($guid, $result, $cache); - } - } else { - // 清空查询条件 - $this->options = []; + $this->parseOptions(); + + return $this->connection->column($this, $field, $key); + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + $this->parseOptions(); + + $result = $this->connection->aggregate($this, $aggregate, $field); + + if (!empty($this->options['fetch_sql'])) { + return $result; + } elseif ($force) { + $result += 0; } + return $result; } /** * COUNT查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return integer|string */ public function count($field = '*') @@ -512,65 +589,72 @@ class Query if (isset($this->options['group'])) { // 支持GROUP $options = $this->getOptions(); - $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); - return $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + $subSql = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql(); + + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + if (!empty($options['fetch_sql'])) { + $query->fetchSql(true); + } + + return $query->aggregate('COUNT', '*', true); } - return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); + return $this->aggregate('COUNT', $field, true); } /** * SUM查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return float|int */ public function sum($field) { - return $this->value('SUM(' . $field . ') AS tp_sum', 0, true); + return $this->aggregate('SUM', $field, true); } /** * MIN查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function min($field, $force = true) { - return $this->value('MIN(' . $field . ') AS tp_min', 0, $force); + return $this->aggregate('MIN', $field, $force); } /** * MAX查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function max($field, $force = true) { - return $this->value('MAX(' . $field . ') AS tp_max', 0, $force); + return $this->aggregate('MAX', $field, $force); } /** * AVG查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return float|int */ public function avg($field) { - return $this->value('AVG(' . $field . ') AS tp_avg', 0, true); + return $this->aggregate('AVG', $field, true); } /** * 设置记录的某个字段值 * 支持使用数据库字段和方法 * @access public - * @param string|array $field 字段名 - * @param mixed $value 字段值 + * @param string|array $field 字段名 + * @param mixed $value 字段值 * @return integer */ public function setField($field, $value = '') @@ -580,103 +664,118 @@ class Query } else { $data[$field] = $value; } + return $this->update($data); } /** * 字段值(延迟)增长 * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) * @return integer|true * @throws Exception */ public function setInc($field, $step = 1, $lazyTime = 0) { $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { // 没有条件不做任何更新 throw new Exception('no data to update'); } + if ($lazyTime > 0) { // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + if (false === $step) { // 清空查询条件 $this->options = []; return true; } } + return $this->setField($field, ['inc', $field, $step]); } /** * 字段值(延迟)减少 * @access public - * @param string $field 字段名 - * @param integer $step 减少值 - * @param integer $lazyTime 延时时间(s) + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) * @return integer|true * @throws Exception */ public function setDec($field, $step = 1, $lazyTime = 0) { $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { // 没有条件不做任何更新 throw new Exception('no data to update'); } + if ($lazyTime > 0) { // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + if (false === $step) { // 清空查询条件 $this->options = []; return true; } - return $this->setField($field, ['inc', $field, $step]); + + $value = ['inc', $field, $step]; + } else { + $value = ['dec', $field, $step]; } - return $this->setField($field, ['dec', $field, $step]); + + return $this->setField($field, $value); } /** * 延时更新检查 返回false表示需要延时 * 否则返回实际写入的数值 * @access protected - * @param string $type 自增或者自减 - * @param string $guid 写入标识 - * @param integer $step 写入步进值 - * @param integer $lazyTime 延时时间(s) + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) * @return false|integer */ protected function lazyWrite($type, $guid, $step, $lazyTime) { - if (!Cache::has($guid . '_time')) { + $cache = Container::get('cache'); + + if (!$cache->has($guid . '_time')) { // 计时开始 - Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); - Cache::$type($guid, $step); - } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { + $cache->set($guid . '_time', time(), 0); + $cache->$type($guid, $step); + } elseif (time() > $cache->get($guid . '_time') + $lazyTime) { // 删除缓存 - $value = Cache::$type($guid, $step); - Cache::rm($guid); - Cache::rm($guid . '_time'); + $value = $cache->$type($guid, $step); + $cache->rm($guid); + $cache->rm($guid . '_time'); return 0 === $value ? false : $value; } else { // 更新缓存 - Cache::$type($guid, $step); + $cache->$type($guid, $step); } + return false; } /** * 查询SQL组装 join * @access public - * @param mixed $join 关联的表名 - * @param mixed $condition 条件 - * @param string $type JOIN类型 + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 * @return $this */ public function join($join, $condition = null, $type = 'INNER') @@ -693,23 +792,61 @@ class Query $this->options['join'][] = [$table, strtoupper($type), $condition]; } + return $this; } /** - * 获取Join表名及别名 支持 - * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * LEFT JOIN * @access public - * @param array|string $join - * @return array|string + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function leftJoin($join, $condition = null) + { + return $this->join($join, $condition, 'LEFT'); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function rightJoin($join, $condition = null) + { + return $this->join($join, $condition, 'RIGHT'); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function fullJoin($join, $condition = null) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string $join + * @param string $alias + * @return string */ protected function getJoinTable($join, &$alias = null) { - // 传入的表名为数组 if (is_array($join)) { $table = $join; } else { $join = trim($join); + if (false !== strpos($join, '(')) { // 使用子查询 $table = $join; @@ -724,22 +861,25 @@ class Query $alias = $join; } } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { $table = $this->getTable($table); } } + if (isset($alias) && $table != $alias) { $table = [$table => $alias]; } } + return $table; } /** * 查询SQL组装 union * @access public - * @param mixed $union - * @param boolean $all + * @param mixed $union + * @param boolean $all * @return $this */ public function union($union, $all = false) @@ -751,17 +891,29 @@ class Query } else { $this->options['union'][] = $union; } + return $this; } + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + /** * 指定查询字段 支持字段排除和指定数据表 * @access public - * @param mixed $field - * @param boolean $except 是否排除 - * @param string $tableName 数据表名 - * @param string $prefix 字段前缀 - * @param string $alias 别名前缀 + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 * @return $this */ public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') @@ -769,18 +921,21 @@ class Query if (empty($field)) { return $this; } + if (is_string($field)) { $field = array_map('trim', explode(',', $field)); } + if (true === $field) { // 获取全部字段 - $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $fields = $this->getTableFields($tableName); $field = $fields ?: ['*']; } elseif ($except) { // 字段排除 - $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $fields = $this->getTableFields($tableName); $field = $fields ? array_diff($fields, $field) : $field; } + if ($tableName) { // 添加统一的前缀 $prefix = $prefix ?: $tableName; @@ -793,17 +948,30 @@ class Query } if (isset($this->options['field'])) { - $field = array_merge($this->options['field'], $field); + $field = array_merge((array) $this->options['field'], $field); } + $this->options['field'] = array_unique($field); + return $this; } + /** + * 设置数据排除字段 + * @access public + * @param mixed $field 字段名或者数据 + * @return $this + */ + public function hidden($field) + { + return $this->field($field, true); + } + /** * 设置数据 * @access public - * @param mixed $field 字段名或者数据 - * @param mixed $value 字段值 + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 * @return $this */ public function data($field, $value = null) @@ -813,46 +981,51 @@ class Query } else { $this->options['data'][$field] = $value; } + return $this; } /** * 字段值增长 * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 + * @param string|array $field 字段名 + * @param integer $step 增长值 * @return $this */ public function inc($field, $step = 1) { $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { $this->data($field, ['inc', $field, $step]); } + return $this; } /** * 字段值减少 * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 + * @param string|array $field 字段名 + * @param integer $step 增长值 * @return $this */ public function dec($field, $step = 1) { $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { $this->data($field, ['dec', $field, $step]); } + return $this; } /** * 使用表达式设置数据 * @access public - * @param string $field 字段名 - * @param string $value 字段值 + * @param string $field 字段名 + * @param string $value 字段值 * @return $this */ public function exp($field, $value) @@ -864,15 +1037,16 @@ class Query /** * 指定JOIN查询字段 * @access public - * @param string|array $table 数据表 - * @param string|array $field 查询字段 - * @param string|array $on JOIN条件 - * @param string $type JOIN类型 + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param string|array $on JOIN条件 + * @param string $type JOIN类型 * @return $this */ public function view($join, $field = true, $on = null, $type = 'INNER') { $this->options['view'] = true; + if (is_array($join) && key($join) !== 0) { foreach ($join as $key => $val) { $this->view($key, $val[0], isset($val[1]) ? $val[1] : null, isset($val[2]) ? $val[2] : 'INNER'); @@ -887,9 +1061,11 @@ class Query if (is_string($field)) { $field = explode(',', $field); } + foreach ($field as $key => $val) { if (is_numeric($key)) { - $fields[] = $alias . '.' . $val; + $fields[] = $alias . '.' . $val; + $this->options['map'][$val] = $alias . '.' . $val; } else { if (preg_match('/[,=\.\'\"\(\s]/', $key)) { @@ -897,238 +1073,238 @@ class Query } else { $name = $alias . '.' . $key; } - $fields[$name] = $val; + + $fields[] = $name . ' AS ' . $val; + $this->options['map'][$val] = $name; } } } + $this->field($fields); + if ($on) { $this->join($table, $on, $type); } else { $this->table($table); } } + return $this; } /** * 设置分表规则 * @access public - * @param array $data 操作的数据 - * @param string $field 分表依据的字段 - * @param array $rule 分表规则 + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 * @return $this */ public function partition($data, $field, $rule = []) { $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + return $this; } /** * 指定AND查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 * @return $this */ public function where($field, $op = null, $condition = null) { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('AND', $field, $op, $condition, $param); - return $this; + return $this->parseWhereExp('AND', $field, $op, $condition, $param); } /** * 指定OR查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 * @return $this */ public function whereOr($field, $op = null, $condition = null) { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('OR', $field, $op, $condition, $param); - return $this; + return $this->parseWhereExp('OR', $field, $op, $condition, $param); } /** * 指定XOR查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 * @return $this */ public function whereXor($field, $op = null, $condition = null) { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('XOR', $field, $op, $condition, $param); - return $this; + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); } /** * 指定Null查询条件 * @access public - * @param mixed $field 查询字段 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereNull($field, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'null', null); - return $this; + return $this->parseWhereExp($logic, $field, 'null', null); } /** * 指定NotNull查询条件 * @access public - * @param mixed $field 查询字段 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereNotNull($field, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'notnull', null); - return $this; + return $this->parseWhereExp($logic, $field, 'notnull', null); } /** * 指定Exists查询条件 * @access public - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereExists($condition, $logic = 'AND') { - $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; + $this->options['where'][strtoupper($logic)][] = ['', 'exists', $condition]; return $this; } /** * 指定NotExists查询条件 * @access public - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereNotExists($condition, $logic = 'AND') { - $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; + $this->options['where'][strtoupper($logic)][] = ['', 'not exists', $condition]; return $this; } /** * 指定In查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereIn($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'in', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'in', $condition); } /** * 指定NotIn查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereNotIn($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'not in', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'not in', $condition); } /** * 指定Like查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereLike($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'like', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'like', $condition); } /** * 指定NotLike查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereNotLike($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'not like', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'not like', $condition); } /** * 指定Between查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereBetween($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'between', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'between', $condition); } /** * 指定NotBetween查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereNotBetween($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'not between', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'not between', $condition); } /** - * 指定Exp查询条件 + * 比较两个字段 * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor + * @param string $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor * @return $this */ - public function whereExp($field, $condition, $logic = 'AND') + public function whereColumn($field1, $operator, $field2 = null, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'exp', $condition); - return $this; + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->whereExp($field1, $operator . ' ' . $field2, $logic); } /** * 设置软删除字段及条件 * @access public - * @param false|string $field 查询字段 - * @param mixed $condition 查询条件 + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 * @return $this */ public function useSoftDelete($field, $condition = null) @@ -1136,117 +1312,131 @@ class Query if ($field) { $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; } + return $this; } + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'exp', $condition); + } + /** * 分析查询表达式 * @access public - * @param string $logic 查询逻辑 and or xor - * @param string|array|\Closure $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @param array $param 查询参数 - * @return void + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return $this */ protected function parseWhereExp($logic, $field, $op, $condition, $param = []) { - $logic = strtoupper($logic); - if ($field instanceof \Closure) { - $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; - return; + if ($field instanceof $this) { + $this->options['where'] = $field->getOptions('where'); + return $this; } + $logic = strtoupper($logic); + if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { $field = $this->options['via'] . '.' . $field; } - if (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { - $where[] = ['exp', $field]; + + if ($field instanceof \Closure) { + $where = is_string($op) ? [$op, $field] : $field; + $field = ''; + } elseif (is_string($field) && preg_match('/[,=\<\'\"\(\s]/', $field)) { + $where = ['', 'exp', $field]; if (is_array($op)) { // 参数绑定 $this->bind($op); } } elseif (is_null($op) && is_null($condition)) { if (is_array($field)) { - // 数组批量查询 - $where = $field; - foreach ($where as $k => $val) { - $this->options['multi'][$logic][$k][] = $val; + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if (is_null($val)) { + $where[$key] = [$key, 'null', '']; + } else { + $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val]; + } + } + } else { + // 数组批量查询 + $where = $field; } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; } elseif ($field && is_string($field)) { // 字符串查询 - $where[$field] = ['null', '']; - $this->options['multi'][$logic][$field][] = $where[$field]; + $where = [$field, 'null', '']; } } elseif (is_array($op)) { - $where[$field] = $param; + array_unshift($param, $field); + $where = $param; } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { // null查询 - $where[$field] = [$op, '']; - $this->options['multi'][$logic][$field][] = $where[$field]; + $where = [$field, $op, '']; } elseif (is_null($condition)) { // 字段相等查询 - $where[$field] = ['eq', $op]; - $this->options['multi'][$logic][$field][] = $where[$field]; + $where = [$field, '=', $op]; } else { - $where[$field] = [$op, $condition, isset($param[2]) ? $param[2] : null]; + $where = [$field, $op, $condition, isset($param[2]) ? $param[2] : null]; + if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) { // 参数绑定 $this->bind($param[2]); } - // 记录一个字段多次查询条件 - $this->options['multi'][$logic][$field][] = $where[$field]; } - if (!empty($where)) { - if (!isset($this->options['where'][$logic])) { - $this->options['where'][$logic] = []; - } - if (is_string($field) && $this->checkMultiField($field, $logic)) { - $where[$field] = $this->options['multi'][$logic][$field]; - } elseif (is_array($field)) { - foreach ($field as $key => $val) { - if ($this->checkMultiField($key, $logic)) { - $where[$key] = $this->options['multi'][$logic][$key]; - } - } - } - $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); - } - } - /** - * 检查是否存在一个字段多次查询条件 - * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return bool - */ - private function checkMultiField($field, $logic) - { - return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + if (!empty($where)) { + if (isset($this->options['where'][$logic][$field])) { + $this->options['where'][$logic][] = $where; + } else { + $this->options['where'][$logic][$field] = $where; + } + } + + return $this; } /** * 去除某个查询条件 * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function removeWhereField($field, $logic = 'AND') { $logic = strtoupper($logic); + if (isset($this->options['where'][$logic][$field])) { unset($this->options['where'][$logic][$field]); - unset($this->options['multi'][$logic][$field]); } + return $this; } /** * 去除查询参数 * @access public - * @param string|bool $option 参数名 true 表示去除所有参数 + * @param string|bool $option 参数名 true 表示去除所有参数 * @return $this */ public function removeOption($option = true) @@ -1256,14 +1446,46 @@ class Query } elseif (is_string($option) && isset($this->options[$option])) { unset($this->options[$option]); } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param \Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param \Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof \Closure) { + $condition = $condition($this); + } + + if ($condition) { + if ($query instanceof \Closure) { + $query($this, $condition); + } elseif (is_array($query)) { + $this->where($query); + } + } elseif ($otherwise) { + if ($otherwise instanceof \Closure) { + $otherwise($this, $condition); + } elseif (is_array($otherwise)) { + $this->where($otherwise); + } + } + return $this; } /** * 指定查询数量 * @access public - * @param mixed $offset 起始位置 - * @param mixed $length 查询数量 + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 * @return $this */ public function limit($offset, $length = null) @@ -1271,15 +1493,17 @@ class Query if (is_null($length) && strpos($offset, ',')) { list($offset, $length) = explode(',', $offset); } + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + return $this; } /** * 指定分页 * @access public - * @param mixed $page 页数 - * @param mixed $listRows 每页数量 + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 * @return $this */ public function page($page, $listRows = null) @@ -1287,15 +1511,18 @@ class Query if (is_null($listRows) && strpos($page, ',')) { list($page, $listRows) = explode(',', $page); } + $this->options['page'] = [intval($page), intval($listRows)]; + return $this; } /** * 分页查询 - * @param int|array $listRows 每页数量 数组表示配置参数 - * @param int|bool $simple 是否简洁模式或者总记录数 - * @param array $config 配置参数 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 * page:当前页, * path:url路径, * query:url额外参数, @@ -1312,11 +1539,14 @@ class Query $total = $simple; $simple = false; } + + $paginate = Container::get('config')->pull('paginate'); + if (is_array($listRows)) { - $config = array_merge(Config::get('paginate'), $listRows); + $config = array_merge($paginate, $listRows); $listRows = $config['list_rows']; } else { - $config = array_merge(Config::get('paginate'), $config); + $config = array_merge($paginate, $config); $listRows = $listRows ?: $config['list_rows']; } @@ -1345,13 +1575,14 @@ class Query } else { $results = $this->page($page, $listRows)->select(); } + return $class::make($results, $listRows, $page, $total, $simple, $config); } /** * 指定当前操作的数据表 * @access public - * @param mixed $table 表名 + * @param mixed $table 表名 * @return $this */ public function table($table) @@ -1362,6 +1593,7 @@ class Query } elseif (strpos($table, ',')) { $tables = explode(',', $table); $table = []; + foreach ($tables as $item) { list($item, $alias) = explode(' ', trim($item)); if ($alias) { @@ -1380,6 +1612,7 @@ class Query } else { $tables = $table; $table = []; + foreach ($tables as $key => $val) { if (is_numeric($key)) { $table[] = $val; @@ -1390,13 +1623,14 @@ class Query } } $this->options['table'] = $table; + return $this; } /** * USING支持 用于多表删除 * @access public - * @param mixed $using + * @param mixed $using * @return $this */ public function using($using) @@ -1408,8 +1642,8 @@ class Query /** * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) * @access public - * @param string|array $field 排序字段 - * @param string $order 排序 + * @param string|array $field 排序字段 + * @param string $order 排序 * @return $this */ public function order($field, $order = null) @@ -1419,6 +1653,7 @@ class Query if (!empty($this->options['via'])) { $field = $this->options['via'] . '.' . $field; } + $field = empty($order) ? $field : [$field => $order]; } elseif (!empty($this->options['via'])) { foreach ($field as $key => $val) { @@ -1430,24 +1665,54 @@ class Query } } } + if (!isset($this->options['order'])) { $this->options['order'] = []; } + if (is_array($field)) { $this->options['order'] = array_merge($this->options['order'], $field); } else { $this->options['order'][] = $field; } } + + return $this; + } + + /** + * 指定Field排序 order('id',[1,2,3],'desc') + * @access public + * @param string|array $field 排序字段 + * @param array $values 排序值 + * @param string $order + * @return $this + */ + public function orderField($field, array $values = [], $order = '') + { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; return $this; } /** * 查询缓存 * @access public - * @param mixed $key 缓存key - * @param integer|\DateTime $expire 缓存有效期 - * @param string $tag 缓存标签 + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 * @return $this */ public function cache($key = true, $expire = null, $tag = null) @@ -1461,13 +1726,14 @@ class Query if (false !== $key) { $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; } + return $this; } /** * 指定group查询 * @access public - * @param string $group GROUP + * @param string $group GROUP * @return $this */ public function group($group) @@ -1479,7 +1745,7 @@ class Query /** * 指定having查询 * @access public - * @param string $having having + * @param string $having having * @return $this */ public function having($having) @@ -1491,20 +1757,21 @@ class Query /** * 指定查询lock * @access public - * @param bool|string $lock 是否lock + * @param bool|string $lock 是否lock * @return $this */ public function lock($lock = false) { $this->options['lock'] = $lock; $this->options['master'] = true; + return $this; } /** * 指定distinct查询 * @access public - * @param string $distinct 是否唯一 + * @param string $distinct 是否唯一 * @return $this */ public function distinct($distinct) @@ -1516,7 +1783,7 @@ class Query /** * 指定数据表别名 * @access public - * @param mixed $alias 数据表别名 + * @param array|string $alias 数据表别名 * @return $this */ public function alias($alias) @@ -1524,7 +1791,7 @@ class Query if (is_array($alias)) { foreach ($alias as $key => $val) { if (false !== strpos($key, '__')) { - $table = $this->parseSqlTable($key); + $table = $this->connection->parseSqlTable($key); } else { $table = $key; } @@ -1534,7 +1801,7 @@ class Query if (isset($this->options['table'])) { $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; if (false !== strpos($table, '__')) { - $table = $this->parseSqlTable($table); + $table = $this->connection->parseSqlTable($table); } } else { $table = $this->getTable(); @@ -1542,13 +1809,14 @@ class Query $this->options['alias'][$table] = $alias; } + return $this; } /** * 指定强制索引 * @access public - * @param string $force 索引名称 + * @param string $force 索引名称 * @return $this */ public function force($force) @@ -1560,7 +1828,7 @@ class Query /** * 查询注释 * @access public - * @param string $comment 注释 + * @param string $comment 注释 * @return $this */ public function comment($comment) @@ -1572,7 +1840,7 @@ class Query /** * 获取执行的SQL语句 * @access public - * @param boolean $fetch 是否返回sql + * @param boolean $fetch 是否返回sql * @return $this */ public function fetchSql($fetch = true) @@ -1584,7 +1852,7 @@ class Query /** * 不主动获取数据集 * @access public - * @param bool $pdo 是否返回 PDOStatement 对象 + * @param bool $pdo 是否返回 PDOStatement 对象 * @return $this */ public function fetchPdo($pdo = true) @@ -1607,7 +1875,7 @@ class Query /** * 设置是否严格检查字段名 * @access public - * @param bool $strict 是否严格检查字段 + * @param bool $strict 是否严格检查字段 * @return $this */ public function strict($strict = true) @@ -1619,7 +1887,7 @@ class Query /** * 设置查询数据不存在是否抛出异常 * @access public - * @param bool $fail 数据不存在是否抛出异常 + * @param bool $fail 数据不存在是否抛出异常 * @return $this */ public function failException($fail = true) @@ -1631,7 +1899,7 @@ class Query /** * 设置自增序列名 * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return $this */ public function sequence($sequence = null) @@ -1640,10 +1908,57 @@ class Query return $this; } + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @return $this + */ + public function json(array $json = []) + { + $this->options['json'] = $json; + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|\Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof \Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + /** * 指定数据表主键 * @access public - * @param string $pk 主键 + * @param string $pk 主键 * @return $this */ public function pk($pk) @@ -1655,9 +1970,22 @@ class Query /** * 查询日期或者时间 * @access public - * @param string $field 日期字段名 - * @param string|array $op 比较运算符或者表达式 - * @param string|array $range 比较范围 + * @param string $name 时间表达式 + * @param string|array $rule 时间范围 + * @return $this + */ + public function timeRule($name, $rule) + { + $this->timeRule[$name] = $rule; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 * @return $this */ public function whereTime($field, $op, $range = null) @@ -1666,113 +1994,49 @@ class Query if (is_array($op)) { $range = $op; } else { - // 使用日期表达式 - switch (strtolower($op)) { - case 'today': - case 'd': - $range = ['today', 'tomorrow']; - break; - case 'week': - case 'w': - $range = ['this week 00:00:00', 'next week 00:00:00']; - break; - case 'month': - case 'm': - $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; - break; - case 'year': - case 'y': - $range = ['this year 1/1', 'next year 1/1']; - break; - case 'yesterday': - $range = ['yesterday', 'today']; - break; - case 'last week': - $range = ['last week 00:00:00', 'this week 00:00:00']; - break; - case 'last month': - $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; - break; - case 'last year': - $range = ['last year 1/1', 'this year 1/1']; - break; - default: - $range = $op; + if (isset($this->timeExp[strtolower($op)])) { + $op = $this->timeExp[strtolower($op)]; + } + + if (isset($this->timeRule[strtolower($op)])) { + $range = $this->timeRule[strtolower($op)]; + } else { + $range = $op; } } - $op = is_array($range) ? 'between' : '>'; + + $op = is_array($range) ? 'between' : '>='; } + $this->where($field, strtolower($op) . ' time', $range); + return $this; } /** - * 获取数据表信息 + * 查询日期或者时间范围 * @access public - * @param mixed $tableName 数据表名 留空自动获取 - * @param string $fetch 获取信息类型 包括 fields type bind pk - * @return mixed + * @param string $field 日期字段名 + * @param string $startTime 开始时间 + * @param string $endTime 结束时间 + * @return $this */ - public function getTableInfo($tableName = '', $fetch = '') + public function whereBetweenTime($field, $startTime, $endTime = null) { - if (!$tableName) { - $tableName = $this->getTable(); - } - if (is_array($tableName)) { - $tableName = key($tableName) ?: current($tableName); + if (is_null($endTime)) { + $time = is_string($startTime) ? strtotime($startTime) : $startTime; + $endTime = strtotime('+1 day', $time); } - if (strpos($tableName, ',')) { - // 多表不获取字段信息 - return false; - } else { - $tableName = $this->parseSqlTable($tableName); - } + $this->where($field, 'between time', [$startTime, $endTime]); - // 修正子查询作为表名的问题 - if (strpos($tableName, ')')) { - return []; - } - - list($guid) = explode(' ', $tableName); - $db = $this->getConfig('database'); - if (!isset(self::$info[$db . '.' . $guid])) { - if (!strpos($guid, '.')) { - $schema = $db . '.' . $guid; - } else { - $schema = $guid; - } - // 读取缓存 - if (!App::$debug && is_file(RUNTIME_PATH . 'schema/' . $schema . '.php')) { - $info = include RUNTIME_PATH . 'schema/' . $schema . '.php'; - } else { - $info = $this->connection->getFields($guid); - } - $fields = array_keys($info); - $bind = $type = []; - foreach ($info as $key => $val) { - // 记录字段类型 - $type[$key] = $val['type']; - $bind[$key] = $this->getFieldBindType($val['type']); - if (!empty($val['primary'])) { - $pk[] = $key; - } - } - if (isset($pk)) { - // 设置主键 - $pk = count($pk) > 1 ? $pk : $pk[0]; - } else { - $pk = null; - } - self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; - } - return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; + return $this; } /** * 获取当前数据表的主键 * @access public - * @param string|array $options 数据表名或者查询参数 + * @param string|array $options 数据表名或者查询参数 * @return string|array */ public function getPk($options = '') @@ -1780,62 +2044,18 @@ class Query if (!empty($this->pk)) { $pk = $this->pk; } else { - $pk = $this->getTableInfo(is_array($options) ? $options['table'] : $options, 'pk'); + $pk = $this->connection->getPk(is_array($options) ? $options['table'] : $this->getTable()); } + return $pk; } - // 获取当前数据表字段信息 - public function getTableFields($table = '') - { - return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); - } - - // 获取当前数据表字段类型 - public function getFieldsType($table = '') - { - return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); - } - - // 获取当前数据表绑定信息 - public function getFieldsBind($table = '') - { - $types = $this->getFieldsType($table); - $bind = []; - if ($types) { - foreach ($types as $key => $type) { - $bind[$key] = $this->getFieldBindType($type); - } - } - return $bind; - } - - /** - * 获取字段绑定类型 - * @access public - * @param string $type 字段类型 - * @return integer - */ - protected function getFieldBindType($type) - { - if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { - $bind = PDO::PARAM_STR; - } elseif (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { - $bind = PDO::PARAM_INT; - } elseif (preg_match('/bool/is', $type)) { - $bind = PDO::PARAM_BOOL; - } else { - $bind = PDO::PARAM_STR; - } - return $bind; - } - /** * 参数绑定 * @access public - * @param mixed $key 参数名 - * @param mixed $value 绑定变量值 - * @param integer $type 绑定类型 + * @param mixed $key 参数名 + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 * @return $this */ public function bind($key, $value = false, $type = PDO::PARAM_STR) @@ -1845,13 +2065,14 @@ class Query } else { $this->bind[$key] = [$value, $type]; } + return $this; } /** * 检测参数是否已经绑定 * @access public - * @param string $key 参数名 + * @param string $key 参数名 * @return bool */ public function isBind($key) @@ -1862,7 +2083,7 @@ class Query /** * 查询参数赋值 * @access protected - * @param array $options 表达式参数 + * @param array $options 表达式参数 * @return $this */ protected function options(array $options) @@ -1874,7 +2095,7 @@ class Query /** * 获取当前的查询参数 * @access public - * @param string $name 参数名 + * @param string $name 参数名 * @return mixed */ public function getOptions($name = '') @@ -1886,10 +2107,23 @@ class Query } } + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption($option, $value) + { + $this->options[$option] = $value; + return $this; + } + /** * 设置关联查询JOIN预查询 * @access public - * @param string|array $with 关联方法名称 + * @param string|array $with 关联方法名称 * @return $this */ public function with($with) @@ -1909,6 +2143,7 @@ class Query foreach ($with as $key => $relation) { $subRelation = ''; $closure = false; + if ($relation instanceof \Closure) { // 支持闭包查询过滤关联条件 $closure = $relation; @@ -1918,45 +2153,53 @@ class Query $subRelation = $relation; $relation = $key; } elseif (is_string($relation) && strpos($relation, '.')) { - $with[$key] = $relation; + $with[$key] = $relation; + list($relation, $subRelation) = explode('.', $relation, 2); } /** @var Relation $model */ $relation = Loader::parseName($relation, 1, false); $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { - $model->eagerly($this, $relation, $subRelation, $closure, $first); + $model->removeOption()->eagerly($this, $relation, $subRelation, $closure, $first); $first = false; } elseif ($closure) { $with[$key] = $closure; } } $this->via(); + if (isset($this->options['with'])) { $this->options['with'] = array_merge($this->options['with'], $with); } else { $this->options['with'] = $with; } + return $this; } /** * 关联统计 - * @access public - * @param string|array $relation 关联方法名 - * @param bool $subQuery 是否使用子查询 + * @access protected + * @param string|array $relation 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 * @return $this */ - public function withCount($relation, $subQuery = true) + protected function withAggregate($relation, $aggregate = 'count', $field = '*', $subQuery = true) { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + if (!$subQuery) { - $this->options['with_count'] = $relation; + $this->options['with_count'][] = [$relations, $aggregate, $field]; } else { - $relations = is_string($relation) ? explode(',', $relation) : $relation; if (!isset($this->options['field'])) { $this->field('*'); } + foreach ($relations as $key => $relation) { $closure = false; if ($relation instanceof \Closure) { @@ -1964,13 +2207,78 @@ class Query $relation = $key; } $relation = Loader::parseName($relation, 1, false); - $count = '(' . $this->model->$relation()->getRelationCountQuery($closure) . ')'; - $this->field([$count => Loader::parseName($relation) . '_count']); + $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field) . ')'; + $this->field([$count => Loader::parseName($relation) . '_' . $aggregate]); } } + return $this; } + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + return $this->withAggregate($relation, 'count', '*', $subQuery); + } + + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } + + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } + + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); + } + /** * 关联预加载中 获取关联指定字段值 * example: @@ -1978,31 +2286,34 @@ class Query * $query->withField("id,name"); * }]) * - * @param string | array $field 指定获取的字段 + * @access public + * @param string | array $field 指定获取的字段 * @return $this */ public function withField($field) { $this->options['with_field'] = $field; + return $this; } /** * 设置当前字段添加的表别名 * @access public - * @param string $via + * @param string $via * @return $this */ public function via($via = '') { $this->options['via'] = $via; + return $this; } /** * 设置关联查询 * @access public - * @param string|array $relation 关联名称 + * @param string|array $relation 关联名称 * @return $this */ public function relation($relation) @@ -2010,113 +2321,44 @@ class Query if (empty($relation)) { return $this; } + if (is_string($relation)) { $relation = explode(',', $relation); } + if (isset($this->options['relation'])) { $this->options['relation'] = array_merge($this->options['relation'], $relation); } else { $this->options['relation'] = $relation; } + return $this; } - /** - * 把主键值转换为查询条件 支持复合主键 - * @access public - * @param array|string $data 主键数据 - * @param mixed $options 表达式参数 - * @return void - * @throws Exception - */ - protected function parsePkWhere($data, &$options) - { - $pk = $this->getPk($options); - // 获取当前数据表 - $table = is_array($options['table']) ? key($options['table']) : $options['table']; - if (!empty($options['alias'][$table])) { - $alias = $options['alias'][$table]; - } - if (is_string($pk)) { - $key = isset($alias) ? $alias . '.' . $pk : $pk; - // 根据主键查询 - if (is_array($data)) { - $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; - } else { - $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; - } - } elseif (is_array($pk) && is_array($data) && !empty($data)) { - // 根据复合主键查询 - foreach ($pk as $key) { - if (isset($data[$key])) { - $attr = isset($alias) ? $alias . '.' . $key : $key; - $where[$attr] = $data[$key]; - } else { - throw new Exception('miss complex primary data'); - } - } - } - - if (!empty($where)) { - if (isset($options['where']['AND'])) { - $options['where']['AND'] = array_merge($options['where']['AND'], $where); - } else { - $options['where']['AND'] = $where; - } - } - return; - } - /** * 插入记录 * @access public - * @param mixed $data 数据 - * @param boolean $replace 是否replace - * @param boolean $getLastInsID 返回自增主键 - * @param string $sequence 自增序列名 + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 * @return integer|string */ public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) { - // 分析查询表达式 - $options = $this->parseExpress(); - $data = array_merge($options['data'], $data); - // 生成SQL语句 - $sql = $this->builder->insert($data, $options, $replace); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } + $this->parseOptions(); - // 执行操作 - $result = 0 === $sql ? 0 : $this->execute($sql, $bind); - if ($result) { - $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); - $lastInsId = $this->getLastInsID($sequence); - if ($lastInsId) { - $pk = $this->getPk($options); - if (is_string($pk)) { - $data[$pk] = $lastInsId; - } - } - $options['data'] = $data; - $this->trigger('after_insert', $options); + $this->options['data'] = array_merge($this->options['data'], $data); - if ($getLastInsID) { - return $lastInsId; - } - } - return $result; + return $this->connection->insert($this, $replace, $getLastInsID, $sequence); } /** * 插入记录并获取自增ID * @access public - * @param mixed $data 数据 - * @param boolean $replace 是否replace - * @param string $sequence 自增序列名 + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 * @return integer|string */ public function insertGetId(array $data, $replace = false, $sequence = null) @@ -2127,145 +2369,87 @@ class Query /** * 批量插入记录 * @access public - * @param mixed $dataSet 数据集 - * @param boolean $replace 是否replace - * @param integer $limit 每次写入数据限制 + * @param array $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 * @return integer|string */ - public function insertAll(array $dataSet, $replace = false, $limit = null) + public function insertAll(array $dataSet = [], $replace = false, $limit = null) { - // 分析查询表达式 - $options = $this->parseExpress(); - if (!is_array(reset($dataSet))) { - return false; + $this->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $this->options['data']; } - // 生成SQL语句 - if (is_null($limit)) { - $sql = $this->builder->insertAll($dataSet, $options, $replace); - } else { - $array = array_chunk($dataSet, $limit, true); - foreach ($array as $item) { - $sql[] = $this->builder->insertAll($item, $options, $replace); - } + if (empty($limit) && !empty($this->options['limit'])) { + $limit = $this->options['limit']; } - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } elseif (is_array($sql)) { - // 执行操作 - return $this->batchQuery($sql, $bind); - } else { - // 执行操作 - return $this->execute($sql, $bind); - } + return $this->connection->insertAll($this, $dataSet, $replace, $limit); } /** * 通过Select方式插入记录 * @access public - * @param string $fields 要插入的数据表字段名 - * @param string $table 要插入的数据表名 + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 * @return integer|string * @throws PDOException */ public function selectInsert($fields, $table) { - // 分析查询表达式 - $options = $this->parseExpress(); - // 生成SQL语句 - $table = $this->parseSqlTable($table); - $sql = $this->builder->selectInsert($fields, $table, $options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } else { - // 执行操作 - return $this->execute($sql, $bind); - } + $this->parseOptions(); + + return $this->connection->selectInsert($this, $fields, $table); } /** * 更新记录 * @access public - * @param mixed $data 数据 + * @param mixed $data 数据 * @return integer|string * @throws Exception * @throws PDOException */ public function update(array $data = []) { - $options = $this->parseExpress(); - $data = array_merge($options['data'], $data); - $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache']['key'])) { - $key = $options['cache']['key']; + $this->parseOptions(); + + $this->options['data'] = array_merge($this->options['data'], $data); + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + $this->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); } - if (empty($options['where'])) { - // 如果存在主键数据 则自动作为更新条件 - if (is_string($pk) && isset($data[$pk])) { - $where[$pk] = $data[$pk]; - if (!isset($key)) { - $key = 'think:' . $options['table'] . '|' . $data[$pk]; - } - unset($data[$pk]); - } elseif (is_array($pk)) { - // 增加复合主键支持 - foreach ($pk as $field) { - if (isset($data[$field])) { - $where[$field] = $data[$field]; - } else { - // 如果缺少复合主键数据则不执行 - throw new Exception('miss complex primary data'); - } - unset($data[$field]); - } - } - if (!isset($where)) { - // 如果没有任何更新条件则不执行 - throw new Exception('miss update condition'); - } else { - $options['where']['AND'] = $where; - } - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); } - // 生成UPDATE SQL语句 - $sql = $this->builder->update($data, $options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } else { - // 检测缓存 - if (isset($key) && Cache::get($key)) { - // 删除缓存 - Cache::rm($key); - } elseif (!empty($options['cache']['tag'])) { - Cache::clear($options['cache']['tag']); - } - // 执行操作 - $result = '' == $sql ? 0 : $this->execute($sql, $bind); - if ($result) { - if (is_string($pk) && isset($where[$pk])) { - $data[$pk] = $where[$pk]; - } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $data[$pk] = $val; - } - $options['data'] = $data; - $this->trigger('after_update', $options); - } - return $result; - } + $this->options['data'] = $data; + + return $this->connection->delete($this); } /** @@ -2275,25 +2459,43 @@ class Query */ public function getPdo() { - // 分析查询表达式 - $options = $this->parseExpress(); - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); + $this->parseOptions(); + + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return \Generator + */ + public function cursor($data = null) + { + if ($data instanceof \Closure) { + $data($this); + $data = null; } - // 执行查询操作 - return $this->query($sql, $bind, $options['master'], true); + + $this->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); } /** * 查找记录 * @access public - * @param array|string|Query|\Closure $data - * @return Collection|false|\PDOStatement|string + * @param array|string|Query|\Closure $data + * @return Collection|array|\PDOStatement|string * @throws DbException * @throws ModelNotFoundException * @throws DataNotFoundException @@ -2303,138 +2505,73 @@ class Query if ($data instanceof Query) { return $data->select(); } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); + $data($this); $data = null; } - // 分析查询表达式 - $options = $this->parseExpress(); + + $this->parseOptions(); if (false === $data) { // 用于子查询 不查询只返回SQL - $options['fetch_sql'] = true; + $this->options['fetch_sql'] = true; } elseif (!is_null($data)) { // 主键条件分析 - $this->parsePkWhere($data, $options); + $this->parsePkWhere($data); } - $resultSet = false; - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - unset($options['cache']); - $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options) . serialize($this->bind)); - $resultSet = Cache::get($key); - } - if (false === $resultSet) { - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } + $this->options['data'] = $data; - $options['data'] = $data; - if ($resultSet = $this->trigger('before_select', $options)) { - } else { - // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + $resultSet = $this->connection->select($this); - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - } - - if (isset($cache) && false !== $resultSet) { - // 缓存数据集 - $this->cacheData($key, $resultSet, $cache); - } + if ($this->options['fetch_sql']) { + return $resultSet; } // 数据列表读取后的处理 if (!empty($this->model)) { // 生成模型对象 if (count($resultSet) > 0) { - foreach ($resultSet as $key => $result) { - /** @var Model $model */ - $model = $this->model->newInstance($result); - $model->isUpdate(true); + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result, $this->options, true); + } - // 关联查询 - if (!empty($options['relation'])) { - $model->relationQuery($options['relation']); - } - // 关联统计 - if (!empty($options['with_count'])) { - $model->relationCount($model, $options['with_count']); - } - $resultSet[$key] = $model; - } - if (!empty($options['with'])) { + if (!empty($this->options['with'])) { // 预载入 - $model->eagerlyResultSet($resultSet, $options['with']); + $result->eagerlyResultSet($resultSet, $this->options['with']); } + // 模型数据集转换 - $resultSet = $model->toCollection($resultSet); + $resultSet = $result->toCollection($resultSet); } else { $resultSet = $this->model->toCollection($resultSet); } - } elseif ('collection' == $this->connection->getConfig('resultset_type')) { - // 返回Collection对象 - $resultSet = new Collection($resultSet); + } else { + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } + } + + if ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } } + // 返回结果处理 - if (!empty($options['fail']) && count($resultSet) == 0) { - $this->throwNotFound($options); + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($this->options); } + return $resultSet; } - /** - * 缓存数据 - * @access public - * @param string $key 缓存标识 - * @param mixed $data 缓存数据 - * @param array $config 缓存参数 - */ - protected function cacheData($key, $data, $config = []) - { - if (isset($config['tag'])) { - Cache::tag($config['tag'])->set($key, $data, $config['expire']); - } else { - Cache::set($key, $data, $config['expire']); - } - } - - /** - * 生成缓存标识 - * @access public - * @param mixed $value 缓存数据 - * @param array $options 缓存参数 - * @param array $bind 绑定参数 - * @return string - */ - protected function getCacheKey($value, $options, $bind = []) - { - if (is_scalar($value)) { - $data = $value; - } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { - $data = $value[1]; - } - if (isset($data)) { - return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; - } else { - return md5(serialize($options) . serialize($bind)); - } - } - /** * 查找单条记录 * @access public - * @param array|string|Query|\Closure $data - * @return array|false|\PDOStatement|string|Model + * @param array|string|Query|\Closure $data + * @return array|null|\PDOStatement|string|Model * @throws DbException * @throws ModelNotFoundException * @throws DataNotFoundException @@ -2444,102 +2581,96 @@ class Query if ($data instanceof Query) { return $data->find(); } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); + $data($this); $data = null; } - // 分析查询表达式 - $options = $this->parseExpress(); - $pk = $this->getPk($options); + + $this->parseOptions(); + if (!is_null($data)) { // AR模式分析主键条件 - $this->parsePkWhere($data, $options); - } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + $this->parsePkWhere($data); } - $options['limit'] = 1; - $result = false; - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - if (true === $cache['key'] && !is_null($data) && !is_array($data)) { - $key = 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; - } elseif (is_string($cache['key'])) { - $key = $cache['key']; - } elseif (!isset($key)) { - $key = md5(serialize($options) . serialize($this->bind)); - } - $result = Cache::get($key); - } - if (false === $result) { - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - if (is_string($pk)) { - if (!is_array($data)) { - if (isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $item[$pk] = $val; - } else { - $item[$pk] = $data; - } - $data = $item; - } - } - $options['data'] = $data; - // 事件回调 - if ($result = $this->trigger('before_find', $options)) { - } else { - // 执行查询 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + $this->options['data'] = $data; - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - $result = isset($resultSet[0]) ? $resultSet[0] : null; - } + $result = $this->connection->find($this); - if (isset($cache) && $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } + if ($this->options['fetch_sql']) { + return $result; } // 数据处理 if (!empty($result)) { if (!empty($this->model)) { // 返回模型对象 - $result = $this->model->newInstance($result); - $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); - // 关联查询 - if (!empty($options['relation'])) { - $result->relationQuery($options['relation']); - } - // 预载入查询 - if (!empty($options['with'])) { - $result->eagerlyResult($result, $options['with']); - } - // 关联统计 - if (!empty($options['with_count'])) { - $result->relationCount($result, $options['with_count']); - } + $this->resultToModel($result, $this->options); + } elseif (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); } - } elseif (!empty($options['fail'])) { - $this->throwNotFound($options); + } elseif (!empty($this->options['fail'])) { + $this->throwNotFound($this->options); } + return $result; } + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 + * @return void + */ + protected function jsonResult(&$result, $json = [], $assoc = false) + { + foreach ($json as $name) { + if (isset($result[$name])) { + $result[$name] = json_decode($result[$name], $assoc); + } + } + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @param array $options 查询参数 + * @param bool $resultSet 是否为数据集查询 + * @return void + */ + protected function resultToModel(&$result, $options = [], $resultSet = false) + { + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json']); + } + + $condition = (!$resultSet && isset($options['where']['AND'])) ? $options['where']['AND'] : null; + $result = $this->model->newInstance($result, $condition); + + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation']); + } + + // 预载入查询 + if (!$resultSet && !empty($options['with'])) { + $result->eagerlyResult($result, $options['with']); + } + + // 关联统计 + if (!empty($options['with_count'])) { + foreach ($options['with_count'] as $val) { + $result->relationCount($result, $val[0], $val[1], $val[2]); + } + } + } + /** * 查询失败 抛出异常 - * @access public - * @param array $options 查询参数 + * @access protected + * @param array $options 查询参数 * @throws ModelNotFoundException * @throws DataNotFoundException */ @@ -2557,7 +2688,7 @@ class Query /** * 查找多条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data + * @param array|string|Query|\Closure $data * @return array|\PDOStatement|string|Model * @throws DbException * @throws ModelNotFoundException @@ -2571,7 +2702,7 @@ class Query /** * 查找单条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data + * @param array|string|Query|\Closure $data * @return array|\PDOStatement|string|Model * @throws DbException * @throws ModelNotFoundException @@ -2585,39 +2716,40 @@ class Query /** * 分批数据返回处理 * @access public - * @param integer $count 每次处理的数据数量 - * @param callable $callback 处理回调方法 - * @param string $column 分批处理的字段名 - * @param string $order 排序规则 + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 * @return boolean - * @throws \LogicException + * @throws DbException */ public function chunk($count, $callback, $column = null, $order = 'asc') { $options = $this->getOptions(); - if (empty($options['table'])) { - $options['table'] = $this->getTable(); - } - $column = $column ?: $this->getPk($options); + $column = $column ?: $this->getPk($options); if (isset($options['order'])) { - if (App::$debug) { - throw new \LogicException('chunk not support call order'); + if (Container::get('app')->isDebug()) { + throw new DbException('chunk not support call order'); } unset($options['order']); } + $bind = $this->bind; + if (is_array($column)) { $times = 1; $query = $this->options($options)->page($times, $count); } else { + $query = $this->options($options)->limit($count); + if (strpos($column, '.')) { list($alias, $key) = explode('.', $column); } else { $key = $column; } - $query = $this->options($options)->limit($count); } + $resultSet = $query->order($column, $order)->select(); while (count($resultSet) > 0) { @@ -2629,13 +2761,14 @@ class Query return false; } - if (is_array($column)) { + if (isset($times)) { $times++; $query = $this->options($options)->page($times, $count); } else { $end = end($resultSet); $lastId = is_array($end) ? $end[$key] : $end->getData($key); - $query = $this->options($options) + + $query = $this->options($options) ->limit($count) ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); } @@ -2649,19 +2782,23 @@ class Query /** * 获取绑定的参数 并清空 * @access public + * @param bool $clear * @return array */ - public function getBind() + public function getBind($clear = true) { - $bind = $this->bind; - $this->bind = []; + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + return $bind; } /** * 创建子查询SQL * @access public - * @param bool $sub + * @param bool $sub * @return string * @throws DbException */ @@ -2671,65 +2808,98 @@ class Query } /** - * 删除记录 - * @access public - * @param mixed $data 表达式 true 表示强制删除 - * @return int - * @throws Exception - * @throws PDOException + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void */ - public function delete($data = null) + protected function parseView(&$options) { - // 分析查询表达式 - $options = $this->parseExpress(); - $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache']['key'])) { - $key = $options['cache']['key']; - } - - if (!is_null($data) && true !== $data) { - if (!isset($key) && !is_array($data)) { - // 缓存标识 - $key = 'think:' . $options['table'] . '|' . $data; + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } } - // AR模式分析主键条件 - $this->parsePkWhere($data, $options); - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); } - if (true !== $data && empty($options['where'])) { - // 如果条件为空 不进行删除操作 除非设置 1=1 - throw new Exception('delete without condition'); - } - // 生成删除SQL语句 - $sql = $this->builder->delete($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - - // 检测缓存 - if (isset($key) && Cache::get($key)) { - // 删除缓存 - Cache::rm($key); - } elseif (!empty($options['cache']['tag'])) { - Cache::clear($options['cache']['tag']); - } - // 执行操作 - $result = $this->execute($sql, $bind); - if ($result) { - if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $item[$pk] = $val; - $data = $item; + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key)) { + if (strpos($val, ' ')) { + list($field, $sort) = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } } - $options['data'] = $data; - $this->trigger('after_delete', $options); } - return $result; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data) + { + $pk = $this->getPk($this->options); + + // 获取当前数据表 + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; + } else { + $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$key] = [$attr, '=', $data[$key]]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($this->options['where']['AND'])) { + $this->options['where']['AND'] = array_merge($this->options['where']['AND'], $where); + } else { + $this->options['where']['AND'] = $where; + } + } + + return; } /** @@ -2737,9 +2907,9 @@ class Query * @access protected * @return array */ - protected function parseExpress() + protected function parseOptions() { - $options = $this->options; + $options = $this->getOptions(); // 获取数据表 if (empty($options['table'])) { @@ -2750,48 +2920,17 @@ class Query $options['where'] = []; } elseif (isset($options['view'])) { // 视图查询条件处理 - foreach (['AND', 'OR'] as $logic) { - if (isset($options['where'][$logic])) { - foreach ($options['where'][$logic] as $key => $val) { - if (array_key_exists($key, $options['map'])) { - $options['where'][$logic][$options['map'][$key]] = $val; - unset($options['where'][$logic][$key]); - } - } - } - } - - if (isset($options['order'])) { - // 视图查询排序处理 - if (is_string($options['order'])) { - $options['order'] = explode(',', $options['order']); - } - foreach ($options['order'] as $key => $val) { - if (is_numeric($key)) { - if (strpos($val, ' ')) { - list($field, $sort) = explode(' ', $val); - if (array_key_exists($field, $options['map'])) { - $options['order'][$options['map'][$field]] = $sort; - unset($options['order'][$key]); - } - } elseif (array_key_exists($val, $options['map'])) { - $options['order'][$options['map'][$val]] = 'asc'; - unset($options['order'][$key]); - } - } elseif (array_key_exists($key, $options['map'])) { - $options['order'][$options['map'][$key]] = $val; - unset($options['order'][$key]); - } - } - } + $this->parseView($options); } if (!isset($options['field'])) { $options['field'] = '*'; } - if (!isset($options['data'])) { - $options['data'] = []; + foreach (['data', 'order'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } } if (!isset($options['strict'])) { @@ -2804,7 +2943,7 @@ class Query } } - foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { + foreach (['join', 'union', 'group', 'having', 'limit', 'force', 'comment'] as $name) { if (!isset($options[$name])) { $options[$name] = ''; } @@ -2819,15 +2958,16 @@ class Query $options['limit'] = $offset . ',' . $listRows; } - $this->options = []; + $this->options = $options; + return $options; } /** * 注册回调方法 * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 + * @param string $event 事件名 + * @param callable $callback 回调方法 * @return void */ public static function event($event, $callback) @@ -2837,18 +2977,19 @@ class Query /** * 触发事件 - * @access protected - * @param string $event 事件名 - * @param mixed $params 额外参数 + * @access public + * @param string $event 事件名 * @return bool */ - protected function trigger($event, $params = []) + public function trigger($event) { $result = false; + if (isset(self::$event[$event])) { - $callback = self::$event[$event]; - $result = call_user_func_array($callback, [$params, $this]); + $result = Container::getInstance()->invoke(self::$event[$event], [$this]); } + return $result; } + } diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php index 30fa506c2..cea8d4ae2 100644 --- a/thinkphp/library/think/db/builder/Mysql.php +++ b/thinkphp/library/think/db/builder/Mysql.php @@ -12,13 +12,26 @@ namespace think\db\builder; use think\db\Builder; -use think\Exception; +use think\db\Query; /** * mysql数据库驱动 */ class Mysql extends Builder { + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + ]; protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; @@ -26,100 +39,176 @@ class Mysql extends Builder /** * 生成insertall SQL * @access public - * @param array $dataSet 数据集 - * @param array $options 表达式 - * @param bool $replace 是否replace + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace * @return string - * @throws Exception */ - public function insertAll($dataSet, $options = [], $replace = false) + public function insertAll(Query $query, $dataSet, $replace = false) { + $options = $query->getOptions(); + // 获取合法的字段 if ('*' == $options['field']) { - $fields = array_keys($this->query->getFieldsType($options['table'])); + $allowFields = $this->connection->getTableFields($options['table']); } else { - $fields = $options['field']; + $allowFields = $options['field']; } - foreach ($dataSet as $data) { - foreach ($data as $key => $val) { - if (!in_array($key, $fields, true)) { - if ($options['strict']) { - throw new Exception('fields not exists:[' . $key . ']'); - } - unset($data[$key]); - } elseif (is_null($val)) { - $data[$key] = 'NULL'; - } elseif (is_scalar($val)) { - $data[$key] = $this->parseValue($val, $key); - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $data[$key] = $val->__toString(); - } else { - // 过滤掉非标量数据 - unset($data[$key]); - } - } - $value = array_values($data); - $values[] = '( ' . implode(',', $value) . ' )'; + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; if (!isset($insertFields)) { - $insertFields = array_map([$this, 'parseKey'], array_keys($data)); + $insertFields = array_keys($data); } } + $fields = []; + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + return str_replace( ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), - implode(' , ', $insertFields), + $this->parseTable($query, $options['table']), + implode(' , ', $fields), implode(' , ', $values), - $this->parseComment($options['comment']), - ], $this->insertAllSql); + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, $key, $exp, $value, $field) + { + return $key . ' ' . $exp . ' ' . $value; } /** * 字段和表名处理 - * @access protected - * @param string $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey($key, $options = []) + public function parseKey(Query $query, $key) { + if (is_int($key)) { + return $key; + } $key = trim($key); - if (strpos($key, '$.') && false === strpos($key, '(')) { + + if (strpos($key, '->') && false === strpos($key, '(')) { // JSON字段支持 - list($field, $name) = explode('$.', $key); - $key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; + list($field, $name) = explode('->', $key); + + $key = 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . $name . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } + if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } + if (isset($table)) { if (strpos($table, '.')) { $table = str_replace('.', '`.`', $table); } + $key = '`' . $table . '`.' . $key; } + return $key; } + /** + * field分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $fields 字段名 + * @return string + */ + protected function parseField(Query $query, $fields) + { + $fieldsStr = parent::parseField($query, $fields); + $options = $query->getOptions(); + + if (!empty($options['point'])) { + $array = []; + foreach ($options['point'] as $key => $field) { + $key = !is_numeric($key) ? $key : $field; + $array[] = 'AsText(' . $this->parseKey($query, $key) . ') AS ' . $this->parseKey($query, $field); + } + $fieldsStr .= ',' . implode(',', $array); + } + + return $fieldsStr; + } + + /** + * 数组数据解析 + * @access protected + * @param array $data + * @return mixed + */ + protected function parseArrayData($data) + { + list($type, $value) = $data; + + switch (strtolower($type)) { + case 'exp': + $result = $value; + break; + case 'point': + $fun = isset($data[2]) ? $data[2] : 'GeomFromText'; + $point = isset($data[3]) ? $data[3] : 'POINT'; + if (is_array($value)) { + $value = implode(' ', $value); + } + $result = $fun . '(\'' . $point . '(' . $value . ')\')'; + break; + default: + $result = false; + } + + return $result; + } + /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'rand()'; } diff --git a/thinkphp/library/think/db/builder/Pgsql.php b/thinkphp/library/think/db/builder/Pgsql.php index 21de525cb..4db6932de 100644 --- a/thinkphp/library/think/db/builder/Pgsql.php +++ b/thinkphp/library/think/db/builder/Pgsql.php @@ -12,24 +12,28 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Query; /** * Pgsql数据库驱动 */ class Pgsql extends Builder { + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; /** * limit分析 * @access protected - * @param mixed $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - public function parseLimit($limit) + public function parseLimit(Query $query, $limit) { $limitStr = ''; + if (!empty($limit)) { $limit = explode(',', $limit); if (count($limit) > 1) { @@ -38,44 +42,54 @@ class Pgsql extends Builder $limitStr .= ' LIMIT ' . $limit[0] . ' '; } } + return $limitStr; } /** * 字段和表名处理 - * @access protected - * @param string $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey($key, $options = []) + public function parseKey(Query $query, $key) { $key = trim($key); - if (strpos($key, '$.') && false === strpos($key, '(')) { + + if (strpos($key, '->') && false === strpos($key, '(')) { // JSON字段支持 - list($field, $name) = explode('$.', $key); + list($field, $name) = explode('->', $key); $key = $field . '->>\'' . $name . '\''; } elseif (strpos($key, '.')) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } + if (isset($table)) { $key = $table . '.' . $key; } + return $key; } /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'RANDOM()'; } diff --git a/thinkphp/library/think/db/builder/Sqlite.php b/thinkphp/library/think/db/builder/Sqlite.php index 960533b77..141410c1e 100644 --- a/thinkphp/library/think/db/builder/Sqlite.php +++ b/thinkphp/library/think/db/builder/Sqlite.php @@ -12,6 +12,7 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Query; /** * Sqlite数据库驱动 @@ -22,12 +23,14 @@ class Sqlite extends Builder /** * limit * @access public - * @param string $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - public function parseLimit($limit) + public function parseLimit(Query $query, $limit) { $limitStr = ''; + if (!empty($limit)) { $limit = explode(',', $limit); if (count($limit) > 1) { @@ -36,41 +39,50 @@ class Sqlite extends Builder $limitStr .= ' LIMIT ' . $limit[0] . ' '; } } + return $limitStr; } /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'RANDOM()'; } /** * 字段和表名处理 - * @access protected - * @param string $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey($key, $options = []) + public function parseKey(Query $query, $key) { $key = trim($key); if (strpos($key, '.')) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } + if (isset($table)) { $key = $table . '.' . $key; } + return $key; } } diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/thinkphp/library/think/db/builder/Sqlsrv.php index 59ea021ff..b95e234ba 100644 --- a/thinkphp/library/think/db/builder/Sqlsrv.php +++ b/thinkphp/library/think/db/builder/Sqlsrv.php @@ -12,6 +12,7 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Query; /** * Sqlsrv数据库驱动 @@ -21,103 +22,120 @@ class Sqlsrv extends Builder protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; - protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; /** * order分析 * @access protected - * @param mixed $order - * @param array $options + * @param Query $query 查询对象 + * @param mixed $order * @return string */ - protected function parseOrder($order, $options = []) + protected function parseOrder(Query $query, $order) { if (is_array($order)) { $array = []; + foreach ($order as $key => $val) { if (is_numeric($key)) { if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val, $options); + $array[] = $this->parseKey($query, $val); } elseif ('[rand]' == $val) { - $array[] = $this->parseRand(); + $array[] = $this->parseRand($query); } else { $array[] = $val; } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($key, $options) . ' ' . $sort; + $array[] = $this->parseKey($query, $key) . ' ' . $sort; } } + $order = implode(',', $array); } + return !empty($order) ? ' ORDER BY ' . $order : ' ORDER BY rand()'; } /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'rand()'; } /** * 字段和表名处理 - * @access protected - * @param string $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey($key, $options = []) + public function parseKey(Query $query, $key) { $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } + if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { $key = '[' . $key . ']'; } + if (isset($table)) { $key = '[' . $table . '].' . $key; } + return $key; } /** * limit * @access protected - * @param mixed $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - protected function parseLimit($limit) + protected function parseLimit(Query $query, $limit) { if (empty($limit)) { return ''; } $limit = explode(',', $limit); + if (count($limit) > 1) { $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; } else { $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; } + return 'WHERE ' . $limitStr; } - public function selectInsert($fields, $table, $options) + public function selectInsert(Query $query, $fields, $table) { $this->selectSql = $this->selectInsertSql; - return parent::selectInsert($fields, $table, $options); + + return parent::selectInsert($query, $fields, $table); } } diff --git a/thinkphp/library/think/db/connector/Mysql.php b/thinkphp/library/think/db/connector/Mysql.php index be1a85ce7..331d550fe 100644 --- a/thinkphp/library/think/db/connector/Mysql.php +++ b/thinkphp/library/think/db/connector/Mysql.php @@ -13,7 +13,7 @@ namespace think\db\connector; use PDO; use think\db\Connection; -use think\Log; +use think\db\Query; /** * mysql数据库驱动 @@ -23,10 +23,32 @@ class Mysql extends Connection protected $builder = '\\think\\db\\builder\\Mysql'; + /** + * 初始化 + * @access protected + * @return void + */ + protected function initialize() + { + // Point类型支持 + Query::extend('point', function ($query, $field, $value = null, $fun = 'GeomFromText', $type = 'POINT') { + if (!is_null($value)) { + $query->data($field, ['point', $value, $fun, $type]); + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + $query->setOption('point', $field); + } + + return $query; + }); + } + /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) @@ -43,28 +65,32 @@ class Mysql extends Connection if (!empty($config['charset'])) { $dsn .= ';charset=' . $config['charset']; } + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) { list($tableName) = explode(' ', $tableName); + if (false === strpos($tableName, '`')) { if (strpos($tableName, '.')) { $tableName = str_replace('.', '`.`', $tableName); } $tableName = '`' . $tableName . '`'; } + $sql = 'SHOW COLUMNS FROM ' . $tableName; $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); @@ -78,13 +104,14 @@ class Mysql extends Connection ]; } } + return $this->fieldCase($info); } /** * 取得数据库的表信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') @@ -93,16 +120,18 @@ class Mysql extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) @@ -110,11 +139,13 @@ class Mysql extends Connection $pdo = $this->linkID->query("EXPLAIN " . $sql); $result = $pdo->fetch(PDO::FETCH_ASSOC); $result = array_change_key_case($result); + if (isset($result['extra'])) { if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { - Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + $this->log('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); } } + return $result; } diff --git a/thinkphp/library/think/db/connector/Pgsql.php b/thinkphp/library/think/db/connector/Pgsql.php index bbcf57683..ee9fca017 100644 --- a/thinkphp/library/think/db/connector/Pgsql.php +++ b/thinkphp/library/think/db/connector/Pgsql.php @@ -21,36 +21,46 @@ class Pgsql extends Connection { protected $builder = '\\think\\db\\builder\\Pgsql'; + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) { $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + if (!empty($config['hostport'])) { $dsn .= ';port=' . $config['hostport']; } + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) { - list($tableName) = explode(' ', $tableName); $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); @@ -64,13 +74,14 @@ class Pgsql extends Connection ]; } } + return $this->fieldCase($info); } /** * 取得数据库的表信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') @@ -79,16 +90,18 @@ class Pgsql extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) diff --git a/thinkphp/library/think/db/connector/Sqlite.php b/thinkphp/library/think/db/connector/Sqlite.php index c4e3a7240..5b9b3fa64 100644 --- a/thinkphp/library/think/db/connector/Sqlite.php +++ b/thinkphp/library/think/db/connector/Sqlite.php @@ -25,19 +25,20 @@ class Sqlite extends Connection /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) { $dsn = 'sqlite:' . $config['database']; + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) @@ -48,6 +49,7 @@ class Sqlite extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); @@ -61,18 +63,18 @@ class Sqlite extends Connection ]; } } + return $this->fieldCase($info); } /** * 取得数据库的表信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') { - $sql = "SELECT name FROM sqlite_master WHERE type='table' " . "UNION ALL SELECT name FROM sqlite_temp_master " . "WHERE type='table' ORDER BY name"; @@ -80,16 +82,18 @@ class Sqlite extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) diff --git a/thinkphp/library/think/db/connector/Sqlsrv.php b/thinkphp/library/think/db/connector/Sqlsrv.php index 08ad45e26..4b5417097 100644 --- a/thinkphp/library/think/db/connector/Sqlsrv.php +++ b/thinkphp/library/think/db/connector/Sqlsrv.php @@ -23,34 +23,40 @@ class Sqlsrv extends Connection protected $params = [ PDO::ATTR_CASE => PDO::CASE_NATURAL, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, PDO::ATTR_STRINGIFY_FETCHES => false, ]; + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) { $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + if (!empty($config['hostport'])) { $dsn .= ',' . $config['hostport']; } + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) { list($tableName) = explode(' ', $tableName); - $sql = "SELECT column_name, data_type, column_default, is_nullable + + $sql = "SELECT column_name, data_type, column_default, is_nullable FROM information_schema.tables AS t JOIN information_schema.columns AS c ON t.table_catalog = c.table_catalog @@ -61,6 +67,7 @@ class Sqlsrv extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); @@ -74,23 +81,30 @@ class Sqlsrv extends Connection ]; } } + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + // 调试开始 $this->debug(true); + $pdo = $this->linkID->query($sql); + // 调试结束 $this->debug(false, $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + if ($result) { $info[$result['column_name']]['primary'] = true; } + return $this->fieldCase($info); } /** * 取得数据表的字段信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') @@ -103,16 +117,18 @@ class Sqlsrv extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) diff --git a/thinkphp/library/think/db/exception/BindParamException.php b/thinkphp/library/think/db/exception/BindParamException.php index 4ed195468..dce0c7bfc 100644 --- a/thinkphp/library/think/db/exception/BindParamException.php +++ b/thinkphp/library/think/db/exception/BindParamException.php @@ -21,11 +21,12 @@ class BindParamException extends DbException /** * BindParamException constructor. - * @param string $message - * @param array $config - * @param string $sql - * @param array $bind - * @param int $code + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code */ public function __construct($message, $config, $sql, $bind, $code = 10502) { diff --git a/thinkphp/library/think/db/exception/DataNotFoundException.php b/thinkphp/library/think/db/exception/DataNotFoundException.php index f2542ac6e..883e333e3 100644 --- a/thinkphp/library/think/db/exception/DataNotFoundException.php +++ b/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -19,9 +19,10 @@ class DataNotFoundException extends DbException /** * DbException constructor. - * @param string $message - * @param string $table - * @param array $config + * @access public + * @param string $message + * @param string $table + * @param array $config */ public function __construct($message, $table = '', array $config = []) { diff --git a/thinkphp/library/think/db/exception/ModelNotFoundException.php b/thinkphp/library/think/db/exception/ModelNotFoundException.php index 6e5f930cc..ae52baf3b 100644 --- a/thinkphp/library/think/db/exception/ModelNotFoundException.php +++ b/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -19,8 +19,10 @@ class ModelNotFoundException extends DbException /** * 构造方法 - * @param string $message - * @param string $model + * @access public + * @param string $message + * @param string $model + * @param array $config */ public function __construct($message, $model = '', array $config = []) { diff --git a/thinkphp/library/think/debug/Console.php b/thinkphp/library/think/debug/Console.php index c17911b2d..dd063c1c3 100644 --- a/thinkphp/library/think/debug/Console.php +++ b/thinkphp/library/think/debug/Console.php @@ -11,11 +11,8 @@ namespace think\debug; -use think\Cache; -use think\Config; +use think\Container; use think\Db; -use think\Debug; -use think\Request; use think\Response; /** @@ -24,7 +21,7 @@ use think\Response; class Console { protected $config = [ - 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], ]; // 实例化并传入参数 @@ -38,13 +35,13 @@ class Console /** * 调试输出接口 * @access public - * @param Response $response Response对象 - * @param array $log 日志信息 + * @param Response $response Response对象 + * @param array $log 日志信息 * @return bool */ public function output(Response $response, array $log = []) { - $request = Request::instance(); + $request = Container::get('request'); $contentType = $response->getHeader('Content-Type'); $accept = $request->header('accept'); if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { @@ -53,9 +50,9 @@ class Console return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - THINK_START_TIME, 10); + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); if (isset($_SERVER['HTTP_HOST'])) { $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; @@ -68,19 +65,18 @@ class Console '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', - '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', - '配置加载' => count(Config::get()), + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', ]; if (session_id()) { $base['会话信息'] = 'SESSION_ID=' . session_id(); } - $info = Debug::getFile(true); + $info = Container::get('debug')->getFile(true); // 页面Trace信息 $trace = []; - foreach ($this->config['trace_tabs'] as $name => $title) { + foreach ($this->config['tabs'] as $name => $title) { $name = strtolower($name); switch ($name) { case 'base': // 基本信息 @@ -121,7 +117,7 @@ JS; protected function console($type, $msg) { $type = strtolower($type); - $trace_tabs = array_values($this->config['trace_tabs']); + $trace_tabs = array_values($this->config['tabs']); $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) ? "console.group('{$type}');" : "console.groupCollapsed('{$type}');"; @@ -137,12 +133,12 @@ JS; } break; case '错误': - $msg = str_replace("\n", '\n', json_encode($m)); + $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m))); $style = 'color:#F4006B;font-size:14px;'; $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; break; case 'sql': - $msg = str_replace("\n", '\n', $m); + $msg = str_replace("\n", '\n', addslashes($m)); $style = "color:#009bb4;"; $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; break; diff --git a/thinkphp/library/think/debug/Html.php b/thinkphp/library/think/debug/Html.php index f8651aa92..ed4c62684 100644 --- a/thinkphp/library/think/debug/Html.php +++ b/thinkphp/library/think/debug/Html.php @@ -11,11 +11,8 @@ namespace think\debug; -use think\Cache; -use think\Config; +use think\Container; use think\Db; -use think\Debug; -use think\Request; use think\Response; /** @@ -24,27 +21,26 @@ use think\Response; class Html { protected $config = [ - 'trace_file' => '', - 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + 'file' => '', + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], ]; // 实例化并传入参数 public function __construct(array $config = []) { - $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; - $this->config = array_merge($this->config, $config); + $this->config = array_merge($this->config, $config); } /** * 调试输出接口 * @access public - * @param Response $response Response对象 - * @param array $log 日志信息 + * @param Response $response Response对象 + * @param array $log 日志信息 * @return bool */ public function output(Response $response, array $log = []) { - $request = Request::instance(); + $request = Container::get('request'); $contentType = $response->getHeader('Content-Type'); $accept = $request->header('accept'); if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { @@ -53,9 +49,9 @@ class Html return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - THINK_START_TIME, 10); + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); // 页面Trace信息 if (isset($_SERVER['HTTP_HOST'])) { @@ -67,19 +63,18 @@ class Html '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', - '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', - '配置加载' => count(Config::get()), + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', ]; if (session_id()) { $base['会话信息'] = 'SESSION_ID=' . session_id(); } - $info = Debug::getFile(true); + $info = Container::get('debug')->getFile(true); // 页面Trace信息 $trace = []; - foreach ($this->config['trace_tabs'] as $name => $title) { + foreach ($this->config['tabs'] as $name => $title) { $name = strtolower($name); switch ($name) { case 'base': // 基本信息 @@ -104,7 +99,7 @@ class Html } // 调用Trace页面模板 ob_start(); - include $this->config['trace_file']; + include $this->config['file']; return ob_get_clean(); } diff --git a/thinkphp/library/think/exception/DbException.php b/thinkphp/library/think/exception/DbException.php index 0ae80ad17..0f504257c 100644 --- a/thinkphp/library/think/exception/DbException.php +++ b/thinkphp/library/think/exception/DbException.php @@ -20,10 +20,11 @@ class DbException extends Exception { /** * DbException constructor. - * @param string $message - * @param array $config - * @param string $sql - * @param int $code + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code */ public function __construct($message, array $config, $sql, $code = 10500) { diff --git a/thinkphp/library/think/exception/ErrorException.php b/thinkphp/library/think/exception/ErrorException.php index b3a9a30a4..3143b8f70 100644 --- a/thinkphp/library/think/exception/ErrorException.php +++ b/thinkphp/library/think/exception/ErrorException.php @@ -29,25 +29,24 @@ class ErrorException extends Exception /** * 错误异常构造函数 - * @param integer $severity 错误级别 - * @param string $message 错误详细信息 - * @param string $file 出错文件路径 - * @param integer $line 出错行号 - * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 + * @access public + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 */ - public function __construct($severity, $message, $file, $line, array $context = []) + public function __construct($severity, $message, $file, $line) { $this->severity = $severity; $this->message = $message; $this->file = $file; $this->line = $line; $this->code = 0; - - empty($context) || $this->setData('Error Context', $context); } /** * 获取错误级别 + * @access public * @return integer 错误级别 */ final public function getSeverity() diff --git a/thinkphp/library/think/exception/Handle.php b/thinkphp/library/think/exception/Handle.php index f523db09f..02c85ec13 100644 --- a/thinkphp/library/think/exception/Handle.php +++ b/thinkphp/library/think/exception/Handle.php @@ -12,11 +12,8 @@ namespace think\exception; use Exception; -use think\App; -use think\Config; use think\console\Output; -use think\Lang; -use think\Log; +use think\Container; use think\Response; class Handle @@ -34,6 +31,7 @@ class Handle /** * Report or log an exception. * + * @access public * @param \Exception $exception * @return void */ @@ -41,7 +39,7 @@ class Handle { if (!$this->isIgnoreReport($exception)) { // 收集异常数据 - if (App::$debug) { + if (Container::get('app')->isDebug()) { $data = [ 'file' => $exception->getFile(), 'line' => $exception->getLine(), @@ -57,11 +55,11 @@ class Handle $log = "[{$data['code']}]{$data['message']}"; } - if (Config::get('record_trace')) { + if (Container::get('app')->config('log.record_trace')) { $log .= "\r\n" . $exception->getTraceAsString(); } - Log::record($log, 'error'); + Container::get('log')->record($log, 'error'); } } @@ -72,12 +70,14 @@ class Handle return true; } } + return false; } /** * Render an exception into an HTTP response. * + * @access public * @param \Exception $e * @return Response */ @@ -85,6 +85,7 @@ class Handle { if ($this->render && $this->render instanceof \Closure) { $result = call_user_func_array($this->render, [$e]); + if ($result) { return $result; } @@ -98,26 +99,30 @@ class Handle } /** - * @param Output $output - * @param Exception $e + * @access public + * @param Output $output + * @param Exception $e */ public function renderForConsole(Output $output, Exception $e) { - if (App::$debug) { + if (Container::get('app')->isDebug()) { $output->setVerbosity(Output::VERBOSITY_DEBUG); } + $output->renderException($e); } /** - * @param HttpException $e + * @access protected + * @param HttpException $e * @return Response */ protected function renderHttpException(HttpException $e) { $status = $e->getStatusCode(); - $template = Config::get('http_exception_template'); - if (!App::$debug && !empty($template[$status])) { + $template = Container::get('app')->config('http_exception_template'); + + if (!Container::get('app')->isDebug() && !empty($template[$status])) { return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); } else { return $this->convertExceptionToResponse($e); @@ -125,13 +130,14 @@ class Handle } /** - * @param Exception $exception + * @access protected + * @param Exception $exception * @return Response */ protected function convertExceptionToResponse(Exception $exception) { // 收集异常数据 - if (App::$debug) { + if (Container::get('app')->isDebug()) { // 调试模式,获取详细的错误信息 $data = [ 'name' => get_class($exception), @@ -160,9 +166,9 @@ class Handle 'message' => $this->getMessage($exception), ]; - if (!Config::get('show_error_msg')) { + if (!Container::get('app')->config('show_error_msg')) { // 不显示详细错误信息 - $data['message'] = Config::get('error_message'); + $data['message'] = Container::get('app')->config('error_message'); } } @@ -175,10 +181,11 @@ class Handle ob_start(); extract($data); - include Config::get('exception_tmpl'); + include Container::get('app')->config('exception_tmpl'); + // 获取并清空缓存 $content = ob_get_clean(); - $response = new Response($content, 'html'); + $response = Response::create($content, 'html'); if ($exception instanceof HttpException) { $statusCode = $exception->getStatusCode(); @@ -189,52 +196,62 @@ class Handle $statusCode = 500; } $response->code($statusCode); + return $response; } /** * 获取错误编码 * ErrorException则使用错误级别作为错误编码 + * @access protected * @param \Exception $exception * @return integer 错误编码 */ protected function getCode(Exception $exception) { $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { $code = $exception->getSeverity(); } + return $code; } /** * 获取错误信息 * ErrorException则使用错误级别作为错误编码 + * @access protected * @param \Exception $exception * @return string 错误信息 */ protected function getMessage(Exception $exception) { $message = $exception->getMessage(); - if (IS_CLI) { + + if (PHP_SAPI == 'cli') { return $message; } + $lang = Container::get('lang'); + if (strpos($message, ':')) { $name = strstr($message, ':', true); - $message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message; + $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message; } elseif (strpos($message, ',')) { $name = strstr($message, ',', true); - $message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message; - } elseif (Lang::has($message)) { - $message = Lang::get($message); + $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif ($lang->has($message)) { + $message = $lang->get($message); } + return $message; } /** * 获取出错文件内容 * 获取错误的前9行和后9行 + * @access protected * @param \Exception $exception * @return array 错误文件内容 */ @@ -253,30 +270,37 @@ class Handle } catch (Exception $e) { $source = []; } + return $source; } /** * 获取异常扩展信息 * 用于非调试模式html返回类型显示 + * @access protected * @param \Exception $exception * @return array 异常类定义的扩展数据 */ protected function getExtendData(Exception $exception) { $data = []; + if ($exception instanceof \think\Exception) { $data = $exception->getData(); } + return $data; } /** * 获取常量列表 + * @access private * @return array 常量列表 */ private static function getConst() { - return get_defined_constants(true)['user']; + $const = get_defined_constants(true); + + return isset($const['user']) ? $const['user'] : []; } } diff --git a/thinkphp/library/think/exception/PDOException.php b/thinkphp/library/think/exception/PDOException.php index 044f82a05..25240b680 100644 --- a/thinkphp/library/think/exception/PDOException.php +++ b/thinkphp/library/think/exception/PDOException.php @@ -19,10 +19,11 @@ class PDOException extends DbException { /** * PDOException constructor. - * @param \PDOException $exception - * @param array $config - * @param string $sql - * @param int $code + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code */ public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) { diff --git a/thinkphp/library/think/facade/App.php b/thinkphp/library/think/facade/App.php new file mode 100644 index 000000000..37d995106 --- /dev/null +++ b/thinkphp/library/think/facade/App.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\App + * @mixin \think\App + * @method \think\App bind(string $bind) static 绑定模块或者控制器 + * @method void initialize() static 初始化应用 + * @method void init(string $module='') static 初始化模块 + * @method \think\Response run() static 执行应用 + * @method \think\App dispatch(\think\route\Dispatch $dispatch) static 设置当前请求的调度信息 + * @method void log(mixed $log, string $type = 'info') static 记录调试信息 + * @method mixed config(string $name='') static 获取配置参数 + * @method \think\route\Dispatch routeCheck() static URL路由检测(根据PATH_INFO) + * @method \think\App routeMust(bool $must = false) static 设置应用的路由检测机制 + * @method \think\Model model(string $name = '', string $layer = 'model', bool $appendSuffix = false, string $common = 'common') static 实例化模型 + * @method object controller(string $name, string $layer = 'controller', bool $appendSuffix = false, string $empty = '') static 实例化控制器 + * @method \think\Validate validate(string $name = '', string $layer = 'validate', bool $appendSuffix = false, string $common = 'common') static 实例化验证器类 + * @method \think\db\Query db(mixed $config = [], mixed $name = false) static 数据库初始化 + * @method mixed action(string $url, $vars = [], $layer = 'controller', $appendSuffix = false) static 调用模块的操作方法 + * @method string parseClass(string $module, string $layer, string $name, bool $appendSuffix = false) static 解析应用类的类名 + * @method string version() static 获取框架版本 + * @method bool isDebug() static 是否为调试模式 + * @method string getModulePath() static 获取当前模块路径 + * @method void setModulePath(string $path) static 设置当前模块路径 + * @method string getRootPath() static 获取应用根目录 + * @method string getAppPath() static 获取应用类库目录 + * @method string getRuntimePath() static 获取应用运行时目录 + * @method string getThinkPath() static 获取核心框架目录 + * @method string getRoutePath() static 获取路由目录 + * @method string getConfigPath() static 获取应用配置目录 + * @method string getConfigExt() static 获取配置后缀 + * @method string setNamespace(string $namespace) static 设置应用类库命名空间 + * @method string getNamespace() static 获取应用类库命名空间 + * @method string getSuffix() static 是否启用类库后缀 + * @method float getBeginTime() static 获取应用开启时间 + * @method integer getBeginMem() static 获取应用初始内存占用 + * @method \think\Container container() static 获取容器实例 + */ +class App extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Build.php b/thinkphp/library/think/facade/Build.php new file mode 100644 index 000000000..465962367 --- /dev/null +++ b/thinkphp/library/think/facade/Build.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Build + * @mixin \think\Build + * @method void run(array $build = [], string $namespace = 'app', bool $suffix = false) static 根据传入的build资料创建目录和文件 + * @method void module(string $module = '', array $list = [], string $namespace = 'app', bool $suffix = false) static 创建模块 + */ +class Build extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Cache.php b/thinkphp/library/think/facade/Cache.php new file mode 100644 index 000000000..3011d6b3a --- /dev/null +++ b/thinkphp/library/think/facade/Cache.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cache + * @mixin \think\Cache + * @method \think\cache\Driver connect(array $options = [], mixed $name = false) static 连接缓存 + * @method \think\cache\Driver init(array $options = []) static 初始化缓存 + * @method \think\cache\Driver store(string $name = '') static 切换缓存类型 + * @method bool has(string $name) static 判断缓存是否存在 + * @method mixed get(string $name, mixed $default = false) static 读取缓存 + * @method mixed pull(string $name) static 读取缓存并删除 + * @method mixed set(string $name, mixed $value, int $expire = null) static 设置缓存 + * @method mixed remember(string $name, mixed $value, int $expire = null) static 如果不存在则写入缓存 + * @method mixed inc(string $name, int $step = 1) static 自增缓存(针对数值缓存) + * @method mixed dec(string $name, int $step = 1) static 自减缓存(针对数值缓存) + * @method bool rm(string $name) static 删除缓存 + * @method bool clear(string $tag = null) static 清除缓存 + * @method mixed tag(string $name, mixed $keys = null, bool $overlay = false) static 缓存标签 + * @method object handler() static 返回句柄对象,可执行其它高级方法 + */ +class Cache extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Config.php b/thinkphp/library/think/facade/Config.php new file mode 100644 index 000000000..3332413b8 --- /dev/null +++ b/thinkphp/library/think/facade/Config.php @@ -0,0 +1,27 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Config + * @mixin \think\Config + * @method bool has(string $name) static 检测配置是否存在 + * @method array pull(string $name) static 获取一级配置 + * @method mixed get(string $name) static 获取配置参数 + * @method mixed set(string $name, mixed $value = null) static 设置配置参数 + * @method array reset(string $prefix ='') static 重置配置参数 + */ +class Config extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Cookie.php b/thinkphp/library/think/facade/Cookie.php new file mode 100644 index 000000000..18efbde0c --- /dev/null +++ b/thinkphp/library/think/facade/Cookie.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cookie + * @mixin \think\Cookie + * @method void init(array $config = []) static 初始化 + * @method bool has(string $name,string $prefix = null) static 判断Cookie数据 + * @method mixed prefix(string $prefix = '') static 设置或者获取cookie作用域(前缀) + * @method mixed get(string $name,string $prefix = null) static Cookie获取 + * @method mixed set(string $name, mixed $value = null, mixed $option = null) static 设置Cookie + * @method void forever(string $name, mixed $value = null, mixed $option = null) static 永久保存Cookie数据 + * @method void delete(string $name, string $prefix = null) static Cookie删除 + * @method void clear($prefix = null) static Cookie清空 + */ +class Cookie extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Debug.php b/thinkphp/library/think/facade/Debug.php new file mode 100644 index 000000000..ac482a41e --- /dev/null +++ b/thinkphp/library/think/facade/Debug.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Debug + * @mixin \think\Debug + * @method void remark(string $name, mixed $value = '') static 记录时间(微秒)和内存使用情况 + * @method int getRangeTime(string $start, string $end, mixed $dec = 6) static 统计某个区间的时间(微秒)使用情况 + * @method int getUseTime(int $dec = 6) static 统计从开始到统计时的时间(微秒)使用情况 + * @method string getThroughputRate(string $start, string $end, mixed $dec = 6) static 获取当前访问的吞吐率情况 + * @method string getRangeMem(string $start, string $end, mixed $dec = 2) static 记录区间的内存使用情况 + * @method int getUseMem(int $dec = 2) static 统计从开始到统计时的内存使用情况 + * @method string getMemPeak(string $start, string $end, mixed $dec = 2) static 统计区间的内存峰值情况 + * @method mixed getFile(bool $detail = false) static 获取文件加载信息 + * @method mixed dump(mixed $var, bool $echo = true, string $label = null, int $flags = ENT_SUBSTITUTE) static 浏览器友好的变量输出 + */ +class Debug extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Env.php b/thinkphp/library/think/facade/Env.php new file mode 100644 index 000000000..d430d683f --- /dev/null +++ b/thinkphp/library/think/facade/Env.php @@ -0,0 +1,25 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Env + * @mixin \think\Env + * @method void load(string $file) static 读取环境变量定义文件 + * @method mixed get(string $name = null, mixed $default = null) static 获取环境变量值 + * @method void set(mixed $env, string $value = null) static 设置环境变量值 + */ +class Env extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Hook.php b/thinkphp/library/think/facade/Hook.php new file mode 100644 index 000000000..e18f83b5d --- /dev/null +++ b/thinkphp/library/think/facade/Hook.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Hook + * @mixin \think\Hook + * @method \think\Hook alias(mixed $name, mixed $behavior = null) static 指定行为标识 + * @method void add(string $tag, mixed $behavior, bool $first = false) static 动态添加行为扩展到某个标签 + * @method void import(array $tags, bool $recursive = true) static 批量导入插件 + * @method array get(string $tag = '') static 获取插件信息 + * @method mixed listen(string $tag, mixed $params = null, bool $once = false) static 监听标签的行为 + * @method mixed exec(mixed $class, mixed $params = null) static 执行行为 + */ +class Hook extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Lang.php b/thinkphp/library/think/facade/Lang.php new file mode 100644 index 000000000..3eb5d4acd --- /dev/null +++ b/thinkphp/library/think/facade/Lang.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Lang + * @mixin \think\Lang + * @method mixed range($range = '') static 设定当前的语言 + * @method mixed set(mixed $name, string $value = null, string $range = '') static 设置语言定义 + * @method array load(mixed $file, string $range = '') static 加载语言定义 + * @method mixed get(string $name = null, array $vars = [], string $range = '') static 获取语言定义 + * @method mixed has(string $name, string $range = '') static 获取语言定义 + * @method string detect() static 自动侦测设置获取语言选择 + * @method void saveToCookie(string $lang = null) static 设置当前语言到Cookie + * @method void setLangDetectVar(string $var) static 设置语言自动侦测的变量 + * @method void setLangCookieVar(string $var) static 设置语言的cookie保存变量 + * @method void setAllowLangList(array $list) static 设置允许的语言列表 + */ +class Lang extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Log.php b/thinkphp/library/think/facade/Log.php new file mode 100644 index 000000000..3a1fbf88f --- /dev/null +++ b/thinkphp/library/think/facade/Log.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Log + * @mixin \think\Log + * @method \think\Log init(array $config = []) static 日志初始化 + * @method mixed getLog(string $type = '') static 获取日志信息 + * @method \think\Log record(mixed $msg, string $type = 'info', array $context = []) static 记录日志信息 + * @method \think\Log clear() static 清空日志信息 + * @method \think\Log key(string $key) static 当前日志记录的授权key + * @method bool check(array $config) static 检查日志写入权限 + * @method bool save() static 保存调试信息 + * @method void write(mixed $msg, string $type = 'info', bool $force = false) static 实时写入日志信息 + * @method void log(string $level,mixed $message, array $context = []) static 记录日志信息 + * @method void emergency(mixed $message, array $context = []) static 记录emergency信息 + * @method void alert(mixed $message, array $context = []) static 记录alert信息 + * @method void critical(mixed $message, array $context = []) static 记录critical信息 + * @method void error(mixed $message, array $context = []) static 记录error信息 + * @method void warning(mixed $message, array $context = []) static 记录warning信息 + * @method void notice(mixed $message, array $context = []) static 记录notice信息 + * @method void info(mixed $message, array $context = []) static 记录info信息 + * @method void debug(mixed $message, array $context = []) static 记录debug信息 + * @method void sql(mixed $message, array $context = []) static 记录sql信息 + */ +class Log extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Request.php b/thinkphp/library/think/facade/Request.php new file mode 100644 index 000000000..20b6429d4 --- /dev/null +++ b/thinkphp/library/think/facade/Request.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Request + * @mixin \think\Request + * @method void hook(mixed $method, mixed $callback = null) static Hook 方法注入 + * @method \think\Request create(string $uri, string $method = 'GET', array $params = [], array $cookie = [], array $files = [], array $server = [], string $content = null) static 创建一个URL请求 + * @method mixed domain(string $domain = null) static 设置或获取当前包含协议的域名 + * @method mixed url(mixed $url = null) static 设置或获取当前完整URL + * @method mixed baseUrl(string $url = null) static 设置或获取当前URL + * @method mixed baseFile(string $file = null) static 设置或获取当前执行的文件 + * @method mixed root(string $url = null) static 设置或获取URL访问根地址 + * @method string rootUrl() static 获取URL访问根目录 + * @method string pathinfo() static 获取当前请求URL的pathinfo信息(含URL后缀) + * @method string path() static 获取当前请求URL的pathinfo信息(不含URL后缀) + * @method string ext() static 当前URL的访问后缀 + * @method float time(bool $float = false) static 获取当前请求的时间 + * @method mixed type() static 当前请求的资源类型 + * @method void mimeType(mixed $type, string $val = '') static 设置资源类型 + * @method string method(bool $method = false) static 当前的请求类型 + * @method bool isGet() static 是否为GET请求 + * @method bool isPost() static 是否为POST请求 + * @method bool isPut() static 是否为PUT请求 + * @method bool isDelete() static 是否为DELTE请求 + * @method bool isHead() static 是否为HEAD请求 + * @method bool isPatch() static 是否为PATCH请求 + * @method bool isOptions() static 是否为OPTIONS请求 + * @method bool isCli() static 是否为cli + * @method bool isCgi() static 是否为cgi + * @method mixed param(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取当前请求的参数 + * @method mixed route(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取路由参数 + * @method mixed get(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取GET参数 + * @method mixed post(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取POST参数 + * @method mixed put(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取PUT参数 + * @method mixed delete(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取DELETE参数 + * @method mixed patch(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取PATCH参数 + * @method mixed request(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取request变量 + * @method mixed session(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取session数据 + * @method mixed cookie(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取cookie参数 + * @method mixed server(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取server参数 + * @method mixed env(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取环境变量 + * @method mixed file(mixed $name = '') static 获取上传的文件信息 + * @method mixed header(mixed $name = '', mixed $default = null) static 设置或者获取当前的Header + * @method mixed input(array $data,mixed $name = '', mixed $default = null, mixed $filter = '') static 获取变量 支持过滤和默认值 + * @method mixed filter(mixed $filter = null) static 设置或获取当前的过滤规则 + * @method mixed has(string $name, string $type = 'param', bool $checkEmpty = false) static 是否存在某个请求参数 + * @method mixed only(mixed $name, string $type = 'param') static 获取指定的参数 + * @method mixed except(mixed $name, string $type = 'param') static 排除指定参数获取 + * @method bool isSsl() static 当前是否ssl + * @method bool isAjax(bool $ajax = false) static 当前是否Ajax请求 + * @method bool isPjax(bool $pjax = false) static 当前是否Pjax请求 + * @method mixed ip(int $type = 0, bool $adv = true) static 获取客户端IP地址 + * @method bool isMobile() static 检测是否使用手机访问 + * @method string scheme() static 当前URL地址中的scheme参数 + * @method string query() static 当前请求URL地址中的query参数 + * @method string host() static 当前请求的host + * @method string port() static 当前请求URL地址中的port参数 + * @method string protocol() static 当前请求 SERVER_PROTOCOL + * @method string remotePort() static 当前请求 REMOTE_PORT + * @method string contentType() static 当前请求 HTTP_CONTENT_TYPE + * @method array routeInfo(array $route = []) static 获取当前请求的路由信息 + * @method array dispatch(array $dispatch = null) static 设置或者获取当前请求的调度信息 + * @method mixed module(string $module = null) static 设置或者获取当前的模块名 + * @method mixed controller(string $controller = null) static 设置或者获取当前的控制器名 + * @method mixed action(string $action = null) static 设置或者获取当前的操作名 + * @method mixed langset(string $lang = null) static 设置或者获取当前的语言 + * @method string getContent() static 设置或者获取当前请求的content + * @method string getInput() static 获取当前请求的php://input + * @method string token(string $name = '__token__', mixed $type = 'md5') static 生成请求令牌 + * @method string cache(string $key, mixed $expire = null, array $except = [], string $tag = null) static 设置当前地址的请求缓存 + * @method string getCache() static 读取请求缓存设置 + */ +class Request extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Response.php b/thinkphp/library/think/facade/Response.php new file mode 100644 index 000000000..4a4de7129 --- /dev/null +++ b/thinkphp/library/think/facade/Response.php @@ -0,0 +1,38 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Response + * @mixin \think\Response + * @method \think\response create(mixed $data = '', string $type = '', int $code = 200, array $header = [], array $options = []) static 创建Response对象 + * @method void send() static 发送数据到客户端 + * @method \think\Response options(mixed $options = []) static 输出的参数 + * @method \think\Response data(mixed $data) static 输出数据设置 + * @method \think\Response header(mixed $name, string $value = null) static 设置响应头 + * @method \think\Response content(mixed $content) static 设置页面输出内容 + * @method \think\Response code(int $code) static 发送HTTP状态 + * @method \think\Response lastModified(string $time) static LastModified + * @method \think\Response expires(string $time) static expires + * @method \think\Response eTag(string $eTag) static eTag + * @method \think\Response cacheControl(string $cache) static 页面缓存控制 + * @method \think\Response contentType(string $contentType, string $charset = 'utf-8') static 页面输出类型 + * @method mixed getHeader(string $name) static 获取头部信息 + * @method mixed getData() static 获取原始数据 + * @method mixed getContent() static 获取输出数据 + * @method int getCode() static 获取状态码 + */ +class Response extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Route.php b/thinkphp/library/think/facade/Route.php new file mode 100644 index 000000000..c197183ba --- /dev/null +++ b/thinkphp/library/think/facade/Route.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Route + * @mixin \think\Route + * @method \think\route\Domain domain(mixed $name, mixed $rule = '', array $option = [], array $pattern = []) static 注册域名路由 + * @method \think\Route pattern(mixed $name, string $rule = '') static 注册变量规则 + * @method \think\Route option(mixed $name, mixed $value = '') static 注册路由参数 + * @method \think\Route bind(string $bind) static 设置路由绑定 + * @method mixed getBind(string $bind) static 读取路由绑定 + * @method \think\Route name(string $name) static 设置当前路由标识 + * @method mixed getName(string $name) static 读取路由标识 + * @method void setName(string $name) static 批量导入路由标识 + * @method void import(array $rules, string $type = '*') static 导入配置文件的路由规则 + * @method \think\route\RuleItem rule(string $rule, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由规则 + * @method void rules(string $rules, string $method = '*', array $option = [], array $pattern = []) static 批量注册路由规则 + * @method \think\route\RuleGroup group(string $name, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由分组 + * @method \think\route\RuleItem any(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem get(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem post(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem put(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem delete(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem patch(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\Resource resource(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册资源路由 + * @method \think\Route controller(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册控制器路由 + * @method \think\Route alias(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册别名路由 + * @method \think\Route setMethodPrefix(mixed $method, string $prefix = '') static 设置不同请求类型下面的方法前缀 + * @method \think\Route rest(string $name, array $resource = []) static rest方法定义和修改 + * @method \think\RuleItem miss(string $route, string $method = '*', array $option = []) static 注册未匹配路由规则后的处理 + * @method \think\RuleItem auto(string $route) static 注册一个自动解析的URL路由 + * @method \think\Dispatch check(string $url, string $depr = '/', bool $must = false, bool $completeMatch = false) static 检测URL路由 + */ +class Route extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Session.php b/thinkphp/library/think/facade/Session.php new file mode 100644 index 000000000..9aad0b698 --- /dev/null +++ b/thinkphp/library/think/facade/Session.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Session + * @mixin \think\Session + * @method void init(array $config = []) static session初始化 + * @method bool has(string $name,string $prefix = null) static 判断session数据 + * @method mixed prefix(string $prefix = '') static 设置或者获取session作用域(前缀) + * @method mixed get(string $name,string $prefix = null) static session获取 + * @method mixed pull(string $name,string $prefix = null) static session获取并删除 + * @method void push(string $key, mixed $value) static 添加数据到一个session数组 + * @method void set(string $name, mixed $value , string $prefix = null) static 设置session数据 + * @method void flash(string $name, mixed $value = null) static session设置 下一次请求有效 + * @method void flush() static 清空当前请求的session数据 + * @method void delete(string $name, string $prefix = null) static 删除session数据 + * @method void clear($prefix = null) static 清空session数据 + * @method void start() static 启动session + * @method void destroy() static 销毁session + * @method void pause() static 暂停session + * @method void regenerate(bool $delete = false) static 重新生成session_id + */ +class Session extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Url.php b/thinkphp/library/think/facade/Url.php new file mode 100644 index 000000000..db5a16f2b --- /dev/null +++ b/thinkphp/library/think/facade/Url.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Url + * @mixin \think\Url + * @method string build(string $url = '', mixed $vars = '', mixed $suffix = true, mixed $domain = false) static URL生成 支持路由反射 + * @method void root(string $root) static 指定当前生成URL地址的root + */ +class Url extends Facade +{ +} diff --git a/thinkphp/library/think/facade/Validate.php b/thinkphp/library/think/facade/Validate.php new file mode 100644 index 000000000..423446d4d --- /dev/null +++ b/thinkphp/library/think/facade/Validate.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Validate + * @mixin \think\Validate + * @method \think\Validate make(array $rules = [], array $message = [], array $field = []) static 创建一个验证器类 + * @method \think\Validate rule(mixed $name, mixed $rule = '') static 添加字段验证规则 + * @method void extend(string $type, mixed $callback = null) static 注册扩展验证(类型)规则 + * @method void setTypeMsg(mixed $type, string $msg = null) static 设置验证规则的默认提示信息 + * @method \think\Validate message(mixed $name, string $message = '') static 设置提示信息 + * @method \think\Validate scene(string $name) static 设置验证场景 + * @method bool hasScene(string $name) static 判断是否存在某个验证场景 + * @method \think\Validate batch(bool $batch = true) static 设置批量验证 + * @method \think\Validate only(array $fields) static 指定需要验证的字段列表 + * @method \think\Validate remove(mixed $field, mixed $rule = true) static 移除某个字段的验证规则 + * @method \think\Validate append(mixed $field, mixed $rule = null) static 追加某个字段的验证规则 + * @method bool confirm(mixed $value, mixed $rule, array $data = [], string $field = '') static 验证是否和某个字段的值一致 + * @method bool different(mixed $value, mixed $rule, array $data = []) static 验证是否和某个字段的值是否不同 + * @method bool egt(mixed $value, mixed $rule, array $data = []) static 验证是否大于等于某个值 + * @method bool gt(mixed $value, mixed $rule, array $data = []) static 验证是否大于某个值 + * @method bool elt(mixed $value, mixed $rule, array $data = []) static 验证是否小于等于某个值 + * @method bool lt(mixed $value, mixed $rule, array $data = []) static 验证是否小于某个值 + * @method bool eq(mixed $value, mixed $rule) static 验证是否等于某个值 + * @method bool must(mixed $value, mixed $rule) static 必须验证 + * @method bool is(mixed $value, mixed $rule, array $data = []) static 验证字段值是否为有效格式 + * @method bool ip(mixed $value, mixed $rule) static 验证是否有效IP + * @method bool requireIf(mixed $value, mixed $rule) static 验证某个字段等于某个值的时候必须 + * @method bool requireCallback(mixed $value, mixed $rule,array $data) static 通过回调方法验证某个字段是否必须 + * @method bool requireWith(mixed $value, mixed $rule, array $data) static 验证某个字段有值的情况下必须 + * @method bool filter(mixed $value, mixed $rule) static 使用filter_var方式验证 + * @method bool in(mixed $value, mixed $rule) static 验证是否在范围内 + * @method bool notIn(mixed $value, mixed $rule) static 验证是否不在范围内 + * @method bool between(mixed $value, mixed $rule) static between验证数据 + * @method bool notBetween(mixed $value, mixed $rule) static 使用notbetween验证数据 + * @method bool length(mixed $value, mixed $rule) static 验证数据长度 + * @method bool max(mixed $value, mixed $rule) static 验证数据最大长度 + * @method bool min(mixed $value, mixed $rule) static 验证数据最小长度 + * @method bool after(mixed $value, mixed $rule) static 验证日期 + * @method bool before(mixed $value, mixed $rule) static 验证日期 + * @method bool expire(mixed $value, mixed $rule) static 验证有效期 + * @method bool allowIp(mixed $value, mixed $rule) static 验证IP许可 + * @method bool denyIp(mixed $value, mixed $rule) static 验证IP禁用 + * @method bool regex(mixed $value, mixed $rule) static 使用正则验证数据 + * @method bool token(mixed $value, mixed $rule) static 验证表单令牌 + * @method bool dateFormat(mixed $value, mixed $rule) static 验证时间和日期是否符合指定格式 + * @method bool unique(mixed $value, mixed $rule, array $data = [], string $field = '') static 验证是否唯一 + * @method bool check(array $data, mixed $rules = [], string $scene = '') static 数据自动验证 + * @method mixed getError(mixed $value, mixed $rule) static 获取错误信息 + */ +class Validate extends Facade +{ +} diff --git a/thinkphp/library/think/facade/View.php b/thinkphp/library/think/facade/View.php new file mode 100644 index 000000000..0309a760f --- /dev/null +++ b/thinkphp/library/think/facade/View.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\View + * @mixin \think\View + * @method \think\View init(mixed $engine = [], array $replace = []) static 初始化 + * @method \think\View share(mixed $name, mixed $value = '') static 模板变量静态赋值 + * @method \think\View assign(mixed $name, mixed $value = '') static 模板变量赋值 + * @method \think\View config(mixed $name, mixed $value = '') static 配置模板引擎 + * @method \think\View exists(mixed $name) static 检查模板是否存在 + * @method \think\View filter(Callable $filter) static 视图内容过滤 + * @method \think\View engine(mixed $engine = []) static 设置当前模板解析的引擎 + * @method string fetch(string $template = '', array $vars = [], array $replace = [], array $config = [], bool $renderContent = false) static 解析和获取模板内容 + * @method string display(string $content = '', array $vars = [], array $replace = [], array $config = []) static 渲染内容输出 + */ +class View extends Facade +{ +} diff --git a/thinkphp/library/think/http/middleware/Dispatcher.php b/thinkphp/library/think/http/middleware/Dispatcher.php new file mode 100644 index 000000000..4218915db --- /dev/null +++ b/thinkphp/library/think/http/middleware/Dispatcher.php @@ -0,0 +1,86 @@ + +// +---------------------------------------------------------------------- + +namespace think\http\middleware; + +use think\Request; +use think\Response; + +class Dispatcher implements DispatcherInterface +{ + protected $queue; + + public function __construct($middlewares = []) + { + $this->queue = (array) $middlewares; + } + + /** + * {@inheritdoc} + */ + public function add($middleware) + { + $this->assertValid($middleware); + $this->queue[] = $middleware; + } + + /** + * {@inheritdoc} + */ + public function insert($middleware) + { + $this->assertValid($middleware); + array_unshift($this->queue, $middleware); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->queue; + } + + /** + * {@inheritdoc} + */ + public function dispatch(Request $request) + { + $requestHandler = $this->resolve(); + return call_user_func($requestHandler, $request); + } + + protected function resolve() + { + return function (Request $request) { + $middleware = array_shift($this->queue); + + if (null !== $middleware) { + $response = call_user_func($middleware, $request, $this->resolve()); + + if (!$response instanceof Response) { + throw new \LogicException('The middleware must return Response instance'); + } + + return $response; + } else { + throw new MissingResponseException('The queue was exhausted, with no response returned'); + } + }; + } + + protected function assertValid($middleware) + { + if (!is_callable($middleware)) { + throw new \InvalidArgumentException('The middleware is invalid'); + } + } +} diff --git a/thinkphp/library/think/http/middleware/DispatcherInterface.php b/thinkphp/library/think/http/middleware/DispatcherInterface.php new file mode 100644 index 000000000..1693f6093 --- /dev/null +++ b/thinkphp/library/think/http/middleware/DispatcherInterface.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- + +namespace think\http\middleware; + +use think\Request; +use think\Response; + +interface DispatcherInterface +{ + /** + * 在队尾添加 middleware + * @param callable $middleware + * @return DispatcherInterface + */ + public function add($middleware); + + /** + * 在队前插入 middleware + * @param callable $middleware + * @return DispatcherInterface + */ + public function insert($middleware); + + /** + * 获取所有的middleware + * @return array + */ + public function all(); + + /** + * 处理 request 并返回 response + * @param Request $request + * @return Response + */ + public function dispatch(Request $request); +} diff --git a/vendor/topthink/think-queue/src/queue/ShouldQueue.php b/thinkphp/library/think/http/middleware/MissingResponseException.php similarity index 64% rename from vendor/topthink/think-queue/src/queue/ShouldQueue.php rename to thinkphp/library/think/http/middleware/MissingResponseException.php index cb49c12d0..7cdcb33df 100644 --- a/vendor/topthink/think-queue/src/queue/ShouldQueue.php +++ b/thinkphp/library/think/http/middleware/MissingResponseException.php @@ -1,17 +1,15 @@ +// | Author: Slince // +---------------------------------------------------------------------- -namespace think\queue; +namespace think\http\middleware; -interface ShouldQueue -{ - -} +class MissingResponseException extends \InvalidArgumentException +{} diff --git a/thinkphp/library/think/http/tests/middleware/DispatcherTest.php b/thinkphp/library/think/http/tests/middleware/DispatcherTest.php new file mode 100644 index 000000000..298cbccc1 --- /dev/null +++ b/thinkphp/library/think/http/tests/middleware/DispatcherTest.php @@ -0,0 +1,69 @@ +add(function () { + }); + $this->assertCount(1, $dispatcher->all()); + $this->expectException(\InvalidArgumentException::class); + $dispatcher->add('foo middleware'); + } + + public function testAddAndInsert() + { + $middleware1 = function () {}; + $middleware2 = function () {}; + $dispatcher = new Dispatcher(); + $dispatcher->add($middleware1); + $dispatcher->insert($middleware2); + $this->assertSame([$middleware2, $middleware1], $dispatcher->all()); + } + + public function testDispatch() + { + $middleware1 = function ($request, $next) { + return $next($request); + }; + $middleware2 = function ($request) { + return Response::create('hello world'); + }; + $dispatcher = new Dispatcher([$middleware1, $middleware2]); + $response = $dispatcher->dispatch(new Request()); + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals('hello world', $response->getContent()); + } + + public function testDispatchWithoutResponse() + { + $middleware1 = function ($request, $next) { + return $next($request); + }; + $middleware2 = function ($request, $next) { + return $next($request); + }; + $dispatcher = new Dispatcher([$middleware1, $middleware2]); + $this->expectException(MissingResponseException::class); + $dispatcher->dispatch(new Request()); + } + + public function testDispatchWithBadResponse() + { + $middleware = function ($request, $next) { + return 'invalid response'; + }; + $dispatcher = new Dispatcher($middleware); + $this->expectException(\LogicException::class); + $dispatcher->dispatch(new Request()); + } +} diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php index 4036cc44a..3ec4cd1c4 100644 --- a/thinkphp/library/think/log/driver/File.php +++ b/thinkphp/library/think/log/driver/File.php @@ -11,8 +11,7 @@ namespace think\log\driver; -use think\App; -use think\Request; +use think\Container; /** * 本地化调试输出到文件 @@ -23,7 +22,7 @@ class File 'time_format' => ' c ', 'single' => false, 'file_size' => 2097152, - 'path' => LOG_PATH, + 'path' => '', 'apart_level' => [], ]; @@ -35,21 +34,26 @@ class File if (is_array($config)) { $this->config = array_merge($this->config, $config); } + + if (empty($this->config['path'])) { + $this->config['path'] = Container::get('app')->getRuntimePath() . 'log/'; + } } /** * 日志写入接口 * @access public - * @param array $log 日志信息 + * @param array $log 日志信息 * @return bool */ public function save(array $log = []) { if ($this->config['single']) { - $destination = $this->config['path'] . 'single.log'; + $name = is_string($single) ? $single : 'single'; + $destination = $this->config['path'] . $name . '.log'; } else { - $cli = IS_CLI ? '_cli' : ''; - $destination = $this->config['path'] . date('Ym') . DS . date('d') . $cli . '.log'; + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + $destination = $this->config['path'] . date('Ym') . '/' . date('d') . $cli . '.log'; } $path = dirname($destination); @@ -64,55 +68,63 @@ class File } $level .= '[ ' . $type . ' ] ' . $msg . "\r\n"; } + if (in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 if ($this->config['single']) { - $filename = $path . DS . $type . '.log'; + $filename = $path . '/' . $name . '_' . $type . '.log'; } else { - $filename = $path . DS . date('d') . '_' . $type . $cli . '.log'; + $filename = $path . '/' . date('d') . '_' . $type . $cli . '.log'; } + $this->write($level, $filename, true); } else { $info .= $level; } } + if ($info) { return $this->write($info, $destination); } + return true; } + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @return bool + */ protected function write($message, $destination, $apart = false) { - //检测日志文件大小,超过配置大小则备份日志文件重新生成 + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { try { - rename($destination, dirname($destination) . DS . time() . '-' . basename($destination)); + rename($destination, dirname($destination) . '/' . time() . '-' . basename($destination)); } catch (\Exception $e) { } + $this->writed[$destination] = false; } - if (empty($this->writed[$destination]) && !IS_CLI) { - if (App::$debug && !$apart) { + if (empty($this->writed[$destination]) && PHP_SAPI != 'cli') { + if (Container::get('app')->isDebug() && !$apart) { // 获取基本信息 - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); - } - - $runtime = round(microtime(true) - THINK_START_TIME, 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); - $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - - $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + $runtime = round(microtime(true) - Container::get('app')->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; } + $now = date($this->config['time_format']); - $ip = Request::instance()->ip(); + $ip = Container::get('request')->ip(); $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; $message = "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}\r\n" . $message; @@ -120,7 +132,7 @@ class File $this->writed[$destination] = true; } - if (IS_CLI) { + if (PHP_SAPI == 'cli') { $now = date($this->config['time_format']); $message = "[{$now}]" . $message; } diff --git a/thinkphp/library/think/log/driver/Socket.php b/thinkphp/library/think/log/driver/Socket.php index d30bba304..d3e717fe3 100644 --- a/thinkphp/library/think/log/driver/Socket.php +++ b/thinkphp/library/think/log/driver/Socket.php @@ -11,7 +11,7 @@ namespace think\log\driver; -use think\App; +use think\Container; /** * github: https://github.com/luofei614/SocketLog @@ -43,9 +43,9 @@ class Socket protected $allowForceClientIds = []; //配置强制推送且被授权的client_id /** - * 构造函数 - * @param array $config 缓存参数 + * 架构函数 * @access public + * @param array $config 缓存参数 */ public function __construct(array $config = []) { @@ -57,7 +57,7 @@ class Socket /** * 调试输出接口 * @access public - * @param array $log 日志信息 + * @param array $log 日志信息 * @return bool */ public function save(array $log = []) @@ -65,12 +65,14 @@ class Socket if (!$this->check()) { return false; } + $trace = []; - if (App::$debug) { - $runtime = round(microtime(true) - THINK_START_TIME, 10); + + if (Container::get('app')->isDebug()) { + $runtime = round(microtime(true) - Container::get('app')->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_use = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; $file_load = ' [文件加载:' . count(get_included_files()) . ']'; @@ -79,6 +81,7 @@ class Socket } else { $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); } + // 基本信息 $trace[] = [ 'type' => 'group', @@ -93,6 +96,7 @@ class Socket 'msg' => '[ ' . $type . ' ]', 'css' => isset($this->css[$type]) ? $this->css[$type] : '', ]; + foreach ($val as $msg) { if (!is_string($msg)) { $msg = var_export($msg, true); @@ -103,6 +107,7 @@ class Socket 'css' => '', ]; } + $trace[] = [ 'type' => 'groupEnd', 'msg' => '', @@ -116,11 +121,13 @@ class Socket 'msg' => '[ file ]', 'css' => '', ]; + $trace[] = [ 'type' => 'log', 'msg' => implode("\n", get_included_files()), 'css' => '', ]; + $trace[] = [ 'type' => 'groupEnd', 'msg' => '', @@ -135,6 +142,7 @@ class Socket ]; $tabid = $this->getClientArg('tabid'); + if (!$client_id = $this->getClientArg('client_id')) { $client_id = ''; } @@ -148,16 +156,18 @@ class Socket } else { $this->sendToClient($tabid, $client_id, $trace, ''); } + return true; } /** * 发送给指定客户端 + * @access protected * @author Zjmainstay - * @param $tabid - * @param $client_id - * @param $logs - * @param $force_client_id + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id */ protected function sendToClient($tabid, $client_id, $logs, $force_client_id) { @@ -167,20 +177,25 @@ class Socket 'logs' => $logs, 'force_client_id' => $force_client_id, ]; + $msg = @json_encode($logs); $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + $this->send($this->config['host'], $msg, $address); } protected function check() { $tabid = $this->getClientArg('tabid'); + //是否记录日志的检查 if (!$tabid && !$this->config['force_client_ids']) { return false; } + //用户认证 $allow_client_ids = $this->config['allow_client_ids']; + if (!empty($allow_client_ids)) { //通过数组交集得出授权强制推送的client_id $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); @@ -195,6 +210,7 @@ class Socket } else { $this->allowForceClientIds = $this->config['force_client_ids']; } + return true; } @@ -211,6 +227,7 @@ class Socket if (!isset($_SERVER[$key])) { return; } + if (empty($args)) { if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { $args = ['tabid' => null]; @@ -218,32 +235,39 @@ class Socket } parse_str($match[1], $args); } + if (isset($args[$name])) { return $args[$name]; } + return; } /** - * @param string $host - $host of socket server - * @param string $message - 发送的消息 - * @param string $address - 地址 + * @access protected + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 * @return bool */ protected function send($host, $message = '', $address = '/') { $url = 'http://' . $host . ':' . $this->port . $address; $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $message); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $headers = [ "Content-Type: application/json;charset=UTF-8", ]; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + return curl_exec($ch); } diff --git a/thinkphp/library/think/log/driver/Test.php b/thinkphp/library/think/log/driver/Test.php index 7f663384c..e67f74727 100644 --- a/thinkphp/library/think/log/driver/Test.php +++ b/thinkphp/library/think/log/driver/Test.php @@ -19,7 +19,7 @@ class Test /** * 日志写入接口 * @access public - * @param array $log 日志信息 + * @param array $log 日志信息 * @return bool */ public function save(array $log = []) diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php index 37d58ae10..3a9b60f59 100644 --- a/thinkphp/library/think/model/Collection.php +++ b/thinkphp/library/think/model/Collection.php @@ -18,36 +18,35 @@ class Collection extends BaseCollection { /** * 返回数组中指定的一列 - * @param string $column_key - * @param string|null $index_key + * @access public + * @param string $column_key + * @param string|null $index_key * @return array */ public function column($column_key, $index_key = null) { - if (function_exists('array_column')) { - return array_column($this->toArray(), $column_key, $index_key); - } - return parent::column($column_key, $index_key); + return array_column($this->toArray(), $column_key, $index_key); } /** * 延迟预载入关联查询 * @access public - * @param mixed $relation 关联 + * @param mixed $relation 关联 * @return $this */ public function load($relation) { $item = current($this->items); $item->eagerlyResultSet($this->items, $relation); + return $this; } /** * 设置需要隐藏的输出属性 * @access public - * @param array $hidden 属性列表 - * @param bool $override 是否覆盖 + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 * @return $this */ public function hidden($hidden = [], $override = false) @@ -56,13 +55,15 @@ class Collection extends BaseCollection /** @var Model $model */ $model->hidden($hidden, $override); }); + return $this; } /** * 设置需要输出的属性 - * @param array $visible - * @param bool $override 是否覆盖 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 * @return $this */ public function visible($visible = [], $override = false) @@ -71,14 +72,15 @@ class Collection extends BaseCollection /** @var Model $model */ $model->visible($visible, $override); }); + return $this; } /** * 设置需要追加的输出属性 * @access public - * @param array $append 属性列表 - * @param bool $override 是否覆盖 + * @param array $append 属性列表 + * @param bool $override 是否覆盖 * @return $this */ public function append($append = [], $override = false) @@ -87,6 +89,7 @@ class Collection extends BaseCollection /** @var Model $model */ $model && $model->append($append, $override); }); + return $this; } diff --git a/thinkphp/library/think/model/Merge.php b/thinkphp/library/think/model/Merge.php deleted file mode 100644 index 4a9da81e4..000000000 --- a/thinkphp/library/think/model/Merge.php +++ /dev/null @@ -1,322 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\model; - -use think\Db; -use think\db\Query; -use think\Model; - -class Merge extends Model -{ - - protected $relationModel = []; // HAS ONE 关联的模型列表 - protected $fk = ''; // 外键名 默认为主表名_id - protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) - - /** - * 构造函数 - * @access public - * @param array|object $data 数据 - */ - public function __construct($data = []) - { - parent::__construct($data); - - // 设置默认外键名 仅支持单一外键 - if (empty($this->fk)) { - $this->fk = strtolower($this->name) . '_id'; - } - } - - /** - * 查找单条记录 - * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param string|array $with 关联预查询 - * @param bool $cache 是否缓存 - * @return \think\Model - */ - public static function get($data = null, $with = [], $cache = false) - { - $query = self::parseQuery($data, $with, $cache); - $query = self::attachQuery($query); - return $query->find($data); - } - - /** - * 附加查询表达式 - * @access protected - * @param \think\db\Query $query 查询对象 - * @return \think\db\Query - */ - protected static function attachQuery($query) - { - $class = new static(); - $master = $class->name; - $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); - $query->alias($master)->field($fields); - - foreach ($class->relationModel as $key => $model) { - $name = is_int($key) ? $model : $key; - $table = is_int($key) ? $query->getTable($name) : $model; - $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); - $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); - $query->field($fields); - } - return $query; - } - - /** - * 获取关联模型的字段 并解决混淆 - * @access protected - * @param \think\db\Query $query 查询对象 - * @param string $name 模型名称 - * @param string $table 关联表名称 - * @param array $map 字段映射 - * @param array $fields 查询字段 - * @return array - */ - protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) - { - // 获取模型的字段信息 - $fields = $fields ?: $query->getTableInfo($table, 'fields'); - $array = []; - foreach ($fields as $field) { - if ($key = array_search($name . '.' . $field, $map)) { - // 需要处理映射字段 - $array[] = $name . '.' . $field . ' AS ' . $key; - } else { - $array[] = $field; - } - } - return $array; - } - - /** - * 查找所有记录 - * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache - * @return array|false|string - */ - public static function all($data = null, $with = [], $cache = false) - { - $query = self::parseQuery($data, $with, $cache); - $query = self::attachQuery($query); - return $query->select($data); - } - - /** - * 处理写入的模型数据 - * @access public - * @param string $model 模型名称 - * @param array $data 数据 - * @return array - */ - protected function parseData($model, $data) - { - $item = []; - foreach ($data as $key => $val) { - if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { - list($name, $key) = explode('.', $this->mapFields[$key]); - if ($model == $name) { - $item[$key] = $val; - } - } else { - $item[$key] = $val; - } - } - return $item; - } - - /** - * 保存模型数据 以及关联数据 - * @access public - * @param mixed $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return false|int - * @throws \Exception - */ - public function save($data = [], $where = [], $sequence = null) - { - if (!empty($data)) { - // 数据自动验证 - if (!$this->validateData($data)) { - return false; - } - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - if (!empty($where)) { - $this->isUpdate = true; - } - } - - // 数据自动完成 - $this->autoCompleteData($this->auto); - - // 自动写入更新时间 - if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { - $this->setAttr($this->updateTime, null); - } - - // 事件回调 - if (false === $this->trigger('before_write', $this)) { - return false; - } - - $db = $this->db(); - $db->startTrans(); - $pk = $this->getPk(); - try { - if ($this->isUpdate) { - // 自动写入 - $this->autoCompleteData($this->update); - - if (false === $this->trigger('before_update', $this)) { - return false; - } - - if (empty($where) && !empty($this->updateWhere)) { - $where = $this->updateWhere; - } - - // 获取有更新的数据 - $data = $this->getChangedData(); - // 保留主键数据 - foreach ($this->data as $key => $val) { - if ($this->isPk($key)) { - $data[$key] = $val; - } - } - // 处理模型数据 - $data = $this->parseData($this->name, $data); - if (is_string($pk) && isset($data[$pk])) { - if (!isset($where[$pk])) { - unset($where); - $where[$pk] = $data[$pk]; - } - unset($data[$pk]); - } - // 写入主表数据 - $result = $db->strict(false)->where($where)->update($data); - - // 写入附表数据 - foreach ($this->relationModel as $key => $model) { - $name = is_int($key) ? $model : $key; - $table = is_int($key) ? $db->getTable($model) : $model; - // 处理关联模型数据 - $data = $this->parseData($name, $data); - if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { - $result = 1; - } - } - - // 新增回调 - $this->trigger('after_update', $this); - } else { - // 自动写入 - $this->autoCompleteData($this->insert); - - // 自动写入创建时间 - if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { - $this->setAttr($this->createTime, null); - } - - if (false === $this->trigger('before_insert', $this)) { - return false; - } - - // 处理模型数据 - $data = $this->parseData($this->name, $this->data); - // 写入主表数据 - $result = $db->name($this->name)->strict(false)->insert($data); - if ($result) { - $insertId = $db->getLastInsID($sequence); - // 写入外键数据 - if ($insertId) { - if (is_string($pk)) { - $this->data[$pk] = $insertId; - } - $this->data[$this->fk] = $insertId; - } - - // 写入附表数据 - $source = $this->data; - if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { - unset($source[$pk]); - } - foreach ($this->relationModel as $key => $model) { - $name = is_int($key) ? $model : $key; - $table = is_int($key) ? $db->getTable($model) : $model; - // 处理关联模型数据 - $data = $this->parseData($name, $source); - Db::table($table)->strict(false)->insert($data); - } - } - // 标记为更新 - $this->isUpdate = true; - // 新增回调 - $this->trigger('after_insert', $this); - } - $db->commit(); - // 写入回调 - $this->trigger('after_write', $this); - - $this->origin = $this->data; - return $result; - } catch (\Exception $e) { - $db->rollback(); - throw $e; - } - } - - /** - * 删除当前的记录 并删除关联数据 - * @access public - * @return int - * @throws \Exception - */ - public function delete() - { - if (false === $this->trigger('before_delete', $this)) { - return false; - } - - $db = $this->db(); - $db->startTrans(); - try { - $result = $db->delete($this->data); - if ($result) { - // 获取主键数据 - $pk = $this->data[$this->getPk()]; - - // 删除关联数据 - foreach ($this->relationModel as $key => $model) { - $table = is_int($key) ? $db->getTable($model) : $model; - $query = new Query; - $query->table($table)->where($this->fk, $pk)->delete(); - } - } - $this->trigger('after_delete', $this); - $db->commit(); - return $result; - } catch (\Exception $e) { - $db->rollback(); - throw $e; - } - } - -} diff --git a/thinkphp/library/think/model/Pivot.php b/thinkphp/library/think/model/Pivot.php index 13525cdd7..a3a395e3f 100644 --- a/thinkphp/library/think/model/Pivot.php +++ b/thinkphp/library/think/model/Pivot.php @@ -24,9 +24,9 @@ class Pivot extends Model /** * 架构函数 * @access public - * @param array|object $data 数据 - * @param Model $parent 上级模型 - * @param string $table 中间数据表名 + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 */ public function __construct($data = [], Model $parent = null, $table = '') { diff --git a/thinkphp/library/think/model/Relation.php b/thinkphp/library/think/model/Relation.php index b67bbf58a..315fb28da 100644 --- a/thinkphp/library/think/model/Relation.php +++ b/thinkphp/library/think/model/Relation.php @@ -49,7 +49,7 @@ abstract class Relation } /** - * 获取当前的关联模型类 + * 获取当前的关联模型类的实例 * @access public * @return Model */ @@ -58,16 +58,6 @@ abstract class Relation return $this->query->getModel(); } - /** - * 获取关联的查询对象 - * @access public - * @return Query - */ - public function getQuery() - { - return $this->query; - } - /** * 设置当前关联为自关联 * @access public @@ -93,7 +83,7 @@ abstract class Relation /** * 封装关联数据集 * @access public - * @param array $resultSet 数据集 + * @param array $resultSet 数据集 * @return mixed */ protected function resultSetBuild($resultSet) @@ -127,6 +117,16 @@ abstract class Relation return $fields; } + protected function getQueryWhere(&$where, $relation) + { + foreach ($where as $key => $val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } + } + } + /** * 执行基础查询(仅执行一次) * @access protected @@ -141,13 +141,9 @@ abstract class Relation // 执行基础查询 $this->baseQuery(); - $result = call_user_func_array([$this->query, $method], $args); - if ($result instanceof Query) { - return $this; - } else { - $this->baseQuery = false; - return $result; - } + $result = call_user_func_array([$this->query->getModel(), $method], $args); + + return $result === $this->query ? $this : $result; } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } diff --git a/thinkphp/library/think/model/concern/Attribute.php b/thinkphp/library/think/model/concern/Attribute.php new file mode 100644 index 000000000..dc104481c --- /dev/null +++ b/thinkphp/library/think/model/concern/Attribute.php @@ -0,0 +1,556 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use InvalidArgumentException; +use think\Exception; +use think\Loader; +use think\model\Relation; + +trait Attribute +{ + /** + * 数据表主键 复合主键使用数组定义 + * @var string|array + */ + protected $pk = 'id'; + + /** + * 数据表字段信息 留空则自动获取 + * @var array + */ + protected $field = []; + + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + + /** + * 数据表废弃字段 + * @var array + */ + protected $disuse = []; + + /** + * 数据表只读字段 + * @var array + */ + protected $readonly = []; + + /** + * 数据表字段类型 + * @var array + */ + protected $type = []; + + /** + * 当前模型数据 + * @var array + */ + private $data = []; + + /** + * 原始数据 + * @var array + */ + private $origin = []; + + /** + * 获取模型对象的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk($key) + { + $pk = $this->getPk(); + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + + return false; + } + + /** + * 设置允许写入的字段 + * @access public + * @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段 + * @return $this + */ + public function allowField($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + + $this->field = $field; + + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param array|string $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + + $this->readonly = $field; + + return $this; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据或者属性名 + * @param mixed $value 值 + * @return $this + */ + public function data($data, $value = null) + { + if (is_string($data)) { + $this->data[$data] = $value; + } else { + // 清空数据 + $this->data = []; + + if (is_object($data)) { + $data = get_object_vars($data); + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + } + + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } elseif (is_array($value)) { + foreach ($value as $name) { + if (isset($data[$name])) { + $this->data[$name] = $data[$name]; + } + } + } else { + $this->data = $data; + } + } + + return $this; + } + + /** + * 批量设置数据对象值 + * @access public + * @param mixed $data 数据 + * @param bool $set 是否需要进行数据处理 + * @return $this + */ + public function appendData($data, $set = false) + { + if ($set) { + // 进行数据处理 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } else { + if (is_object($data)) { + $data = get_object_vars($data); + } + + $this->data = array_merge($this->data, $data); + } + + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回null + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + */ + public function getOrigin($name = null) + { + if (is_null($name)) { + return $this->origin; + } else { + return array_key_exists($name, $this->origin) ? $this->origin[$name] : null; + } + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData($name = null) + { + if (is_null($name)) { + return $this->data; + } elseif (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + if ($this->force) { + $data = $this->data; + } else { + $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + + return is_object($a) || $a != $b ? 1 : 0; + }); + } + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + + /** + * 修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setAttr($name, $value, $data = []) + { + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp($name); + } else { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + + return $this; + } + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $auto; + + return $this; + } + + /** + * 自动写入时间戳 + * @access protected + * @param string $name 时间戳字段 + * @return mixed + */ + protected function autoWriteTimestamp($name) + { + if (isset($this->type[$name])) { + $type = $this->type[$name]; + + if (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'datetime': + case 'date': + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(time(), $format); + break; + case 'timestamp': + case 'integer': + default: + $value = time(); + break; + } + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ])) { + $value = $this->formatDateTime(time(), $this->dateFormat); + } else { + $value = $this->formatDateTime(time(), $this->dateFormat, true); + } + + return $value; + } + + /** + * 数据写入 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $format = !empty($param) ? $param : $this->dateFormat; + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($value, $format); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + } + + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @param array $item 数据 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr($name, &$item = null) + { + try { + $notFound = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $notFound = true; + $value = null; + } + + // 检测属性获取器 + $method = 'get' . Loader::parseName($name, 1) . 'Attr'; + + if (method_exists($this, $method)) { + if ($notFound && $relation = $this->isRelationAttr($name)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + } + + $value = $this->$method($value, $this->data); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$name]); + } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ])) { + $value = $this->formatDateTime(strtotime($value), $this->dateFormat); + } else { + $value = $this->formatDateTime($value, $this->dateFormat); + } + } elseif ($notFound) { + $relation = $this->isRelationAttr($name); + + if ($relation) { + $modelRelation = $this->$relation(); + if ($modelRelation instanceof Relation) { + $value = $this->getRelationData($modelRelation); + + if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + return false; + } + + // 保存关联对象值 + $this->relation[$name] = $value; + + return $value; + } + } + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + return $value; + } + + /** + * 数据读取 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($value, $format); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(strtotime($value), $format); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + $value = unserialize($value); + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + + return $value; + } + +} diff --git a/thinkphp/library/think/model/concern/Conversion.php b/thinkphp/library/think/model/concern/Conversion.php new file mode 100644 index 000000000..697e495bf --- /dev/null +++ b/thinkphp/library/think/model/concern/Conversion.php @@ -0,0 +1,276 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Collection; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Collection as ModelCollection; + +/** + * 模型数据转换处理 + */ +trait Conversion +{ + /** + * 数据输出显示的属性 + * @var array + */ + protected $visible = []; + + /** + * 数据输出隐藏的属性 + * @var array + */ + protected $hidden = []; + + /** + * 数据输出需要追加的属性 + * @var array + */ + protected $append = []; + + /** + * 数据集对象名 + * @var string + */ + protected $resultSetType; + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->append = $override ? $append : array_merge($this->append, $append); + + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $attr 关联属性 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($attr, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + + $relation = Loader::parseName($attr, 1, false); + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $this->data[$key] = $model->$attr; + } + } + } + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->visible = $override ? $visible : array_merge($this->visible, $visible); + + return $this; + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray() + { + $item = []; + $visible = []; + $hidden = []; + + // 合并关联数据 + $data = array_merge($this->data, $this->relation); + + // 过滤属性 + if (!empty($this->visible)) { + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($data, array_flip($array)); + } elseif (!empty($this->hidden)) { + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($data, array_flip($array)); + } + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + if (isset($visible[$key])) { + $val->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $val->hidden($hidden[$key]); + } + // 关联模型对象 + $item[$key] = $val->toArray(); + } else { + // 模型属性 + $item[$key] = $this->getAttr($key); + } + } + + // 追加属性(必须定义获取器) + if (!empty($this->append)) { + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $value = $this->getAttr($name, $item); + if (false !== $value) { + $item[$name] = $value; + } + } + } + } + + return $item; + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 + * @return Collection + */ + public function toCollection($collection, $resultSetType = null) + { + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); + } else { + $collection = new ModelCollection($collection); + } + + return $collection; + } + + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + + if ($visible) { + $array[] = $key; + } + + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + + return $array; + } +} diff --git a/thinkphp/library/think/model/concern/ModelEvent.php b/thinkphp/library/think/model/concern/ModelEvent.php new file mode 100644 index 000000000..1f5bdafcd --- /dev/null +++ b/thinkphp/library/think/model/concern/ModelEvent.php @@ -0,0 +1,157 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Container; + +/** + * 模型事件处理 + */ +trait ModelEvent +{ + /** + * 模型回调 + * @var array + */ + private static $event = []; + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 + * @return void + */ + public static function event($event, $callback, $override = false) + { + $class = static::class; + + if ($override) { + self::$event[$class][$event] = []; + } + + self::$event[$class][$event][] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @return bool + */ + protected function trigger($event) + { + $class = static::class; + + if (isset(self::$event[$class][$event])) { + foreach (self::$event[$class][$event] as $callback) { + $result = Container::getInstance()->invoke($callback, [$this]); + + if (false === $result) { + return false; + } + } + } + + return true; + } + + /** + * 模型before_insert事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + /** + * 模型after_insert事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + /** + * 模型before_update事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + /** + * 模型after_update事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + /** + * 模型before_write事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + /** + * 模型after_write事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + /** + * 模型before_delete事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + /** + * 模型after_delete事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + +} diff --git a/thinkphp/library/think/model/concern/RelationShip.php b/thinkphp/library/think/model/concern/RelationShip.php new file mode 100644 index 000000000..516119589 --- /dev/null +++ b/thinkphp/library/think/model/concern/RelationShip.php @@ -0,0 +1,643 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Collection; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; + +/** + * 模型关联处理 + */ +trait RelationShip +{ + /** + * 父关联模型对象 + * @var object + */ + private $parent; + + /** + * 模型关联数据 + * @var array + */ + private $relation = []; + + /** + * 关联写入定义信息 + * @var array + */ + private $together; + + /** + * 关联自动写入信息 + * @var array + */ + protected $relationWrite; + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + return; + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setRelation($name, $value, $data = []) + { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } + + $this->relation[$name] = $value; + + return $this; + } + + /** + * 关联数据写入 + * @access public + * @param array|string $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + + $this->together = $relation; + + $this->checkAutoRelationWrite(); + + return $this; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public static function has($relation, $operator = '>=', $count = 1, $id = '*') + { + $relation = (new static())->$relation(); + + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + + return $relation->has($operator, $count, $id); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public static function hasWhere($relation, $where = [], $fields = '*') + { + return (new static())->$relation()->hasWhere($where, $fields); + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param string|array $relations 关联名 + * @return $this + */ + public function relationQuery($relations) + { + if (is_string($relations)) { + $relations = explode(',', $relations); + } + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $method = Loader::parseName($relation, 1, false); + + $this->relation[$relation] = $this->$method()->getRelation($subRelation, $closure); + } + + return $this; + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $relation = Loader::parseName($relation, 1, false); + + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @return Model + */ + public function eagerlyResult(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $relation = Loader::parseName($relation, 1, false); + + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param array $relations 关联名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @return void + */ + public function relationCount(&$result, $relations, $aggregate = 'sum', $field = '*') + { + foreach ($relations as $key => $relation) { + $closure = false; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + + $relation = Loader::parseName($relation, 1, false); + $count = $this->$relation()->relationCount($result, $closure, $aggregate, $field); + + if (!isset($name)) { + $name = Loader::parseName($relation) . '_' . $aggregate; + } + + $result->setAttr($name, $count); + } + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasOne + */ + public function hasOne($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasOne($this, $model, $foreignKey, $localKey); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @return BelongsTo + */ + public function belongsTo($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName()); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + return new BelongsTo($this, $model, $foreignKey, $localKey, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasMany + */ + public function hasMany($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @return HasManyThrough + */ + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Loader::parseName(basename(str_replace('\\', '/', $model))); + $table = $table ?: Loader::parseName($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + $type = $type ?: get_class($this); + + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + + $type = $type ?: get_class($this); + + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (false === strpos($model, '\\')) { + $path = explode('\\', static::class); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 获取模型的默认外键名 + * @access protected + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + + return Loader::parseName($name) . '_id'; + } + + /** + * 检查属性是否为关联属性 如果是则返回关联方法名 + * @access protected + * @param string $attr 关联属性名 + * @return string|false + */ + protected function isRelationAttr($attr) + { + $relation = Loader::parseName($attr, 1, false); + + if (method_exists($this, $relation)) { + return $relation; + } + + return false; + } + + /** + * 智能获取关联模型数据 + * @access protected + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) { + $value = $this->parent; + } else { + // 获取关联数据 + $value = $modelRelation->getRelation(); + } + + return $value; + } + + /** + * 关联数据自动写入检查 + * @access protected + * @return void + */ + protected function checkAutoRelationWrite() + { + foreach ($this->together as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $this->relationWrite[$key] = []; + // 绑定关联属性 + foreach ((array) $name as $val) { + if (isset($this->data[$val])) { + $this->relationWrite[$key][$val] = $this->data[$val]; + } + } + } else { + // 直接传入关联数据 + $this->relationWrite[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $this->relationWrite[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $this->relationWrite[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + /** + * 自动关联数据更新(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationUpdate() + { + foreach ($this->relationWrite as $name => $val) { + if ($val instanceof Model) { + $val->isUpdate()->save(); + } else { + $model = $this->getRelation($name); + if ($model instanceof Model) { + $model->isUpdate()->save($val); + } + } + } + } + + /** + * 自动关联数据写入(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationInsert() + { + foreach ($this->relationWrite as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + + /** + * 自动关联数据删除(支持一对一及一对多关联) + * @access protected + * @return void + */ + protected function autoRelationDelete() + { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } +} diff --git a/thinkphp/library/traits/model/SoftDelete.php b/thinkphp/library/think/model/concern/SoftDelete.php similarity index 60% rename from thinkphp/library/traits/model/SoftDelete.php rename to thinkphp/library/think/model/concern/SoftDelete.php index 4c8a6b82c..16b75b728 100644 --- a/thinkphp/library/traits/model/SoftDelete.php +++ b/thinkphp/library/think/model/concern/SoftDelete.php @@ -1,15 +1,15 @@ getDeleteTimeField(); - if ($field && !empty($this->data[$field])) { + if ($field && !empty($this->getOrigin($field))) { return true; } + return false; } /** - * 查询包含软删除的数据 + * 查询软删除数据 * @access public * @return Query */ public static function withTrashed() { - return (new static )->getQuery(); + $model = new static(); + + return $model->db(false); } /** @@ -46,16 +49,18 @@ trait SoftDelete $field = $model->getDeleteTimeField(true); if ($field) { - return $model->getQuery()->useSoftDelete($field, ['not null', '']); + return $model + ->db(false) + ->useSoftDelete($field, ['not null', '']); } else { - return $model->getQuery(); + return $model->db(false); } } /** * 删除当前的记录 * @access public - * @param bool $force 是否强制删除 + * @param bool $force 是否强制删除 * @return integer */ public function delete($force = false) @@ -65,33 +70,29 @@ trait SoftDelete } $name = $this->getDeleteTimeField(); + if ($name && !$force) { // 软删除 - $this->data[$name] = $this->autoWriteTimestamp($name); - $result = $this->isUpdate()->save(); + $this->data($name, $this->autoWriteTimestamp($name)); + + $result = $this->isUpdate()->save(); } else { - // 强制删除当前模型数据 - $result = $this->getQuery()->where($this->getWhere())->delete(); + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->db(false)->where($where)->delete(); } // 关联删除 if (!empty($this->relationWrite)) { - foreach ($this->relationWrite as $key => $name) { - $name = is_numeric($key) ? $name : $key; - $result = $this->getRelation($name); - if ($result instanceof Model) { - $result->delete(); - } elseif ($result instanceof Collection || is_array($result)) { - foreach ($result as $model) { - $model->delete(); - } - } - } + $this->autoRelationDelete(); } $this->trigger('after_delete', $this); - // 清空原始数据 + // 清空数据 + $this->data = []; $this->origin = []; return $result; @@ -100,28 +101,29 @@ trait SoftDelete /** * 删除记录 * @access public - * @param mixed $data 主键列表(支持闭包查询条件) - * @param bool $force 是否强制删除 + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 * @return integer 成功删除的记录数 */ public static function destroy($data, $force = false) { - if (is_null($data)) { - return 0; - } - // 包含软删除数据 $query = self::withTrashed(); + if (is_array($data) && key($data) !== 0) { $query->where($data); $data = null; } elseif ($data instanceof \Closure) { call_user_func_array($data, [ & $query]); $data = null; + } elseif (is_null($data)) { + return 0; } - $count = 0; - if ($resultSet = $query->select($data)) { + $resultSet = $query->select($data); + $count = 0; + + if ($resultSet) { foreach ($resultSet as $data) { $result = $data->delete($force); $count += $result; @@ -134,52 +136,38 @@ trait SoftDelete /** * 恢复被软删除的记录 * @access public - * @param array $where 更新条件 + * @param array $where 更新条件 * @return integer */ public function restore($where = []) { + $name = $this->getDeleteTimeField(); + if (empty($where)) { $pk = $this->getPk(); - $where[$pk] = $this->getData($pk); + $where[$pk] = [$pk, '=', $this->getData($pk)]; } - $name = $this->getDeleteTimeField(); - if ($name) { // 恢复删除 - return $this->getQuery() - ->useSoftDelete($name, ['not null', '']) + return $this->db(false) ->where($where) + ->useSoftDelete($name, ['not null', '']) ->update([$name => null]); } else { return 0; } } - /** - * 查询默认不包含软删除数据 - * @access protected - * @param Query $query 查询对象 - * @return Query - */ - protected function base($query) - { - $field = $this->getDeleteTimeField(true); - return $field ? $query->useSoftDelete($field) : $query; - } - /** * 获取软删除字段 - * @access public - * @param bool $read 是否查询操作(写操作的时候会自动去掉表别名) - * @return string + * @access protected + * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 + * @return string|false */ protected function getDeleteTimeField($read = false) { - $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? - $this->deleteTime : - 'delete_time'; + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; if (false === $field) { return false; diff --git a/thinkphp/library/think/model/concern/TimeStamp.php b/thinkphp/library/think/model/concern/TimeStamp.php new file mode 100644 index 000000000..923b7d453 --- /dev/null +++ b/thinkphp/library/think/model/concern/TimeStamp.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +/** + * 自动时间戳 + */ +trait TimeStamp +{ + /** + * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + * @var bool|string + */ + protected $autoWriteTimestamp; + + /** + * 创建时间字段 false表示关闭 + * @var false|string + */ + protected $createTime = 'create_time'; + + /** + * 更新时间字段 false表示关闭 + * @var false|string + */ + protected $updateTime = 'update_time'; + + /** + * 时间字段显示格式 + * @var string + */ + protected $dateFormat; + + /** + * 时间日期字段格式化处理 + * @access protected + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($time, $format, $timestamp = false) + { + if (false !== strpos($format, '\\')) { + $time = new $format($time); + } elseif (!$timestamp && false !== $format) { + $time = date($format, $time); + } + + return $time; + } + + /** + * 检查时间字段写入 + * @access protected + * @return void + */ + protected function checkTimeStampWrite() + { + // 自动写入创建时间和更新时间 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php index dfd611471..bcbab2ac1 100644 --- a/thinkphp/library/think/model/relation/BelongsTo.php +++ b/thinkphp/library/think/model/relation/BelongsTo.php @@ -11,46 +11,46 @@ namespace think\model\relation; -use think\db\Query; use think\Loader; use think\Model; class BelongsTo extends OneToOne { /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param string $joinType JOIN类型 - * @param string $relation 关联名 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) + public function __construct(Model $parent, $model, $foreignKey, $localKey, $relation = null) { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->joinType = $joinType; + $this->joinType = 'INNER'; $this->query = (new $model)->db(); $this->relation = $relation; } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 * @access public - * @return array|false|\PDOStatement|string|Model + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { - $foreignKey = $this->foreignKey; if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } + + $foreignKey = $this->foreignKey; + $relationModel = $this->query ->where($this->localKey, $this->parent->$foreignKey) ->relation($subRelation) @@ -66,9 +66,9 @@ class BelongsTo extends OneToOne /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*') @@ -79,8 +79,8 @@ class BelongsTo extends OneToOne /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -90,16 +90,13 @@ class BelongsTo extends OneToOne $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } + $this->getQueryWhere($where, $relation); } + $fields = $this->getRelationQueryFields($fields, $model); - return $this->parent->db()->alias($model) + return $this->parent->db() + ->alias($model) ->field($fields) ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) ->where($where); @@ -107,11 +104,11 @@ class BelongsTo extends OneToOne /** * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) @@ -128,14 +125,13 @@ class BelongsTo extends OneToOne } if (!empty($range)) { - $data = $this->eagerlyWhere($this->query, [ - $localKey => [ - 'in', - $range, - ], + $data = $this->eagerlyWhere([ + [$localKey, 'in', $range], ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { // 关联模型 @@ -149,7 +145,7 @@ class BelongsTo extends OneToOne if (!empty($this->bindAttr)) { // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); + $this->bindAttr($relationModel, $result); } else { // 设置关联属性 $result->setRelation($attr, $relationModel); @@ -160,18 +156,22 @@ class BelongsTo extends OneToOne /** * 预载入关联查询(数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; - $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $relation, $subRelation, $closure); + // 关联模型 if (!isset($data[$result->$foreignKey])) { $relationModel = null; @@ -180,9 +180,10 @@ class BelongsTo extends OneToOne $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + if (!empty($this->bindAttr)) { // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); + $this->bindAttr($relationModel, $result); } else { // 设置关联属性 $result->setRelation(Loader::parseName($relation), $relationModel); @@ -192,7 +193,7 @@ class BelongsTo extends OneToOne /** * 添加关联数据 * @access public - * @param Model $model 关联模型对象 + * @param Model $model 关联模型对象 * @return Model */ public function associate($model) diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php index 2f451a72c..203c198b5 100644 --- a/thinkphp/library/think/model/relation/BelongsToMany.php +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -18,7 +18,6 @@ use think\Loader; use think\Model; use think\model\Pivot; use think\model\Relation; -use think\Paginator; class BelongsToMany extends Relation { @@ -30,13 +29,13 @@ class BelongsToMany extends Relation protected $pivot; /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型外键 - * @param string $localKey 当前模型关联键 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 */ public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) { @@ -44,23 +43,22 @@ class BelongsToMany extends Relation $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; + if (false !== strpos($table, '\\')) { $this->pivotName = $table; $this->middle = basename(str_replace('\\', '/', $table)); } else { $this->middle = $table; } + $this->query = (new $model)->db(); $this->pivot = $this->newPivot(); - - if ('think\model\Pivot' == get_class($this->pivot)) { - $this->pivot->name($this->middle); - } } /** * 设置中间表模型 - * @param $pivot + * @access public + * @param $pivot * @return $this */ public function pivot($pivot) @@ -71,7 +69,8 @@ class BelongsToMany extends Relation /** * 实例化中间表模型 - * @param $data + * @access public + * @param $data * @return Pivot * @throws Exception */ @@ -88,85 +87,101 @@ class BelongsToMany extends Relation /** * 合成中间表模型 - * @param array|Collection|Paginator $models + * @access protected + * @param array|Collection|Paginator $models */ protected function hydratePivot($models) { foreach ($models as $model) { $pivot = []; + foreach ($model->getData() as $key => $val) { if (strpos($key, '__')) { list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { $pivot[$attr] = $val; unset($model->$key); } } } + $model->setRelation('pivot', $this->newPivot($pivot)); } } /** * 创建关联查询Query对象 + * @access protected * @return Query */ protected function buildQuery() { $foreignKey = $this->foreignKey; $localKey = $this->localKey; - $pk = $this->parent->getPk(); + // 关联查询 - $condition['pivot.' . $localKey] = $this->parent->$pk; + $pk = $this->parent->getPk(); + + $condition[] = ['pivot.' . $localKey, '=', $this->parent->$pk]; + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Collection */ public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } + $result = $this->buildQuery()->relation($subRelation)->select(); $this->hydratePivot($result); + return $result; } /** * 重载select方法 - * @param null $data - * @return false|\PDOStatement|string|Collection + * @access public + * @param mixed $data + * @return Collection */ public function select($data = null) { $result = $this->buildQuery()->select($data); $this->hydratePivot($result); + return $result; } /** * 重载paginate方法 - * @param null $listRows - * @param bool $simple - * @param array $config + * @access public + * @param null $listRows + * @param bool $simple + * @param array $config * @return Paginator */ public function paginate($listRows = null, $simple = false, $config = []) { $result = $this->buildQuery()->paginate($listRows, $simple, $config); $this->hydratePivot($result); + return $result; } /** * 重载find方法 - * @param null $data - * @return array|false|\PDOStatement|string|Model + * @access public + * @param mixed $data + * @return Model */ public function find($data = null) { @@ -174,14 +189,15 @@ class BelongsToMany extends Relation if ($result) { $this->hydratePivot([$result]); } + return $result; } /** * 查找多条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model + * @param array|string|Query|\Closure $data + * @return Collection */ public function selectOrFail($data = null) { @@ -191,8 +207,8 @@ class BelongsToMany extends Relation /** * 查找单条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model + * @param array|string|Query|\Closure $data + * @return Model */ public function findOrFail($data = null) { @@ -202,10 +218,10 @@ class BelongsToMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -216,8 +232,8 @@ class BelongsToMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query * @throws Exception */ @@ -228,25 +244,25 @@ class BelongsToMany extends Relation /** * 设置中间表的查询条件 - * @param $field - * @param null $op - * @param null $condition + * @access public + * @param string $field + * @param string $op + * @param mixed $condition * @return $this */ public function wherePivot($field, $op = null, $condition = null) { - $field = 'pivot.' . $field; - $this->query->where($field, $op, $condition); + $this->query->where('pivot.' . $field, $op, $condition); return $this; } /** * 预载入关联查询(数据集) * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -266,13 +282,12 @@ class BelongsToMany extends Relation if (!empty($range)) { // 查询关联数据 $data = $this->eagerlyManyToMany([ - 'pivot.' . $localKey => [ - 'in', - $range, - ], + ['pivot.' . $localKey, 'in', $range], ], $relation, $subRelation); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { @@ -287,24 +302,28 @@ class BelongsToMany extends Relation /** * 预载入关联查询(单个数据) * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); + if (isset($result->$pk)) { $pk = $result->$pk; // 查询管理数据 - $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $relation, $subRelation); // 关联数据封装 if (!isset($data[$pk])) { $data[$pk] = []; } + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); } } @@ -312,49 +331,58 @@ class BelongsToMany extends Relation /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*') { $pk = $result->getPk(); $count = 0; + if (isset($result->$pk)) { $pk = $result->$pk; - $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); } + return $count; } /** * 获取关联统计子查询 * @access public - * @param \Closure $closure 闭包 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return string */ - public function getRelationCountQuery($closure) + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*') { return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ - 'pivot.' . $this->localKey => [ - 'exp', - '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + [ + 'pivot.' . $this->localKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), ], - ])->fetchSql()->count(); + ])->fetchSql()->$aggregate($field); } /** * 多对多 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 * @return array */ protected function eagerlyManyToMany($where, $relation, $subRelation = '') { // 预载入关联查询 支持嵌套预载入 - $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->select(); // 组装模型数据 $data = []; @@ -369,18 +397,21 @@ class BelongsToMany extends Relation } } } + $set->setRelation('pivot', $this->newPivot($pivot)); + $data[$pivot[$this->localKey]][] = $set; } + return $data; } /** * BELONGS TO MANY 关联查询 - * @access public - * @param string $foreignKey 关联模型关联键 - * @param string $localKey 当前模型关联键 - * @param array $condition 关联查询条件 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 * @return Query */ protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) @@ -390,7 +421,8 @@ class BelongsToMany extends Relation $table = $this->pivot->getTable(); $fields = $this->getQueryFields($tableName); - $query = $this->query->field($fields) + $query = $this->query + ->field($fields) ->field(true, false, $table, 'pivot', 'pivot__'); if (empty($this->baseQuery)) { @@ -398,15 +430,16 @@ class BelongsToMany extends Relation $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) ->where($condition); } + return $query; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot */ public function save($data, array $pivot = []) { @@ -417,30 +450,33 @@ class BelongsToMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 - * @param array $pivot 中间表额外数据 - * @param bool $samePivot 额外数据是否相同 - * @return integer + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false */ public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) { - $result = false; + $result = []; + foreach ($dataSet as $key => $data) { if (!$samePivot) { $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; } else { $pivotData = $pivot; } - $result = $this->attach($data, $pivotData); + + $result[] = $this->attach($data, $pivotData); } - return $result; + + return empty($result) ? false : $result; } /** * 附加关联的一个中间表数据 * @access public - * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 - * @param array $pivot 中间表额外数据 + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 * @return array|Pivot * @throws Exception */ @@ -469,15 +505,18 @@ class BelongsToMany extends Relation $pk = $this->parent->getPk(); $pivot[$this->localKey] = $this->parent->$pk; $ids = (array) $id; + foreach ($ids as $id) { $pivot[$this->foreignKey] = $id; $this->pivot->insert($pivot, true); $result[] = $this->newPivot($pivot); } + if (count($result) == 1) { // 返回中间表模型对象 $result = $result[0]; } + return $result; } else { throw new Exception('miss relation data'); @@ -487,8 +526,8 @@ class BelongsToMany extends Relation /** * 解除关联的一个中间表数据 * @access public - * @param integer|array $data 数据 可以使用关联对象的主键 - * @param bool $relationDel 是否同时删除关联表数据 + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 * @return integer */ public function detach($data = null, $relationDel = false) @@ -503,13 +542,17 @@ class BelongsToMany extends Relation $relationFk = $data->getPk(); $id = $data->$relationFk; } + // 删除中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; + $pk = $this->parent->getPk(); + $pivot[] = [$this->localKey, '=', $this->parent->$pk]; + if (isset($id)) { - $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + $pivot[] = is_array($id) ? [$this->foreignKey, 'in', $id] : [$this->foreignKey, '=', $id]; } + $this->pivot->where($pivot)->delete(); + // 删除关联表数据 if (isset($id) && $relationDel) { $model = $this->model; @@ -519,8 +562,9 @@ class BelongsToMany extends Relation /** * 数据同步 - * @param array $ids - * @param bool $detaching + * @access public + * @param array $ids + * @param bool $detaching * @return array */ public function sync($ids, $detaching = true) @@ -530,9 +574,13 @@ class BelongsToMany extends Relation 'detached' => [], 'updated' => [], ]; - $pk = $this->parent->getPk(); - $current = $this->pivot->where($this->localKey, $this->parent->$pk) + + $pk = $this->parent->getPk(); + + $current = $this->pivot + ->where($this->localKey, $this->parent->$pk) ->column($this->foreignKey); + $records = []; foreach ($ids as $key => $value) { @@ -547,7 +595,6 @@ class BelongsToMany extends Relation if ($detaching && count($detach) > 0) { $this->detach($detach); - $changes['detached'] = $detach; } @@ -555,19 +602,16 @@ class BelongsToMany extends Relation if (!in_array($id, $current)) { $this->attach($id, $attributes); $changes['attached'][] = $id; - } elseif (count($attributes) > 0 && - $this->attach($id, $attributes) - ) { + } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) { $changes['updated'][] = $id; } } return $changes; - } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ @@ -576,7 +620,10 @@ class BelongsToMany extends Relation if (empty($this->baseQuery) && $this->parent->getData()) { $pk = $this->parent->getPk(); $table = $this->pivot->getTable(); - $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + + $this->query + ->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk()) + ->where('pivot.' . $this->localKey, $this->parent->$pk); $this->baseQuery = true; } } diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php index 3a9b111d6..3a8822981 100644 --- a/thinkphp/library/think/model/relation/HasMany.php +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -19,12 +19,12 @@ use think\model\Relation; class HasMany extends Relation { /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 */ public function __construct(Model $parent, $model, $foreignKey, $localKey) { @@ -37,16 +37,22 @@ class HasMany extends Relation /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection */ public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } - $list = $this->relation($subRelation)->select(); + + $list = $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select(); + $parent = clone $this->parent; foreach ($list as &$model) { @@ -58,17 +64,18 @@ class HasMany extends Relation /** * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $range = []; + foreach ($resultSet as $result) { // 获取关联外键列表 if (isset($result->$localKey)) { @@ -77,36 +84,37 @@ class HasMany extends Relation } if (!empty($range)) { - $data = $this->eagerlyOneToMany($this->query, [ - $this->foreignKey => [ - 'in', - $range, - ], - ], $relation, $subRelation, $closure); + $where = [ + [$this->foreignKey, 'in', $range], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; } - foreach ($data[$result->$localKey] as &$relationModel) { + foreach ($data[$pk] as &$relationModel) { $relationModel->setParent(clone $result); } - $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); + $result->setRelation($attr, $this->resultSetBuild($data[$pk])); } } } /** * 预载入关联查询 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) @@ -114,91 +122,106 @@ class HasMany extends Relation $localKey = $this->localKey; if (isset($result->$localKey)) { - $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + $pk = $result->$localKey; + $where = [ + [$this->foreignKey, '=', $pk], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + // 关联数据封装 - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; + if (!isset($data[$pk])) { + $data[$pk] = []; } - foreach ($data[$result->$localKey] as &$relationModel) { + foreach ($data[$pk] as &$relationModel) { $relationModel->setParent(clone $result); } - $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*') { $localKey = $this->localKey; $count = 0; + if (isset($result->$localKey)) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } - $count = $this->query->where([$this->foreignKey => $result->$localKey])->count(); + + $count = $this->query->where($this->foreignKey, '=', $result->$localKey)->$aggregate($field); } + return $count; } /** * 创建关联统计子查询 * @access public - * @param \Closure $closure 闭包 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return string */ - public function getRelationCountQuery($closure) + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*') { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } - $localKey = $this->localKey ?: $this->parent->getPk(); - return $this->query->where([ - $this->foreignKey => [ - 'exp', - '=' . $this->parent->getTable() . '.' . $localKey, - ], - ])->fetchSql()->count(); + + return $this->query + ->where($this->foreignKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->fetchSql() + ->$aggregate($field); } /** * 一对多 关联模型预查询 * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool $closure + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure * @return array */ - protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = false) { $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [ & $model]); + $closure($this->query); } - $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); + + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; + foreach ($list as $set) { $data[$set->$foreignKey][] = $set; } + return $data; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return Model|false */ public function save($data) @@ -206,34 +229,39 @@ class HasMany extends Relation if ($data instanceof Model) { $data = $data->getData(); } + // 保存关联表数据 - $model = new $this->model; $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + $model = new $this->model; + return $model->save($data) ? $model : false; } /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 - * @return integer + * @param array $dataSet 数据集 + * @return array|false */ public function saveAll(array $dataSet) { - $result = false; + $result = []; + foreach ($dataSet as $key => $data) { - $result = $this->save($data); + $result[] = $this->save($data); } - return $result; + + return empty($result) ? false : $result; } /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -253,7 +281,7 @@ class HasMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $where 查询条件(数组或者闭包) * @param mixed $fields 字段 * @return Query */ @@ -264,25 +292,21 @@ class HasMany extends Relation $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } + $this->getQueryWhere($where, $relation); } $fields = $this->getRelationQueryFields($fields, $model); - return $this->parent->db()->alias($model) - ->field($fields) + return $this->parent->db() + ->alias($model) ->group($model . '.' . $this->localKey) + ->field($fields) ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) ->where($where); } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ @@ -291,8 +315,9 @@ class HasMany extends Relation if (empty($this->baseQuery)) { if (isset($this->parent->{$this->localKey})) { // 关联查询带入关联条件 - $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); } + $this->baseQuery = true; } } diff --git a/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php index b7b3333de..411f3fccf 100644 --- a/thinkphp/library/think/model/relation/HasManyThrough.php +++ b/thinkphp/library/think/model/relation/HasManyThrough.php @@ -25,14 +25,14 @@ class HasManyThrough extends Relation protected $through; /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $foreignKey 关联外键 - * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 */ public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) { @@ -47,26 +47,29 @@ class HasManyThrough extends Relation /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection */ public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } - return $this->relation($subRelation)->select(); + $this->baseQuery(); + + return $this->query->relation($subRelation)->select(); } /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -77,8 +80,8 @@ class HasManyThrough extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -89,10 +92,10 @@ class HasManyThrough extends Relation /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -101,10 +104,10 @@ class HasManyThrough extends Relation /** * 预载入关联查询 返回模型对象 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) @@ -113,15 +116,17 @@ class HasManyThrough extends Relation /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*') {} /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ @@ -134,10 +139,15 @@ class HasManyThrough extends Relation $pk = (new $through)->getPk(); $throughKey = $this->throughKey; $modelTable = $this->parent->getTable(); - $this->query->field($alias . '.*')->alias($alias) + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->alias($alias) ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + $this->baseQuery = true; } } diff --git a/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php index f64f133ff..915dea47c 100644 --- a/thinkphp/library/think/model/relation/HasOne.php +++ b/thinkphp/library/think/model/relation/HasOne.php @@ -18,39 +18,43 @@ use think\Model; class HasOne extends OneToOne { /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @param string $joinType JOIN类型 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + public function __construct(Model $parent, $model, $foreignKey, $localKey) { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->joinType = $joinType; + $this->joinType = 'INNER'; $this->query = (new $model)->db(); } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return array|false|\PDOStatement|string|Model + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { - // 执行关联定义方法 $localKey = $this->localKey; + if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } + // 判断关联类型执行查询 - $relationModel = $this->query->where($this->foreignKey, $this->parent->$localKey)->relation($subRelation)->find(); + $relationModel = $this->query + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); if ($relationModel) { $relationModel->setParent(clone $this->parent); @@ -71,18 +75,21 @@ class HasOne extends OneToOne $relation = basename(str_replace('\\', '/', $this->model)); $localKey = $this->localKey; $foreignKey = $this->foreignKey; + return $this->parent->db() ->alias($model) ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { - $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); }); } /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -92,16 +99,13 @@ class HasOne extends OneToOne $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } + $this->getQueryWhere($where, $relation); } + $fields = $this->getRelationQueryFields($fields, $model); - return $this->parent->db()->alias($model) + return $this->parent->db() + ->alias($model) ->field($fields) ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) ->where($where); @@ -109,11 +113,11 @@ class HasOne extends OneToOne /** * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) @@ -130,14 +134,13 @@ class HasOne extends OneToOne } if (!empty($range)) { - $data = $this->eagerlyWhere($this, [ - $foreignKey => [ - 'in', - $range, - ], + $data = $this->eagerlyWhere([ + [$foreignKey, 'in', $range], ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { // 关联模型 @@ -148,6 +151,7 @@ class HasOne extends OneToOne $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + if (!empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); @@ -161,18 +165,20 @@ class HasOne extends OneToOne /** * 预载入关联查询(数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; - $data = $this->eagerlyWhere($this, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $relation, $subRelation, $closure); // 关联模型 if (!isset($data[$result->$localKey])) { @@ -182,6 +188,7 @@ class HasOne extends OneToOne $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + if (!empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php index 23f7c52a7..b3a4f39f2 100644 --- a/thinkphp/library/think/model/relation/MorphMany.php +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -26,13 +26,13 @@ class MorphMany extends Relation protected $type; /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $morphKey 关联外键 - * @param string $morphType 多态字段名 - * @param string $type 多态类型 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 */ public function __construct(Model $parent, $model, $morphKey, $morphType, $type) { @@ -46,16 +46,20 @@ class MorphMany extends Relation /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection */ public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } - $list = $this->relation($subRelation)->select(); + + $this->baseQuery(); + + $list = $this->query->relation($subRelation)->select(); $parent = clone $this->parent; foreach ($list as &$model) { @@ -68,10 +72,10 @@ class MorphMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -82,8 +86,8 @@ class MorphMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -94,10 +98,10 @@ class MorphMany extends Relation /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -106,6 +110,7 @@ class MorphMany extends Relation $morphKey = $this->morphKey; $type = $this->type; $range = []; + foreach ($resultSet as $result) { $pk = $result->getPk(); // 获取关联外键列表 @@ -115,21 +120,26 @@ class MorphMany extends Relation } if (!empty($range)) { - $data = $this->eagerlyMorphToMany([ - $morphKey => ['in', $range], - $morphType => $type, - ], $relation, $subRelation, $closure); + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { $data[$result->$pk] = []; } + foreach ($data[$result->$pk] as &$relationModel) { $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); } } @@ -138,104 +148,124 @@ class MorphMany extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); - if (isset($result->$pk)) { - $data = $this->eagerlyMorphToMany([ - $this->morphKey => $result->$pk, - $this->morphType => $this->type, - ], $relation, $subRelation, $closure); - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; + if (isset($result->$pk)) { + $key = $result->$pk; + $where = [ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + + if (!isset($data[$key])) { + $data[$key] = []; } - foreach ($data[$result->$pk] as &$relationModel) { + foreach ($data[$key] as &$relationModel) { $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } - $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$key])); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*') { $pk = $result->getPk(); $count = 0; + if (isset($result->$pk)) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closur($this->query); } - $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); + + $count = $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); } + return $count; } /** * 获取关联统计子查询 * @access public - * @param \Closure $closure 闭包 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return string */ - public function getRelationCountQuery($closure) + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*') { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } - return $this->query->where([ - $this->morphKey => [ - 'exp', - '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), - ], - $this->morphType => $this->type, - ])->fetchSql()->count(); + return $this->query + ->where([ + [$this->morphKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()], + [$this->morphType, '=', $this->type], + ]) + ->fetchSql() + ->$aggregate($field); } /** * 多态一对多 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 * @return array */ protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 + $this->query->removeOptions('where'); + if ($closure) { - call_user_func_array($closure, [ & $this]); + $closure($this->query); } + $list = $this->query->where($where)->with($subRelation)->select(); $morphKey = $this->morphKey; + // 组装模型数据 $data = []; foreach ($list as $set) { $data[$set->$morphKey][] = $set; } + return $data; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return Model|false */ public function save($data) @@ -243,42 +273,50 @@ class MorphMany extends Relation if ($data instanceof Model) { $data = $data->getData(); } + // 保存关联表数据 $pk = $this->parent->getPk(); - $model = new $this->model; + $model = new $this->model; + $data[$this->morphKey] = $this->parent->$pk; $data[$this->morphType] = $this->type; + return $model->save($data) ? $model : false; } /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 - * @return integer + * @param array $dataSet 数据集 + * @return array|false */ public function saveAll(array $dataSet) { - $result = false; + $result = []; + foreach ($dataSet as $key => $data) { - $result = $this->save($data); + $result[] = $this->save($data); } - return $result; + + return empty($result) ? false : $result; } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ protected function baseQuery() { if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $map[$this->morphKey] = $this->parent->$pk; - $map[$this->morphType] = $this->type; - $this->query->where($map); + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; } } diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php index 2337599b4..094440089 100644 --- a/thinkphp/library/think/model/relation/MorphOne.php +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -28,11 +28,11 @@ class MorphOne extends Relation /** * 构造函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $morphKey 关联外键 - * @param string $morphType 多态字段名 - * @param string $type 多态类型 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 */ public function __construct(Model $parent, $model, $morphKey, $morphType, $type) { @@ -46,16 +46,20 @@ class MorphOne extends Relation /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } - $relationModel = $this->relation($subRelation)->find(); + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); if ($relationModel) { $relationModel->setParent(clone $this->parent); @@ -67,10 +71,10 @@ class MorphOne extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -81,8 +85,8 @@ class MorphOne extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -93,10 +97,10 @@ class MorphOne extends Relation /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -105,6 +109,7 @@ class MorphOne extends Relation $morphKey = $this->morphKey; $type = $this->type; $range = []; + foreach ($resultSet as $result) { $pk = $result->getPk(); // 获取关联外键列表 @@ -115,11 +120,13 @@ class MorphOne extends Relation if (!empty($range)) { $data = $this->eagerlyMorphToOne([ - $morphKey => ['in', $range], - $morphType => $type, + [$morphKey, 'in', $range], + [$morphType, '=', $type], ], $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { @@ -138,20 +145,21 @@ class MorphOne extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); + if (isset($result->$pk)) { $pk = $result->$pk; $data = $this->eagerlyMorphToOne([ - $this->morphKey => $pk, - $this->morphType => $this->type, + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], ], $relation, $subRelation, $closure); if (isset($data[$pk])) { @@ -168,33 +176,37 @@ class MorphOne extends Relation /** * 多态一对一 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 * @return array */ protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [ & $this]); + $closure($this->query); } + $list = $this->query->where($where)->with($subRelation)->find(); $morphKey = $this->morphKey; + // 组装模型数据 $data = []; + foreach ($list as $set) { - $data[$set->$morphKey][] = $set; + $data[$set->$morphKey] = $set; } + return $data; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return Model|false */ public function save($data) @@ -205,7 +217,8 @@ class MorphOne extends Relation // 保存关联表数据 $pk = $this->parent->getPk(); - $model = new $this->model; + $model = new $this->model; + $data[$this->morphKey] = $this->parent->$pk; $data[$this->morphType] = $this->type; return $model->save($data) ? $model : false; @@ -219,10 +232,12 @@ class MorphOne extends Relation protected function baseQuery() { if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $map[$this->morphKey] = $this->parent->$pk; - $map[$this->morphType] = $this->type; - $this->query->where($map); + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); $this->baseQuery = true; } } diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php index 504130a4b..208c6ecf8 100644 --- a/thinkphp/library/think/model/relation/MorphTo.php +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -23,16 +23,17 @@ class MorphTo extends Relation protected $morphType; // 多态别名 protected $alias; + // 关联名 protected $relation; /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $morphType 多态字段名 - * @param string $morphKey 外键名 - * @param array $alias 多态别名定义 - * @param string $relation 关联名 + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 */ public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) { @@ -52,38 +53,44 @@ class MorphTo extends Relation { $morphType = $this->morphType; $model = $this->parseModel($this->parent->$morphType); + return (new $model); } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return mixed + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { $morphKey = $this->morphKey; $morphType = $this->morphType; + // 多态模型 $model = $this->parseModel($this->parent->$morphType); + // 主键数据 - $pk = $this->parent->$morphKey; + $pk = $this->parent->$morphKey; + $relationModel = (new $model)->relation($subRelation)->find($pk); if ($relationModel) { $relationModel->setParent(clone $this->parent); } + return $relationModel; } /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -94,8 +101,8 @@ class MorphTo extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -106,7 +113,7 @@ class MorphTo extends Relation /** * 解析模型的完整命名空间 * @access public - * @param string $model 模型名(或者完整类名) + * @param string $model 模型名(或者完整类名) * @return string */ protected function parseModel($model) @@ -114,24 +121,27 @@ class MorphTo extends Relation if (isset($this->alias[$model])) { $model = $this->alias[$model]; } + if (false === strpos($model, '\\')) { $path = explode('\\', get_class($this->parent)); array_pop($path); array_push($path, Loader::parseName($model, 1)); $model = implode('\\', $path); } + return $model; } /** * 设置多态别名 * @access public - * @param array $alias 别名定义 + * @param array $alias 别名定义 * @return $this */ public function setAlias($alias) { $this->alias = $alias; + return $this; } @@ -148,10 +158,10 @@ class MorphTo extends Relation /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void * @throws Exception */ @@ -160,6 +170,7 @@ class MorphTo extends Relation $morphKey = $this->morphKey; $morphType = $this->morphType; $range = []; + foreach ($resultSet as $result) { // 获取关联外键列表 if (!empty($result->$morphKey)) { @@ -170,6 +181,7 @@ class MorphTo extends Relation if (!empty($range)) { // 关联属性名 $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { // 多态类型映射 $model = $this->parseModel($key); @@ -177,9 +189,11 @@ class MorphTo extends Relation $pk = $obj->getPk(); $list = $obj->all($val, $subRelation); $data = []; + foreach ($list as $k => $vo) { $data[$vo->$pk] = $vo; } + foreach ($resultSet as $result) { if ($key == $result->$morphType) { // 关联模型 @@ -201,10 +215,10 @@ class MorphTo extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) @@ -213,27 +227,29 @@ class MorphTo extends Relation $morphType = $this->morphType; // 多态类型映射 $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return integer */ - public function relationCount($result, $closure) - { - } + public function relationCount($result, $closure, $aggregate = 'count', $field = '*') + {} /** * 多态MorphTo 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param string $relation 关联名 - * @param $result - * @param string $subRelation 子关联 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param string $subRelation 子关联 * @return void */ protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') @@ -241,18 +257,20 @@ class MorphTo extends Relation // 预载入关联查询 支持嵌套预载入 $pk = $this->parent->{$this->morphKey}; $data = (new $model)->with($subRelation)->find($pk); + if ($data) { $data->setParent(clone $result); $data->isUpdate(true); } + $result->setRelation(Loader::parseName($relation), $data ?: null); } /** * 添加关联数据 * @access public - * @param Model $model 关联模型对象 - * @param string $type 多态类型 + * @param Model $model 关联模型对象 + * @param string $type 多态类型 * @return Model */ public function associate($model, $type = '') diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php index 02d7d5639..195d0bdda 100644 --- a/thinkphp/library/think/model/relation/OneToOne.php +++ b/thinkphp/library/think/model/relation/OneToOne.php @@ -30,13 +30,13 @@ abstract class OneToOne extends Relation protected $joinType; // 要绑定的属性 protected $bindAttr = []; - // 关联方法名 + // 关联名 protected $relation; /** * 设置join类型 * @access public - * @param string $type JOIN类型 + * @param string $type JOIN类型 * @return $this */ public function joinType($type) @@ -48,28 +48,29 @@ abstract class OneToOne extends Relation /** * 预载入关联查询(JOIN方式) * @access public - * @param Query $query 查询对象 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param \Closure $closure 闭包条件 - * @param bool $first + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first * @return void */ public function eagerly(Query $query, $relation, $subRelation, $closure, $first) { - $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); if ($first) { $table = $query->getTable(); $query->table([$table => $name]); + if ($query->getOptions('field')) { $field = $query->getOptions('field'); $query->removeOption('field'); } else { $field = true; } + $query->field($field, false, $table, $name); - $field = null; } // 预载入封装 @@ -85,35 +86,42 @@ abstract class OneToOne extends Relation if ($closure) { // 执行闭包查询 - call_user_func_array($closure, [ & $query]); + $closure($query); // 使用withField指定获取关联的字段,如 // $query->where(['id'=>1])->withField('id,name'); if ($query->getOptions('with_field')) { $field = $query->getOptions('with_field'); $query->removeOption('with_field'); + } else { + $field = true; } } elseif (isset($this->option['field'])) { $field = $this->option['field']; + } else { + $field = true; } - $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); + + $query->field($field, false, $joinTable, $joinAlias, $relation . '__'); } /** * 预载入关联查询(数据集) - * @param array $resultSet - * @param string $relation - * @param string $subRelation - * @param \Closure $closure + * @access protected + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure * @return mixed */ abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); /** * 预载入关联查询(数据) - * @param Model $result - * @param string $relation - * @param string $subRelation - * @param \Closure $closure + * @access protected + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure * @return mixed */ abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); @@ -121,10 +129,10 @@ abstract class OneToOne extends Relation /** * 预载入关联查询(数据集) * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -143,10 +151,10 @@ abstract class OneToOne extends Relation /** * 预载入关联查询(数据) * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) @@ -163,7 +171,7 @@ abstract class OneToOne extends Relation /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return Model|false */ public function save($data) @@ -171,21 +179,24 @@ abstract class OneToOne extends Relation if ($data instanceof Model) { $data = $data->getData(); } + $model = new $this->model; // 保存关联表数据 $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; } /** * 设置预载入方式 * @access public - * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 * @return $this */ public function setEagerlyType($type) { $this->eagerlyType = $type; + return $this; } @@ -202,7 +213,7 @@ abstract class OneToOne extends Relation /** * 绑定关联表的属性到父模型属性 * @access public - * @param mixed $attr 要绑定的属性列表 + * @param mixed $attr 要绑定的属性列表 * @return $this */ public function bind($attr) @@ -211,6 +222,7 @@ abstract class OneToOne extends Relation $attr = explode(',', $attr); } $this->bindAttr = $attr; + return $this; } @@ -227,20 +239,23 @@ abstract class OneToOne extends Relation /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*') { + throw new Exception('relation not support: ' . $aggregate); } /** * 一对一 关联模型预查询拼装 * @access public - * @param string $model 模型名称 - * @param string $relation 关联名 - * @param Model $result 模型对象实例 + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 * @return void */ protected function match($model, $relation, &$result) @@ -267,21 +282,21 @@ abstract class OneToOne extends Relation } else { $relationModel = null; } + $result->setRelation(Loader::parseName($relation), $relationModel); } /** * 绑定关联属性到父模型 * @access protected - * @param Model $model 关联模型对象 - * @param Model $result 父模型对象 - * @param array $bindAttr 绑定属性 + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 * @return void * @throws Exception */ - protected function bindAttr($model, &$result, $bindAttr) + protected function bindAttr($model, &$result) { - foreach ($bindAttr as $key => $attr) { + foreach ($this->bindAttr as $key => $attr) { $key = is_numeric($key) ? $attr : $key; if (isset($result->$key)) { throw new Exception('bind attr has exists:' . $key); @@ -294,30 +309,33 @@ abstract class OneToOne extends Relation /** * 一对一 关联模型预查询(IN方式) * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $key 关联键名 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure * @return array */ - protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [ & $model]); - if ($field = $model->getOptions('with_field')) { - $model->field($field)->removeOption('with_field'); + $closure($this->query); + + if ($field = $this->query->getOptions('with_field')) { + $this->query->field($field)->removeOption('with_field'); } } - $list = $model->where($where)->with($subRelation)->select(); + + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; + foreach ($list as $set) { $data[$set->$key] = $set; } + return $data; } diff --git a/thinkphp/library/think/paginator/Collection.php b/thinkphp/library/think/paginator/Collection.php new file mode 100644 index 000000000..4005c9ff9 --- /dev/null +++ b/thinkphp/library/think/paginator/Collection.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator; + +use Exception; +use think\Paginator; + +/** + * Class Collection + * @package think\paginator + * @method integer total() + * @method integer listRows() + * @method integer currentPage() + * @method string render() + * @method Paginator fragment($fragment) + * @method Paginator appends($key, $value) + * @method integer lastPage() + * @method boolean hasPages() + */ +class Collection extends \think\Collection +{ + + /** @var Paginator */ + protected $paginator; + + public function __construct($items = [], Paginator $paginator = null) + { + $this->paginator = $paginator; + parent::__construct($items); + } + + public static function make($items = [], Paginator $paginator = null) + { + return new static($items, $paginator); + } + + public function toArray() + { + if ($this->paginator) { + try { + $total = $this->total(); + } catch (Exception $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'data' => parent::toArray(), + ]; + } else { + return parent::toArray(); + } + } + + public function __call($method, $args) + { + if ($this->paginator && method_exists($this->paginator, $method)) { + return call_user_func_array([$this->paginator, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/paginator/driver/Bootstrap.php b/thinkphp/library/think/paginator/driver/Bootstrap.php index c5ac60db8..ab5315c06 100644 --- a/thinkphp/library/think/paginator/driver/Bootstrap.php +++ b/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -57,13 +57,14 @@ class Bootstrap extends Paginator */ protected function getLinks() { - if ($this->simple) + if ($this->simple) { return ''; + } $block = [ 'first' => null, 'slider' => null, - 'last' => null + 'last' => null, ]; $side = 3; @@ -196,7 +197,7 @@ class Bootstrap extends Paginator */ protected function getPageLinkWrapper($url, $page) { - if ($page == $this->currentPage()) { + if ($this->currentPage() == $page) { return $this->getActivePageWrapper($page); } diff --git a/thinkphp/library/think/process/exception/Faild.php b/thinkphp/library/think/process/exception/Faild.php new file mode 100644 index 000000000..38647bc11 --- /dev/null +++ b/thinkphp/library/think/process/exception/Faild.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Faild extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/thinkphp/library/think/response/Json.php b/thinkphp/library/think/response/Json.php index c906bfc18..aa5bbd6fa 100644 --- a/thinkphp/library/think/response/Json.php +++ b/thinkphp/library/think/response/Json.php @@ -25,7 +25,7 @@ class Json extends Response /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed * @throws \Exception */ @@ -35,7 +35,7 @@ class Json extends Response // 返回JSON数据格式到客户端 包含状态信息 $data = json_encode($data, $this->options['json_encode_param']); - if ($data === false) { + if (false === $data) { throw new \InvalidArgumentException(json_last_error_msg()); } diff --git a/thinkphp/library/think/response/Jsonp.php b/thinkphp/library/think/response/Jsonp.php index 404bacbe6..9e53da2d1 100644 --- a/thinkphp/library/think/response/Jsonp.php +++ b/thinkphp/library/think/response/Jsonp.php @@ -11,7 +11,7 @@ namespace think\response; -use think\Request; +use think\Container; use think\Response; class Jsonp extends Response @@ -28,7 +28,7 @@ class Jsonp extends Response /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed * @throws \Exception */ @@ -36,16 +36,17 @@ class Jsonp extends Response { try { // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] - $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $var_jsonp_handler = Container::get('request')->param($this->options['var_jsonp_handler'], ""); $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; $data = json_encode($data, $this->options['json_encode_param']); - if ($data === false) { + if (false === $data) { throw new \InvalidArgumentException(json_last_error_msg()); } $data = $handler . '(' . $data . ');'; + return $data; } catch (\Exception $e) { if ($e->getPrevious()) { diff --git a/thinkphp/library/think/response/Jump.php b/thinkphp/library/think/response/Jump.php new file mode 100644 index 000000000..214afc7d4 --- /dev/null +++ b/thinkphp/library/think/response/Jump.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Container; +use think\Response; + +class Jump extends Response +{ + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + $config = Container::get('config'); + $data = Container::get('view') + ->init($config->pull('template')) + ->fetch($this->options['jump_template'], $data); + return $data; + } +} diff --git a/thinkphp/library/think/response/Redirect.php b/thinkphp/library/think/response/Redirect.php index 91694cb2c..d6e5101a1 100644 --- a/thinkphp/library/think/response/Redirect.php +++ b/thinkphp/library/think/response/Redirect.php @@ -11,10 +11,8 @@ namespace think\response; -use think\Request; +use think\Container; use think\Response; -use think\Session; -use think\Url; class Redirect extends Response { @@ -27,42 +25,48 @@ class Redirect extends Response public function __construct($data = '', $code = 302, array $header = [], array $options = []) { parent::__construct($data, $code, $header, $options); + $this->cacheControl('no-cache,must-revalidate'); } /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) { $this->header['Location'] = $this->getTargetUrl(); + return; } /** * 重定向传值(通过Session) * @access protected - * @param string|array $name 变量名或者数组 - * @param mixed $value 值 + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 * @return $this */ public function with($name, $value = null) { + $session = Container::get('session'); + if (is_array($name)) { foreach ($name as $key => $val) { - Session::flash($key, $val); + $session->flash($key, $val); } } else { - Session::flash($name, $value); + $session->flash($name, $value); } + return $this; } /** * 获取跳转地址 + * @access public * @return string */ public function getTargetUrl() @@ -70,36 +74,43 @@ class Redirect extends Response if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { return $this->data; } else { - return Url::build($this->data, $this->params); + return Container::get('url')->build($this->data, $this->params); } } public function params($params = []) { $this->params = $params; + return $this; } /** * 记住当前url后跳转 + * @access public * @return $this */ public function remember() { - Session::set('redirect_url', Request::instance()->url()); + Container::get('session')->set('redirect_url', Container::get('request')->url()); + return $this; } /** * 跳转到上次记住的url + * @access public * @return $this */ public function restore() { - if (Session::has('redirect_url')) { - $this->data = Session::get('redirect_url'); - Session::delete('redirect_url'); + $session = Container::get('session'); + + if ($session->has('redirect_url')) { + $this->data = $session->get('redirect_url'); + $session->delete('redirect_url'); } + return $this; } } diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php index 48f944a71..fc32aa567 100644 --- a/thinkphp/library/think/response/View.php +++ b/thinkphp/library/think/response/View.php @@ -11,35 +11,37 @@ namespace think\response; -use think\Config; +use think\Container; use think\Response; -use think\View as ViewTemplate; class View extends Response { // 输出参数 - protected $options = []; - protected $vars = []; - protected $replace = []; + protected $options = []; + protected $vars = []; + protected $filter; protected $contentType = 'text/html'; /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) { // 渲染模板输出 - return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) - ->fetch($data, $this->vars, $this->replace); + $config = Container::get('config'); + return Container::get('view') + ->init($config->pull('template')) + ->filter($this->filter) + ->fetch($data, $this->vars); } /** * 获取视图变量 * @access public - * @param string $name 模板变量 + * @param string $name 模板变量 * @return mixed */ public function getVars($name = null) @@ -54,8 +56,8 @@ class View extends Response /** * 模板变量赋值 * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 + * @param mixed $name 变量名 + * @param mixed $value 变量值 * @return $this */ public function assign($name, $value = '') @@ -66,24 +68,33 @@ class View extends Response } else { $this->vars[$name] = $value; } + return $this; } /** - * 视图内容替换 + * 视图内容过滤 * @access public - * @param string|array $content 被替换内容(支持批量替换) - * @param string $replace 替换内容 + * @param callable $filter * @return $this */ - public function replace($content, $replace = '') + public function filter($filter) { - if (is_array($content)) { - $this->replace = array_merge($this->replace, $content); - } else { - $this->replace[$content] = $replace; - } + $this->filter = $filter; return $this; } + /** + * 检查模板是否存在 + * @access private + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return Container::get('view') + ->init(Container::get('config')->pull('template')) + ->exists($name); + } + } diff --git a/thinkphp/library/think/response/Xml.php b/thinkphp/library/think/response/Xml.php index 3bdc052a2..f92b99c77 100644 --- a/thinkphp/library/think/response/Xml.php +++ b/thinkphp/library/think/response/Xml.php @@ -36,7 +36,7 @@ class Xml extends Response /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) @@ -47,12 +47,13 @@ class Xml extends Response /** * XML编码 - * @param mixed $data 数据 - * @param string $root 根节点名 - * @param string $item 数字索引的子节点名 - * @param string $attr 根节点属性 - * @param string $id 数字索引子节点key转换的属性名 - * @param string $encoding 数据编码 + * @access protected + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 * @return string */ protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) @@ -64,20 +65,23 @@ class Xml extends Response } $attr = implode(' ', $array); } + $attr = trim($attr); $attr = empty($attr) ? '' : " {$attr}"; $xml = ""; $xml .= "<{$root}{$attr}>"; $xml .= $this->dataToXml($data, $item, $id); $xml .= ""; + return $xml; } /** * 数据XML编码 - * @param mixed $data 数据 - * @param string $item 数字索引时的节点名称 - * @param string $id 数字索引key转换为的属性名 + * @access protected + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 * @return string */ protected function dataToXml($data, $item, $id) @@ -97,6 +101,7 @@ class Xml extends Response $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; $xml .= ""; } + return $xml; } } diff --git a/thinkphp/library/think/route/Dispatch.php b/thinkphp/library/think/route/Dispatch.php new file mode 100644 index 000000000..ca6b5d63d --- /dev/null +++ b/thinkphp/library/think/route/Dispatch.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; + +abstract class Dispatch +{ + // 应用实例 + protected $app; + // 调度信息 + protected $dispatch; + // 调度参数 + protected $param; + // 状态码 + protected $code; + // 是否进行大小写转换 + protected $convert; + + public function __construct($dispatch, $param = [], $code = null) + { + $this->app = Container::get('app'); + $this->dispatch = $dispatch; + $this->param = $param; + $this->code = $code; + } + + public function convert($convert) + { + $this->convert = $convert; + + return $this; + } + + public function getDispatch() + { + return $this->dispatch; + } + + public function getParam() + { + return $this->param; + } + + abstract public function run(); + +} diff --git a/thinkphp/library/think/route/Domain.php b/thinkphp/library/think/route/Domain.php new file mode 100644 index 000000000..d8f3e4f28 --- /dev/null +++ b/thinkphp/library/think/route/Domain.php @@ -0,0 +1,257 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Loader; +use think\Response; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; +use think\route\dispatch\Module as ModuleDispatch; +use think\route\dispatch\Response as ResponseDispatch; + +class Domain extends RuleGroup +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param string $name 分组名称 + * @param mixed $rule 域名路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, $name = '', $rule = null, $option = [], $pattern = []) + { + $this->router = $router; + $this->name = trim($name, '/'); + $this->option = $option; + $this->rule = $rule; + $this->pattern = $pattern; + } + + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $depr = '/', $completeMatch = false) + { + if ($this->rule) { + // 延迟解析域名路由 + if ($this->rule instanceof Response) { + return new ResponseDispatch($this->rule); + } + + $group = new RuleGroup($this->router); + + $this->addRule($group); + + $this->router->setGroup($group); + + $this->router->parseGroupRule($this, $this->rule); + + $this->rule = null; + } + + // 检测别名路由 + if ($this->router->getAlias($url) || $this->router->getAlias(strstr($url, '|', true))) { + // 检测路由别名 + $result = $this->checkRouteAlias($request, $url, $depr); + if (false !== $result) { + return $result; + } + } + + // 检测URL绑定 + $result = $this->checkUrlBind($url, $depr); + + if (false !== $result) { + return $result; + } + + return parent::check($request, $url, $depr, $completeMatch); + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @return Dispatch|false + */ + private function checkRouteAlias($request, $url, $depr) + { + $array = explode('|', $url); + $alias = array_shift($array); + $item = $this->router->getAlias($alias); + + if (is_array($item)) { + list($rule, $option) = $item; + $action = $array[0]; + + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } + } else { + $rule = $item; + } + + $bind = implode('|', $array); + + // 参数有效性检查 + if (isset($option) && !$this->checkOption($option, $request)) { + // 路由不匹配 + return false; + } elseif (0 === strpos($rule, '\\')) { + // 路由到类 + return $this->bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { + // 路由到控制器类 + return $this->bindToController($bind, substr($rule, 1), $depr); + } else { + // 路由到模块/控制器 + return $this->bindToModule($bind, $rule, $depr); + } + } + + /** + * 检测URL绑定 + * @access private + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @return Dispatch|false + */ + private function checkUrlBind(&$url, $depr = '/') + { + $bind = $this->router->getBind($this->name); + + if (!empty($bind)) { + // 记录绑定信息 + Container::get('app')->log('[ BIND ] ' . var_export($bind, true)); + + // 如果有URL绑定 则进行绑定检测 + if (0 === strpos($bind, '\\')) { + // 绑定到类 + return $this->bindToClass($url, substr($bind, 1), $depr); + } elseif (0 === strpos($bind, '@')) { + // 绑定到控制器类 + return $this->bindToController($url, substr($bind, 1), $depr); + } elseif (0 === strpos($bind, ':')) { + // 绑定到命名空间 + return $this->bindToNamespace($url, substr($bind, 1), $depr); + } + } + + return false; + } + + /** + * 绑定到类 + * @access public + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @param string $depr URL分隔符 + * @return CallbackDispatch + */ + public function bindToClass($url, $class, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); + + if (!empty($array[1])) { + $this->parseUrlParams($array[1]); + } + + return new CallbackDispatch([$class, $action]); + } + + /** + * 绑定到命名空间 + * @access public + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @param string $depr URL分隔符 + * @return CallbackDispatch + */ + public function bindToNamespace($url, $namespace, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_controller'); + $method = !empty($array[1]) ? $array[1] : Container::get('config')->get('default_action'); + + if (!empty($array[2])) { + $this->parseUrlParams($array[2]); + } + + return new CallbackDispatch([$namespace . '\\' . Loader::parseName($class, 1), $method]); + } + + /** + * 绑定到控制器类 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @param string $depr URL分隔符 + * @return ControllerDispatch + */ + public function bindToController($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); + + if (!empty($array[1])) { + $this->parseUrlParams($array[1]); + } + + return new ControllerDispatch($controller . '/' . $action); + } + + /** + * 绑定到模块/控制器 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @param string $depr URL分隔符 + * @return ModuleDispatch + */ + public function bindToModule($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); + + if (!empty($array[1])) { + $this->parseUrlParams($array[1]); + } + + return new ModuleDispatch($controller . '/' . $action); + } + +} diff --git a/thinkphp/library/think/route/Resource.php b/thinkphp/library/think/route/Resource.php new file mode 100644 index 000000000..7072623c7 --- /dev/null +++ b/thinkphp/library/think/route/Resource.php @@ -0,0 +1,170 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Route; + +class Resource extends RuleGroup +{ + // 资源路由名称 + protected $resource; + // 资源路由地址 + protected $route; + // REST路由方法定义 + protected $rest = []; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $group 路由所属分组对象 + * @param string $name 资源名称 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param array $rest 资源定义 + */ + public function __construct(Route $router, RuleGroup $group = null, $name = '', $route = '', $option = [], $pattern = [], $rest = []) + { + $this->router = $router; + $this->parent = $group; + $this->resource = $name; + $this->route = $route; + $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; + + $this->setFullName(); + + // 资源路由默认为完整匹配 + $option['complete_match'] = true; + + $this->pattern = $pattern; + $this->option = $option; + $this->rest = $rest; + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch + */ + public function check($request, $url, $depr = '/', $completeMatch = false) + { + // 生成资源路由的路由规则 + $this->buildResourceRule($this->resource, $this->option); + + return parent::check($request, $url, $depr, $completeMatch); + } + + /** + * 生成资源路由规则 + * @access protected + * @param string $rule 路由规则 + * @param array $option 路由参数 + * @return void + */ + protected function buildResourceRule($rule, $option = []) + { + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + + foreach ($array as $val) { + $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); + } + + $rule = implode('/', $item) . '/' . $last; + } + + // 注册分组 + $group = $this->router->getGroup(); + + $this->router->setGroup($this); + + // 注册资源路由 + foreach ($this->rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + + if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { + $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); + } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { + $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); + } + + $option['rest'] = $key; + + $this->router->rule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); + } + + $this->router->setGroup($group); + } + + /** + * rest方法定义和修改 + * @access public + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + + /** + * 设置资源允许 + * @access public + * @param array $only + * @return $this + */ + public function only($only) + { + return $this->option('only', $only); + } + + /** + * 设置资源排除 + * @access public + * @param array $except + * @return $this + */ + public function except($except) + { + return $this->option('except', $except); + } + + /** + * 设置资源路由的变量 + * @access public + * @param array $vars + * @return $this + */ + public function vars($vars) + { + return $this->option('var', $vars); + } + +} diff --git a/thinkphp/library/think/route/Rule.php b/thinkphp/library/think/route/Rule.php new file mode 100644 index 000000000..7fe8d3e84 --- /dev/null +++ b/thinkphp/library/think/route/Rule.php @@ -0,0 +1,909 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\exception\ValidateException; +use think\Request; +use think\Response; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; +use think\route\dispatch\Module as ModuleDispatch; +use think\route\dispatch\Redirect as RedirectDispatch; +use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\View as ViewDispatch; + +abstract class Rule +{ + protected $name; + // 路由对象实例 + protected $router; + // 路由父对象 + protected $parent; + // 路由参数 + protected $option = []; + // 路由变量规则 + protected $pattern = []; + // 需要合并的路由参数 + protected $mergeOptions = ['after', 'before', 'model']; + + abstract public function check($request, $url, $depr = '/'); + + /** + * 注册路由参数 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value = '') + { + if (is_array($name)) { + $this->option = array_merge($this->option, $name); + } else { + $this->option[$name] = $value; + } + + return $this; + } + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this + */ + public function pattern($name, $rule = '') + { + if (is_array($name)) { + $this->pattern = array_merge($this->pattern, $name); + } else { + $this->pattern[$name] = $rule; + } + + return $this; + } + + /** + * 设置Name + * @access public + * @param string|array $name 变量名 + * @return $this + */ + public function name($name) + { + $this->name = '/' != $name ? ltrim($name, '/') : '/'; + + return $this; + } + + /** + * 获取Name + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 获取变量规则定义 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getPattern($name = '') + { + if ('' === $name) { + return $this->pattern; + } + + return isset($this->pattern[$name]) ? $this->pattern[$name] : null; + } + + /** + * 获取路由参数定义 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOption($name = '') + { + if ('' === $name) { + return $this->option; + } + + return isset($this->option[$name]) ? $this->option[$name] : null; + } + + /** + * 附加路由隐式参数 + * @access public + * @param array $append + * @return $this + */ + public function append(array $append = []) + { + if (isset($this->option['append'])) { + $this->option['append'] = array_merge($this->option['append'], $append); + } else { + $this->option['append'] = $append; + } + + return $this; + } + + /** + * 设置路由请求类型 + * @access public + * @param string $method + * @return $this + */ + public function method($method) + { + return $this->option('method', strtolower($method)); + } + + /** + * 设置路由前置行为 + * @access public + * @param array|\Closure $before + * @return $this + */ + public function before($before) + { + return $this->option('before', $before); + } + + /** + * 设置路由后置行为 + * @access public + * @param array|\Closure $after + * @return $this + */ + public function after($after) + { + return $this->option('after', $after); + } + + /** + * 检查后缀 + * @access public + * @param string $ext + * @return $this + */ + public function ext($ext = '') + { + return $this->option('ext', $ext); + } + + /** + * 检查禁止后缀 + * @access public + * @param string $ext + * @return $this + */ + public function denyExt($ext = '') + { + return $this->option('deny_ext', $ext); + } + + /** + * 检查域名 + * @access public + * @param string $domain + * @return $this + */ + public function domain($domain) + { + return $this->option('domain', $domain); + } + + /** + * 绑定模型 + * @access public + * @param array|string $var 路由变量名 多个使用 & 分割 + * @param string|\Closure $model 绑定模型类 + * @param bool $exception 是否抛出异常 + * @return $this + */ + public function model($var, $model = null, $exception = true) + { + if (is_array($var)) { + $this->option['model'] = $var; + } elseif (is_null($model)) { + $this->option['model']['id'] = [$var, true]; + } else { + $this->option['model'][$var] = [$model, $exception]; + } + + return $this; + } + + /** + * 绑定验证 + * @access public + * @param mixed $validate 验证器类 + * @param string $scene 验证场景 + * @param array $message 验证提示 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($validate, $scene = null, $message = [], $batch = false) + { + $this->option['validate'] = [$validate, $scene, $message, $batch]; + + return $this; + } + + /** + * 绑定Response对象 + * @access public + * @param mixed $response + * @return $this + */ + public function response($response) + { + return $this->option('response', $response); + } + + /** + * 设置Response Header信息 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($header, $value = null) + { + if (empty($this->option['header'])) { + $this->option['header'] = []; + } + + if (is_array($header)) { + $this->option['header'] = array_merge($this->option['header'], $header); + } else { + $this->option['header'][$header] = $value; + } + + return $this; + } + + /** + * 设置路由缓存 + * @access public + * @param array|string $cache + * @return $this + */ + public function cache($cache) + { + return $this->option('cache', $cache); + } + + /** + * 检查URL分隔符 + * @access public + * @param bool $depr + * @return $this + */ + public function depr($depr) + { + return $this->option('param_depr', $depr); + } + + /** + * 是否合并额外参数 + * @access public + * @param bool $merge + * @return $this + */ + public function mergeExtraVars($merge = true) + { + return $this->option('merge_extra_vars', $merge); + } + + /** + * 设置需要合并的路由参数 + * @access public + * @param array $option + * @return $this + */ + public function mergeOptions($option = []) + { + $this->mergeOptions = array_merge($this->mergeOptions, $option); + return $this; + } + + /** + * 检查是否为HTTPS请求 + * @access public + * @param bool $https + * @return $this + */ + public function https($https = true) + { + return $this->option('https', $https); + } + + /** + * 检查是否为AJAX请求 + * @access public + * @param bool $ajax + * @return $this + */ + public function ajax($ajax = true) + { + return $this->option('ajax', $ajax); + } + + /** + * 检查是否为PJAX请求 + * @access public + * @param bool $pjax + * @return $this + */ + public function pjax($pjax = true) + { + return $this->option('pjax', $pjax); + } + + /** + * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量 + * @access public + * @param bool|array $view + * @return $this + */ + public function view($view = true) + { + return $this->option('view', $view); + } + + /** + * 当前路由为重定向 + * @access public + * @param bool $redirect 是否为重定向 + * @return $this + */ + public function redirect($redirect = true) + { + return $this->option('redirect', $redirect); + } + + /** + * 设置路由完整匹配 + * @access public + * @param bool $match + * @return $this + */ + public function completeMatch($match = true) + { + return $this->option('complete_match', $match); + } + + /** + * 是否去除URL最后的斜线 + * @access public + * @param bool $remove + * @return $this + */ + public function removeSlash($remove = true) + { + return $this->option('remove_slash', $remove); + } + + /** + * 设置是否允许跨域 + * @access public + * @param bool $allow + * @param array $header + * @return $this + */ + public function allowCrossDomain($allow = true, $header = []) + { + if (!empty($header)) { + $this->header($header); + } + + if ($allow && $this->parent) { + $this->parent->addRule($this, 'options'); + } + + return $this->option('cross_domain', $allow); + } + + /** + * 检查OPTIONS请求 + * @access public + * @param Request $request + * @return Dispatch|void + */ + protected function checkCrossDomain($request) + { + if (!empty($this->option['cross_domain'])) { + + $header = [ + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE', + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With', + ]; + + if (!empty($this->option['header'])) { + $header = array_merge($header, $this->option['header']); + } + + $this->option['header'] = $header; + + if ($request->method(true) == 'OPTIONS') { + return new ResponseDispatch(Response::create()->code(204)->header($header)); + } + } + } + + /** + * 设置路由规则全局有效 + * @access public + * @return $this + */ + public function crossDomainRule() + { + if ($this instanceof RuleGroup) { + $method = '*'; + } else { + $method = $this->method; + } + + $this->router->setCrossDomainRule($this, $method); + + return $this; + } + + /** + * 合并分组参数 + * @access protected + * @return void + */ + protected function mergeGroupOptions() + { + $parentOption = $this->parent->getOption(); + // 合并分组参数 + foreach ($this->mergeOptions as $item) { + if (isset($parentOption[$item]) && isset($this->option[$item])) { + $this->option[$item] = array_merge($parentOption[$item], $this->option[$item]); + } + } + + $this->option = array_merge($parentOption, $this->option); + } + + /** + * 路由绑定模型实例 + * @access protected + * @param array|\Clousre $bindModel 绑定模型 + * @param array $matches 路由变量 + * @return void + */ + protected function createBindModel($bindModel, $matches) + { + foreach ($bindModel as $key => $val) { + if ($val instanceof \Closure) { + $result = Container::getInstance()->invokeFunction($val, $matches); + } else { + $fields = explode('&', $key); + + if (is_array($val)) { + list($model, $exception) = $val; + } else { + $model = $val; + $exception = true; + } + + $where = []; + $match = true; + + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[] = [$field, '=', $matches[$field]]; + } + } + + if ($match) { + $query = strpos($model, '\\') ? $model::where($where) : Container::get('app')->model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + + if (!empty($result)) { + // 注入容器 + Container::getInstance()->instance(get_class($result), $result); + } + } + } + + /** + * 处理路由请求缓存 + * @access protected + * @param Request $request 请求对象 + * @param string|array $cache 路由缓存 + * @return void + */ + protected function parseRequestCache($request, $cache) + { + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $request->url()); + $expire = $cache; + $tag = null; + } + + $request->cache($key, $expire, $tag); + } + + /** + * 解析匹配到的规则路由 + * @access public + * @param Request $request 请求对象 + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @return Dispatch + */ + public function parseRule($request, $rule, $route, $url, $option = [], $matches = []) + { + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + foreach ($matches as $key => $val) { + if (false !== strpos($route, ':' . $key)) { + $route = str_replace(':' . $key, $val, $route); + } + } + } + + // 绑定模型数据 + if (isset($option['model'])) { + $this->createBindModel($option['model'], $matches); + } + + // 指定Header数据 + if (!empty($option['header'])) { + $header = $option['header']; + Container::get('hook')->add('response_send', function ($response) use ($header) { + $response->header($header); + }); + } + + // 指定Response响应数据 + if (!empty($option['response'])) { + Container::get('hook')->add('response_send', $option['response']); + } + + // 开启请求缓存 + if (isset($option['cache']) && $request->isGet()) { + $this->parseRequestCache($request, $option['cache']); + } + + // 解析额外参数 + $count = substr_count($rule, '/'); + $url = array_slice(explode('|', $url), $count + 1); + $this->parseUrlParams(implode('|', $url), $matches); + + // 记录匹配的路由信息 + $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); + + // 检测路由after行为 + if (!empty($option['after'])) { + $dispatch = $this->checkAfter($option['after']); + + if (false !== $dispatch) { + return $dispatch; + } + } + + // 数据自动验证 + if (isset($option['validate'])) { + $this->autoValidate($option['validate'], $request); + } + + // 发起路由调度 + return $this->dispatch($request, $route, $option); + } + + /** + * 验证数据 + * @access protected + * @param array $option + * @param \think\Request $request + * @return void + * @throws ValidateException + */ + protected function autoValidate($option, $request) + { + list($validate, $scene, $message, $batch) = $option; + + if (is_array($validate)) { + // 指定验证规则 + $v = Container::get('app')->validate(); + $v->rule($validate); + } else { + // 调用验证器 + $v = Container::get('app')->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } + } + + if (!empty($message)) { + $v->message($message); + } + + // 批量验证 + if ($batch) { + $v->batch(true); + } + + if (!$v->check($request->param())) { + throw new ValidateException($v->getError()); + } + } + + /** + * 检查路由前置行为 + * @access protected + * @param mixed $before 前置行为 + * @return mixed + */ + protected function checkBefore($before) + { + $hook = Container::get('hook'); + + foreach ((array) $before as $behavior) { + $result = $hook->exec($behavior); + + if (false === $result) { + return false; + } + } + } + + /** + * 检查路由后置行为 + * @access protected + * @param mixed $after 后置行为 + * @return mixed + */ + protected function checkAfter($after) + { + $hook = Container::get('hook'); + + $result = null; + + foreach ((array) $after as $behavior) { + $result = $hook->exec($behavior); + + if (!is_null($result)) { + break; + } + } + + // 路由规则重定向 + if ($result instanceof Response) { + return new ResponseDispatch($result); + } elseif ($result instanceof Dispatch) { + return $result; + } else { + return false; + } + } + + /** + * 发起路由调度 + * @access protected + * @param Request $request Request对象 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @return Dispatch + */ + protected function dispatch($request, $route, $option) + { + if ($route instanceof \Closure) { + // 执行闭包 + $result = new CallbackDispatch($route); + } elseif ($route instanceof Response) { + $result = new ResponseDispatch($route); + } elseif (isset($option['view']) && false !== $option['view']) { + $result = new ViewDispatch($route, is_array($option['view']) ? $option['view'] : []); + } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = new RedirectDispatch($route, [], isset($option['status']) ? $option['status'] : 301); + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + list($path, $var) = $this->parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = new CallbackDispatch($method, $var); + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $route = substr($route, 1); + list($route, $var) = $this->parseUrlPath($route); + $result = new ControllerDispatch(implode('/', $route), $var); + + $request->action(array_pop($route)); + $app = Container::get('app'); + $request->controller($route ? array_pop($route) : $app->config('default_controller')); + $request->module($route ? array_pop($route) : $app->config('default_module')); + $app->setModulePath($app->getAppPath() . ($app->config('app_multi_module') ? $request->module() . DIRECTORY_SEPARATOR : '')); + } else { + // 路由到模块/控制器/操作 + $result = $this->parseModule($route); + } + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param string $url URL地址 + * @return array + */ + protected function parseModule($url) + { + list($path, $var) = $this->parseUrlPath($url); + $config = Container::get('config'); + $request = Container::get('request'); + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = $config->get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = $request->method(); + + if ($config->get('use_action_prefix') && $this->router->getMethodPrefix($method)) { + $prefix = $this->router->getMethodPrefix($method); + // 操作方法前缀支持 + $action = 0 !== strpos($action, $prefix) ? $prefix . $action : $action; + } + + // 设置当前请求的路由变量 + $request->route($var); + + // 路由到模块/控制器/操作 + return (new ModuleDispatch([$module, $controller, $action]))->convert(false); + } + + /** + * 路由检查 + * @access protected + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + protected function checkOption($option, Request $request) + { + // 请求类型检测 + if (!empty($option['method'])) { + if (is_string($option['method']) && false === stripos($option['method'], $request->method())) { + return false; + } + } + + // AJAX PJAX 请求检查 + foreach (['ajax', 'pjax'] as $item) { + if (isset($option[$item])) { + $call = 'is' . $item; + if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) { + return false; + } + } + } + + // 伪静态后缀检测 + if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) { + return false; + } + + // 域名检查 + if ((isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], $request->subDomain()]))) { + return false; + } + + // HTTPS检查 + if ((isset($option['https']) && $option['https'] && !$request->isSsl()) + || (isset($option['https']) && !$option['https'] && $request->isSsl())) { + return false; + } + + return true; + } + + /** + * 解析URL地址中的参数Request对象 + * @access protected + * @param string $rule 路由规则 + * @param array $var 变量 + * @return void + */ + protected function parseUrlParams($url, &$var = []) + { + if ($url) { + if (Container::get('config')->get('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + + // 设置当前请求的参数 + Container::get('request')->route($var); + } + + /** + * 解析URL的pathinfo参数和变量 + * @access protected + * @param string $url URL地址 + * @return array + */ + protected function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } elseif (false !== strpos($url, '=')) { + // 参数1=值1&参数2=值2... + parse_str($url, $var); + } else { + $path = [$url]; + } + + return [$path, $var]; + } + + /** + * 设置路由参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return $this + */ + public function __call($method, $args) + { + if (count($args) > 1) { + $args[0] = $args; + } + array_unshift($args, $method); + + return call_user_func_array([$this, 'option'], $args); + } +} diff --git a/thinkphp/library/think/route/RuleGroup.php b/thinkphp/library/think/route/RuleGroup.php new file mode 100644 index 000000000..3f8317414 --- /dev/null +++ b/thinkphp/library/think/route/RuleGroup.php @@ -0,0 +1,291 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Request; +use think\Response; +use think\Route; +use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\Url as UrlDispatch; + +class RuleGroup extends Rule +{ + // 分组路由(包括子分组) + protected $rules = [ + '*' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'patch' => [], + 'delete' => [], + 'head' => [], + 'options' => [], + ]; + + protected $rule; + + // MISS路由 + protected $miss; + + // 自动路由 + protected $auto; + + // 完整名称 + protected $fullName; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $group 路由所属分组对象 + * @param string $name 分组名称 + * @param mixed $rule 分组路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, RuleGroup $group = null, $name = '', $rule = [], $option = [], $pattern = []) + { + $this->router = $router; + $this->parent = $group; + $this->rule = $rule; + $this->name = trim($name, '/'); + $this->option = $option; + $this->pattern = $pattern; + + $this->setFullName(); + } + + /** + * 设置分组的路由规则 + * @access public + * @return $this + */ + protected function setFullName() + { + if ($this->parent && $this->parent->getFullName()) { + $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); + } else { + $this->fullName = $this->name; + } + } + + /** + * 设置分组的路由规则 + * @access public + * @param mixed $rule 路由规则 + * @return $this + */ + public function setRule($rule) + { + $this->rule = $rule; + return $this; + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $depr = '/', $completeMatch = false) + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + if ($this->fullName) { + // 分组URL匹配检查 + $pos = strpos(str_replace('<', ':', $this->fullName), ':'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if (0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + if ($this->rule) { + // 延迟解析分组路由 + if ($this->rule instanceof Response) { + return new ResponseDispatch($this->rule); + } + + $group = $this->router->getGroup(); + + $this->router->setGroup($this); + + $this->router->parseGroupRule($this, $this->rule); + + $this->router->setGroup($group); + + $this->rule = null; + } + + // 分组匹配后执行的行为 + + // 指定Response响应数据 + if (!empty($this->option['response'])) { + Container::get('hook')->add('response_send', $this->option['response']); + } + + // 开启请求缓存 + if (isset($this->option['cache']) && $request->isGet()) { + $this->parseRequestCache($request, $this->option['cache']); + } + + // 获取当前路由规则 + $method = strtolower($request->method()); + $rules = array_merge($this->rules['*'], $this->rules[$method]); + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + } + + if (isset($this->option['complete_match'])) { + $completeMatch = $this->option['complete_match']; + } + + if (!empty($this->option['append'])) { + $request->route($this->option['append']); + } + + if (isset($rules[$url])) { + // 快速定位 + $item = $rules[$url]; + $result = $item->check($request, $url, $depr, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + // 遍历分组路由 + foreach ($rules as $key => $item) { + $result = $item->check($request, $url, $depr, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + if (isset($this->auto)) { + // 自动解析URL地址 + $result = new UrlDispatch($this->auto->getRoute() . '/' . $url, ['depr' => $depr, 'auto_search' => false]); + } elseif (isset($this->miss)) { + // 未匹配所有路由的路由规则处理 + $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); + } else { + $result = false; + } + + return $result; + } + + /** + * 设置自动路由 + * @access public + * @param RuleItem $rule 路由规则 + * @return $this + */ + public function setAutoRule(RuleItem $rule) + { + $this->auto = $rule; + return $this; + } + + /** + * 设置为MISS路由 + * @access public + * @param RuleItem $rule 路由规则 + * @return $this + */ + public function setMissRule(RuleItem $rule) + { + $this->miss = $rule; + return $this; + } + + /** + * 添加分组下的路由规则或者子分组 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function addRule($rule, $method = '*') + { + if (strpos($method, '|')) { + $rule->method($method); + $method = '*'; + } + + $this->rules[$method][] = $rule; + + return $this; + } + + /** + * 设置分组的路由前缀 + * @access public + * @param string $prefix + * @return $this + */ + public function prefix($prefix) + { + if ($this->parent->getOption('prefix')) { + $prefix = $this->parent->getOption('prefix') . $prefix; + } + + return $this->option('prefix', $prefix); + } + + /** + * 获取完整分组Name + * @access public + * @return string + */ + public function getFullName() + { + return $this->fullName; + } + + /** + * 获取分组的路由规则 + * @access public + * @param string $method + * @return array + */ + public function getRules($method = '') + { + if ('' === $method) { + return $this->rules; + } else { + return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : []; + } + } + +} diff --git a/thinkphp/library/think/route/RuleItem.php b/thinkphp/library/think/route/RuleItem.php new file mode 100644 index 000000000..e26637f62 --- /dev/null +++ b/thinkphp/library/think/route/RuleItem.php @@ -0,0 +1,298 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Route; + +class RuleItem extends Rule +{ + /** + * 路由规则 + * @var string + */ + protected $name; + + /** + * 路由地址 + * @var string|\Closure + */ + protected $route; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $group 路由所属分组对象 + * @param string|array $name 路由规则 + * @param string $method 请求类型 + * @param string|\Closure $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, RuleGroup $group, $name, $route, $method = '*', $option = [], $pattern = []) + { + $this->router = $router; + $this->parent = $group; + $this->route = $route; + $this->method = $method; + $this->option = $option; + $this->pattern = $pattern; + + $this->setRule($name); + } + + /** + * 路由规则预处理 + * @access public + * @param string $rule 路由规则 + * @return void + */ + public function setRule($rule) + { + if ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $rule = substr($rule, 0, -1); + + $this->option['complete_match'] = true; + } + + $this->name($rule); + } + + /** + * 获取当前路由地址 + * @access public + * @return mixed + */ + public function getRoute() + { + return $this->route; + } + + /** + * 设置为自动路由 + * @access public + * @return $this + */ + public function isAuto() + { + $this->parent->setAutoRule($this); + return $this; + } + + /** + * 设置为MISS路由 + * @access public + * @return $this + */ + public function isMiss() + { + $this->parent->setMissRule($this); + return $this; + } + + /** + * 检测路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch + */ + public function check($request, $url, $depr = '/', $completeMatch = false) + { + if ($this->parent && $prefix = $this->parent->getFullName()) { + $this->name = $prefix . ($this->name ? '/' . ltrim($this->name, '/') : ''); + } + + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + // 合并分组参数 + $this->mergeGroupOptions(); + $option = $this->option; + + if (!empty($option['append'])) { + $request->route($option['append']); + } + + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->name) { + $this->name = rtrim($this->name, '/'); + $url = rtrim($url, '|'); + } + + // 检查前置行为 + if (isset($option['before']) && false === $this->checkBefore($option['before'])) { + return false; + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + return $this->checkRule($request, $url, $depr, $completeMatch, $option); + } + + /** + * 检测路由规则 + * @access private + * @param Request $request 请求对象 + * @param string $url URL地址 + * @param string $depr URL分隔符(全局) + * @param bool $completeMatch 路由是否完全匹配 + * @param array $option 路由参数 + * @return array|false + */ + private function checkRule($request, $url, $depr, $completeMatch = false, $option = []) + { + // 检查完整规则定义 + if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); + } + + $len1 = substr_count($url, '|'); + $len2 = substr_count($this->name, '/'); + + // 多余参数是否合并 + $merge = !empty($option['merge_extra_vars']) ? true : false; + + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } + + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + if ($len1 >= $len2 || strpos($this->name, '[')) { + // 完整匹配 + if ($completeMatch && (!$merge && $len1 != $len2 && (false === strpos($this->name, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->name, '[')))) { + return false; + } + + $pattern = array_merge($this->parent->getPattern(), $this->pattern); + + if (false !== $match = $this->match($url, $pattern)) { + // 匹配到路由规则 + return $this->parseRule($request, $this->name, $this->route, $url, $option, $match); + } + } + + return false; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param array $pattern 变量规则 + * @return array|false + */ + private function match($url, $pattern) + { + $m2 = explode('/', $this->name); + $m1 = explode('|', $url); + + $var = []; + + foreach ($m2 as $key => $val) { + // val中定义了多个变量 + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + $value = []; + $replace = []; + + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; + } else { + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; + } + $value[] = $name; + } + + $val = str_replace($matches[0], $replace, $val); + + if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { + array_shift($match); + foreach ($value as $k => $name) { + if (isset($match[$k])) { + $var[$name] = $match[$k]; + } + } + continue; + } else { + return false; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $val = substr($val, 1, -1); + $optional = true; + } else { + $optional = false; + } + + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + + if (!$optional && !isset($m1[$key])) { + return false; + } + + if (isset($m1[$key]) && isset($pattern[$name])) { + // 检查变量规则 + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } + } + + $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; + } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { + return false; + } + } + + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + +} diff --git a/thinkphp/library/think/route/dispatch/Callback.php b/thinkphp/library/think/route/dispatch/Callback.php new file mode 100644 index 000000000..7168228c3 --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Callback.php @@ -0,0 +1,27 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Container; +use think\route\Dispatch; + +class Callback extends Dispatch +{ + public function run() + { + // 执行回调方法 + $vars = array_merge($this->app['request']->param(), $this->param); + + return Container::getInstance()->invoke($this->dispatch, $vars); + } + +} diff --git a/thinkphp/library/think/route/dispatch/Controller.php b/thinkphp/library/think/route/dispatch/Controller.php new file mode 100644 index 000000000..b48bccde2 --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Controller.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\route\Dispatch; + +class Controller extends Dispatch +{ + public function run() + { + // 执行控制器的操作方法 + $vars = array_merge($this->app['request']->param(), $this->param); + + return $this->app->action( + $this->dispatch, $vars, + $this->app->config('app.url_controller_layer'), + $this->app->config('app.controller_suffix') + ); + } + +} diff --git a/thinkphp/library/think/route/dispatch/Module.php b/thinkphp/library/think/route/dispatch/Module.php new file mode 100644 index 000000000..20bb812df --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Module.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Container; +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\Loader; +use think\route\Dispatch; + +class Module extends Dispatch +{ + public function run() + { + $result = $this->dispatch; + + if (is_string($result)) { + $result = explode('/', $result); + } + + if ($this->app->config('app.app_multi_module')) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $this->app->config('app.default_module'))); + $bind = $this->app['route']->getBind(); + $available = false; + + if ($bind && preg_match('/^[a-z]/is', $bind)) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + if (empty($result[0])) { + $module = $bindModule; + } + $available = true; + } elseif (!in_array($module, $this->app->config('app.deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { + $available = true; + } elseif ($this->app->config('app.empty_module')) { + $module = $this->app->config('app.empty_module'); + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $this->app['request']->module($module); + $this->app->init($module); + + // 加载当前模块语言包 + $this->app['lang']->load($this->app->getAppPath() . $module . '/lang/' . $this->app['request']->langset() . '.php'); + + // 模块请求缓存检查 + $this->app['request']->cache( + $this->app->config('app.request_cache'), + $this->app->config('app.request_cache_expire'), + $this->app->config('app.request_cache_except') + ); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } else { + // 单一模块部署 + $module = ''; + $this->app['request']->module($module); + } + + // 当前模块路径 + $this->app->setModulePath($this->app->getAppPath() . ($module ? $module . '/' : '')); + + // 是否自动转换控制器和操作名 + $convert = is_bool($this->convert) ? $this->convert : $this->app->config('app.url_convert'); + // 获取控制器名 + $controller = strip_tags($result[1] ?: $this->app->config('app.default_controller')); + $controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $actionName = strip_tags($result[2] ?: $this->app->config('app.default_action')); + $actionName = $convert ? strtolower($actionName) : $actionName; + + // 设置当前请求的控制器、操作 + $this->app['request']->controller(Loader::parseName($controller, 1))->action($actionName); + + // 监听module_init + $this->app['hook']->listen('module_init'); + + // 实例化控制器 + try { + $instance = $this->app->controller($controller, + $this->app->config('app.url_controller_layer'), + $this->app->config('app.controller_suffix'), + $this->app->config('app.empty_controller')); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 获取当前操作名 + $action = $actionName . $this->app->config('app.action_suffix'); + + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + // 自动获取请求变量 + $vars = $this->app->config('app.url_param_type') + ? $this->app['request']->route() + : $this->app['request']->param(); + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + $this->app['hook']->listen('action_begin', $call); + + return Container::getInstance()->invokeMethod($call, $vars); + } +} diff --git a/thinkphp/console.php b/thinkphp/library/think/route/dispatch/Redirect.php similarity index 54% rename from thinkphp/console.php rename to thinkphp/library/think/route/dispatch/Redirect.php index 578e4a7c4..1a9208b0c 100644 --- a/thinkphp/console.php +++ b/thinkphp/library/think/route/dispatch/Redirect.php @@ -1,20 +1,23 @@ +// | Author: liu21st // +---------------------------------------------------------------------- -namespace think; +namespace think\route\dispatch; -// ThinkPHP 引导文件 -// 加载基础文件 -require __DIR__ . '/base.php'; +use think\Response; +use think\route\Dispatch; -// 执行应用 -App::initCommon(); -Console::init(); +class Redirect extends Dispatch +{ + public function run() + { + return Response::create($this->dispatch, 'redirect')->code($this->code); + } +} diff --git a/thinkphp/start.php b/thinkphp/library/think/route/dispatch/Response.php similarity index 78% rename from thinkphp/start.php rename to thinkphp/library/think/route/dispatch/Response.php index adb1bc65c..4b5fa16e5 100644 --- a/thinkphp/start.php +++ b/thinkphp/library/think/route/dispatch/Response.php @@ -9,11 +9,15 @@ // | Author: liu21st // +---------------------------------------------------------------------- -namespace think; +namespace think\route\dispatch; -// ThinkPHP 引导文件 -// 1. 加载基础文件 -require __DIR__ . '/base.php'; +use think\route\Dispatch; -// 2. 执行应用 -App::run()->send(); +class Response extends Dispatch +{ + public function run() + { + return $this->dispatch; + } + +} diff --git a/thinkphp/library/think/route/dispatch/Url.php b/thinkphp/library/think/route/dispatch/Url.php new file mode 100644 index 000000000..1d5b9d01a --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Url.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\exception\HttpException; +use think\Loader; +use think\route\Dispatch; + +class Url extends Dispatch +{ + public function run() + { + // 解析默认的URL规则 + $url = str_replace($this->param['depr'], '|', $this->dispatch); + $result = $this->parseUrl($url); + + return (new Module($result))->run(); + } + + /** + * 解析URL地址 + * @access protected + * @param string $url URL + * @return array + */ + protected function parseUrl($url) + { + $router = $this->app['route']; + $bind = $router->getBind(); + $depr = $this->param['depr']; + + if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { + $bind = str_replace('/', $depr, $bind); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + + list($path, $var) = $this->parseUrlPath($url); + if (empty($path)) { + return [null, null, null]; + } + + // 解析模块 + $module = $this->app->config('app_multi_module') ? array_shift($path) : null; + if ($this->param['auto_search']) { + $controller = $this->autoFindController($module, $path); + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + + // 解析额外参数 + if ($path) { + if ($this->app['config']->get('url_param_type')) { + $var += $path; + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, implode('|', $path)); + } + } + + $panDomain = $this->app['request']->panDomain(); + if ($panDomain && $key = array_search('*', $var)) { + // 泛域名赋值 + $var[$key] = $panDomain; + } + + // 设置当前请求的参数 + $this->app['request']->route($var); + + // 封装路由 + $route = [$module, $controller, $action]; + + if ($this->hasDefinedRoute($route, $bind)) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + + return $route; + } + + /** + * 检查URL是否已经定义过路由 + * @access protected + * @param string $route 路由信息 + * @param string $bind 绑定信息 + * @return bool + */ + protected function hasDefinedRoute($route, $bind) + { + list($module, $controller, $action) = $route; + + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + + $name2 = ''; + + if (empty($module) || $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + $router = $this->app['route']; + + if ($router->getName($name) || $router->getName($name2)) { + return true; + } + + return false; + } + + /** + * 自动定位控制器类 + * @access protected + * @param string $module 模块名 + * @param array $path URL + * @return string + */ + protected function autoFindController($module, &$path) + { + $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->app->config('url_controller_layer'); + $suffix = $this->app->getSuffix() || $this->app->config('controller_suffix') ? ucfirst($this->app->config('url_controller_layer')) : ''; + + $item = []; + $find = false; + + foreach ($path as $val) { + $item[] = $val; + $file = $dir . '/' . str_replace('.', '/', $val) . $suffix . '.php'; + $file = pathinfo($file, PATHINFO_DIRNAME) . '/' . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . '.php'; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= '/' . Loader::parseName($val); + } + } + + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + + return $controller; + } + + /** + * 解析URL的pathinfo参数和变量 + * @access private + * @param string $url URL地址 + * @return array + */ + private function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } elseif (false !== strpos($url, '=')) { + // 参数1=值1&参数2=值2... + parse_str($url, $var); + } else { + $path = [$url]; + } + + return [$path, $var]; + } +} diff --git a/thinkphp/library/think/route/dispatch/View.php b/thinkphp/library/think/route/dispatch/View.php new file mode 100644 index 000000000..85ef9d283 --- /dev/null +++ b/thinkphp/library/think/route/dispatch/View.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Container; +use think\route\Dispatch; + +class View extends Dispatch +{ + public function run() + { + // 渲染模板输出 + $vars = array_merge($this->app['request']->param(), $this->param); + + return Container::get('view') + ->init(Container::get('config')->pull('template')) + ->fetch($this->dispatch, $vars); + } +} diff --git a/thinkphp/library/think/session/driver/Memcache.php b/thinkphp/library/think/session/driver/Memcache.php index 877d7bd77..40d7bb825 100644 --- a/thinkphp/library/think/session/driver/Memcache.php +++ b/thinkphp/library/think/session/driver/Memcache.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Memcache extends SessionHandler +class Memcache implements SessionHandlerInterface { protected $handler = null; protected $config = [ @@ -34,8 +34,8 @@ class Memcache extends SessionHandler /** * 打开Session * @access public - * @param string $savePath - * @param mixed $sessName + * @param string $savePath + * @param mixed $sessName */ public function open($savePath, $sessName) { @@ -43,13 +43,17 @@ class Memcache extends SessionHandler if (!extension_loaded('memcache')) { throw new Exception('not support:memcache'); } + $this->handler = new \Memcache; + // 支持集群 $hosts = explode(',', $this->config['host']); $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 foreach ((array) $hosts as $i => $host) { $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; @@ -57,6 +61,7 @@ class Memcache extends SessionHandler $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : $this->handler->addServer($host, $port, $this->config['persistent'], 1); } + return true; } @@ -69,13 +74,14 @@ class Memcache extends SessionHandler $this->gc(ini_get('session.gc_maxlifetime')); $this->handler->close(); $this->handler = null; + return true; } /** * 读取Session * @access public - * @param string $sessID + * @param string $sessID */ public function read($sessID) { @@ -85,8 +91,8 @@ class Memcache extends SessionHandler /** * 写入Session * @access public - * @param string $sessID - * @param String $sessData + * @param string $sessID + * @param string $sessData * @return bool */ public function write($sessID, $sessData) @@ -97,7 +103,7 @@ class Memcache extends SessionHandler /** * 删除Session * @access public - * @param string $sessID + * @param string $sessID * @return bool */ public function destroy($sessID) @@ -108,7 +114,7 @@ class Memcache extends SessionHandler /** * Session 垃圾回收 * @access public - * @param string $sessMaxLifeTime + * @param string $sessMaxLifeTime * @return true */ public function gc($sessMaxLifeTime) diff --git a/thinkphp/library/think/session/driver/Memcached.php b/thinkphp/library/think/session/driver/Memcached.php index 2994a07c0..074b2ff73 100644 --- a/thinkphp/library/think/session/driver/Memcached.php +++ b/thinkphp/library/think/session/driver/Memcached.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Memcached extends SessionHandler +class Memcached implements SessionHandlerInterface { protected $handler = null; protected $config = [ @@ -35,8 +35,8 @@ class Memcached extends SessionHandler /** * 打开Session * @access public - * @param string $savePath - * @param mixed $sessName + * @param string $savePath + * @param mixed $sessName */ public function open($savePath, $sessName) { @@ -44,27 +44,35 @@ class Memcached extends SessionHandler if (!extension_loaded('memcached')) { throw new Exception('not support:memcached'); } + $this->handler = new \Memcached; + // 设置连接超时时间(单位:毫秒) if ($this->config['timeout'] > 0) { $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); } + // 支持集群 $hosts = explode(',', $this->config['host']); $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 $servers = []; foreach ((array) $hosts as $i => $host) { $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; } + $this->handler->addServers($servers); + if ('' != $this->config['username']) { $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); } + return true; } @@ -77,13 +85,14 @@ class Memcached extends SessionHandler $this->gc(ini_get('session.gc_maxlifetime')); $this->handler->quit(); $this->handler = null; + return true; } /** * 读取Session * @access public - * @param string $sessID + * @param string $sessID */ public function read($sessID) { @@ -93,8 +102,8 @@ class Memcached extends SessionHandler /** * 写入Session * @access public - * @param string $sessID - * @param String $sessData + * @param string $sessID + * @param string $sessData * @return bool */ public function write($sessID, $sessData) @@ -105,7 +114,7 @@ class Memcached extends SessionHandler /** * 删除Session * @access public - * @param string $sessID + * @param string $sessID * @return bool */ public function destroy($sessID) @@ -116,7 +125,7 @@ class Memcached extends SessionHandler /** * Session 垃圾回收 * @access public - * @param string $sessMaxLifeTime + * @param string $sessMaxLifeTime * @return true */ public function gc($sessMaxLifeTime) diff --git a/thinkphp/library/think/session/driver/Redis.php b/thinkphp/library/think/session/driver/Redis.php index 8d05126bf..e09988ee1 100644 --- a/thinkphp/library/think/session/driver/Redis.php +++ b/thinkphp/library/think/session/driver/Redis.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Redis extends SessionHandler +class Redis implements SessionHandlerInterface { /** @var \Redis */ protected $handler = null; @@ -37,8 +37,8 @@ class Redis extends SessionHandler /** * 打开Session * @access public - * @param string $savePath - * @param mixed $sessName + * @param string $savePath + * @param mixed $sessName * @return bool * @throws Exception */ @@ -48,6 +48,7 @@ class Redis extends SessionHandler if (!extension_loaded('redis')) { throw new Exception('not support:redis'); } + $this->handler = new \Redis; // 建立连接 @@ -74,13 +75,14 @@ class Redis extends SessionHandler $this->gc(ini_get('session.gc_maxlifetime')); $this->handler->close(); $this->handler = null; + return true; } /** * 读取Session * @access public - * @param string $sessID + * @param string $sessID * @return string */ public function read($sessID) @@ -91,8 +93,8 @@ class Redis extends SessionHandler /** * 写入Session * @access public - * @param string $sessID - * @param String $sessData + * @param string $sessID + * @param string $sessData * @return bool */ public function write($sessID, $sessData) @@ -107,7 +109,7 @@ class Redis extends SessionHandler /** * 删除Session * @access public - * @param string $sessID + * @param string $sessID * @return bool */ public function destroy($sessID) @@ -118,11 +120,50 @@ class Redis extends SessionHandler /** * Session 垃圾回收 * @access public - * @param string $sessMaxLifeTime + * @param string $sessMaxLifeTime * @return bool */ public function gc($sessMaxLifeTime) { return true; } + + /** + * Redis Session 驱动的加锁机制 + * @access public + * @param string $sessID 用于加锁的sessID + * @param integer $timeout 默认过期时间 + * @return bool + */ + public function lock($sessID, $timeout = 10) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $lockKey = 'LOCK_PREFIX_' . $sessID; + // 使用setnx操作加锁 + $isLock = $this->handler->setnx($lockKey, 1); + if ($isLock) { + // 设置过期时间,防止死任务的出现 + $this->handler->expire($lockKey, $timeout); + return true; + } + + return false; + } + + /** + * Redis Session 驱动的解锁机制 + * @access public + * @param string $sessID 用于解锁的sessID + */ + public function unlock($sessID) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $this->handler->del('LOCK_PREFIX_' . $sessID); + } } diff --git a/thinkphp/library/think/template/TagLib.php b/thinkphp/library/think/template/TagLib.php index c5b72f916..3653b7d2f 100644 --- a/thinkphp/library/think/template/TagLib.php +++ b/thinkphp/library/think/template/TagLib.php @@ -68,9 +68,9 @@ class TagLib protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; /** - * 构造函数 + * 架构函数 * @access public - * @param \stdClass $template 模板引擎对象 + * @param \stdClass $template 模板引擎对象 */ public function __construct($template) { @@ -88,6 +88,7 @@ class TagLib { $tags = []; $lib = $lib ? strtolower($lib) . ':' : ''; + foreach ($this->tags as $name => $val) { $close = !isset($val['close']) || $val['close'] ? 1 : 0; $tags[$close][$lib . $name] = $name; @@ -136,11 +137,14 @@ class TagLib // 对应的标签名 $name = $tags[1][$node['name']]; $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + // 解析标签属性 $attrs = $this->parseAttr($node['begin'][0], $name, $alias); $method = 'tag' . $name; + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 $replace = explode($break, $this->$method($attrs, $break)); + if (count($replace) > 1) { while ($beginArray) { $begin = end($beginArray); @@ -160,6 +164,7 @@ class TagLib $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; } } + while ($beginArray) { $begin = array_pop($beginArray); // 替换标签头部 @@ -180,12 +185,13 @@ class TagLib return $this->$method($attrs, ''); }, $content); } + return; } /** * 按标签生成正则 - * @access private + * @access public * @param array|string $tags 标签名 * @param boolean $close 是否为闭合标签 * @return string @@ -196,6 +202,7 @@ class TagLib $end = $this->tpl->config('taglib_end'); $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; $tagName = is_array($tags) ? implode('|', $tags) : $tags; + if ($single) { if ($close) { // 如果是闭合标签 @@ -211,25 +218,28 @@ class TagLib $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; } } + return '/' . $regex . '/is'; } /** * 分析标签属性 正则方式 * @access public - * @param string $str 标签属性字符串 - * @param string $name 标签名 - * @param string $alias 别名 + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 * @return array */ public function parseAttr($str, $name, $alias = '') { $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; $result = []; + if (preg_match_all($regex, $str, $matches)) { foreach ($matches['name'] as $key => $val) { $result[$val] = $matches['value'][$key]; } + if (!isset($this->tags[$name])) { // 检测是否存在别名定义 foreach ($this->tags as $key => $val) { @@ -251,6 +261,7 @@ class TagLib $result[$type] = $alias; } } + if (!empty($tag['must'])) { $must = explode(',', $tag['must']); foreach ($must as $name) { @@ -275,6 +286,7 @@ class TagLib throw new Exception('tag error:' . $name); } } + return $result; } @@ -289,8 +301,10 @@ class TagLib if (strpos($condition, ':')) { $condition = ' ' . substr(strstr($condition, ':'), 1); } + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); $this->tpl->parseVar($condition); + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 return $condition; } @@ -298,12 +312,13 @@ class TagLib /** * 自动识别构建变量 * @access public - * @param string $name 变量描述 + * @param string $name 变量描述 * @return string */ public function autoBuildVar(&$name) { $flag = substr($name, 0, 1); + if (':' == $flag) { // 以:开头为函数调用,解析前去掉: $name = substr($name, 1); @@ -313,11 +328,14 @@ class TagLib if (defined($name)) { return $name; } + // 不以$开头并且也不是常量,自动补上$前缀 $name = '$' . $name; } + $this->tpl->parseVar($name); - $this->tpl->parseVarFunction($name); + $this->tpl->parseVarFunction($name, false); + return $name; } @@ -326,7 +344,6 @@ class TagLib * @access public * @return array */ - // 获取标签定义 public function getTags() { return $this->tags; diff --git a/thinkphp/library/think/template/driver/File.php b/thinkphp/library/think/template/driver/File.php index bf3d22af0..95a614039 100644 --- a/thinkphp/library/think/template/driver/File.php +++ b/thinkphp/library/think/template/driver/File.php @@ -17,17 +17,20 @@ class File { /** * 写入编译缓存 - * @param string $cacheFile 缓存的文件名 - * @param string $content 缓存的内容 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 * @return void|array */ public function write($cacheFile, $content) { // 检测模板目录 $dir = dirname($cacheFile); + if (!is_dir($dir)) { mkdir($dir, 0755, true); } + // 生成模板缓存文件 if (false === file_put_contents($cacheFile, $content)) { throw new Exception('cache write error:' . $cacheFile, 11602); @@ -36,8 +39,9 @@ class File /** * 读取编译编译 - * @param string $cacheFile 缓存的文件名 - * @param array $vars 变量数组 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 * @return void */ public function read($cacheFile, $vars = []) @@ -46,14 +50,16 @@ class File // 模板阵列变量分解成为独立变量 extract($vars, EXTR_OVERWRITE); } + //载入模版缓存文件 include $cacheFile; } /** * 检查编译缓存是否有效 - * @param string $cacheFile 缓存的文件名 - * @param int $cacheTime 缓存时间 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 * @return boolean */ public function check($cacheFile, $cacheTime) @@ -62,10 +68,12 @@ class File if (!file_exists($cacheFile)) { return false; } - if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { + + if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) { // 缓存是否在有效期 return false; } + return true; } } diff --git a/thinkphp/library/think/template/taglib/Cx.php b/thinkphp/library/think/template/taglib/Cx.php index 31e0698da..ad741f289 100644 --- a/thinkphp/library/think/template/taglib/Cx.php +++ b/thinkphp/library/think/template/taglib/Cx.php @@ -56,8 +56,8 @@ class Cx extends Taglib * 格式: * {php}echo $name{/php} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagPhp($tag, $content) @@ -74,8 +74,8 @@ class Cx extends Taglib * {user.email} * {/volist} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string|void */ public function tagVolist($tag, $content) @@ -90,6 +90,7 @@ class Cx extends Taglib // 允许使用函数设定数据集 {$vo.name} $parseStr = 'autoBuildVar($name); $parseStr .= '$_result=' . $name . ';'; @@ -99,12 +100,14 @@ class Cx extends Taglib } $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; } else { $parseStr .= ' $__LIST__ = ' . $name . ';'; } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; $parseStr .= 'else: '; $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; @@ -116,6 +119,7 @@ class Cx extends Taglib if (!empty($parseStr)) { return $parseStr; } + return; } @@ -126,8 +130,8 @@ class Cx extends Taglib * {user.username} * {/foreach} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string|void */ public function tagForeach($tag, $content) @@ -141,6 +145,7 @@ class Cx extends Taglib $parseStr .= ''; return $parseStr; } + $name = $tag['name']; $key = !empty($tag['key']) ? $tag['key'] : 'key'; $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; @@ -149,6 +154,7 @@ class Cx extends Taglib $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; $parseStr = 'autoBuildVar($name); } + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { if (!isset($var)) { @@ -177,7 +185,9 @@ class Cx extends Taglib $index = $tag['index']; $parseStr .= '$' . $index . '=0; '; } + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + // 设置了索引项 if (isset($tag['index'])) { $index = $tag['index']; @@ -187,6 +197,7 @@ class Cx extends Taglib } $parseStr .= '++$' . $index . '; '; } + $parseStr .= '?>'; // 循环体中的内容 $parseStr .= $content; @@ -195,6 +206,7 @@ class Cx extends Taglib if (!empty($parseStr)) { return $parseStr; } + return; } @@ -207,8 +219,8 @@ class Cx extends Taglib * {/if} * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagIf($tag, $content) @@ -216,6 +228,7 @@ class Cx extends Taglib $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; $condition = $this->parseCondition($condition); $parseStr = '' . $content . ''; + return $parseStr; } @@ -223,8 +236,8 @@ class Cx extends Taglib * elseif标签解析 * 格式:见if标签 * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagElseif($tag, $content) @@ -232,6 +245,7 @@ class Cx extends Taglib $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; $condition = $this->parseCondition($condition); $parseStr = ''; + return $parseStr; } @@ -239,12 +253,13 @@ class Cx extends Taglib * else标签解析 * 格式:见if标签 * @access public - * @param array $tag 标签属性 + * @param array $tag 标签属性 * @return string */ public function tagElse($tag) { $parseStr = ''; + return $parseStr; } @@ -257,8 +272,8 @@ class Cx extends Taglib * {default /}other * {/switch} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagSwitch($tag, $content) @@ -266,20 +281,22 @@ class Cx extends Taglib $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; $name = $this->autoBuildVar($name); $parseStr = '' . $content . ''; + return $parseStr; } /** * case标签解析 需要配合switch才有效 * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagCase($tag, $content) { $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($value); $value = 'case ' . $value . ':'; @@ -292,11 +309,14 @@ class Cx extends Taglib } else { $value = 'case "' . $value . '":'; } + $parseStr = '' . $content; $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if ('' == $isBreak || $isBreak) { $parseStr .= ''; } + return $parseStr; } @@ -304,13 +324,14 @@ class Cx extends Taglib * default标签解析 需要配合switch才有效 * 使用: {default /}ddfdf * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagDefault($tag) { $parseStr = ''; + return $parseStr; } @@ -319,8 +340,8 @@ class Cx extends Taglib * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq * 格式: {compare name="" type="eq" value="" }content{/compare} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagCompare($tag, $content) @@ -330,11 +351,13 @@ class Cx extends Taglib $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 $name = $this->autoBuildVar($name); $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($value); } else { $value = '\'' . $value . '\''; } + switch ($type) { case 'equal': $type = 'eq'; @@ -345,6 +368,7 @@ class Cx extends Taglib } $type = $this->parseCondition(' ' . $type . ' '); $parseStr = '' . $content . ''; + return $parseStr; } @@ -354,8 +378,8 @@ class Cx extends Taglib * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} * example: {range name="a" value="1,2,3" type='in' }content{/range} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagRange($tag, $content) @@ -366,6 +390,7 @@ class Cx extends Taglib $name = $this->autoBuildVar($name); $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($value); $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; @@ -373,6 +398,7 @@ class Cx extends Taglib $value = '"' . $value . '"'; $str = 'explode(\',\',' . $value . ')'; } + if ('between' == $type) { $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; } elseif ('notbetween' == $type) { @@ -381,6 +407,7 @@ class Cx extends Taglib $fun = ('in' == $type) ? 'in_array' : '!in_array'; $parseStr = '' . $content . ''; } + return $parseStr; } @@ -389,8 +416,8 @@ class Cx extends Taglib * 如果某个变量已经设置 则输出内容 * 格式: {present name="" }content{/present} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagPresent($tag, $content) @@ -398,6 +425,7 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = '' . $content . ''; + return $parseStr; } @@ -406,8 +434,8 @@ class Cx extends Taglib * 如果某个变量没有设置,则输出内容 * 格式: {notpresent name="" }content{/notpresent} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagNotpresent($tag, $content) @@ -415,6 +443,7 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = '' . $content . ''; + return $parseStr; } @@ -423,8 +452,8 @@ class Cx extends Taglib * 如果某个变量为empty 则输出内容 * 格式: {empty name="" }content{/empty} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagEmpty($tag, $content) @@ -432,6 +461,7 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = 'isEmpty())): ?>' . $content . ''; + return $parseStr; } @@ -440,8 +470,8 @@ class Cx extends Taglib * 如果某个变量不为empty 则输出内容 * 格式: {notempty name="" }content{/notempty} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagNotempty($tag, $content) @@ -449,34 +479,39 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = 'isEmpty()))): ?>' . $content . ''; + return $parseStr; } /** * 判断是否已经定义了该常量 * {defined name='TXT'}已定义{/defined} - * @param array $tag - * @param string $content + * @access public + * @param array $tag + * @param string $content * @return string */ public function tagDefined($tag, $content) { $name = $tag['name']; $parseStr = '' . $content . ''; + return $parseStr; } /** * 判断是否没有定义了该常量 * {notdefined name='TXT'}已定义{/notdefined} - * @param array $tag - * @param string $content + * @access public + * @param array $tag + * @param string $content * @return string */ public function tagNotdefined($tag, $content) { $name = $tag['name']; $parseStr = '' . $content . ''; + return $parseStr; } @@ -484,16 +519,18 @@ class Cx extends Taglib * load 标签解析 {load file="/static/js/base.js" /} * 格式:{load file="/static/css/base.css" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagLoad($tag, $content) { - $file = isset($tag['file']) ? $tag['file'] : $tag['href']; - $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + $parseStr = ''; $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) if (isset($tag['value'])) { $name = $tag['value']; @@ -505,6 +542,7 @@ class Cx extends Taglib // 文件方式导入 $array = explode(',', $file); + foreach ($array as $val) { $type = strtolower(substr(strrchr($val, '.'), 1)); switch ($type) { @@ -519,6 +557,7 @@ class Cx extends Taglib break; } } + return $parseStr . $endStr; } @@ -527,20 +566,23 @@ class Cx extends Taglib * 在模板中给某个变量赋值 支持变量赋值 * 格式: {assign name="" value="" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagAssign($tag, $content) { $name = $this->autoBuildVar($tag['name']); $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($tag['value']); } else { $value = '\'' . $tag['value'] . '\''; } + $parseStr = ''; + return $parseStr; } @@ -549,20 +591,23 @@ class Cx extends Taglib * 在模板中定义常量 支持变量赋值 * 格式: {define name="" value="" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagDefine($tag, $content) { $name = '\'' . $tag['name'] . '\''; $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($tag['value']); } else { $value = '\'' . $tag['value'] . '\''; } + $parseStr = ''; + return $parseStr; } @@ -573,8 +618,8 @@ class Cx extends Taglib * content * {/for} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagFor($tag, $content) @@ -586,6 +631,7 @@ class Cx extends Taglib $comparison = 'lt'; $name = 'i'; $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 foreach ($tag as $key => $value) { $value = trim($value); @@ -617,6 +663,7 @@ class Cx extends Taglib $parseStr .= 'for($' . $name . '=$__FOR_START_' . $rand . '__;' . $this->parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; $parseStr .= $content; $parseStr .= ''; + return $parseStr; } @@ -624,8 +671,8 @@ class Cx extends Taglib * url函数的tag标签 * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagUrl($tag, $content) @@ -634,6 +681,7 @@ class Cx extends Taglib $vars = isset($tag['vars']) ? $tag['vars'] : ''; $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + return ''; } @@ -650,8 +698,8 @@ class Cx extends Taglib * {/if} * {/function} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagFunction($tag, $content) @@ -660,14 +708,17 @@ class Cx extends Taglib $vars = !empty($tag['vars']) ? $tag['vars'] : ''; $call = !empty($tag['call']) ? $tag['call'] : ''; $use = ['&$' . $name]; + if (!empty($tag['use'])) { foreach (explode(',', $tag['use']) as $val) { $use[] = '&' . ltrim(trim($val), '&'); } } + $parseStr = '' . $content . '' : '?>'; + return $parseStr; } } diff --git a/thinkphp/library/think/validate/ValidateRule.php b/thinkphp/library/think/validate/ValidateRule.php new file mode 100644 index 000000000..5253465fa --- /dev/null +++ b/thinkphp/library/think/validate/ValidateRule.php @@ -0,0 +1,171 @@ + +// +---------------------------------------------------------------------- + +namespace think\validate; + +/** + * Class ValidateRule + * @package think\validate + * @method ValidateRule confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateRule different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateRule egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateRule gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateRule elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateRule notIn(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateRule max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateRule min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateRule after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateRule allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateRule denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isRequire(mixed $rule, string $msg = '') static 验证字段必须 + * @method ValidateRule isNumber(mixed $rule, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isArray(mixed $rule, string $msg = '') static 验证字段值是否为数组 + * @method ValidateRule isInteger(mixed $rule, string $msg = '') static 验证字段值是否为整形 + * @method ValidateRule isFloat(mixed $rule, string $msg = '') static 验证字段值是否为浮点数 + * @method ValidateRule isMobile(mixed $rule, string $msg = '') static 验证字段值是否为手机 + * @method ValidateRule isIdCard(mixed $rule, string $msg = '') static 验证字段值是否为身份证号码 + * @method ValidateRule isChs(mixed $rule, string $msg = '') static 验证字段值是否为中文 + * @method ValidateRule isChsDash(mixed $rule, string $msg = '') static 验证字段值是否为中文字母及下划线 + * @method ValidateRule isChsAlpha(mixed $rule, string $msg = '') static 验证字段值是否为中文和字母 + * @method ValidateRule isChsAlphaNum(mixed $rule, string $msg = '') static 验证字段值是否为中文字母和数字 + * @method ValidateRule isDate(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isBool(mixed $rule, string $msg = '') static 验证字段值是否为布尔值 + * @method ValidateRule isAlpha(mixed $rule, string $msg = '') static 验证字段值是否为字母 + * @method ValidateRule isAlphaDash(mixed $rule, string $msg = '') static 验证字段值是否为字母和下划线 + * @method ValidateRule isAlphaNum(mixed $rule, string $msg = '') static 验证字段值是否为字母和数字 + * @method ValidateRule isAccepted(mixed $rule, string $msg = '') static 验证字段值是否为yes, on, 或是 1 + * @method ValidateRule isEmail(mixed $rule, string $msg = '') static 验证字段值是否为有效邮箱格式 + * @method ValidateRule isUrl(mixed $rule, string $msg = '') static 验证字段值是否为有效URL地址 + * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateRule fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateRule fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateRule image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateRule method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateRule dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateRule unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateRule behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateRule filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule must(mixed $rule=null, string $msg = '') static 必须验证 + */ +class ValidateRule +{ + // 验证字段的名称 + protected $title; + + // 当前验证规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + + /** + * 添加验证因子 + * @access protected + * @param string $name 验证名称 + * @param mixed $rule 验证规则 + * @param string $msg 提示信息 + * @return $this + */ + protected function addItem($name, $rule = null, $msg = '') + { + if ($rule || 0 === $rule) { + $this->rule[$name] = $rule; + } else { + $this->rule[] = $name; + } + + $this->message[] = $msg; + + return $this; + } + + /** + * 获取验证规则 + * @access public + * @return array + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取验证字段名称 + * @access public + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * 获取验证提示 + * @access public + * @return array + */ + public function getMsg() + { + return $this->message; + } + + /** + * 设置验证字段名称 + * @access public + * @return $this + */ + public function title($title) + { + $this->title = $title; + + return $this; + } + + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$this, 'addItem'], $args); + } + + public static function __callStatic($method, $args) + { + $rule = new static(); + + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$rule, 'addItem'], $args); + } +} diff --git a/thinkphp/library/think/view/driver/Php.php b/thinkphp/library/think/view/driver/Php.php index fa5053502..e2091aada 100644 --- a/thinkphp/library/think/view/driver/Php.php +++ b/thinkphp/library/think/view/driver/Php.php @@ -11,11 +11,9 @@ namespace think\view\driver; -use think\App; +use think\Container; use think\exception\TemplateNotFoundException; use think\Loader; -use think\Log; -use think\Request; class Php { @@ -28,18 +26,18 @@ class Php // 模板文件后缀 'view_suffix' => 'php', // 模板文件名分隔符 - 'view_depr' => DS, + 'view_depr' => DIRECTORY_SEPARATOR, ]; public function __construct($config = []) { - $this->config = array_merge($this->config, $config); + $this->config = array_merge($this->config, (array) $config); } /** * 检测是否存在模板文件 * @access public - * @param string $template 模板文件或者模板规则 + * @param string $template 模板文件或者模板规则 * @return bool */ public function exists($template) @@ -48,14 +46,15 @@ class Php // 获取模板文件名 $template = $this->parseTemplate($template); } + return is_file($template); } /** * 渲染模板文件 * @access public - * @param string $template 模板文件 - * @param array $data 模板变量 + * @param string $template 模板文件 + * @param array $data 模板变量 * @return void */ public function fetch($template, $data = []) @@ -64,12 +63,16 @@ class Php // 获取模板文件名 $template = $this->parseTemplate($template); } + // 模板不存在 抛出异常 if (!is_file($template)) { throw new TemplateNotFoundException('template not exists:' . $template, $template); } + // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + Container::get('app') + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + if (isset($data['template'])) { $__template__ = $template; extract($data, EXTR_OVERWRITE); @@ -83,8 +86,8 @@ class Php /** * 渲染模板内容 * @access public - * @param string $content 模板内容 - * @param array $data 模板变量 + * @param string $content 模板内容 + * @param array $data 模板变量 * @return void */ public function display($content, $data = []) @@ -102,52 +105,57 @@ class Php /** * 自动定位模板文件 * @access private - * @param string $template 模板文件规则 + * @param string $template 模板文件规则 * @return string */ private function parseTemplate($template) { if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; + $this->config['view_path'] = Container::get('app')->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } - $request = Request::instance(); + $request = Container::get('request'); + // 获取视图根目录 if (strpos($template, '@')) { // 跨模块调用 list($module, $template) = explode('@', $template); } + if ($this->config['view_base']) { // 基础视图目录 $module = isset($module) ? $module : $request->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $depr, $template); $controller = Loader::parseName($request->controller()); + if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . $request->action(); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $request->action(); } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } } } else { $template = str_replace(['/', ':'], $depr, substr($template, 1)); } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } /** * 配置模板引擎 * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 + * @param string|array $name 参数名 + * @param mixed $value 参数值 * @return void */ public function config($name, $value = null) diff --git a/thinkphp/library/think/view/driver/Think.php b/thinkphp/library/think/view/driver/Think.php index 46829d51e..83daa1c57 100644 --- a/thinkphp/library/think/view/driver/Think.php +++ b/thinkphp/library/think/view/driver/Think.php @@ -11,11 +11,9 @@ namespace think\view\driver; -use think\App; +use think\Container; use think\exception\TemplateNotFoundException; use think\Loader; -use think\Log; -use think\Request; use think\Template; class Think @@ -31,16 +29,16 @@ class Think // 模板文件后缀 'view_suffix' => 'html', // 模板文件名分隔符 - 'view_depr' => DS, + 'view_depr' => DIRECTORY_SEPARATOR, // 是否开启模板编译缓存,设为false则每次都会重新编译 'tpl_cache' => true, ]; public function __construct($config = []) { - $this->config = array_merge($this->config, $config); + $this->config = array_merge($this->config, (array) $config); if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; + $this->config['view_path'] = Container::get('app')->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } $this->template = new Template($this->config); @@ -49,7 +47,7 @@ class Think /** * 检测是否存在模板文件 * @access public - * @param string $template 模板文件或者模板规则 + * @param string $template 模板文件或者模板规则 * @return bool */ public function exists($template) @@ -58,15 +56,16 @@ class Think // 获取模板文件名 $template = $this->parseTemplate($template); } + return is_file($template); } /** * 渲染模板文件 * @access public - * @param string $template 模板文件 - * @param array $data 模板变量 - * @param array $config 模板参数 + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 * @return void */ public function fetch($template, $data = [], $config = []) @@ -75,21 +74,25 @@ class Think // 获取模板文件名 $template = $this->parseTemplate($template); } + // 模板不存在 抛出异常 if (!is_file($template)) { throw new TemplateNotFoundException('template not exists:' . $template, $template); } + // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + Container::get('app') + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + $this->template->fetch($template, $data, $config); } /** * 渲染模板内容 * @access public - * @param string $template 模板内容 - * @param array $data 模板变量 - * @param array $config 模板参数 + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 * @return void */ public function display($template, $data = [], $config = []) @@ -100,49 +103,53 @@ class Think /** * 自动定位模板文件 * @access private - * @param string $template 模板文件规则 + * @param string $template 模板文件规则 * @return string */ private function parseTemplate($template) { // 分析模板文件规则 - $request = Request::instance(); + $request = Container::get('request'); + // 获取视图根目录 if (strpos($template, '@')) { // 跨模块调用 list($module, $template) = explode('@', $template); } + if ($this->config['view_base']) { // 基础视图目录 $module = isset($module) ? $module : $request->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $depr, $template); $controller = Loader::parseName($request->controller()); if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . $request->action(); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $request->action(); } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } } } else { $template = str_replace(['/', ':'], $depr, substr($template, 1)); } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } /** * 配置或者获取模板引擎参数 * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 + * @param string|array $name 参数名 + * @param mixed $value 参数值 * @return mixed */ public function config($name, $value = null) diff --git a/thinkphp/library/traits/controller/Jump.php b/thinkphp/library/traits/controller/Jump.php index 6a572246a..8ea2ee4a2 100644 --- a/thinkphp/library/traits/controller/Jump.php +++ b/thinkphp/library/traits/controller/Jump.php @@ -1,7 +1,7 @@ server('HTTP_REFERER'))) { - $url = Request::instance()->server('HTTP_REFERER'); - } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { - $url = Url::build($url); + if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { + $url = $_SERVER["HTTP_REFERER"]; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); } - $type = $this->getResponseType(); $result = [ 'code' => 1, 'msg' => $msg, @@ -51,15 +46,13 @@ trait Jump 'wait' => $wait, ]; + $type = $this->getResponseType(); + // 把跳转模板的渲染下沉,这样在 response_send 行为里通过getData()获得的数据是一致性的格式 if ('html' == strtolower($type)) { - $template = Config::get('template'); - $view = Config::get('view_replace_str'); - - $result = ViewTemplate::instance($template, $view) - ->fetch(Config::get('dispatch_success_tmpl'), $result); + $type = 'jump'; } - $response = Response::create($result, $type)->header($header); + $response = Response::create($result, $type)->header($header)->options(['jump_template' => Container::get('config')->get('dispatch_success_tmpl')]); throw new HttpResponseException($response); } @@ -67,23 +60,21 @@ trait Jump /** * 操作错误跳转的快捷方法 * @access protected - * @param mixed $msg 提示信息 - * @param string $url 跳转的 URL 地址 - * @param mixed $data 返回的数据 - * @param int $wait 跳转等待时间 - * @param array $header 发送的 Header 信息 + * @param mixed $msg 提示信息 + * @param string $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @param array $header 发送的Header信息 * @return void - * @throws HttpResponseException */ protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) { if (is_null($url)) { - $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; - } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { - $url = Url::build($url); + $url = Container::get('request')->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); } - $type = $this->getResponseType(); $result = [ 'code' => 0, 'msg' => $msg, @@ -92,38 +83,35 @@ trait Jump 'wait' => $wait, ]; + $type = $this->getResponseType(); if ('html' == strtolower($type)) { - $template = Config::get('template'); - $view = Config::get('view_replace_str'); - - $result = ViewTemplate::instance($template, $view) - ->fetch(Config::get('dispatch_error_tmpl'), $result); + $type = 'jump'; } - $response = Response::create($result, $type)->header($header); + $response = Response::create($result, $type)->header($header)->options(['jump_template' => Container::get('config')->get('dispatch_error_tmpl')]); throw new HttpResponseException($response); } /** - * 返回封装后的 API 数据到客户端 + * 返回封装后的API数据到客户端 * @access protected - * @param mixed $data 要返回的数据 - * @param int $code 返回的 code - * @param mixed $msg 提示信息 - * @param string $type 返回数据格式 - * @param array $header 发送的 Header 信息 + * @param mixed $data 要返回的数据 + * @param integer $code 返回的code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的Header信息 * @return void - * @throws HttpResponseException */ protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) { $result = [ 'code' => $code, 'msg' => $msg, - 'time' => Request::instance()->server('REQUEST_TIME'), + 'time' => time(), 'data' => $data, ]; + $type = $type ?: $this->getResponseType(); $response = Response::create($result, $type)->header($header); @@ -131,37 +119,40 @@ trait Jump } /** - * URL 重定向 + * URL重定向 * @access protected - * @param string $url 跳转的 URL 表达式 - * @param array|int $params 其它 URL 参数 - * @param int $code http code - * @param array $with 隐式传参 + * @param string $url 跳转的URL表达式 + * @param array|integer $params 其它URL参数 + * @param integer $code http code + * @param array $with 隐式传参 * @return void - * @throws HttpResponseException */ protected function redirect($url, $params = [], $code = 302, $with = []) { + $response = new Redirect($url); + if (is_integer($params)) { $code = $params; $params = []; } - $response = new Redirect($url); $response->code($code)->params($params)->with($with); throw new HttpResponseException($response); } /** - * 获取当前的 response 输出类型 + * 获取当前的response 输出类型 * @access protected * @return string */ protected function getResponseType() { - return Request::instance()->isAjax() - ? Config::get('default_ajax_return') - : Config::get('default_return_type'); + $isAjax = Container::get('request')->isAjax(); + $config = Container::get('config'); + + return $isAjax + ? $config->get('default_ajax_return') + : $config->get('default_return_type'); } } diff --git a/thinkphp/library/traits/think/Instance.php b/thinkphp/library/traits/think/Instance.php deleted file mode 100644 index 428c8fde6..000000000 --- a/thinkphp/library/traits/think/Instance.php +++ /dev/null @@ -1,54 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace traits\think; - -use think\Exception; - -trait Instance -{ - /** - * @var null|static 实例对象 - */ - protected static $instance = null; - - /** - * 获取示例 - * @param array $options 实例配置 - * @return static - */ - public static function instance($options = []) - { - if (is_null(self::$instance)) self::$instance = new self($options); - - return self::$instance; - } - - /** - * 静态调用 - * @param string $method 调用方法 - * @param array $params 调用参数 - * @return mixed - * @throws Exception - */ - public static function __callStatic($method, array $params) - { - if (is_null(self::$instance)) self::$instance = new self(); - - $call = substr($method, 1); - - if (0 !== strpos($method, '_') || !is_callable([self::$instance, $call])) { - throw new Exception("method not exists:" . $method); - } - - return call_user_func_array([self::$instance, $call], $params); - } -} diff --git a/thinkphp/phpunit.xml b/thinkphp/phpunit.xml deleted file mode 100644 index 7c6ef03ca..000000000 --- a/thinkphp/phpunit.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - ./tests/thinkphp/ - - - - - - - - ./ - - tests - vendor - - - - - - - - - - diff --git a/thinkphp/phpunit.xml.dist b/thinkphp/phpunit.xml.dist new file mode 100644 index 000000000..37c3d2b53 --- /dev/null +++ b/thinkphp/phpunit.xml.dist @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + ./library/think/*/tests/ + + + + + + ./library/ + + ./library/think/*/tests + ./library/think/*/assets + ./library/think/*/resources + ./library/think/*/vendor + + + + \ No newline at end of file diff --git a/thinkphp/tpl/default_index.tpl b/thinkphp/tpl/default_index.tpl index 8538b4dfa..740b19283 100644 --- a/thinkphp/tpl/default_index.tpl +++ b/thinkphp/tpl/default_index.tpl @@ -5,6 +5,6 @@ class Index{$suffix} { public function index() { - return ''; + return '

                    :) 2018新年快乐

                    ThinkPHP V5.1
                    12载初心不改(2006-2018) - 你值得信赖的PHP框架

                    '; } } diff --git a/thinkphp/tpl/page_trace.tpl b/thinkphp/tpl/page_trace.tpl index 7c5df6fb5..2e5afbab9 100644 --- a/thinkphp/tpl/page_trace.tpl +++ b/thinkphp/tpl/page_trace.tpl @@ -24,7 +24,7 @@
                    -
                    +
                    getUseTime().'s ';?>
                    diff --git a/thinkphp/tpl/think_exception.tpl b/thinkphp/tpl/think_exception.tpl index 21bbafc3d..19ecbdc1b 100644 --- a/thinkphp/tpl/think_exception.tpl +++ b/thinkphp/tpl/think_exception.tpl @@ -79,17 +79,15 @@ - <?php echo \think\Lang::get('System Error'); ?> + 系统发生错误 -