From eae151fd57d175cbcd6eb9a40b3938874dac3dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BB=BA=E7=90=A6?= Date: Wed, 26 Aug 2026 18:30:49 +0800 Subject: [PATCH] =?UTF-8?q?FastMovieAI=20AI=E7=9F=AD=E5=89=A7=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E5=B9=B3=E5=8F=B0=20=E4=BA=8C=E5=BC=80=E5=9F=BA?= =?UTF-8?q?=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 源码: xhadmincn/FastMovieAI (Apache 2.0) - 二开: Docker部署, Swoole改Select, route.php修复, 补update/VERSION - vendor/示例资源已gitignore --- .gitignore | 129 + LICENSE | 201 + README.en.md | 575 ++ README.md | 694 ++ fastmovie-admin/.env.example | 39 + fastmovie-admin/.gitignore | 22 + fastmovie-admin/.htaccess | 1 + fastmovie-admin/404.html | 16 + fastmovie-admin/Dockerfile | 22 + fastmovie-admin/LICENSE | 30 + fastmovie-admin/README.md | 50 + fastmovie-admin/VERSION | 2 + fastmovie-admin/app/Basic.php | 50 + fastmovie-admin/app/admin/admin.json | 235 + fastmovie-admin/app/admin/api/Install.php | 14 + fastmovie-admin/app/admin/config/route.php | 73 + .../app/admin/controller/AdminController.php | 601 ++ .../admin/controller/AdminRoleController.php | 294 + .../admin/controller/CaptchaController.php | 38 + .../app/admin/controller/IndexController.php | 56 + .../app/admin/controller/LoginController.php | 70 + .../admin/controller/PaymentController.php | 187 + .../controller/PaymentTemplateController.php | 639 ++ .../admin/controller/PlatformController.php | 20 + .../app/admin/controller/PublicController.php | 211 + .../app/admin/controller/SystemController.php | 228 + .../admin/controller/UploadsController.php | 16 + fastmovie-admin/app/admin/middleware/Auth.php | 9 + .../app/controller/IndexController.php | 56 + .../app/controller/NotifyController.php | 113 + .../WechatOfficialAccountController.php | 32 + fastmovie-admin/app/exception/Handler.php | 30 + fastmovie-admin/app/expose/api/Install.php | 76 + .../expose/build/builder/ActionBuilder.php | 72 + .../app/expose/build/builder/AppsBuilder.php | 48 + .../expose/build/builder/ComponentBuilder.php | 25 + .../expose/build/builder/DataboardBuilder.php | 27 + .../expose/build/builder/ExamineBuilder.php | 117 + .../app/expose/build/builder/FormBuilder.php | 172 + .../expose/build/builder/ImagesBuilder.php | 96 + .../app/expose/build/builder/InfoBuilder.php | 154 + .../expose/build/builder/RechargeBuilder.php | 60 + .../app/expose/build/builder/SoltBuilder.php | 31 + .../app/expose/build/builder/TableBuilder.php | 118 + .../build/builder/databoardBuilder/Basic.php | 9 + .../databoardBuilder/component/Apply.php | 53 + .../databoardBuilder/component/Domain.php | 50 + .../databoardBuilder/component/Echarts.php | 40 + .../component/Placeholder.php | 33 + .../databoardBuilder/component/Statistic.php | 56 + .../interface/DataboardBuilderInterface.php | 11 + .../app/expose/build/config/Action.php | 29 + .../app/expose/build/config/Apis.php | 16 + .../app/expose/build/config/Login.php | 38 + .../app/expose/build/config/Web.php | 90 + fastmovie-admin/app/expose/enum/Action.php | 53 + .../app/expose/enum/AlipayRsaModel.php | 17 + fastmovie-admin/app/expose/enum/EventName.php | 114 + fastmovie-admin/app/expose/enum/Examine.php | 30 + .../app/expose/enum/Filesystem.php | 41 + fastmovie-admin/app/expose/enum/Methods.php | 29 + .../app/expose/enum/PaymentChannels.php | 73 + .../app/expose/enum/Permission.php | 25 + fastmovie-admin/app/expose/enum/Platform.php | 29 + .../app/expose/enum/ResponseCode.php | 86 + .../app/expose/enum/ResponseEvent.php | 17 + .../app/expose/enum/SmsChannels.php | 21 + fastmovie-admin/app/expose/enum/State.php | 17 + .../app/expose/enum/SubmitEvent.php | 18 + fastmovie-admin/app/expose/enum/Week.php | 58 + .../app/expose/enum/WxpayMchType.php | 17 + .../app/expose/enum/WxpayVersion.php | 17 + .../app/expose/enum/builder/Enum.php | 100 + .../app/expose/exception/Exception.php | 19 + fastmovie-admin/app/expose/helper/Captcha.php | 109 + fastmovie-admin/app/expose/helper/Config.php | 142 + .../app/expose/helper/ConfigGroup.php | 110 + fastmovie-admin/app/expose/helper/Menus.php | 116 + fastmovie-admin/app/expose/helper/Payment.php | 188 + fastmovie-admin/app/expose/helper/Uploads.php | 368 + fastmovie-admin/app/expose/helper/Vcode.php | 79 + fastmovie-admin/app/expose/helper/Wechat.php | 126 + .../app/expose/middleware/AdminAuth.php | 142 + .../app/expose/middleware/Platform.php | 59 + .../app/expose/middleware/Template.php | 27 + .../app/expose/template/email/Vcode.php | 13 + .../app/expose/template/sms/Basic.php | 19 + .../app/expose/template/sms/Vcode.php | 10 + fastmovie-admin/app/expose/trait/Config.php | 75 + fastmovie-admin/app/expose/trait/Json.php | 111 + fastmovie-admin/app/expose/trait/Uploads.php | 277 + .../app/expose/utils/DataModel.php | 86 + fastmovie-admin/app/expose/utils/Email.php | 27 + fastmovie-admin/app/expose/utils/Json.php | 108 + fastmovie-admin/app/expose/utils/Password.php | 30 + fastmovie-admin/app/expose/utils/Rsa.php | 132 + fastmovie-admin/app/expose/utils/Sms.php | 89 + fastmovie-admin/app/expose/utils/Str.php | 262 + .../app/expose/utils/TreeModel.php | 36 + .../app/expose/utils/TwofaAuthenticator.php | 251 + .../expose/utils/wechat/OfficialAccount.php | 57 + .../app/expose/utils/wechat/modules/Event.php | 73 + .../app/expose/utils/wechat/modules/Reply.php | 94 + .../app/expose/validate/Validate.php | 64 + fastmovie-admin/app/functions.php | 37 + fastmovie-admin/app/middleware/Access.php | 28 + fastmovie-admin/app/middleware/Platform.php | 9 + fastmovie-admin/app/middleware/StaticFile.php | 42 + fastmovie-admin/app/middleware/Template.php | 9 + fastmovie-admin/app/model/Admin.php | 66 + fastmovie-admin/app/model/AdminRole.php | 37 + fastmovie-admin/app/model/Basic.php | 10 + fastmovie-admin/app/model/Config.php | 16 + fastmovie-admin/app/model/PaymentConfig.php | 7 + .../app/model/PaymentNotifyWechat.php | 8 + fastmovie-admin/app/model/PaymentTemplate.php | 38 + fastmovie-admin/app/model/Uploads.php | 7 + fastmovie-admin/app/model/UploadsClassify.php | 7 + .../app/process/AutoDeleteUpload.php | 27 + fastmovie-admin/app/process/Http.php | 10 + fastmovie-admin/app/process/Monitor.php | 262 + fastmovie-admin/app/validate/Admin.php | 33 + fastmovie-admin/app/validate/User.php | 31 + fastmovie-admin/app/view/index/index.html | 5 + fastmovie-admin/app/view/main.html | 19 + fastmovie-admin/composer.json | 95 + fastmovie-admin/composer.lock | 6881 +++++++++++++++++ fastmovie-admin/config/app.php | 27 + fastmovie-admin/config/autoload.php | 21 + fastmovie-admin/config/bootstrap.php | 19 + fastmovie-admin/config/cache.php | 31 + fastmovie-admin/config/container.php | 15 + fastmovie-admin/config/database.php | 15 + fastmovie-admin/config/dependence.php | 15 + fastmovie-admin/config/event.php | 5 + fastmovie-admin/config/exception.php | 6 + fastmovie-admin/config/log.php | 32 + fastmovie-admin/config/middleware.php | 10 + fastmovie-admin/config/oauth.php | 16 + .../config/plugin/shopwwi/filesystem/app.php | 118 + .../config/plugin/webman/console/app.php | 22 + .../config/plugin/webman/event/app.php | 4 + .../config/plugin/webman/event/bootstrap.php | 17 + .../config/plugin/webman/event/command.php | 7 + .../config/plugin/webman/push/app.php | 12 + .../config/plugin/webman/push/process.php | 21 + .../config/plugin/webman/push/route.php | 26 + fastmovie-admin/config/process.php | 73 + fastmovie-admin/config/redis.php | 29 + fastmovie-admin/config/route.php | 8 + fastmovie-admin/config/server.php | 22 + fastmovie-admin/config/session.php | 65 + fastmovie-admin/config/settings-tabs.php | 15 + fastmovie-admin/config/settings-tabs/sms.php | 272 + fastmovie-admin/config/settings.php | 15 + fastmovie-admin/config/settings/basic.php | 180 + .../config/settings/wechat_miniproject.php | 138 + .../settings/wechat_official_account.php | 142 + fastmovie-admin/config/static.php | 23 + fastmovie-admin/config/thinkorm.php | 43 + fastmovie-admin/config/translation.php | 25 + fastmovie-admin/config/view.php | 23 + fastmovie-admin/database.sql | 1286 +++ fastmovie-admin/index.html | 39 + fastmovie-admin/lua/module/config.lua | 41 + fastmovie-admin/lua/module/load_env.lua | 122 + fastmovie-admin/lua/qps.lua | 121 + fastmovie-admin/lua/test.lua | 38 + fastmovie-admin/menu.example | 22 + fastmovie-admin/nginx.example | 39 + fastmovie-admin/package.json | 13 + fastmovie-admin/plugin/article/admin.json | 92 + .../plugin/article/api/Control.php | 15 + .../plugin/article/api/Install.php | 14 + .../plugin/article/api/Install.sql | 76 + fastmovie-admin/plugin/article/app/Basic.php | 10 + .../controller/AnnouncementController.php | 516 ++ .../admin/controller/ClassifyController.php | 301 + .../app/admin/controller/IndexController.php | 544 ++ .../article/app/admin/middleware/Auth.php | 9 + .../app/api/controller/ArticleController.php | 38 + .../controller/AgreementController.php | 177 + .../controller/AnnouncementController.php | 670 ++ .../control/controller/ClassifyController.php | 312 + .../control/controller/IndexController.php | 545 ++ .../plugin/article/app/functions.php | 6 + .../article/app/model/PluginArticle.php | 42 + .../app/model/PluginArticleClassify.php | 33 + .../app/model/PluginArticleContent.php | 10 + .../plugin/article/app/validate/Article.php | 34 + fastmovie-admin/plugin/article/config/app.php | 9 + .../plugin/article/config/autoload.php | 6 + .../plugin/article/config/container.php | 2 + .../plugin/article/config/event.php | 4 + .../plugin/article/config/exception.php | 5 + fastmovie-admin/plugin/article/config/log.php | 20 + .../plugin/article/config/middleware.php | 21 + .../plugin/article/config/process.php | 11 + .../plugin/article/config/static.php | 6 + .../plugin/article/config/translation.php | 25 + fastmovie-admin/plugin/article/control.json | 154 + .../plugin/article/expose/helper/Article.php | 88 + fastmovie-admin/plugin/article/package.json | 12 + .../article/process/JoinPushMessage.php | 68 + .../plugin/article/public/static/index.css | 35 + .../plugin/article/public/template.html | 24 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/messages.php | 5 + .../plugin/article/utils/enum/CrowdEnum.php | 25 + fastmovie-admin/plugin/control/README.md | 10 + fastmovie-admin/plugin/control/admin.json | 39 + .../plugin/control/api/Control.php | 15 + .../plugin/control/api/Install.php | 15 + .../admin/controller/SettingsController.php | 20 + .../app/admin/controller/UserController.php | 426 + .../app/api/controller/PublicController.php | 113 + .../control/controller/CaptchaController.php | 39 + .../control/controller/DomainController.php | 289 + .../control/controller/IndexController.php | 216 + .../control/controller/LoginController.php | 157 + .../control/controller/PaymentController.php | 190 + .../controller/PaymentTemplateController.php | 645 ++ .../control/controller/PlatformController.php | 22 + .../control/controller/PublicController.php | 195 + .../app/control/controller/RoleController.php | 260 + .../control/controller/SystemController.php | 326 + .../control/controller/UploadsController.php | 22 + .../app/control/controller/UserController.php | 385 + .../WechatOfficialAccountController.php | 32 + .../plugin/control/app/middleware/Auth.php | 9 + .../app/model/PluginChannelsDomain.php | 7 + .../control/app/model/PluginChannelsRole.php | 23 + .../control/app/model/PluginChannelsUser.php | 155 + .../app/model/PluginChannelsWechat.php | 15 + fastmovie-admin/plugin/control/config/app.php | 9 + .../plugin/control/config/autoload.php | 3 + .../plugin/control/config/container.php | 2 + .../plugin/control/config/event.php | 4 + .../plugin/control/config/exception.php | 5 + fastmovie-admin/plugin/control/config/log.php | 20 + .../plugin/control/config/middleware.php | 19 + .../plugin/control/config/process.php | 5 + .../plugin/control/config/route.php | 70 + .../control/config/settings-tabs/sms.php | 272 + .../plugin/control/config/settings/basic.php | 219 + .../plugin/control/config/settings/state.php | 24 + .../config/settings/wechat_miniproject.php | 138 + .../settings/wechat_official_account.php | 142 + .../plugin/control/config/settings/yidevs.php | 25 + .../plugin/control/config/translation.php | 25 + .../plugin/control/config/view.php | 23 + fastmovie-admin/plugin/control/control.json | 235 + .../control/event/WechatOfficialAccount.php | 69 + .../plugin/control/expose/api/Control.php | 56 + .../plugin/control/expose/helper/Control.php | 46 + .../plugin/control/expose/helper/Uploads.php | 382 + .../plugin/control/expose/helper/Vcode.php | 80 + .../plugin/control/expose/helper/Wechat.php | 129 + .../control/expose/middleware/ControlAuth.php | 127 + .../expose/middleware/DomainMapping.php | 80 + .../plugin/control/expose/trait/Uploads.php | 293 + .../resource/translations/en/admin.php | 9 + .../control/resource/translations/en/api.php | 6 + .../resource/translations/en/control.php | 89 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 9 + .../resource/translations/zh_CN/api.php | 6 + .../resource/translations/zh_CN/control.php | 89 + .../resource/translations/zh_CN/messages.php | 5 + .../plugin/control/utils/LRUCache.php | 43 + .../plugin/control/utils/yidevs/Client.php | 97 + .../yidevs/Exception/HttpCurlException.php | 7 + .../Exception/InvalidResultException.php | 7 + .../Exception/InvalidTokenException.php | 7 + .../plugin/control/utils/yidevs/Yidevs.php | 17 + .../control/utils/yidevs/stream/Client.php | 121 + .../control/utils/yidevs/trait/Audio.php | 45 + .../utils/yidevs/trait/AudioAssistant.php | 33 + .../control/utils/yidevs/trait/Chat.php | 37 + .../utils/yidevs/trait/ChatAssistant.php | 33 + .../control/utils/yidevs/trait/Draw.php | 33 + .../utils/yidevs/trait/DrawAssistant.php | 33 + .../control/utils/yidevs/trait/Video.php | 33 + .../utils/yidevs/trait/VideoAssistant.php | 33 + fastmovie-admin/plugin/finance/admin.json | 40 + .../plugin/finance/api/Control.php | 15 + .../plugin/finance/api/Install.php | 15 + .../plugin/finance/api/notify/Wechat.php | 48 + .../app/admin/controller/IndexController.php | 248 + .../app/admin/controller/OrdersController.php | 520 ++ .../admin/controller/SettingsController.php | 15 + .../app/api/controller/IndexController.php | 115 + .../app/api/controller/OrdersController.php | 301 + .../app/api/controller/UserController.php | 22 + .../control/controller/IndexController.php | 439 ++ .../control/controller/OrdersController.php | 686 ++ .../control/controller/WalletController.php | 357 + .../plugin/finance/app/functions.php | 6 + .../finance/app/model/PluginFinanceOrders.php | 169 + .../app/model/PluginFinanceOrdersLog.php | 51 + .../app/model/PluginFinancePaymentNotify.php | 9 + .../finance/app/model/PluginFinanceWallet.php | 33 + fastmovie-admin/plugin/finance/config/app.php | 9 + .../plugin/finance/config/autoload.php | 6 + .../plugin/finance/config/container.php | 2 + .../plugin/finance/config/event.php | 12 + .../plugin/finance/config/exception.php | 5 + .../plugin/finance/config/middleware.php | 18 + .../plugin/finance/config/process.php | 10 + .../plugin/finance/config/settings/basic.php | 27 + .../plugin/finance/config/translation.php | 25 + fastmovie-admin/plugin/finance/control.json | 34 + .../plugin/finance/event/Orders.php | 73 + .../plugin/finance/expose/helper/Account.php | 513 ++ fastmovie-admin/plugin/finance/package.json | 10 + .../plugin/finance/process/Expire.php | 37 + .../resource/translations/en/admin.php | 5 + .../finance/resource/translations/en/api.php | 6 + .../resource/translations/en/control.php | 68 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 5 + .../resource/translations/zh_CN/api.php | 6 + .../resource/translations/zh_CN/control.php | 68 + .../resource/translations/zh_CN/messages.php | 5 + .../plugin/finance/utils/enum/BillScene.php | 16 + .../finance/utils/enum/OrdersLogLevel.php | 44 + .../plugin/finance/utils/enum/OrdersState.php | 79 + .../plugin/finance/utils/enum/OrdersType.php | 25 + .../finance/utils/enum/PointsBillScene.php | 58 + .../finance/utils/enum/ValidityPeriod.php | 23 + fastmovie-admin/plugin/marketing/admin.json | 59 + .../plugin/marketing/api/Control.php | 15 + .../plugin/marketing/api/Install.php | 15 + .../admin/controller/CouponCodeController.php | 312 + .../app/admin/controller/CouponController.php | 651 ++ .../app/admin/controller/IndexController.php | 96 + .../app/api/controller/CouponController.php | 216 + .../app/api/controller/PaymentController.php | 16 + .../app/api/controller/PointsController.php | 21 + .../api/controller/UserCouponController.php | 79 + .../app/api/controller/VipController.php | 21 + .../controller/CouponCodeController.php | 313 + .../control/controller/CouponController.php | 657 ++ .../control/controller/IndexController.php | 96 + .../app/control/controller/PlanController.php | 475 ++ .../control/controller/PointsController.php | 243 + .../plugin/marketing/app/functions.php | 6 + .../app/model/PluginMarketingCoupon.php | 95 + .../app/model/PluginMarketingCouponCode.php | 111 + .../model/PluginMarketingCouponPassword.php | 10 + .../app/model/PluginMarketingCouponServer.php | 10 + .../app/model/PluginMarketingPlan.php | 13 + .../app/model/PluginMarketingPlanPrice.php | 10 + .../app/model/PluginMarketingPoints.php | 10 + .../app/validate/PluginMarketingCoupon.php | 28 + .../plugin/marketing/config/app.php | 9 + .../plugin/marketing/config/autoload.php | 6 + .../plugin/marketing/config/container.php | 2 + .../plugin/marketing/config/exception.php | 5 + .../plugin/marketing/config/log.php | 20 + .../plugin/marketing/config/middleware.php | 18 + .../plugin/marketing/config/process.php | 10 + .../plugin/marketing/config/translation.php | 25 + fastmovie-admin/plugin/marketing/control.json | 138 + fastmovie-admin/plugin/marketing/package.json | 10 + .../plugin/marketing/process/Expire.php | 53 + .../resource/translations/en/admin.php | 5 + .../resource/translations/en/api.php | 6 + .../resource/translations/en/control.php | 68 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 5 + .../resource/translations/zh_CN/api.php | 6 + .../resource/translations/zh_CN/control.php | 68 + .../resource/translations/zh_CN/messages.php | 5 + .../marketing/utils/enum/BillingCycle.php | 17 + .../marketing/utils/enum/CouponRule.php | 17 + .../plugin/marketing/utils/enum/PlanKey.php | 17 + .../marketing/utils/enum/ReceiveType.php | 37 + .../plugin/marketing/utils/enum/UseState.php | 30 + .../plugin/marketing/utils/enum/UseType.php | 23 + fastmovie-admin/plugin/model/admin.json | 18 + fastmovie-admin/plugin/model/api/Control.php | 15 + fastmovie-admin/plugin/model/api/Install.php | 15 + .../app/admin/controller/ModelController.php | 213 + .../app/admin/controller/TaskController.php | 271 + .../app/admin/controller/VoiceController.php | 271 + .../app/api/controller/ModelController.php | 43 + .../app/api/controller/TaskController.php | 44 + .../app/api/controller/VoiceController.php | 166 + .../api/controller/VoiceTextController.php | 24 + .../control/controller/ModelController.php | 765 ++ .../app/control/controller/TaskController.php | 294 + .../control/controller/VoiceController.php | 173 + .../controller/VoiceTextController.php | 203 + .../model/app/controller/NotifyController.php | 242 + .../plugin/model/app/functions.php | 6 + .../plugin/model/app/model/PluginModel.php | 42 + .../model/app/model/PluginModelTask.php | 38 + .../model/app/model/PluginModelTaskResult.php | 44 + .../model/app/model/PluginModelVoice.php | 32 + .../model/app/model/PluginModelVoiceText.php | 14 + fastmovie-admin/plugin/model/config/app.php | 9 + .../plugin/model/config/autoload.php | 6 + .../plugin/model/config/container.php | 2 + .../plugin/model/config/exception.php | 5 + .../plugin/model/config/middleware.php | 19 + .../plugin/model/config/process.php | 30 + .../plugin/model/config/settings/basic.php | 40 + .../plugin/model/config/translation.php | 25 + fastmovie-admin/plugin/model/config/view.php | 23 + fastmovie-admin/plugin/model/control.json | 88 + fastmovie-admin/plugin/model/modal.json | 275 + fastmovie-admin/plugin/model/package.json | 10 + .../model/process/audio/AudioTransfer.php | 101 + .../plugin/model/process/chat/Submit.php | 450 ++ .../model/process/draw/ImageTransfer.php | 271 + .../model/process/video/VideoTransfer.php | 92 + .../model/resource/translations/en/admin.php | 5 + .../model/resource/translations/en/api.php | 6 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 5 + .../model/resource/translations/zh_CN/api.php | 6 + .../resource/translations/zh_CN/messages.php | 5 + .../model/utils/enum/ModelPointType.php | 19 + .../plugin/model/utils/enum/ModelScene.php | 199 + .../model/utils/enum/ModelTaskStatus.php | 58 + .../plugin/model/utils/enum/ModelType.php | 37 + .../model/utils/enum/ModelVoiceStatus.php | 37 + .../plugin/notification/admin.json | 57 + .../plugin/notification/api/Install.php | 15 + .../plugin/notification/api/Install.sql | 35 + .../plugin/notification/app/Basic.php | 10 + .../admin/controller/MessageController.php | 287 + .../app/admin/controller/SystemController.php | 28 + .../controller/WechatTemplateController.php | 31 + .../app/api/controller/MessageController.php | 71 + .../app/api/controller/PushController.php | 60 + .../app/controller/PushController.php | 101 + .../plugin/notification/app/functions.php | 6 + .../app/model/PluginNotificationMessage.php | 32 + .../PluginNotificationMessageContent.php | 11 + .../app/model/PluginNotificationOnline.php | 8 + .../plugin/notification/config/app.php | 9 + .../plugin/notification/config/autoload.php | 6 + .../plugin/notification/config/container.php | 2 + .../plugin/notification/config/event.php | 5 + .../plugin/notification/config/exception.php | 5 + .../plugin/notification/config/middleware.php | 15 + .../plugin/notification/config/process.php | 5 + .../notification/config/settings/push.php | 43 + .../config/settings/sms_template.php | 24 + .../notification/config/settings/strategy.php | 94 + .../config/settings/wechat_template.php | 50 + .../plugin/notification/config/static.php | 6 + .../notification/config/translation.php | 25 + .../notification/expose/helper/Message.php | 134 + .../notification/expose/helper/Notice.php | 163 + .../notification/expose/helper/Online.php | 14 + .../notification/expose/helper/Push.php | 27 + .../expose/template/sms/Notice.php | 16 + .../plugin/notification/package.json | 12 + .../resource/translations/en/admin.php | 10 + .../resource/translations/en/api.php | 7 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 10 + .../resource/translations/zh_CN/api.php | 7 + .../resource/translations/zh_CN/messages.php | 5 + .../plugin/notification/utils/Api.php | 237 + .../notification/utils/PushException.php | 7 + .../notification/utils/enum/AuthType.php | 100 + .../notification/utils/enum/MessageScene.php | 13 + .../plugin/notification/utils/enum/Method.php | 30 + .../plugin/notification/utils/enum/Scene.php | 34 + .../plugin/pluginExample/admin.json | 56 + .../plugin/pluginExample/api/Install.php | 15 + .../admin/controller/DataboardController.php | 163 + .../admin/controller/ExamineController.php | 90 + .../app/admin/controller/FormController.php | 141 + .../admin/controller/GroupFormController.php | 184 + .../app/admin/controller/ImagesController.php | 112 + .../app/admin/controller/InfoController.php | 50 + .../admin/controller/SettingsController.php | 16 + .../app/admin/controller/TableController.php | 269 + .../plugin/pluginExample/app/functions.php | 6 + .../plugin/pluginExample/config/app.php | 9 + .../plugin/pluginExample/config/autoload.php | 6 + .../plugin/pluginExample/config/container.php | 2 + .../plugin/pluginExample/config/exception.php | 5 + .../plugin/pluginExample/config/log.php | 20 + .../pluginExample/config/middleware.php | 11 + .../plugin/pluginExample/config/process.php | 2 + .../pluginExample/config/settings/basic.php | 40 + .../pluginExample/config/translation.php | 25 + .../plugin/pluginExample/config/view.php | 23 + .../plugin/pluginExample/package.json | 10 + .../resource/translations/en/admin.php | 5 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 5 + .../resource/translations/zh_CN/messages.php | 5 + fastmovie-admin/plugin/shortplay/admin.json | 98 + .../plugin/shortplay/api/Control.php | 15 + .../plugin/shortplay/api/Install.php | 15 + .../app/admin/controller/ActorController.php | 225 + .../app/admin/controller/DramaController.php | 180 + .../controller/DramaEpisodeController.php | 176 + .../app/admin/controller/StyleController.php | 171 + .../app/api/controller/ActorController.php | 632 ++ .../controller/CharacterLookController.php | 189 + .../app/api/controller/DramaController.php | 290 + .../api/controller/DramaEpisodeController.php | 213 + .../app/api/controller/GenerateController.php | 1470 ++++ .../app/api/controller/IndexController.php | 258 + .../app/api/controller/PropController.php | 415 + .../app/api/controller/SceneController.php | 280 + .../app/api/controller/SquareController.php | 108 + .../api/controller/StoryboardController.php | 780 ++ .../StoryboardDialogueController.php | 186 + .../app/api/controller/StyleController.php | 28 + .../app/api/controller/UploadsController.php | 17 + .../app/api/controller/VoiceController.php | 49 + .../app/api/controller/WorksController.php | 118 + .../control/controller/ActorController.php | 441 ++ .../control/controller/DramaController.php | 165 + .../controller/DramaEpisodeController.php | 167 + .../control/controller/StyleController.php | 319 + .../app/controller/DownloadController.php | 20 + .../plugin/shortplay/app/functions.php | 6 + .../app/model/PluginShortplayActor.php | 153 + .../PluginShortplayActorCharacterLook.php | 40 + .../model/PluginShortplayCharacterLook.php | 31 + .../app/model/PluginShortplayDrama.php | 47 + .../app/model/PluginShortplayDramaActor.php | 39 + .../app/model/PluginShortplayDramaEpisode.php | 54 + .../PluginShortplayDramaEpisodeActor.php | 39 + .../app/model/PluginShortplayDramaScene.php | 24 + .../model/PluginShortplayDramaStoryboard.php | 58 + .../PluginShortplayDramaStoryboardActor.php | 53 + ...PluginShortplayDramaStoryboardDialogue.php | 43 + .../PluginShortplayDramaStoryboardProp.php | 13 + .../app/model/PluginShortplayProp.php | 40 + .../app/model/PluginShortplayShare.php | 23 + .../app/model/PluginShortplayShareEpisode.php | 22 + .../app/model/PluginShortplayShareLikes.php | 9 + .../app/model/PluginShortplayStyle.php | 23 + .../app/model/PluginShortplayVoice.php | 23 + .../plugin/shortplay/config/app.php | 9 + .../plugin/shortplay/config/autoload.php | 6 + .../plugin/shortplay/config/container.php | 2 + .../plugin/shortplay/config/exception.php | 5 + .../plugin/shortplay/config/middleware.php | 18 + .../plugin/shortplay/config/process.php | 12 + .../plugin/shortplay/config/route.php | 4 + .../plugin/shortplay/config/translation.php | 10 + fastmovie-admin/plugin/shortplay/control.json | 91 + .../resource/translations/en/admin.php | 9 + .../resource/translations/en/api.php | 6 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 9 + .../resource/translations/zh_CN/api.php | 6 + .../resource/translations/zh_CN/messages.php | 5 + .../shortplay/utils/PricingCalculator.php | 115 + .../plugin/shortplay/utils/enum/ActorAge.php | 29 + .../shortplay/utils/enum/ActorGender.php | 21 + .../shortplay/utils/enum/ActorSpeciesType.php | 45 + .../shortplay/utils/enum/ActorStatus.php | 44 + .../shortplay/utils/enum/PropStatus.php | 44 + .../shortplay/utils/enum/StyleClassify.php | 29 + .../shortplay/utils/enum/VoiceEmotion.php | 37 + .../shortplay/utils/enum/VoiceLanguage.php | 73 + fastmovie-admin/plugin/user/admin.json | 54 + fastmovie-admin/plugin/user/api/Control.php | 15 + fastmovie-admin/plugin/user/api/Install.php | 15 + .../plugin/user/api/admin/IndexController.php | 32 + .../admin/controller/BalanceController.php | 227 + .../app/admin/controller/PointsController.php | 232 + .../admin/controller/SettingController.php | 15 + .../app/admin/controller/UserController.php | 440 ++ .../app/api/controller/BillController.php | 36 + .../controller/BindThirdPartyController.php | 109 + .../app/api/controller/CaptchaController.php | 34 + .../app/api/controller/LoginController.php | 265 + .../app/api/controller/SignupController.php | 46 + .../app/api/controller/UploadsController.php | 15 + .../app/api/controller/UserBillController.php | 46 + .../app/api/controller/UserController.php | 445 ++ .../control/controller/BalanceController.php | 204 + .../controller/InvitationCodeController.php | 306 + .../control/controller/PointsController.php | 217 + .../control/controller/SettingController.php | 15 + .../control/controller/SystemController.php | 22 + .../app/control/controller/UserController.php | 409 + fastmovie-admin/plugin/user/app/functions.php | 6 + .../plugin/user/app/model/PluginUser.php | 182 + .../plugin/user/app/model/PluginUserBill.php | 96 + .../app/model/PluginUserInvitationCode.php | 70 + .../user/app/model/PluginUserPoints.php | 9 + .../user/app/model/PluginUserPointsBill.php | 14 + .../user/app/model/PluginUserTwofaSecret.php | 13 + .../plugin/user/app/model/PluginUserVip.php | 10 + .../user/app/model/PluginUserWechat.php | 15 + fastmovie-admin/plugin/user/config/app.php | 9 + .../plugin/user/config/autoload.php | 6 + .../plugin/user/config/container.php | 2 + fastmovie-admin/plugin/user/config/event.php | 16 + .../plugin/user/config/exception.php | 5 + fastmovie-admin/plugin/user/config/log.php | 20 + .../plugin/user/config/middleware.php | 18 + .../plugin/user/config/process.php | 2 + .../plugin/user/config/settings/basic.php | 30 + .../plugin/user/config/settings/register.php | 52 + .../plugin/user/config/translation.php | 25 + fastmovie-admin/plugin/user/config/view.php | 23 + fastmovie-admin/plugin/user/control.json | 92 + .../plugin/user/event/InvitationCode.php | 18 + fastmovie-admin/plugin/user/event/User.php | 28 + .../user/event/WECHAT_OFFICIAL_ACCOUNT.php | 32 + .../plugin/user/expose/helper/User.php | 188 + .../expose/helper/WechatOfficialAccount.php | 111 + .../plugin/user/expose/middleware/Twofa.php | 79 + .../user/expose/middleware/UserAuth.php | 109 + fastmovie-admin/plugin/user/package.json | 10 + .../user/resource/translations/en/admin.php | 9 + .../user/resource/translations/en/api.php | 6 + .../user/resource/translations/en/control.php | 9 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 9 + .../user/resource/translations/zh_CN/api.php | 6 + .../resource/translations/zh_CN/control.php | 9 + .../resource/translations/zh_CN/messages.php | 5 + .../user/utils/enum/CertificateType.php | 33 + .../plugin/user/utils/enum/MoneyAction.php | 23 + .../plugin/user/utils/enum/PropsType.php | 51 + .../plugin/user/utils/enum/TotpApp.php | 37 + .../plugin/user/utils/enum/UserPermission.php | 41 + .../plugin/user/utils/enum/VcodeScene.php | 34 + fastmovie-admin/process/Monitor.php | 243 + fastmovie-admin/public/404.html | 12 + fastmovie-admin/public/article/.gitignore | 2 + fastmovie-admin/public/favicon.ico | Bin 0 -> 4286 bytes fastmovie-admin/public/icon.svg | 1 + fastmovie-admin/public/index.html | 25 + fastmovie-admin/public/index.php | 22 + .../public/static/apps/platform/app.png | Bin 0 -> 4152 bytes .../public/static/apps/platform/h5.png | Bin 0 -> 3016 bytes .../static/apps/platform/mini_alipay.png | Bin 0 -> 4350 bytes .../static/apps/platform/mini_baidu.png | Bin 0 -> 5381 bytes .../static/apps/platform/mini_douyin.png | Bin 0 -> 5897 bytes .../public/static/apps/platform/mini_jd.png | Bin 0 -> 5468 bytes .../static/apps/platform/mini_kuaishou.png | Bin 0 -> 4812 bytes .../static/apps/platform/mini_wechat.png | Bin 0 -> 4818 bytes .../public/static/apps/platform/other.png | Bin 0 -> 3242 bytes .../public/static/apps/platform/pc.png | Bin 0 -> 5129 bytes .../apps/platform/wechat_official_account.png | Bin 0 -> 4543 bytes fastmovie-admin/public/static/beian.gov.png | Bin 0 -> 19256 bytes fastmovie-admin/public/static/bg.jpg | Bin 0 -> 53034 bytes fastmovie-admin/public/static/icode.js | 57 + .../public/static/image/alipay.png | Bin 0 -> 6212 bytes .../public/static/image/balance.png | Bin 0 -> 6541 bytes .../public/static/image/coupon.png | Bin 0 -> 6579 bytes .../public/static/image/integral.png | Bin 0 -> 6579 bytes fastmovie-admin/public/static/image/wxpay.png | Bin 0 -> 7331 bytes fastmovie-admin/public/static/version.json | 3 + .../public/template/fastmovie.html | 18 + .../resource/translations/en/admin.php | 68 + .../resource/translations/en/messages.php | 5 + .../resource/translations/zh_CN/admin.php | 68 + .../resource/translations/zh_CN/messages.php | 5 + fastmovie-admin/start.php | 5 + fastmovie-admin/support/Event.php | 19 + fastmovie-admin/support/Request.php | 24 + fastmovie-admin/support/Response.php | 24 + fastmovie-admin/support/ThinkPHP.php | 75 + fastmovie-admin/support/bootstrap.php | 139 + fastmovie-admin/support/helpers.php | 2 + .../update/1003/php_plugin_model.sql | 1 + .../update/1020/php_plugin_model.sql | 1 + .../1034/php_plugin_shortplay_actor.sql | 1 + fastmovie-admin/update/VERSION | 2 + fastmovie-admin/webman | 73 + fastmovie-admin/windows.php | 136 + fastmovie-vue/.env.development | 1 + fastmovie-vue/.env.production | 1 + fastmovie-vue/.eslintrc-auto-import.json | 81 + fastmovie-vue/.gitignore | 24 + fastmovie-vue/.htaccess | 1 + fastmovie-vue/README.md | 5 + fastmovie-vue/auto-imports.d.ts | 75 + fastmovie-vue/build-version.mjs | 29 + fastmovie-vue/components.d.ts | 90 + fastmovie-vue/index.html | 15 + fastmovie-vue/package-lock.json | 6431 +++++++++++++++ fastmovie-vue/package.json | 46 + .../fastmovie/static/image/login-image.png | Bin 0 -> 197907 bytes .../public/fastmovie/static/version.json | 3 + fastmovie-vue/public/icon.svg | 1 + fastmovie-vue/scss/_variables.scss | 62 + fastmovie-vue/scss/common.scss | 576 ++ fastmovie-vue/scss/css/common.css | 6418 +++++++++++++++ fastmovie-vue/scss/css/common.css.map | 10 + fastmovie-vue/scss/css/style.css | 698 ++ fastmovie-vue/scss/css/style.css.map | 9 + fastmovie-vue/scss/css/theme.css | 120 + fastmovie-vue/scss/css/theme.css.map | 9 + fastmovie-vue/scss/style.scss | 619 ++ fastmovie-vue/scss/theme.scss | 117 + fastmovie-vue/shared/types/index.d.ts | 105 + fastmovie-vue/src/App.vue | 106 + fastmovie-vue/src/assets/css/common.min.css | 2 + .../src/assets/css/common.min.css.map | 10 + fastmovie-vue/src/assets/css/style.min.css | 2 + .../src/assets/css/style.min.css.map | 9 + fastmovie-vue/src/assets/css/theme.min.css | 4 + .../src/assets/css/theme.min.css.map | 9 + fastmovie-vue/src/assets/vue.svg | 1 + fastmovie-vue/src/common/PricingCalculator.ts | 122 + .../src/common/av-cliper/ClipExporter.ts | 353 + .../src/common/av-cliper/ImageDurationClip.ts | 71 + fastmovie-vue/src/common/const.ts | 19 + fastmovie-vue/src/common/functions.ts | 525 ++ fastmovie-vue/src/common/http.ts | 200 + fastmovie-vue/src/common/push/channel.ts | 34 + fastmovie-vue/src/common/push/connection.ts | 130 + fastmovie-vue/src/common/push/dispatcher.ts | 90 + fastmovie-vue/src/common/push/index.ts | 164 + fastmovie-vue/src/common/time/IntervalTask.ts | 19 + fastmovie-vue/src/common/time/MinHeap.ts | 52 + fastmovie-vue/src/common/time/Scheduler.ts | 74 + fastmovie-vue/src/common/time/TimeoutTask.ts | 14 + .../src/components/x-header-tools/index.vue | 194 + .../x-header-tools/modules/code.vue | 6 + .../src/components/x-login/index.vue | 496 ++ .../src/components/x-vcode/index.vue | 102 + .../src/components/xl-actor/index.vue | 150 + .../components/xl-actor/xl-actor-create.vue | 621 ++ .../src/components/xl-aspect-ratio/index.vue | 47 + .../components/xl-character-look/index.vue | 336 + .../xl-character-look-create.vue | 242 + .../xl-dialogue/xl-dialogue-create.vue | 200 + .../components/xl-episode-duration/index.vue | 56 + .../src/components/xl-episode-sum/index.vue | 58 + .../components/xl-header-userinfo/index.vue | 168 + .../src/components/xl-icode/index.vue | 361 + .../components/xl-invitation-code/index.vue | 280 + .../src/components/xl-loading/index.vue | 150 + .../src/components/xl-models/index.vue | 121 + .../src/components/xl-notice/index.vue | 166 + fastmovie-vue/src/components/xl-pay/index.vue | 203 + .../src/components/xl-prop/index.vue | 96 + .../src/components/xl-prop/xl-prop-create.vue | 543 ++ .../src/components/xl-qrcode-view/index.vue | 138 + .../src/components/xl-qrcode/index.vue | 25 + .../components/xl-scene/xl-scene-create.vue | 272 + .../src/components/xl-storyboard/index.vue | 88 + .../src/components/xl-style/index.vue | 134 + .../src/components/xl-tabs-item/index.vue | 63 + .../src/components/xl-tabs/index.vue | 155 + .../src/components/xl-user-points/index.vue | 128 + .../src/components/xl-userinfo/index.vue | 122 + .../xl-userinfo/modules/xl-user.vue | 501 ++ .../src/components/xl-voice/index.vue | 190 + fastmovie-vue/src/composables/useAVCanvas.ts | 228 + fastmovie-vue/src/composables/useCompsite.ts | 405 + .../src/composables/useInvitationCode.ts | 41 + fastmovie-vue/src/composables/useLoading.ts | 34 + fastmovie-vue/src/composables/useLogin.ts | 82 + fastmovie-vue/src/composables/useNotify.ts | 8 + fastmovie-vue/src/composables/usePoints.ts | 24 + fastmovie-vue/src/composables/usePush.ts | 37 + fastmovie-vue/src/composables/useStorage.ts | 80 + fastmovie-vue/src/composables/useVcode.ts | 66 + fastmovie-vue/src/layouts/control.vue | 375 + fastmovie-vue/src/locale/en/index.ts | 50 + fastmovie-vue/src/locale/index.ts | 19 + fastmovie-vue/src/locale/zh-cn/index.ts | 50 + fastmovie-vue/src/main.ts | 22 + fastmovie-vue/src/pages/actor/index.vue | 9 + fastmovie-vue/src/pages/article/content.vue | 21 + fastmovie-vue/src/pages/creative/index.vue | 9 + fastmovie-vue/src/pages/fail.vue | 5 + .../src/pages/generate/actors/index.vue | 920 +++ .../src/pages/generate/drama/index.vue | 271 + .../pages/generate/drama/modules/episode.vue | 73 + .../pages/generate/drama/modules/scene.vue | 467 ++ .../generate/drama/modules/storyboard.vue | 813 ++ fastmovie-vue/src/pages/generate/index.vue | 177 + .../src/pages/generate/props/index.vue | 771 ++ .../src/pages/generate/scene/index.vue | 934 +++ .../src/pages/generate/storyboard/index.vue | 2927 +++++++ fastmovie-vue/src/pages/index/index.vue | 736 ++ .../src/pages/marketing/modules/price.vue | 40 + fastmovie-vue/src/pages/marketing/points.vue | 271 + fastmovie-vue/src/pages/marketing/vip.vue | 253 + fastmovie-vue/src/pages/play/index.vue | 238 + fastmovie-vue/src/pages/user/code.vue | 407 + fastmovie-vue/src/pages/user/index.vue | 112 + .../src/pages/user/modules/actors.vue | 383 + .../src/pages/user/modules/share.vue | 243 + .../src/pages/user/modules/voice.vue | 111 + .../src/pages/user/modules/works.vue | 241 + fastmovie-vue/src/pages/voice/create.vue | 798 ++ fastmovie-vue/src/pages/voice/index.vue | 9 + .../src/pages/voice/modules/edit.vue | 382 + .../src/pages/voice/modules/recording.vue | 497 ++ .../src/pages/voice/modules/voice-model.vue | 57 + fastmovie-vue/src/pages/works/drama.vue | 127 + fastmovie-vue/src/pages/works/index.vue | 342 + .../src/pages/works/modules/actors.vue | 826 ++ .../src/pages/works/modules/details.vue | 465 ++ .../src/pages/works/modules/episodes.vue | 348 + .../src/pages/works/modules/props.vue | 746 ++ fastmovie-vue/src/pages/works/square.vue | 232 + fastmovie-vue/src/routers/index.ts | 369 + fastmovie-vue/src/stores/index.ts | 14 + fastmovie-vue/src/stores/modules/model.ts | 52 + fastmovie-vue/src/stores/modules/state.ts | 52 + fastmovie-vue/src/stores/modules/user.ts | 74 + fastmovie-vue/src/stores/modules/wallet.ts | 43 + fastmovie-vue/src/stores/modules/webconfig.ts | 20 + fastmovie-vue/src/svg/icon/drama.vue | 17 + fastmovie-vue/src/svg/icon/helper.vue | 17 + fastmovie-vue/src/svg/icon/icon-about.vue | 15 + .../src/svg/icon/icon-actor-three-view.vue | 15 + fastmovie-vue/src/svg/icon/icon-actor.vue | 36 + fastmovie-vue/src/svg/icon/icon-alipay.vue | 25 + .../src/svg/icon/icon-aspect-ratio.vue | 11 + fastmovie-vue/src/svg/icon/icon-at.vue | 8 + fastmovie-vue/src/svg/icon/icon-batch.vue | 15 + fastmovie-vue/src/svg/icon/icon-book.vue | 8 + fastmovie-vue/src/svg/icon/icon-close.vue | 8 + fastmovie-vue/src/svg/icon/icon-clothes.vue | 14 + fastmovie-vue/src/svg/icon/icon-dubbing.vue | 18 + fastmovie-vue/src/svg/icon/icon-dunpai.vue | 19 + fastmovie-vue/src/svg/icon/icon-edit.vue | 8 + fastmovie-vue/src/svg/icon/icon-email.vue | 11 + fastmovie-vue/src/svg/icon/icon-help.vue | 8 + fastmovie-vue/src/svg/icon/icon-image.vue | 18 + fastmovie-vue/src/svg/icon/icon-like.vue | 8 + .../src/svg/icon/icon-login-phone.vue | 25 + .../src/svg/icon/icon-login-wechat.vue | 15 + fastmovie-vue/src/svg/icon/icon-logo.vue | 40 + fastmovie-vue/src/svg/icon/icon-logout.vue | 15 + fastmovie-vue/src/svg/icon/icon-model.vue | 8 + fastmovie-vue/src/svg/icon/icon-music.vue | 18 + fastmovie-vue/src/svg/icon/icon-next-step.vue | 15 + fastmovie-vue/src/svg/icon/icon-optimize.vue | 17 + fastmovie-vue/src/svg/icon/icon-pause.vue | 8 + fastmovie-vue/src/svg/icon/icon-play-1.vue | 8 + fastmovie-vue/src/svg/icon/icon-play.vue | 11 + fastmovie-vue/src/svg/icon/icon-points.vue | 15 + fastmovie-vue/src/svg/icon/icon-prev-step.vue | 15 + fastmovie-vue/src/svg/icon/icon-prop.vue | 11 + fastmovie-vue/src/svg/icon/icon-replace.vue | 15 + fastmovie-vue/src/svg/icon/icon-security.vue | 30 + fastmovie-vue/src/svg/icon/icon-select.vue | 15 + .../src/svg/icon/icon-storyboard.vue | 17 + fastmovie-vue/src/svg/icon/icon-style.vue | 11 + fastmovie-vue/src/svg/icon/icon-tips.vue | 15 + .../src/svg/icon/icon-transition.vue | 17 + .../src/svg/icon/icon-upload-image.vue | 21 + fastmovie-vue/src/svg/icon/icon-user.vue | 15 + fastmovie-vue/src/svg/icon/icon-video.vue | 8 + fastmovie-vue/src/svg/icon/icon-vip.vue | 17 + fastmovie-vue/src/svg/icon/icon-wechat.vue | 15 + fastmovie-vue/src/svg/icon/icon-wxpay.vue | 25 + fastmovie-vue/src/svg/icon/language.vue | 17 + fastmovie-vue/src/svg/icon/logo.vue | 61 + fastmovie-vue/src/svg/icon/upload.vue | 25 + fastmovie-vue/src/svg/icon/video-file.vue | 17 + fastmovie-vue/src/svg/tabs/actor-active.vue | 19 + fastmovie-vue/src/svg/tabs/actor.vue | 40 + .../src/svg/tabs/creative-active.vue | 24 + fastmovie-vue/src/svg/tabs/creative.vue | 29 + fastmovie-vue/src/svg/tabs/home-active.vue | 17 + fastmovie-vue/src/svg/tabs/home.vue | 17 + fastmovie-vue/src/svg/tabs/notice-active.vue | 15 + fastmovie-vue/src/svg/tabs/notice.vue | 15 + fastmovie-vue/src/svg/tabs/square-active.vue | 15 + fastmovie-vue/src/svg/tabs/square.vue | 15 + fastmovie-vue/src/svg/tabs/user-active.vue | 10 + fastmovie-vue/src/svg/tabs/user.vue | 10 + fastmovie-vue/src/svg/tabs/voice-active.vue | 17 + fastmovie-vue/src/svg/tabs/voice.vue | 22 + fastmovie-vue/src/svg/tabs/works-active.vue | 17 + fastmovie-vue/src/svg/tabs/works.vue | 17 + fastmovie-vue/src/workers/loadStream.ts | 295 + fastmovie-vue/tsconfig.json | 46 + fastmovie-vue/tsconfig.node.json | 11 + fastmovie-vue/vite.config.ts | 90 + 888 files changed, 105571 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.en.md create mode 100644 README.md create mode 100644 fastmovie-admin/.env.example create mode 100644 fastmovie-admin/.gitignore create mode 100644 fastmovie-admin/.htaccess create mode 100644 fastmovie-admin/404.html create mode 100644 fastmovie-admin/Dockerfile create mode 100644 fastmovie-admin/LICENSE create mode 100644 fastmovie-admin/README.md create mode 100644 fastmovie-admin/VERSION create mode 100644 fastmovie-admin/app/Basic.php create mode 100644 fastmovie-admin/app/admin/admin.json create mode 100644 fastmovie-admin/app/admin/api/Install.php create mode 100644 fastmovie-admin/app/admin/config/route.php create mode 100644 fastmovie-admin/app/admin/controller/AdminController.php create mode 100644 fastmovie-admin/app/admin/controller/AdminRoleController.php create mode 100644 fastmovie-admin/app/admin/controller/CaptchaController.php create mode 100644 fastmovie-admin/app/admin/controller/IndexController.php create mode 100644 fastmovie-admin/app/admin/controller/LoginController.php create mode 100644 fastmovie-admin/app/admin/controller/PaymentController.php create mode 100644 fastmovie-admin/app/admin/controller/PaymentTemplateController.php create mode 100644 fastmovie-admin/app/admin/controller/PlatformController.php create mode 100644 fastmovie-admin/app/admin/controller/PublicController.php create mode 100644 fastmovie-admin/app/admin/controller/SystemController.php create mode 100644 fastmovie-admin/app/admin/controller/UploadsController.php create mode 100644 fastmovie-admin/app/admin/middleware/Auth.php create mode 100644 fastmovie-admin/app/controller/IndexController.php create mode 100644 fastmovie-admin/app/controller/NotifyController.php create mode 100644 fastmovie-admin/app/controller/WechatOfficialAccountController.php create mode 100644 fastmovie-admin/app/exception/Handler.php create mode 100644 fastmovie-admin/app/expose/api/Install.php create mode 100644 fastmovie-admin/app/expose/build/builder/ActionBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/AppsBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/ComponentBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/DataboardBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/ExamineBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/FormBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/ImagesBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/InfoBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/RechargeBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/SoltBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/TableBuilder.php create mode 100644 fastmovie-admin/app/expose/build/builder/databoardBuilder/Basic.php create mode 100644 fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Apply.php create mode 100644 fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Domain.php create mode 100644 fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Echarts.php create mode 100644 fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Placeholder.php create mode 100644 fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Statistic.php create mode 100644 fastmovie-admin/app/expose/build/builder/databoardBuilder/interface/DataboardBuilderInterface.php create mode 100644 fastmovie-admin/app/expose/build/config/Action.php create mode 100644 fastmovie-admin/app/expose/build/config/Apis.php create mode 100644 fastmovie-admin/app/expose/build/config/Login.php create mode 100644 fastmovie-admin/app/expose/build/config/Web.php create mode 100644 fastmovie-admin/app/expose/enum/Action.php create mode 100644 fastmovie-admin/app/expose/enum/AlipayRsaModel.php create mode 100644 fastmovie-admin/app/expose/enum/EventName.php create mode 100644 fastmovie-admin/app/expose/enum/Examine.php create mode 100644 fastmovie-admin/app/expose/enum/Filesystem.php create mode 100644 fastmovie-admin/app/expose/enum/Methods.php create mode 100644 fastmovie-admin/app/expose/enum/PaymentChannels.php create mode 100644 fastmovie-admin/app/expose/enum/Permission.php create mode 100644 fastmovie-admin/app/expose/enum/Platform.php create mode 100644 fastmovie-admin/app/expose/enum/ResponseCode.php create mode 100644 fastmovie-admin/app/expose/enum/ResponseEvent.php create mode 100644 fastmovie-admin/app/expose/enum/SmsChannels.php create mode 100644 fastmovie-admin/app/expose/enum/State.php create mode 100644 fastmovie-admin/app/expose/enum/SubmitEvent.php create mode 100644 fastmovie-admin/app/expose/enum/Week.php create mode 100644 fastmovie-admin/app/expose/enum/WxpayMchType.php create mode 100644 fastmovie-admin/app/expose/enum/WxpayVersion.php create mode 100644 fastmovie-admin/app/expose/enum/builder/Enum.php create mode 100644 fastmovie-admin/app/expose/exception/Exception.php create mode 100644 fastmovie-admin/app/expose/helper/Captcha.php create mode 100644 fastmovie-admin/app/expose/helper/Config.php create mode 100644 fastmovie-admin/app/expose/helper/ConfigGroup.php create mode 100644 fastmovie-admin/app/expose/helper/Menus.php create mode 100644 fastmovie-admin/app/expose/helper/Payment.php create mode 100644 fastmovie-admin/app/expose/helper/Uploads.php create mode 100644 fastmovie-admin/app/expose/helper/Vcode.php create mode 100644 fastmovie-admin/app/expose/helper/Wechat.php create mode 100644 fastmovie-admin/app/expose/middleware/AdminAuth.php create mode 100644 fastmovie-admin/app/expose/middleware/Platform.php create mode 100644 fastmovie-admin/app/expose/middleware/Template.php create mode 100644 fastmovie-admin/app/expose/template/email/Vcode.php create mode 100644 fastmovie-admin/app/expose/template/sms/Basic.php create mode 100644 fastmovie-admin/app/expose/template/sms/Vcode.php create mode 100644 fastmovie-admin/app/expose/trait/Config.php create mode 100644 fastmovie-admin/app/expose/trait/Json.php create mode 100644 fastmovie-admin/app/expose/trait/Uploads.php create mode 100644 fastmovie-admin/app/expose/utils/DataModel.php create mode 100644 fastmovie-admin/app/expose/utils/Email.php create mode 100644 fastmovie-admin/app/expose/utils/Json.php create mode 100644 fastmovie-admin/app/expose/utils/Password.php create mode 100644 fastmovie-admin/app/expose/utils/Rsa.php create mode 100644 fastmovie-admin/app/expose/utils/Sms.php create mode 100644 fastmovie-admin/app/expose/utils/Str.php create mode 100644 fastmovie-admin/app/expose/utils/TreeModel.php create mode 100644 fastmovie-admin/app/expose/utils/TwofaAuthenticator.php create mode 100644 fastmovie-admin/app/expose/utils/wechat/OfficialAccount.php create mode 100644 fastmovie-admin/app/expose/utils/wechat/modules/Event.php create mode 100644 fastmovie-admin/app/expose/utils/wechat/modules/Reply.php create mode 100644 fastmovie-admin/app/expose/validate/Validate.php create mode 100644 fastmovie-admin/app/functions.php create mode 100644 fastmovie-admin/app/middleware/Access.php create mode 100644 fastmovie-admin/app/middleware/Platform.php create mode 100644 fastmovie-admin/app/middleware/StaticFile.php create mode 100644 fastmovie-admin/app/middleware/Template.php create mode 100644 fastmovie-admin/app/model/Admin.php create mode 100644 fastmovie-admin/app/model/AdminRole.php create mode 100644 fastmovie-admin/app/model/Basic.php create mode 100644 fastmovie-admin/app/model/Config.php create mode 100644 fastmovie-admin/app/model/PaymentConfig.php create mode 100644 fastmovie-admin/app/model/PaymentNotifyWechat.php create mode 100644 fastmovie-admin/app/model/PaymentTemplate.php create mode 100644 fastmovie-admin/app/model/Uploads.php create mode 100644 fastmovie-admin/app/model/UploadsClassify.php create mode 100644 fastmovie-admin/app/process/AutoDeleteUpload.php create mode 100644 fastmovie-admin/app/process/Http.php create mode 100644 fastmovie-admin/app/process/Monitor.php create mode 100644 fastmovie-admin/app/validate/Admin.php create mode 100644 fastmovie-admin/app/validate/User.php create mode 100644 fastmovie-admin/app/view/index/index.html create mode 100644 fastmovie-admin/app/view/main.html create mode 100644 fastmovie-admin/composer.json create mode 100644 fastmovie-admin/composer.lock create mode 100644 fastmovie-admin/config/app.php create mode 100644 fastmovie-admin/config/autoload.php create mode 100644 fastmovie-admin/config/bootstrap.php create mode 100644 fastmovie-admin/config/cache.php create mode 100644 fastmovie-admin/config/container.php create mode 100644 fastmovie-admin/config/database.php create mode 100644 fastmovie-admin/config/dependence.php create mode 100644 fastmovie-admin/config/event.php create mode 100644 fastmovie-admin/config/exception.php create mode 100644 fastmovie-admin/config/log.php create mode 100644 fastmovie-admin/config/middleware.php create mode 100644 fastmovie-admin/config/oauth.php create mode 100644 fastmovie-admin/config/plugin/shopwwi/filesystem/app.php create mode 100644 fastmovie-admin/config/plugin/webman/console/app.php create mode 100644 fastmovie-admin/config/plugin/webman/event/app.php create mode 100644 fastmovie-admin/config/plugin/webman/event/bootstrap.php create mode 100644 fastmovie-admin/config/plugin/webman/event/command.php create mode 100644 fastmovie-admin/config/plugin/webman/push/app.php create mode 100644 fastmovie-admin/config/plugin/webman/push/process.php create mode 100644 fastmovie-admin/config/plugin/webman/push/route.php create mode 100644 fastmovie-admin/config/process.php create mode 100644 fastmovie-admin/config/redis.php create mode 100644 fastmovie-admin/config/route.php create mode 100644 fastmovie-admin/config/server.php create mode 100644 fastmovie-admin/config/session.php create mode 100644 fastmovie-admin/config/settings-tabs.php create mode 100644 fastmovie-admin/config/settings-tabs/sms.php create mode 100644 fastmovie-admin/config/settings.php create mode 100644 fastmovie-admin/config/settings/basic.php create mode 100644 fastmovie-admin/config/settings/wechat_miniproject.php create mode 100644 fastmovie-admin/config/settings/wechat_official_account.php create mode 100644 fastmovie-admin/config/static.php create mode 100644 fastmovie-admin/config/thinkorm.php create mode 100644 fastmovie-admin/config/translation.php create mode 100644 fastmovie-admin/config/view.php create mode 100644 fastmovie-admin/database.sql create mode 100644 fastmovie-admin/index.html create mode 100644 fastmovie-admin/lua/module/config.lua create mode 100644 fastmovie-admin/lua/module/load_env.lua create mode 100644 fastmovie-admin/lua/qps.lua create mode 100644 fastmovie-admin/lua/test.lua create mode 100644 fastmovie-admin/menu.example create mode 100644 fastmovie-admin/nginx.example create mode 100644 fastmovie-admin/package.json create mode 100644 fastmovie-admin/plugin/article/admin.json create mode 100644 fastmovie-admin/plugin/article/api/Control.php create mode 100644 fastmovie-admin/plugin/article/api/Install.php create mode 100644 fastmovie-admin/plugin/article/api/Install.sql create mode 100644 fastmovie-admin/plugin/article/app/Basic.php create mode 100644 fastmovie-admin/plugin/article/app/admin/controller/AnnouncementController.php create mode 100644 fastmovie-admin/plugin/article/app/admin/controller/ClassifyController.php create mode 100644 fastmovie-admin/plugin/article/app/admin/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/article/app/admin/middleware/Auth.php create mode 100644 fastmovie-admin/plugin/article/app/api/controller/ArticleController.php create mode 100644 fastmovie-admin/plugin/article/app/control/controller/AgreementController.php create mode 100644 fastmovie-admin/plugin/article/app/control/controller/AnnouncementController.php create mode 100644 fastmovie-admin/plugin/article/app/control/controller/ClassifyController.php create mode 100644 fastmovie-admin/plugin/article/app/control/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/article/app/functions.php create mode 100644 fastmovie-admin/plugin/article/app/model/PluginArticle.php create mode 100644 fastmovie-admin/plugin/article/app/model/PluginArticleClassify.php create mode 100644 fastmovie-admin/plugin/article/app/model/PluginArticleContent.php create mode 100644 fastmovie-admin/plugin/article/app/validate/Article.php create mode 100644 fastmovie-admin/plugin/article/config/app.php create mode 100644 fastmovie-admin/plugin/article/config/autoload.php create mode 100644 fastmovie-admin/plugin/article/config/container.php create mode 100644 fastmovie-admin/plugin/article/config/event.php create mode 100644 fastmovie-admin/plugin/article/config/exception.php create mode 100644 fastmovie-admin/plugin/article/config/log.php create mode 100644 fastmovie-admin/plugin/article/config/middleware.php create mode 100644 fastmovie-admin/plugin/article/config/process.php create mode 100644 fastmovie-admin/plugin/article/config/static.php create mode 100644 fastmovie-admin/plugin/article/config/translation.php create mode 100644 fastmovie-admin/plugin/article/control.json create mode 100644 fastmovie-admin/plugin/article/expose/helper/Article.php create mode 100644 fastmovie-admin/plugin/article/package.json create mode 100644 fastmovie-admin/plugin/article/process/JoinPushMessage.php create mode 100644 fastmovie-admin/plugin/article/public/static/index.css create mode 100644 fastmovie-admin/plugin/article/public/template.html create mode 100644 fastmovie-admin/plugin/article/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/article/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/article/utils/enum/CrowdEnum.php create mode 100644 fastmovie-admin/plugin/control/README.md create mode 100644 fastmovie-admin/plugin/control/admin.json create mode 100644 fastmovie-admin/plugin/control/api/Control.php create mode 100644 fastmovie-admin/plugin/control/api/Install.php create mode 100644 fastmovie-admin/plugin/control/app/admin/controller/SettingsController.php create mode 100644 fastmovie-admin/plugin/control/app/admin/controller/UserController.php create mode 100644 fastmovie-admin/plugin/control/app/api/controller/PublicController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/CaptchaController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/DomainController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/LoginController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/PaymentController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/PaymentTemplateController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/PlatformController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/PublicController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/RoleController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/SystemController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/UploadsController.php create mode 100644 fastmovie-admin/plugin/control/app/control/controller/UserController.php create mode 100644 fastmovie-admin/plugin/control/app/controller/WechatOfficialAccountController.php create mode 100644 fastmovie-admin/plugin/control/app/middleware/Auth.php create mode 100644 fastmovie-admin/plugin/control/app/model/PluginChannelsDomain.php create mode 100644 fastmovie-admin/plugin/control/app/model/PluginChannelsRole.php create mode 100644 fastmovie-admin/plugin/control/app/model/PluginChannelsUser.php create mode 100644 fastmovie-admin/plugin/control/app/model/PluginChannelsWechat.php create mode 100644 fastmovie-admin/plugin/control/config/app.php create mode 100644 fastmovie-admin/plugin/control/config/autoload.php create mode 100644 fastmovie-admin/plugin/control/config/container.php create mode 100644 fastmovie-admin/plugin/control/config/event.php create mode 100644 fastmovie-admin/plugin/control/config/exception.php create mode 100644 fastmovie-admin/plugin/control/config/log.php create mode 100644 fastmovie-admin/plugin/control/config/middleware.php create mode 100644 fastmovie-admin/plugin/control/config/process.php create mode 100644 fastmovie-admin/plugin/control/config/route.php create mode 100644 fastmovie-admin/plugin/control/config/settings-tabs/sms.php create mode 100644 fastmovie-admin/plugin/control/config/settings/basic.php create mode 100644 fastmovie-admin/plugin/control/config/settings/state.php create mode 100644 fastmovie-admin/plugin/control/config/settings/wechat_miniproject.php create mode 100644 fastmovie-admin/plugin/control/config/settings/wechat_official_account.php create mode 100644 fastmovie-admin/plugin/control/config/settings/yidevs.php create mode 100644 fastmovie-admin/plugin/control/config/translation.php create mode 100644 fastmovie-admin/plugin/control/config/view.php create mode 100644 fastmovie-admin/plugin/control/control.json create mode 100644 fastmovie-admin/plugin/control/event/WechatOfficialAccount.php create mode 100644 fastmovie-admin/plugin/control/expose/api/Control.php create mode 100644 fastmovie-admin/plugin/control/expose/helper/Control.php create mode 100644 fastmovie-admin/plugin/control/expose/helper/Uploads.php create mode 100644 fastmovie-admin/plugin/control/expose/helper/Vcode.php create mode 100644 fastmovie-admin/plugin/control/expose/helper/Wechat.php create mode 100644 fastmovie-admin/plugin/control/expose/middleware/ControlAuth.php create mode 100644 fastmovie-admin/plugin/control/expose/middleware/DomainMapping.php create mode 100644 fastmovie-admin/plugin/control/expose/trait/Uploads.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/en/api.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/en/control.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/zh_CN/api.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/zh_CN/control.php create mode 100644 fastmovie-admin/plugin/control/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/control/utils/LRUCache.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/Client.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/Exception/HttpCurlException.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/Exception/InvalidResultException.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/Exception/InvalidTokenException.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/Yidevs.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/stream/Client.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/Audio.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/AudioAssistant.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/Chat.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/ChatAssistant.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/Draw.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/DrawAssistant.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/Video.php create mode 100644 fastmovie-admin/plugin/control/utils/yidevs/trait/VideoAssistant.php create mode 100644 fastmovie-admin/plugin/finance/admin.json create mode 100644 fastmovie-admin/plugin/finance/api/Control.php create mode 100644 fastmovie-admin/plugin/finance/api/Install.php create mode 100644 fastmovie-admin/plugin/finance/api/notify/Wechat.php create mode 100644 fastmovie-admin/plugin/finance/app/admin/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/finance/app/admin/controller/OrdersController.php create mode 100644 fastmovie-admin/plugin/finance/app/admin/controller/SettingsController.php create mode 100644 fastmovie-admin/plugin/finance/app/api/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/finance/app/api/controller/OrdersController.php create mode 100644 fastmovie-admin/plugin/finance/app/api/controller/UserController.php create mode 100644 fastmovie-admin/plugin/finance/app/control/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/finance/app/control/controller/OrdersController.php create mode 100644 fastmovie-admin/plugin/finance/app/control/controller/WalletController.php create mode 100644 fastmovie-admin/plugin/finance/app/functions.php create mode 100644 fastmovie-admin/plugin/finance/app/model/PluginFinanceOrders.php create mode 100644 fastmovie-admin/plugin/finance/app/model/PluginFinanceOrdersLog.php create mode 100644 fastmovie-admin/plugin/finance/app/model/PluginFinancePaymentNotify.php create mode 100644 fastmovie-admin/plugin/finance/app/model/PluginFinanceWallet.php create mode 100644 fastmovie-admin/plugin/finance/config/app.php create mode 100644 fastmovie-admin/plugin/finance/config/autoload.php create mode 100644 fastmovie-admin/plugin/finance/config/container.php create mode 100644 fastmovie-admin/plugin/finance/config/event.php create mode 100644 fastmovie-admin/plugin/finance/config/exception.php create mode 100644 fastmovie-admin/plugin/finance/config/middleware.php create mode 100644 fastmovie-admin/plugin/finance/config/process.php create mode 100644 fastmovie-admin/plugin/finance/config/settings/basic.php create mode 100644 fastmovie-admin/plugin/finance/config/translation.php create mode 100644 fastmovie-admin/plugin/finance/control.json create mode 100644 fastmovie-admin/plugin/finance/event/Orders.php create mode 100644 fastmovie-admin/plugin/finance/expose/helper/Account.php create mode 100644 fastmovie-admin/plugin/finance/package.json create mode 100644 fastmovie-admin/plugin/finance/process/Expire.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/en/api.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/en/control.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/zh_CN/api.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/zh_CN/control.php create mode 100644 fastmovie-admin/plugin/finance/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/finance/utils/enum/BillScene.php create mode 100644 fastmovie-admin/plugin/finance/utils/enum/OrdersLogLevel.php create mode 100644 fastmovie-admin/plugin/finance/utils/enum/OrdersState.php create mode 100644 fastmovie-admin/plugin/finance/utils/enum/OrdersType.php create mode 100644 fastmovie-admin/plugin/finance/utils/enum/PointsBillScene.php create mode 100644 fastmovie-admin/plugin/finance/utils/enum/ValidityPeriod.php create mode 100644 fastmovie-admin/plugin/marketing/admin.json create mode 100644 fastmovie-admin/plugin/marketing/api/Control.php create mode 100644 fastmovie-admin/plugin/marketing/api/Install.php create mode 100644 fastmovie-admin/plugin/marketing/app/admin/controller/CouponCodeController.php create mode 100644 fastmovie-admin/plugin/marketing/app/admin/controller/CouponController.php create mode 100644 fastmovie-admin/plugin/marketing/app/admin/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/marketing/app/api/controller/CouponController.php create mode 100644 fastmovie-admin/plugin/marketing/app/api/controller/PaymentController.php create mode 100644 fastmovie-admin/plugin/marketing/app/api/controller/PointsController.php create mode 100644 fastmovie-admin/plugin/marketing/app/api/controller/UserCouponController.php create mode 100644 fastmovie-admin/plugin/marketing/app/api/controller/VipController.php create mode 100644 fastmovie-admin/plugin/marketing/app/control/controller/CouponCodeController.php create mode 100644 fastmovie-admin/plugin/marketing/app/control/controller/CouponController.php create mode 100644 fastmovie-admin/plugin/marketing/app/control/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/marketing/app/control/controller/PlanController.php create mode 100644 fastmovie-admin/plugin/marketing/app/control/controller/PointsController.php create mode 100644 fastmovie-admin/plugin/marketing/app/functions.php create mode 100644 fastmovie-admin/plugin/marketing/app/model/PluginMarketingCoupon.php create mode 100644 fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponCode.php create mode 100644 fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponPassword.php create mode 100644 fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponServer.php create mode 100644 fastmovie-admin/plugin/marketing/app/model/PluginMarketingPlan.php create mode 100644 fastmovie-admin/plugin/marketing/app/model/PluginMarketingPlanPrice.php create mode 100644 fastmovie-admin/plugin/marketing/app/model/PluginMarketingPoints.php create mode 100644 fastmovie-admin/plugin/marketing/app/validate/PluginMarketingCoupon.php create mode 100644 fastmovie-admin/plugin/marketing/config/app.php create mode 100644 fastmovie-admin/plugin/marketing/config/autoload.php create mode 100644 fastmovie-admin/plugin/marketing/config/container.php create mode 100644 fastmovie-admin/plugin/marketing/config/exception.php create mode 100644 fastmovie-admin/plugin/marketing/config/log.php create mode 100644 fastmovie-admin/plugin/marketing/config/middleware.php create mode 100644 fastmovie-admin/plugin/marketing/config/process.php create mode 100644 fastmovie-admin/plugin/marketing/config/translation.php create mode 100644 fastmovie-admin/plugin/marketing/control.json create mode 100644 fastmovie-admin/plugin/marketing/package.json create mode 100644 fastmovie-admin/plugin/marketing/process/Expire.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/en/api.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/en/control.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/zh_CN/api.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/zh_CN/control.php create mode 100644 fastmovie-admin/plugin/marketing/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/marketing/utils/enum/BillingCycle.php create mode 100644 fastmovie-admin/plugin/marketing/utils/enum/CouponRule.php create mode 100644 fastmovie-admin/plugin/marketing/utils/enum/PlanKey.php create mode 100644 fastmovie-admin/plugin/marketing/utils/enum/ReceiveType.php create mode 100644 fastmovie-admin/plugin/marketing/utils/enum/UseState.php create mode 100644 fastmovie-admin/plugin/marketing/utils/enum/UseType.php create mode 100644 fastmovie-admin/plugin/model/admin.json create mode 100644 fastmovie-admin/plugin/model/api/Control.php create mode 100644 fastmovie-admin/plugin/model/api/Install.php create mode 100644 fastmovie-admin/plugin/model/app/admin/controller/ModelController.php create mode 100644 fastmovie-admin/plugin/model/app/admin/controller/TaskController.php create mode 100644 fastmovie-admin/plugin/model/app/admin/controller/VoiceController.php create mode 100644 fastmovie-admin/plugin/model/app/api/controller/ModelController.php create mode 100644 fastmovie-admin/plugin/model/app/api/controller/TaskController.php create mode 100644 fastmovie-admin/plugin/model/app/api/controller/VoiceController.php create mode 100644 fastmovie-admin/plugin/model/app/api/controller/VoiceTextController.php create mode 100644 fastmovie-admin/plugin/model/app/control/controller/ModelController.php create mode 100644 fastmovie-admin/plugin/model/app/control/controller/TaskController.php create mode 100644 fastmovie-admin/plugin/model/app/control/controller/VoiceController.php create mode 100644 fastmovie-admin/plugin/model/app/control/controller/VoiceTextController.php create mode 100644 fastmovie-admin/plugin/model/app/controller/NotifyController.php create mode 100644 fastmovie-admin/plugin/model/app/functions.php create mode 100644 fastmovie-admin/plugin/model/app/model/PluginModel.php create mode 100644 fastmovie-admin/plugin/model/app/model/PluginModelTask.php create mode 100644 fastmovie-admin/plugin/model/app/model/PluginModelTaskResult.php create mode 100644 fastmovie-admin/plugin/model/app/model/PluginModelVoice.php create mode 100644 fastmovie-admin/plugin/model/app/model/PluginModelVoiceText.php create mode 100644 fastmovie-admin/plugin/model/config/app.php create mode 100644 fastmovie-admin/plugin/model/config/autoload.php create mode 100644 fastmovie-admin/plugin/model/config/container.php create mode 100644 fastmovie-admin/plugin/model/config/exception.php create mode 100644 fastmovie-admin/plugin/model/config/middleware.php create mode 100644 fastmovie-admin/plugin/model/config/process.php create mode 100644 fastmovie-admin/plugin/model/config/settings/basic.php create mode 100644 fastmovie-admin/plugin/model/config/translation.php create mode 100644 fastmovie-admin/plugin/model/config/view.php create mode 100644 fastmovie-admin/plugin/model/control.json create mode 100644 fastmovie-admin/plugin/model/modal.json create mode 100644 fastmovie-admin/plugin/model/package.json create mode 100644 fastmovie-admin/plugin/model/process/audio/AudioTransfer.php create mode 100644 fastmovie-admin/plugin/model/process/chat/Submit.php create mode 100644 fastmovie-admin/plugin/model/process/draw/ImageTransfer.php create mode 100644 fastmovie-admin/plugin/model/process/video/VideoTransfer.php create mode 100644 fastmovie-admin/plugin/model/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/model/resource/translations/en/api.php create mode 100644 fastmovie-admin/plugin/model/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/model/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/model/resource/translations/zh_CN/api.php create mode 100644 fastmovie-admin/plugin/model/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/model/utils/enum/ModelPointType.php create mode 100644 fastmovie-admin/plugin/model/utils/enum/ModelScene.php create mode 100644 fastmovie-admin/plugin/model/utils/enum/ModelTaskStatus.php create mode 100644 fastmovie-admin/plugin/model/utils/enum/ModelType.php create mode 100644 fastmovie-admin/plugin/model/utils/enum/ModelVoiceStatus.php create mode 100644 fastmovie-admin/plugin/notification/admin.json create mode 100644 fastmovie-admin/plugin/notification/api/Install.php create mode 100644 fastmovie-admin/plugin/notification/api/Install.sql create mode 100644 fastmovie-admin/plugin/notification/app/Basic.php create mode 100644 fastmovie-admin/plugin/notification/app/admin/controller/MessageController.php create mode 100644 fastmovie-admin/plugin/notification/app/admin/controller/SystemController.php create mode 100644 fastmovie-admin/plugin/notification/app/admin/controller/WechatTemplateController.php create mode 100644 fastmovie-admin/plugin/notification/app/api/controller/MessageController.php create mode 100644 fastmovie-admin/plugin/notification/app/api/controller/PushController.php create mode 100644 fastmovie-admin/plugin/notification/app/controller/PushController.php create mode 100644 fastmovie-admin/plugin/notification/app/functions.php create mode 100644 fastmovie-admin/plugin/notification/app/model/PluginNotificationMessage.php create mode 100644 fastmovie-admin/plugin/notification/app/model/PluginNotificationMessageContent.php create mode 100644 fastmovie-admin/plugin/notification/app/model/PluginNotificationOnline.php create mode 100644 fastmovie-admin/plugin/notification/config/app.php create mode 100644 fastmovie-admin/plugin/notification/config/autoload.php create mode 100644 fastmovie-admin/plugin/notification/config/container.php create mode 100644 fastmovie-admin/plugin/notification/config/event.php create mode 100644 fastmovie-admin/plugin/notification/config/exception.php create mode 100644 fastmovie-admin/plugin/notification/config/middleware.php create mode 100644 fastmovie-admin/plugin/notification/config/process.php create mode 100644 fastmovie-admin/plugin/notification/config/settings/push.php create mode 100644 fastmovie-admin/plugin/notification/config/settings/sms_template.php create mode 100644 fastmovie-admin/plugin/notification/config/settings/strategy.php create mode 100644 fastmovie-admin/plugin/notification/config/settings/wechat_template.php create mode 100644 fastmovie-admin/plugin/notification/config/static.php create mode 100644 fastmovie-admin/plugin/notification/config/translation.php create mode 100644 fastmovie-admin/plugin/notification/expose/helper/Message.php create mode 100644 fastmovie-admin/plugin/notification/expose/helper/Notice.php create mode 100644 fastmovie-admin/plugin/notification/expose/helper/Online.php create mode 100644 fastmovie-admin/plugin/notification/expose/helper/Push.php create mode 100644 fastmovie-admin/plugin/notification/expose/template/sms/Notice.php create mode 100644 fastmovie-admin/plugin/notification/package.json create mode 100644 fastmovie-admin/plugin/notification/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/notification/resource/translations/en/api.php create mode 100644 fastmovie-admin/plugin/notification/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/notification/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/notification/resource/translations/zh_CN/api.php create mode 100644 fastmovie-admin/plugin/notification/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/notification/utils/Api.php create mode 100644 fastmovie-admin/plugin/notification/utils/PushException.php create mode 100644 fastmovie-admin/plugin/notification/utils/enum/AuthType.php create mode 100644 fastmovie-admin/plugin/notification/utils/enum/MessageScene.php create mode 100644 fastmovie-admin/plugin/notification/utils/enum/Method.php create mode 100644 fastmovie-admin/plugin/notification/utils/enum/Scene.php create mode 100644 fastmovie-admin/plugin/pluginExample/admin.json create mode 100644 fastmovie-admin/plugin/pluginExample/api/Install.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/DataboardController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/ExamineController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/FormController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/GroupFormController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/ImagesController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/InfoController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/SettingsController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/admin/controller/TableController.php create mode 100644 fastmovie-admin/plugin/pluginExample/app/functions.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/app.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/autoload.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/container.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/exception.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/log.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/middleware.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/process.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/settings/basic.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/translation.php create mode 100644 fastmovie-admin/plugin/pluginExample/config/view.php create mode 100644 fastmovie-admin/plugin/pluginExample/package.json create mode 100644 fastmovie-admin/plugin/pluginExample/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/pluginExample/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/shortplay/admin.json create mode 100644 fastmovie-admin/plugin/shortplay/api/Control.php create mode 100644 fastmovie-admin/plugin/shortplay/api/Install.php create mode 100644 fastmovie-admin/plugin/shortplay/app/admin/controller/ActorController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/admin/controller/DramaController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/admin/controller/DramaEpisodeController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/admin/controller/StyleController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/ActorController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/CharacterLookController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/DramaController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/DramaEpisodeController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/GenerateController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/IndexController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/PropController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/SceneController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/SquareController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardDialogueController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/StyleController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/UploadsController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/VoiceController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/api/controller/WorksController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/control/controller/ActorController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/control/controller/DramaController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/control/controller/DramaEpisodeController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/control/controller/StyleController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/controller/DownloadController.php create mode 100644 fastmovie-admin/plugin/shortplay/app/functions.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayActor.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayActorCharacterLook.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayCharacterLook.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDrama.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaActor.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisode.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisodeActor.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaScene.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboard.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardActor.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardDialogue.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardProp.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayProp.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShare.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareEpisode.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareLikes.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayStyle.php create mode 100644 fastmovie-admin/plugin/shortplay/app/model/PluginShortplayVoice.php create mode 100644 fastmovie-admin/plugin/shortplay/config/app.php create mode 100644 fastmovie-admin/plugin/shortplay/config/autoload.php create mode 100644 fastmovie-admin/plugin/shortplay/config/container.php create mode 100644 fastmovie-admin/plugin/shortplay/config/exception.php create mode 100644 fastmovie-admin/plugin/shortplay/config/middleware.php create mode 100644 fastmovie-admin/plugin/shortplay/config/process.php create mode 100644 fastmovie-admin/plugin/shortplay/config/route.php create mode 100644 fastmovie-admin/plugin/shortplay/config/translation.php create mode 100644 fastmovie-admin/plugin/shortplay/control.json create mode 100644 fastmovie-admin/plugin/shortplay/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/shortplay/resource/translations/en/api.php create mode 100644 fastmovie-admin/plugin/shortplay/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/api.php create mode 100644 fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/PricingCalculator.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/ActorAge.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/ActorGender.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/ActorSpeciesType.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/ActorStatus.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/PropStatus.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/StyleClassify.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/VoiceEmotion.php create mode 100644 fastmovie-admin/plugin/shortplay/utils/enum/VoiceLanguage.php create mode 100644 fastmovie-admin/plugin/user/admin.json create mode 100644 fastmovie-admin/plugin/user/api/Control.php create mode 100644 fastmovie-admin/plugin/user/api/Install.php create mode 100644 fastmovie-admin/plugin/user/api/admin/IndexController.php create mode 100644 fastmovie-admin/plugin/user/app/admin/controller/BalanceController.php create mode 100644 fastmovie-admin/plugin/user/app/admin/controller/PointsController.php create mode 100644 fastmovie-admin/plugin/user/app/admin/controller/SettingController.php create mode 100644 fastmovie-admin/plugin/user/app/admin/controller/UserController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/BillController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/BindThirdPartyController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/CaptchaController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/LoginController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/SignupController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/UploadsController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/UserBillController.php create mode 100644 fastmovie-admin/plugin/user/app/api/controller/UserController.php create mode 100644 fastmovie-admin/plugin/user/app/control/controller/BalanceController.php create mode 100644 fastmovie-admin/plugin/user/app/control/controller/InvitationCodeController.php create mode 100644 fastmovie-admin/plugin/user/app/control/controller/PointsController.php create mode 100644 fastmovie-admin/plugin/user/app/control/controller/SettingController.php create mode 100644 fastmovie-admin/plugin/user/app/control/controller/SystemController.php create mode 100644 fastmovie-admin/plugin/user/app/control/controller/UserController.php create mode 100644 fastmovie-admin/plugin/user/app/functions.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUser.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUserBill.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUserInvitationCode.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUserPoints.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUserPointsBill.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUserTwofaSecret.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUserVip.php create mode 100644 fastmovie-admin/plugin/user/app/model/PluginUserWechat.php create mode 100644 fastmovie-admin/plugin/user/config/app.php create mode 100644 fastmovie-admin/plugin/user/config/autoload.php create mode 100644 fastmovie-admin/plugin/user/config/container.php create mode 100644 fastmovie-admin/plugin/user/config/event.php create mode 100644 fastmovie-admin/plugin/user/config/exception.php create mode 100644 fastmovie-admin/plugin/user/config/log.php create mode 100644 fastmovie-admin/plugin/user/config/middleware.php create mode 100644 fastmovie-admin/plugin/user/config/process.php create mode 100644 fastmovie-admin/plugin/user/config/settings/basic.php create mode 100644 fastmovie-admin/plugin/user/config/settings/register.php create mode 100644 fastmovie-admin/plugin/user/config/translation.php create mode 100644 fastmovie-admin/plugin/user/config/view.php create mode 100644 fastmovie-admin/plugin/user/control.json create mode 100644 fastmovie-admin/plugin/user/event/InvitationCode.php create mode 100644 fastmovie-admin/plugin/user/event/User.php create mode 100644 fastmovie-admin/plugin/user/event/WECHAT_OFFICIAL_ACCOUNT.php create mode 100644 fastmovie-admin/plugin/user/expose/helper/User.php create mode 100644 fastmovie-admin/plugin/user/expose/helper/WechatOfficialAccount.php create mode 100644 fastmovie-admin/plugin/user/expose/middleware/Twofa.php create mode 100644 fastmovie-admin/plugin/user/expose/middleware/UserAuth.php create mode 100644 fastmovie-admin/plugin/user/package.json create mode 100644 fastmovie-admin/plugin/user/resource/translations/en/admin.php create mode 100644 fastmovie-admin/plugin/user/resource/translations/en/api.php create mode 100644 fastmovie-admin/plugin/user/resource/translations/en/control.php create mode 100644 fastmovie-admin/plugin/user/resource/translations/en/messages.php create mode 100644 fastmovie-admin/plugin/user/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/plugin/user/resource/translations/zh_CN/api.php create mode 100644 fastmovie-admin/plugin/user/resource/translations/zh_CN/control.php create mode 100644 fastmovie-admin/plugin/user/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/plugin/user/utils/enum/CertificateType.php create mode 100644 fastmovie-admin/plugin/user/utils/enum/MoneyAction.php create mode 100644 fastmovie-admin/plugin/user/utils/enum/PropsType.php create mode 100644 fastmovie-admin/plugin/user/utils/enum/TotpApp.php create mode 100644 fastmovie-admin/plugin/user/utils/enum/UserPermission.php create mode 100644 fastmovie-admin/plugin/user/utils/enum/VcodeScene.php create mode 100644 fastmovie-admin/process/Monitor.php create mode 100644 fastmovie-admin/public/404.html create mode 100644 fastmovie-admin/public/article/.gitignore create mode 100644 fastmovie-admin/public/favicon.ico create mode 100644 fastmovie-admin/public/icon.svg create mode 100644 fastmovie-admin/public/index.html create mode 100644 fastmovie-admin/public/index.php create mode 100644 fastmovie-admin/public/static/apps/platform/app.png create mode 100644 fastmovie-admin/public/static/apps/platform/h5.png create mode 100644 fastmovie-admin/public/static/apps/platform/mini_alipay.png create mode 100644 fastmovie-admin/public/static/apps/platform/mini_baidu.png create mode 100644 fastmovie-admin/public/static/apps/platform/mini_douyin.png create mode 100644 fastmovie-admin/public/static/apps/platform/mini_jd.png create mode 100644 fastmovie-admin/public/static/apps/platform/mini_kuaishou.png create mode 100644 fastmovie-admin/public/static/apps/platform/mini_wechat.png create mode 100644 fastmovie-admin/public/static/apps/platform/other.png create mode 100644 fastmovie-admin/public/static/apps/platform/pc.png create mode 100644 fastmovie-admin/public/static/apps/platform/wechat_official_account.png create mode 100644 fastmovie-admin/public/static/beian.gov.png create mode 100644 fastmovie-admin/public/static/bg.jpg create mode 100644 fastmovie-admin/public/static/icode.js create mode 100644 fastmovie-admin/public/static/image/alipay.png create mode 100644 fastmovie-admin/public/static/image/balance.png create mode 100644 fastmovie-admin/public/static/image/coupon.png create mode 100644 fastmovie-admin/public/static/image/integral.png create mode 100644 fastmovie-admin/public/static/image/wxpay.png create mode 100644 fastmovie-admin/public/static/version.json create mode 100644 fastmovie-admin/public/template/fastmovie.html create mode 100644 fastmovie-admin/resource/translations/en/admin.php create mode 100644 fastmovie-admin/resource/translations/en/messages.php create mode 100644 fastmovie-admin/resource/translations/zh_CN/admin.php create mode 100644 fastmovie-admin/resource/translations/zh_CN/messages.php create mode 100644 fastmovie-admin/start.php create mode 100644 fastmovie-admin/support/Event.php create mode 100644 fastmovie-admin/support/Request.php create mode 100644 fastmovie-admin/support/Response.php create mode 100644 fastmovie-admin/support/ThinkPHP.php create mode 100644 fastmovie-admin/support/bootstrap.php create mode 100644 fastmovie-admin/support/helpers.php create mode 100644 fastmovie-admin/update/1003/php_plugin_model.sql create mode 100644 fastmovie-admin/update/1020/php_plugin_model.sql create mode 100644 fastmovie-admin/update/1034/php_plugin_shortplay_actor.sql create mode 100644 fastmovie-admin/update/VERSION create mode 100644 fastmovie-admin/webman create mode 100644 fastmovie-admin/windows.php create mode 100644 fastmovie-vue/.env.development create mode 100644 fastmovie-vue/.env.production create mode 100644 fastmovie-vue/.eslintrc-auto-import.json create mode 100644 fastmovie-vue/.gitignore create mode 100644 fastmovie-vue/.htaccess create mode 100644 fastmovie-vue/README.md create mode 100644 fastmovie-vue/auto-imports.d.ts create mode 100644 fastmovie-vue/build-version.mjs create mode 100644 fastmovie-vue/components.d.ts create mode 100644 fastmovie-vue/index.html create mode 100644 fastmovie-vue/package-lock.json create mode 100644 fastmovie-vue/package.json create mode 100644 fastmovie-vue/public/fastmovie/static/image/login-image.png create mode 100644 fastmovie-vue/public/fastmovie/static/version.json create mode 100644 fastmovie-vue/public/icon.svg create mode 100644 fastmovie-vue/scss/_variables.scss create mode 100644 fastmovie-vue/scss/common.scss create mode 100644 fastmovie-vue/scss/css/common.css create mode 100644 fastmovie-vue/scss/css/common.css.map create mode 100644 fastmovie-vue/scss/css/style.css create mode 100644 fastmovie-vue/scss/css/style.css.map create mode 100644 fastmovie-vue/scss/css/theme.css create mode 100644 fastmovie-vue/scss/css/theme.css.map create mode 100644 fastmovie-vue/scss/style.scss create mode 100644 fastmovie-vue/scss/theme.scss create mode 100644 fastmovie-vue/shared/types/index.d.ts create mode 100644 fastmovie-vue/src/App.vue create mode 100644 fastmovie-vue/src/assets/css/common.min.css create mode 100644 fastmovie-vue/src/assets/css/common.min.css.map create mode 100644 fastmovie-vue/src/assets/css/style.min.css create mode 100644 fastmovie-vue/src/assets/css/style.min.css.map create mode 100644 fastmovie-vue/src/assets/css/theme.min.css create mode 100644 fastmovie-vue/src/assets/css/theme.min.css.map create mode 100644 fastmovie-vue/src/assets/vue.svg create mode 100644 fastmovie-vue/src/common/PricingCalculator.ts create mode 100644 fastmovie-vue/src/common/av-cliper/ClipExporter.ts create mode 100644 fastmovie-vue/src/common/av-cliper/ImageDurationClip.ts create mode 100644 fastmovie-vue/src/common/const.ts create mode 100644 fastmovie-vue/src/common/functions.ts create mode 100644 fastmovie-vue/src/common/http.ts create mode 100644 fastmovie-vue/src/common/push/channel.ts create mode 100644 fastmovie-vue/src/common/push/connection.ts create mode 100644 fastmovie-vue/src/common/push/dispatcher.ts create mode 100644 fastmovie-vue/src/common/push/index.ts create mode 100644 fastmovie-vue/src/common/time/IntervalTask.ts create mode 100644 fastmovie-vue/src/common/time/MinHeap.ts create mode 100644 fastmovie-vue/src/common/time/Scheduler.ts create mode 100644 fastmovie-vue/src/common/time/TimeoutTask.ts create mode 100644 fastmovie-vue/src/components/x-header-tools/index.vue create mode 100644 fastmovie-vue/src/components/x-header-tools/modules/code.vue create mode 100644 fastmovie-vue/src/components/x-login/index.vue create mode 100644 fastmovie-vue/src/components/x-vcode/index.vue create mode 100644 fastmovie-vue/src/components/xl-actor/index.vue create mode 100644 fastmovie-vue/src/components/xl-actor/xl-actor-create.vue create mode 100644 fastmovie-vue/src/components/xl-aspect-ratio/index.vue create mode 100644 fastmovie-vue/src/components/xl-character-look/index.vue create mode 100644 fastmovie-vue/src/components/xl-character-look/xl-character-look-create.vue create mode 100644 fastmovie-vue/src/components/xl-dialogue/xl-dialogue-create.vue create mode 100644 fastmovie-vue/src/components/xl-episode-duration/index.vue create mode 100644 fastmovie-vue/src/components/xl-episode-sum/index.vue create mode 100644 fastmovie-vue/src/components/xl-header-userinfo/index.vue create mode 100644 fastmovie-vue/src/components/xl-icode/index.vue create mode 100644 fastmovie-vue/src/components/xl-invitation-code/index.vue create mode 100644 fastmovie-vue/src/components/xl-loading/index.vue create mode 100644 fastmovie-vue/src/components/xl-models/index.vue create mode 100644 fastmovie-vue/src/components/xl-notice/index.vue create mode 100644 fastmovie-vue/src/components/xl-pay/index.vue create mode 100644 fastmovie-vue/src/components/xl-prop/index.vue create mode 100644 fastmovie-vue/src/components/xl-prop/xl-prop-create.vue create mode 100644 fastmovie-vue/src/components/xl-qrcode-view/index.vue create mode 100644 fastmovie-vue/src/components/xl-qrcode/index.vue create mode 100644 fastmovie-vue/src/components/xl-scene/xl-scene-create.vue create mode 100644 fastmovie-vue/src/components/xl-storyboard/index.vue create mode 100644 fastmovie-vue/src/components/xl-style/index.vue create mode 100644 fastmovie-vue/src/components/xl-tabs-item/index.vue create mode 100644 fastmovie-vue/src/components/xl-tabs/index.vue create mode 100644 fastmovie-vue/src/components/xl-user-points/index.vue create mode 100644 fastmovie-vue/src/components/xl-userinfo/index.vue create mode 100644 fastmovie-vue/src/components/xl-userinfo/modules/xl-user.vue create mode 100644 fastmovie-vue/src/components/xl-voice/index.vue create mode 100644 fastmovie-vue/src/composables/useAVCanvas.ts create mode 100644 fastmovie-vue/src/composables/useCompsite.ts create mode 100644 fastmovie-vue/src/composables/useInvitationCode.ts create mode 100644 fastmovie-vue/src/composables/useLoading.ts create mode 100644 fastmovie-vue/src/composables/useLogin.ts create mode 100644 fastmovie-vue/src/composables/useNotify.ts create mode 100644 fastmovie-vue/src/composables/usePoints.ts create mode 100644 fastmovie-vue/src/composables/usePush.ts create mode 100644 fastmovie-vue/src/composables/useStorage.ts create mode 100644 fastmovie-vue/src/composables/useVcode.ts create mode 100644 fastmovie-vue/src/layouts/control.vue create mode 100644 fastmovie-vue/src/locale/en/index.ts create mode 100644 fastmovie-vue/src/locale/index.ts create mode 100644 fastmovie-vue/src/locale/zh-cn/index.ts create mode 100644 fastmovie-vue/src/main.ts create mode 100644 fastmovie-vue/src/pages/actor/index.vue create mode 100644 fastmovie-vue/src/pages/article/content.vue create mode 100644 fastmovie-vue/src/pages/creative/index.vue create mode 100644 fastmovie-vue/src/pages/fail.vue create mode 100644 fastmovie-vue/src/pages/generate/actors/index.vue create mode 100644 fastmovie-vue/src/pages/generate/drama/index.vue create mode 100644 fastmovie-vue/src/pages/generate/drama/modules/episode.vue create mode 100644 fastmovie-vue/src/pages/generate/drama/modules/scene.vue create mode 100644 fastmovie-vue/src/pages/generate/drama/modules/storyboard.vue create mode 100644 fastmovie-vue/src/pages/generate/index.vue create mode 100644 fastmovie-vue/src/pages/generate/props/index.vue create mode 100644 fastmovie-vue/src/pages/generate/scene/index.vue create mode 100644 fastmovie-vue/src/pages/generate/storyboard/index.vue create mode 100644 fastmovie-vue/src/pages/index/index.vue create mode 100644 fastmovie-vue/src/pages/marketing/modules/price.vue create mode 100644 fastmovie-vue/src/pages/marketing/points.vue create mode 100644 fastmovie-vue/src/pages/marketing/vip.vue create mode 100644 fastmovie-vue/src/pages/play/index.vue create mode 100644 fastmovie-vue/src/pages/user/code.vue create mode 100644 fastmovie-vue/src/pages/user/index.vue create mode 100644 fastmovie-vue/src/pages/user/modules/actors.vue create mode 100644 fastmovie-vue/src/pages/user/modules/share.vue create mode 100644 fastmovie-vue/src/pages/user/modules/voice.vue create mode 100644 fastmovie-vue/src/pages/user/modules/works.vue create mode 100644 fastmovie-vue/src/pages/voice/create.vue create mode 100644 fastmovie-vue/src/pages/voice/index.vue create mode 100644 fastmovie-vue/src/pages/voice/modules/edit.vue create mode 100644 fastmovie-vue/src/pages/voice/modules/recording.vue create mode 100644 fastmovie-vue/src/pages/voice/modules/voice-model.vue create mode 100644 fastmovie-vue/src/pages/works/drama.vue create mode 100644 fastmovie-vue/src/pages/works/index.vue create mode 100644 fastmovie-vue/src/pages/works/modules/actors.vue create mode 100644 fastmovie-vue/src/pages/works/modules/details.vue create mode 100644 fastmovie-vue/src/pages/works/modules/episodes.vue create mode 100644 fastmovie-vue/src/pages/works/modules/props.vue create mode 100644 fastmovie-vue/src/pages/works/square.vue create mode 100644 fastmovie-vue/src/routers/index.ts create mode 100644 fastmovie-vue/src/stores/index.ts create mode 100644 fastmovie-vue/src/stores/modules/model.ts create mode 100644 fastmovie-vue/src/stores/modules/state.ts create mode 100644 fastmovie-vue/src/stores/modules/user.ts create mode 100644 fastmovie-vue/src/stores/modules/wallet.ts create mode 100644 fastmovie-vue/src/stores/modules/webconfig.ts create mode 100644 fastmovie-vue/src/svg/icon/drama.vue create mode 100644 fastmovie-vue/src/svg/icon/helper.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-about.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-actor-three-view.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-actor.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-alipay.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-aspect-ratio.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-at.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-batch.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-book.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-close.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-clothes.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-dubbing.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-dunpai.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-edit.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-email.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-help.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-image.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-like.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-login-phone.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-login-wechat.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-logo.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-logout.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-model.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-music.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-next-step.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-optimize.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-pause.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-play-1.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-play.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-points.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-prev-step.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-prop.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-replace.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-security.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-select.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-storyboard.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-style.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-tips.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-transition.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-upload-image.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-user.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-video.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-vip.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-wechat.vue create mode 100644 fastmovie-vue/src/svg/icon/icon-wxpay.vue create mode 100644 fastmovie-vue/src/svg/icon/language.vue create mode 100644 fastmovie-vue/src/svg/icon/logo.vue create mode 100644 fastmovie-vue/src/svg/icon/upload.vue create mode 100644 fastmovie-vue/src/svg/icon/video-file.vue create mode 100644 fastmovie-vue/src/svg/tabs/actor-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/actor.vue create mode 100644 fastmovie-vue/src/svg/tabs/creative-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/creative.vue create mode 100644 fastmovie-vue/src/svg/tabs/home-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/home.vue create mode 100644 fastmovie-vue/src/svg/tabs/notice-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/notice.vue create mode 100644 fastmovie-vue/src/svg/tabs/square-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/square.vue create mode 100644 fastmovie-vue/src/svg/tabs/user-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/user.vue create mode 100644 fastmovie-vue/src/svg/tabs/voice-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/voice.vue create mode 100644 fastmovie-vue/src/svg/tabs/works-active.vue create mode 100644 fastmovie-vue/src/svg/tabs/works.vue create mode 100644 fastmovie-vue/src/workers/loadStream.ts create mode 100644 fastmovie-vue/tsconfig.json create mode 100644 fastmovie-vue/tsconfig.node.json create mode 100644 fastmovie-vue/vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50bee52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# docs +docs/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Kiro IDE +.kiro +.vscode + +.git/ + +# 忽略子目录的 .git 目录(避免被当作 git 子模块) +fastmovie-admin/.git/ +fastmovie-vue/.git/ +/readme_assets diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..fb5aa89 --- /dev/null +++ b/README.en.md @@ -0,0 +1,575 @@ +
+ +FastMovieAI Banner + +# FastMovieAI + +**Commercial-Ready All-in-One Short Drama Creation Platform** + +[Live Demo](https://fastmovie.ai) | [Official Website](https://www.fastmovieai.com) | [简体中文](./README.md) + +![PHP](https://img.shields.io/badge/PHP-8.1+-777BB4?style=flat-square&logo=php&logoColor=white) +![Webman](https://img.shields.io/badge/Webman-2.1+-00ADD8?style=flat-square) +![Vue](https://img.shields.io/badge/Vue-3.5+-4FC08D?style=flat-square&logo=vue.js&logoColor=white) +![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-3178C6?style=flat-square&logo=typescript&logoColor=white) +![Vite](https://img.shields.io/badge/Vite-7.1+-646CFF?style=flat-square&logo=vite&logoColor=white) +![License](https://img.shields.io/badge/License-Apache%202.0-blue?style=flat-square) + +
+ +--- + +## 📖 Introduction + +FastMovieAI is a feature-complete open-source short drama/video creation platform with a decoupled frontend-backend architecture, providing AI-driven video content creation capabilities. The platform integrates user management, payment systems, content management, video generation, and other complete functional modules, suitable for content creators and video production teams. + +## 🎯 Our Vision + +FastMovieAI is committed to building the **world's leading open-source short drama creation ecosystem**, comprehensively benchmarking against industry-leading commercial short drama creation platforms such as SenseTime Seko, Huanzhou, Jurilou, Kemeng, Oii, Mashu, Flova, Baidu Intelligent Creation, Volcano Engine, and Alibaba Cloud Video AI. + +### Why Choose FastMovieAI? + +🌟 **True Open Source Freedom** - Unlike closed-source commercial platforms' black-box operations, we provide completely open source code, allowing every developer to deeply customize and redevelop to create their own exclusive short drama creation tools. + +💰 **Zero-Cost Commercial Use** - No need to pay expensive SaaS subscription fees, no worries about astronomical pay-as-you-go bills. Deploy once, use forever, truly achieving cost reduction and efficiency improvement. + +🚀 **Continuous Evolution** - We believe in the power of open source. Through community-driven approaches, we rapidly iterate features and respond to user needs, keeping the platform technologically advanced. + +🤝 **Co-Creation and Win-Win** - We sincerely invite film production companies, short drama teams, MCN agencies, independent creators, and technical developers to join our ecosystem, polish product details together, share industry experience, and make FastMovieAI a tool that truly understands creators. + +### Our Commitments + +- ✅ **Open and Transparent** - All code is open source, all decisions are public, subject to community supervision +- ✅ **User First** - Take every feedback seriously, respond quickly to every need +- ✅ **Technological Leadership** - Keep up with AI technology frontiers, continuously introduce the latest video generation, voice synthesis, and intelligent editing capabilities +- ✅ **Ecosystem Co-construction** - Provide comprehensive plugin mechanisms, support third-party developers to contribute functional modules +- ✅ **Long-term Maintenance** - Commit to long-term maintenance and updates, will not abandon the open-source version due to commercialization + +### Join Us + +Whether you are: +- 🎬 **Film Companies** - Seeking solutions to reduce production costs and improve creative efficiency +- 📱 **Short Drama Teams** - Need batch and industrialized content production tools +- 🎨 **Individual Creators** - Independent producers who want to realize creative ideas at low cost +- 💻 **Technical Developers** - Engineers interested in AI video technology + +We welcome you to join! Let's use the power of open source to redefine the future of short drama creation and enable everyone to become an excellent content creator! + +
+ FastMovieAI Platform Showcase +
+ +### ✨ Core Features + +- 🎬 **AI Video Creation** - AI-based short drama video generation and editing capabilities +- 🎭 **Character Management** - Virtual character creation, configuration, and management +- 🎙️ **Voice Synthesis** - Multi-language voice synthesis and dubbing functions +- 📝 **Script Editing** - Visual script editor and storyboard management +- 💰 **Payment Integration** - Support for Alipay, WeChat Pay, and other payment methods +- 👥 **User System** - Complete user registration, login, and VIP membership system +- 💎 **Points System** - Flexible points recharge and consumption mechanism +- 📱 **WeChat Integration** - Support for WeChat Official Account and Mini Program integration +- 🔌 **Plugin Architecture** - Modular plugin system, easy to extend +- 🌍 **Multi-language Support** - Chinese and English interface switching + +### 🎯 Use Cases + +- Short video content creation platform +- AI-driven video production tools +- Short drama creation and distribution system +- Content creator community platform + +## 🏗️ Technical Architecture + +### Backend Stack + +- **Framework**: Webman v2.1+ (High-performance PHP framework based on Workerman) +- **PHP Version**: ≥8.1 +- **Database**: MySQL ≥8.0, Redis +- **ORM**: ThinkORM v2.1+ +- **Template Engine**: ThinkTemplate v3.0+ +- **Payment**: Yansongda/Pay v3.7+ (Alipay, WeChat Pay) +- **Video Processing**: php-ffmpeg v1.3+ +- **WebSocket**: webman/push v1.1+ + +### Frontend Stack + +- **Framework**: Vue 3.5+ (Composition API) +- **Build Tool**: Vite 7.1+ +- **Language**: TypeScript 5.9+ +- **UI Framework**: Element Plus 2.11+ +- **State Management**: Pinia 3.0+ +- **Router**: Vue Router 4.5+ +- **Video Processing**: @webav/av-cliper, mp4box + +### Project Structure + +``` +fastmovieai/ +├── fastmovie-admin/ # Backend application (PHP/Webman) +│ ├── app/ # Application code +│ ├── config/ # Configuration files +│ ├── plugin/ # Plugin system +│ ├── public/ # Public resources +│ └── start.php # Entry point +├── fastmovie-vue/ # Frontend application (Vue3/TypeScript) +│ ├── src/ # Source code +│ ├── public/ # Static resources +│ └── vite.config.ts # Vite configuration +└── README.md # Project documentation +``` + +## 📦 Requirements + +### Backend Requirements + +- PHP ≥ 8.1 +- MySQL ≥ 8.0 +- Redis +- Required PHP Extensions: + - PDO, PDO_MySQL, MySQLi + - Redis + - cURL, OpenSSL + - GD, Fileinfo + - MBString, JSON + - Event (recommended for better performance) + +### Frontend Requirements + +- Node.js (LTS version recommended) +- npm or yarn + +## 🚀 Quick Start + +### 1. Clone the Project + +This project consists of two independent Git repositories that need to be cloned separately: + +```bash +# Create project directory +mkdir fastmovieai +cd fastmovieai + +# Clone backend repository +git clone https://gitee.com/yc_open/ai-short-play.git fastmovie-admin + +# Clone frontend repository +git clone https://gitee.com/yc_open/ai-short-play-vue.git fastmovie-vue +``` + +After cloning, the directory structure will be: +``` +fastmovieai/ +├── fastmovie-admin/ # Backend repository +└── fastmovie-vue/ # Frontend repository +``` + +### 2. Backend Installation + +#### Using Web Installation Wizard (Recommended) + +**Step 1: Configure Site** + +If using BT Panel: +1. Create a site and set the root directory to `fastmovie-admin/public` +2. Select PHP version 8.1 or higher + +**Step 2: Access Installation Wizard** + +Visit `http://your-domain.com/install` and follow the wizard to complete installation: + +1. **License Agreement** - Read and agree to the open source license +2. **Environment Check** - Automatically detect PHP version, extensions, permissions, etc. +3. **Parameter Configuration** - Configure database and Redis connection, set admin account +4. **Start Installation** - Automatically complete database initialization and configuration file generation + +**After Installation:** + +The installation wizard will automatically complete the following tasks: +- Generate `.env` configuration file (based on `.env.example`) +- Automatically configure database connection pool parameters +- Generate random PUSH_KEY and PUSH_SECRET (32-character strings) +- Synchronize and update PUSH_KEY in `nginx.example` +- Create `install.lock` file to prevent duplicate installation + +After installation, the page will prompt you to complete the following operations: + +**1. Configure URL Rewrite Rules** +- Copy the entire content of `fastmovie-admin/nginx.example` file +- Paste and save in BT Panel's "Site Settings → URL Rewrite" +- PUSH_KEY has been automatically updated to the actual generated random value + +**2. Start Backend Service** +```bash +cd fastmovie-admin +php start.php start -d +``` +Or configure process daemon in BT Panel (recommended) + +**3. Delete Installation Directory** +```bash +# Delete installation directory (Important!) +rm -rf public/install +``` + +⚠️ **Important Notes**: +- Change the default admin password immediately after first login +- Ensure `.env` file permissions are secure and not exposed to the public +- `install.lock` file is used to prevent duplicate installation, do not delete + +#### Manual Installation (Advanced Users) + +If not using the Web installation wizard, you can configure manually: + +```bash +cd fastmovie-admin + +# 1. Copy environment configuration file +copy .env.example .env + +# 2. Edit .env file and configure the following: +# Database configuration +# - DATABASE_HOST=127.0.0.1 +# - DATABASE_PORT=3306 +# - DATABASE_NAME=your_database +# - DATABASE_USERNAME=your_username +# - DATABASE_PASSWORD=your_password +# - DATABASE_PREFIX=php_ +# +# Database connection pool configuration (required) +# - DATABASE_MAX_CONNECTIONS=10 +# - DATABASE_MIN_CONNECTIONS=1 +# - DATABASE_WAIT_TIMEOUT=3 +# - DATABASE_IDLE_TIMEOUT=60 +# - DATABASE_HEARTBEAT_INTERVAL=50 +# +# Redis configuration +# - REDIS_HOST=127.0.0.1 +# - REDIS_PORT=6379 +# - REDIS_PASSWORD= +# - REDIS_DATABASE=2 +# +# WebSocket push configuration +# - PUSH_KEY=generate 32-character random string +# - PUSH_SCERET=generate 32-character random string +# - PUSH_API_PORT=37000 +# - PUSH_WSS_PORT=37001 + +# 3. Import database +mysql -u root -p your_database < database.sql + +# 4. Update PUSH_KEY in nginx.example +# Replace /app/PUSH_KEY with /app/your_actual_PUSH_KEY_value +``` + +### 3. Frontend Installation + +```bash +cd fastmovie-vue + +# Install dependencies +npm install + +# Configure backend API address +# Edit .env.development file, set VITE_API_URL +``` + +### 4. Start Services + +**Backend Service** + +```bash +cd fastmovie-admin + +# Development mode +php start.php start + +# Daemon mode (production) +php start.php start -d + +# Stop service +php start.php stop + +# Restart service +php start.php restart + +# Check status +php start.php status +``` + +**Frontend Service** + +```bash +cd fastmovie-vue + +# Start development server (default port 36310) +npm run dev + +# Build for production +npm run build +``` + +### 5. Access the System + +- **Frontend**: http://localhost:36310 +- **Backend API**: http://localhost:36999 +- **Default Admin Account**: admin +- **Default Password**: 123456 + +⚠️ **Security Notice**: Please change the default password immediately after first login! + +## 🔧 Production Deployment + +### BT Panel Deployment (Recommended) + +1. **Create Site** + - Create a new site in BT Panel + - Set site root directory to `fastmovie-admin/public` + - Configure PHP version to 8.1 or higher + +2. **Configure URL Rewrite** + +Add URL rewrite rules in site settings: + +```nginx +location / { + try_files $uri $uri/ /index.php?$query_string; +} +``` + +3. **Start Backend Service** + +```bash +cd /www/wwwroot/your-site/fastmovie-admin +php start.php start -d +``` + +4. **Configure Process Daemon** + +Add in BT Panel's "Process Daemon": +- Name: FastMovieAI +- Start Command: `cd /www/wwwroot/your-site/fastmovie-admin && php start.php start` +- Working Directory: `/www/wwwroot/your-site/fastmovie-admin` + +5. **Access Installation Wizard** + +Visit `http://your-domain.com/install` to complete installation configuration. + +### Manual Deployment + +#### Backend Deployment + +1. **Configure Nginx Reverse Proxy** + +Refer to `fastmovie-admin/nginx.example` configuration file: + +```nginx +upstream fastmovie { + server 127.0.0.1:36999; + keepalive 10240; +} + +server { + listen 80; + server_name your-domain.com; + root /path/to/fastmovie-admin/public; + + location / { + proxy_pass http://fastmovie; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +2. **Start Backend Service** + +```bash +cd fastmovie-admin +php start.php start -d +``` + +3. **Configure Process Daemon** + +Use systemd or supervisor to manage processes and ensure automatic service restart. + +#### Frontend Deployment + +1. **Build for Production** + +```bash +cd fastmovie-vue +npm run build +``` + +2. **Deploy Build Files** + +After building, deploy the generated files to the `fastmovie-admin/public/assets/` directory, or configure an independent static file server. + +3. **Configure Nginx** + +```nginx +server { + listen 80; + server_name your-domain.com; + root /path/to/fastmovie-admin/public; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://127.0.0.1:36999; + } +} +``` + +## 📚 Documentation + +### Development Documentation + +- [Backend Development Guide](./docs/backend-development.md) - PHP/Webman backend development guide +- [Frontend Development Guide](./docs/frontend-development.md) - Vue3/TypeScript frontend development guide + +### Plugin System + +FastMovieAI adopts a modular plugin architecture. Core plugins include: + +- **user** - User management and authentication +- **finance** - Payment and financial management +- **marketing** - Marketing and promotion tools +- **article** - Content management system +- **shortplay** - Short drama creation core functionality +- **model** - AI model management +- **notification** - Notification and message push +- **control** - Platform control and configuration + +Each plugin is an independent functional module that can be enabled or disabled as needed. + +### Common Commands + +**Backend Commands** + +```bash +# View all commands +php webman + +# Database migration (if supported) +php webman migrate + +# Clear cache +php webman cache:clear +``` + +**Frontend Commands** + +```bash +# Development server +npm run dev + +# Production build +npm run build + +# Preview build result +npm run preview + +# Type checking +vue-tsc --noEmit +``` + +### Port Configuration + +- **Backend Service**: 36999 (configurable via SERVER_PORT in .env) +- **Frontend Development**: 36310 +- **WebSocket Push**: 37000 +- **WebSocket WSS**: 37001 + +## 🤝 Contributing + +We welcome all forms of contribution, including but not limited to: + +1. 🐛 Submit bug reports +2. 💡 Propose new feature suggestions +3. 📝 Improve documentation +4. 🔧 Submit code fixes or new features + +### Contribution Process + +1. Fork the corresponding repository (backend or frontend) +2. Create a feature branch (`git checkout -b feature/AmazingFeature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/AmazingFeature`) +5. Submit a Pull Request + +**Note**: Backend and frontend are independent repositories. Please submit PRs to the corresponding repository based on your changes. + +### Development Guidelines + +- Backend code follows PSR-12 coding standards +- Frontend code uses ESLint and Prettier formatting +- Commit messages should clearly describe changes +- Add necessary code comments and documentation + +## 📄 License + +This project is licensed under the [Apache License 2.0](./LICENSE). + +## 🔗 Links + +- [Live Demo](https://fastmovie.ai) +- [Backend Repository (Gitee)](https://gitee.com/yc_open/ai-short-play) +- [Frontend Repository (Gitee)](https://gitee.com/yc_open/ai-short-play-vue) +- [Backend Development Guide](./docs/backend-development.md) +- [Frontend Development Guide](./docs/frontend-development.md) +- [Issue Tracker](https://gitee.com/yc_open/ai-short-play/issues) + +## ⚠️ Disclaimer + +This project is for learning and research purposes only. Users are responsible for all consequences arising from the use of this project. Please comply with relevant laws and regulations and do not use it for illegal purposes. + +## 💬 Contact + +For questions or suggestions, please contact us through: + +- Submit Issue: + - Backend issues: [ai-short-play Issues](https://gitee.com/yc_open/ai-short-play/issues) + - Frontend issues: [ai-short-play-vue Issues](https://gitee.com/yc_open/ai-short-play-vue/issues) +- Email: 416716328@qq.com +- Live Demo: [https://fastmovie.ai](https://fastmovie.ai) + +### Join the Community + +
+ + + + + + +
+ User Community +
+ User Community +
+ Scan to join and communicate with creators +
+ Business Cooperation +
+ Technical Consultation & Business Cooperation +
+ Scan to contact for cooperation +
+ +
+ +--- + +
+ +**If this project helps you, please give us a ⭐️ Star!** + +Made with ❤️ by FastMovieAI Team + +
diff --git a/README.md b/README.md new file mode 100644 index 0000000..ace27cc --- /dev/null +++ b/README.md @@ -0,0 +1,694 @@ +
+ +FastMovieAI Banner + +# FastMovieAI + +**可商用的一站式短剧创作平台** + +[在线演示](https://fastmovie.ai) | [官方网站](https://www.fastmovieai.com) | [English](./README.en.md) + +![NestJS](https://img.shields.io/badge/PHP-8.1+-777BB4?style=flat-square&logo=php&logoColor=white) +![Webman](https://img.shields.io/badge/Webman-2.1+-00ADD8?style=flat-square) +![Vue](https://img.shields.io/badge/Vue-3.5+-4FC08D?style=flat-square&logo=vue.js&logoColor=white) +![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-3178C6?style=flat-square&logo=typescript&logoColor=white) +![Vite](https://img.shields.io/badge/Vite-7.1+-646CFF?style=flat-square&logo=vite&logoColor=white) +![License](https://img.shields.io/badge/License-Apache%202.0-blue?style=flat-square) + +
+ +--- + +## 📖 项目介绍 + +FastMovieAI 是一个功能完整的开源短剧/短视频创作平台,采用前后端分离架构,提供 AI 驱动的视频内容创作能力。平台集成了用户管理、支付系统、内容管理、视频生成等完整功能模块,适合内容创作者和视频制作团队使用。 + + +
+ FastMovieAI 平台展示 +
+ +## 🎯 我们的愿景 + +FastMovieAI 致力于打造**全球领先的开源短剧创作生态系统**,全面对标商汤 Seko、幻舟、巨日禄、可梦、Oii、麻署、Flova、百度智能创作、火山引擎、阿里云视频 AI 等业界顶尖的商业化短剧创作平台。 + +### 为什么选择 FastMovieAI? + +🌟 **真正的开源自由** - 不同于闭源商业平台的黑盒操作,我们提供完全开放的源代码,让每一位开发者都能深度定制、二次开发,打造专属的短剧创作工具。 + +💰 **零成本商用** - 无需支付高昂的 SaaS 订阅费用,无需担心按量计费的天价账单,一次部署,永久使用,真正实现降本增效。 + +🚀 **持续进化** - 我们相信开源的力量,通过社区驱动的方式,快速迭代功能,响应用户需求,让平台始终保持技术领先。 + +🤝 **共创共赢** - 我们诚挚邀请影视制作公司、短剧团队、MCN 机构、独立创作者、技术开发者加入我们的生态,共同打磨产品细节,分享行业经验,让 FastMovieAI 成为真正懂创作者的工具。 + +### 我们的承诺 + +- ✅ **开放透明** - 所有代码开源,所有决策公开,接受社区监督 +- ✅ **用户至上** - 认真对待每一条反馈,快速响应每一个需求 +- ✅ **技术领先** - 紧跟 AI 技术前沿,持续引入最新的视频生成、语音合成、智能剪辑能力 +- ✅ **生态共建** - 提供完善的插件机制,支持第三方开发者贡献功能模块 +- ✅ **长期维护** - 承诺长期维护更新,不会因商业化而放弃开源版本 + +### 加入我们 + +无论你是: +- 🎬 **影视公司** - 寻求降低制作成本、提升创作效率的解决方案 +- 📱 **短剧团队** - 需要批量化、工业化的内容生产工具 +- 🎨 **个人创作者** - 想要低成本实现创意想法的独立制作人 +- 💻 **技术开发者** - 对 AI 视频技术感兴趣的工程师 + +我们都欢迎你的加入!让我们一起,用开源的力量,重新定义短剧创作的未来,让每个人都能成为优秀的内容创作者! + +### ✨ 核心特性 + +- 🎬 **AI 视频创作** - 基于 AI 的短剧视频生成和编辑能力 +- 🎭 **角色管理** - 虚拟角色创建、配置和管理 +- 🎙️ **语音合成** - 多语言语音合成和配音功能 +- 📝 **剧本编辑** - 可视化剧本编辑器和分镜头管理 +- 💰 **支付集成** - 支持支付宝、微信支付等多种支付方式 +- 👥 **用户系统** - 完整的用户注册、登录、VIP 会员体系 +- 💎 **积分系统** - 灵活的积分充值和消费机制 +- 📱 **微信集成** - 支持微信公众号和小程序对接 +- 🔌 **插件架构** - 模块化插件系统,易于扩展 +- 🌍 **多语言支持** - 中文和英文界面切换 + +### 🎯 适用场景 + +- 短视频内容创作平台 +- AI 驱动的视频制作工具 +- 短剧创作和分发系统 +- 内容创作者社区平台 + +## 🏗️ 技术架构 + +### 后端技术栈 + +- **框架**: Webman v2.1+ (基于 Workerman 的高性能 PHP 框架) +- **PHP 版本**: ≥8.1 +- **数据库**: MySQL ≥8.0, Redis +- **ORM**: ThinkORM v2.1+ +- **模板引擎**: ThinkTemplate v3.0+ +- **支付**: Yansongda/Pay v3.7+ (支付宝、微信支付) +- **视频处理**: php-ffmpeg v1.3+ +- **WebSocket**: webman/push v1.1+ + +### 前端技术栈 + +- **框架**: Vue 3.5+ (Composition API) +- **构建工具**: Vite 7.1+ +- **语言**: TypeScript 5.9+ +- **UI 框架**: Element Plus 2.11+ +- **状态管理**: Pinia 3.0+ +- **路由**: Vue Router 4.5+ +- **视频处理**: @webav/av-cliper, mp4box + +### 项目结构 + +``` +fastmovieai/ +├── fastmovie-admin/ # 后端应用 (PHP/Webman) +│ ├── app/ # 应用代码 +│ ├── config/ # 配置文件 +│ ├── plugin/ # 插件系统 +│ ├── public/ # 公共资源 +│ └── start.php # 启动入口 +├── fastmovie-vue/ # 前端应用 (Vue3/TypeScript) +│ ├── src/ # 源代码 +│ ├── public/ # 静态资源 +│ └── vite.config.ts # Vite 配置 +└── README.md # 项目文档 +``` + +## 📦 环境要求 + +### 后端环境 + +- PHP ≥ 8.1 +- MySQL ≥ 8.0 +- Redis +- 必需的 PHP 扩展: + - PDO, PDO_MySQL, MySQLi + - Redis + - cURL, OpenSSL + - GD, Fileinfo + - MBString, JSON + - Event (推荐,提升性能) + +### 前端环境 + +- Node.js (推荐 LTS 版本) +- npm 或 yarn + +## 🚀 快速开始 + +### 1. 克隆项目 + +本项目由两个独立的 Git 仓库组成,需要分别克隆: + +```bash +# 创建项目目录 +mkdir fastmovieai +cd fastmovieai + +# 克隆后端仓库 +git clone https://gitee.com/yc_open/ai-short-play.git fastmovie-admin + +# 克隆前端仓库 +git clone https://gitee.com/yc_open/ai-short-play-vue.git fastmovie-vue +``` + +克隆完成后,目录结构如下: +``` +fastmovieai/ +├── fastmovie-admin/ # 后端仓库 +└── fastmovie-vue/ # 前端仓库 +``` + +### 2. 后端安装 + +#### 使用 Web 安装向导(推荐) + +**步骤 1:配置站点** + +如果使用宝塔面板: +1. 创建站点,将运行目录设置为 `fastmovie-admin/public` +2. PHP 版本选择 8.1 或更高 + +**步骤 2:访问安装向导** + +访问 `http://your-domain.com/install`,按照向导完成安装: + +1. **许可协议** - 阅读并同意开源协议 +2. **环境检测** - 自动检测 PHP 版本、扩展、权限等 +3. **参数配置** - 配置数据库和 Redis 连接信息,设置管理员账号 +4. **开始安装** - 自动完成数据库初始化和配置文件生成 + +**安装完成后的操作:** + +安装向导会自动完成以下工作: +- 生成 `.env` 配置文件(基于 `.env.example`) +- 自动配置数据库连接池参数 +- 生成随机的 PUSH_KEY 和 PUSH_SECRET(32位字符串) +- 同步更新 `nginx.example` 中的 PUSH_KEY +- 创建 `install.lock` 锁定文件防止重复安装 + +安装完成后,页面会提示您完成以下操作: + +**1. 配置伪静态规则** +- 复制 `fastmovie-admin/nginx.example` 文件的全部内容 +- 在宝塔面板的"站点设置 → 伪静态"中粘贴并保存 +- PUSH_KEY 已自动更新为实际生成的随机值 + +**2. 启动后端服务** +```bash +cd fastmovie-admin +php start.php start -d +``` +或在宝塔面板配置进程守护(推荐) + +**3. 删除安装目录** +```bash +# 删除安装目录(重要!) +rm -rf public/install +``` + +⚠️ **重要提示**: +- 首次登录后立即修改默认管理员密码 +- 确保 `.env` 文件权限安全,不要暴露到公网 +- `install.lock` 文件用于防止重复安装,请勿删除 + +#### 手动安装(高级用户) + +如果不使用 Web 安装向导,可以手动配置: + +```bash +cd fastmovie-admin + +# 1. 复制环境配置文件 +copy .env.example .env + +# 2. 编辑 .env 文件,配置以下信息: +# 数据库配置 +# - DATABASE_HOST=127.0.0.1 +# - DATABASE_PORT=3306 +# - DATABASE_NAME=your_database +# - DATABASE_USERNAME=your_username +# - DATABASE_PASSWORD=your_password +# - DATABASE_PREFIX=php_ +# +# 数据库连接池配置(必需) +# - DATABASE_MAX_CONNECTIONS=10 +# - DATABASE_MIN_CONNECTIONS=1 +# - DATABASE_WAIT_TIMEOUT=3 +# - DATABASE_IDLE_TIMEOUT=60 +# - DATABASE_HEARTBEAT_INTERVAL=50 +# +# Redis 配置 +# - REDIS_HOST=127.0.0.1 +# - REDIS_PORT=6379 +# - REDIS_PASSWORD= +# - REDIS_DATABASE=2 +# +# WebSocket 推送配置 +# - PUSH_KEY=生成32位随机字符串 +# - PUSH_SCERET=生成32位随机字符串 +# - PUSH_API_PORT=37000 +# - PUSH_WSS_PORT=37001 + +# 3. 导入数据库 +mysql -u root -p your_database < database.sql + +# 4. 更新 nginx.example 中的 PUSH_KEY +# 将 /app/PUSH_KEY 替换为 /app/你的实际PUSH_KEY值 +``` + +### 3. 前端安装 + +```bash +cd fastmovie-vue + +# 安装依赖 +npm install + +# 配置后端 API 地址 +# 编辑 .env.development 文件,设置 VITE_API_URL +``` + +### 4. 启动服务 + +**后端服务** + +```bash +cd fastmovie-admin + +# 开发模式启动 +php start.php start + +# 守护进程模式(生产环境) +php start.php start -d + +# 停止服务 +php start.php stop + +# 重启服务 +php start.php restart + +# 查看状态 +php start.php status +``` + +**前端服务** + +```bash +cd fastmovie-vue + +# 启动开发服务器(默认端口 36310) +npm run dev + +# 构建生产版本 +npm run build +``` + +### 5. 访问系统 + +- **前端地址**: http://localhost:36310 +- **后端 API**: http://localhost:36999 +- **默认管理员账号**: admin +- **默认密码**: 123456 + +⚠️ **安全提示**: 首次登录后请立即修改默认密码! + +## 🔧 生产环境部署 + +### 宝塔面板部署(推荐) + +1. **创建站点** + - 在宝塔面板中创建新站点 + - 将站点运行目录设置为 `fastmovie-admin/public` + - 配置 PHP 版本为 8.1 或更高 + +2. **配置伪静态** + +在站点设置的"伪静态"选项中,复制 `fastmovie-admin/nginx.example` 文件的全部内容并保存。 + +**nginx.example 完整内容:** + +```nginx +# 站内信推送 +location /app/PUSH_KEY { + proxy_pass http://127.0.0.1:36302; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header X-Real-IP $remote_addr; +} + +# 将请求转发到webman +location ^~ / { + # 接口QPS插件,nginx版本需要选择:nginx openresty + # 如需使用接口QPS插件,取消注释 + # access_by_lua_file $document_root/lua/qps.lua; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_http_version 1.1; + proxy_set_header Connection ""; + if (!-f $request_filename){ + proxy_pass http://127.0.0.1:36300; + } +} + +# 拒绝访问所有以 .php 结尾的文件 +location ~ \.php$ { + return 404; +} + +# 允许访问 .well-known 目录 +location ~ ^/\.well-known/ { + allow all; +} + +# 拒绝访问所有以 . 开头的文件或目录 +location ~ /\. { + return 404; +} +``` + +⚠️ **重要说明**: +- `/app/PUSH_KEY` 中的 `PUSH_KEY` 在安装完成后会自动替换为实际的 32 位随机字符串 +- 如果手动安装,需要将 `PUSH_KEY` 替换为 `.env` 文件中的实际值 +- `proxy_pass http://127.0.0.1:36300` 中的端口需要与 `.env` 中的 `SERVER_PORT` 一致(默认 36999) + +3. **启动后端服务** + +```bash +cd /www/wwwroot/your-site/fastmovie-admin +php start.php start -d +``` + +4. **配置进程守护** + +在宝塔面板的"进程守护"中添加: +- 名称: FastMovieAI +- 启动命令: `cd /www/wwwroot/your-site/fastmovie-admin && php start.php start` +- 运行目录: `/www/wwwroot/your-site/fastmovie-admin` + +5. **访问安装向导** + +访问 `http://your-domain.com/install` 完成安装配置。安装程序会自动: +- 生成 `.env` 配置文件 +- 创建数据库表结构 +- 生成随机的 PUSH_KEY 和 PUSH_SECRET +- 同步更新 `nginx.example` 中的 PUSH_KEY +- 创建管理员账号 + +安装完成后,记得删除 `public/install` 目录。 + +### 手动部署 + +#### 后端部署 + +1. **配置 Nginx 反向代理** + +参考 `fastmovie-admin/nginx.example` 配置文件,完整配置示例: + +```nginx +upstream fastmovie { + server 127.0.0.1:36999; + keepalive 10240; +} + +server { + listen 80; + server_name your-domain.com; + root /path/to/fastmovie-admin/public; + index index.html index.htm; + + # 站内信推送(PUSH_KEY 需要替换为实际值) + location /app/YOUR_ACTUAL_PUSH_KEY { + proxy_pass http://127.0.0.1:37000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header X-Real-IP $remote_addr; + } + + # 将请求转发到 webman + location ^~ / { + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_http_version 1.1; + proxy_set_header Connection ""; + if (!-f $request_filename){ + proxy_pass http://fastmovie; + } + } + + # 拒绝访问所有以 .php 结尾的文件 + location ~ \.php$ { + return 404; + } + + # 允许访问 .well-known 目录 + location ~ ^/\.well-known/ { + allow all; + } + + # 拒绝访问所有以 . 开头的文件或目录 + location ~ /\. { + return 404; + } +} +``` + +⚠️ **配置说明**: +- `YOUR_ACTUAL_PUSH_KEY` 需要替换为 `.env` 文件中的实际 PUSH_KEY 值 +- upstream 端口(36999)需要与 `.env` 中的 SERVER_PORT 一致 +- WebSocket 推送端口(37000)需要与 `.env` 中的 PUSH_API_PORT 一致 + +2. **启动后端服务** + +```bash +cd fastmovie-admin +php start.php start -d +``` + +3. **配置进程守护** + +使用 systemd 或 supervisor 管理进程,确保服务自动重启。 + +**systemd 示例** (`/etc/systemd/system/fastmovie.service`): + +```ini +[Unit] +Description=FastMovieAI Backend Service +After=network.target + +[Service] +Type=forking +User=www-data +WorkingDirectory=/path/to/fastmovie-admin +ExecStart=/usr/bin/php start.php start -d +ExecStop=/usr/bin/php start.php stop +ExecReload=/usr/bin/php start.php restart +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +启用服务: +```bash +sudo systemctl enable fastmovie +sudo systemctl start fastmovie +``` + +### 前端部署 + +1. **构建生产版本** + +```bash +cd fastmovie-vue +npm run build +``` + +2. **部署构建文件** + +构建完成后,将生成的文件部署到 `fastmovie-admin/public/assets/` 目录,或配置独立的静态文件服务器。 + +3. **配置 Nginx** + +```nginx +server { + listen 80; + server_name your-domain.com; + root /path/to/fastmovie-admin/public; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://127.0.0.1:36999; + } +} +``` + +## 📚 使用说明 + +### 开发文档 + +- [后端开发文档](./docs/backend-development.md) - PHP/Webman 后端开发指南 +- [前端开发文档](./docs/frontend-development.md) - Vue3/TypeScript 前端开发指南 + +### 插件系统 + +FastMovieAI 采用模块化插件架构,核心插件包括: + +- **user** - 用户管理和认证 +- **finance** - 支付和财务管理 +- **marketing** - 营销和推广工具 +- **article** - 内容管理系统 +- **shortplay** - 短剧创作核心功能 +- **model** - AI 模型管理 +- **notification** - 通知和消息推送 +- **control** - 平台控制和配置 + +每个插件都是独立的功能模块,可以根据需要启用或禁用。 + +### 开发命令 + +**后端常用命令** + +```bash +# 查看所有命令 +php webman + +# 数据库迁移(如果支持) +php webman migrate + +# 清除缓存 +php webman cache:clear +``` + +**前端常用命令** + +```bash +# 开发服务器 +npm run dev + +# 生产构建 +npm run build + +# 预览构建结果 +npm run preview + +# 类型检查 +vue-tsc --noEmit +``` + +### 端口配置 + +- **后端服务**: 36999 (可在 .env 中配置 SERVER_PORT) +- **前端开发**: 36310 +- **WebSocket Push**: 37000 +- **WebSocket WSS**: 37001 + +## 🤝 参与贡献 + +我们欢迎所有形式的贡献,包括但不限于: + +1. 🐛 提交 Bug 报告 +2. 💡 提出新功能建议 +3. 📝 改进文档 +4. 🔧 提交代码修复或新功能 + +### 贡献流程 + +1. Fork 对应的仓库(后端或前端) +2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m '添加某个很棒的功能'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 提交 Pull Request + +**注意**: 后端和前端是独立的仓库,请根据修改内容向对应的仓库提交 PR。 + +### 开发规范 + +- 后端代码遵循 PSR-12 编码规范 +- 前端代码使用 ESLint 和 Prettier 格式化 +- 提交信息使用中文,清晰描述改动内容 +- 添加必要的代码注释和文档 + +## 📄 开源协议 + +本项目采用 [Apache License 2.0](./LICENSE) 开源协议。 + +## 🔗 相关链接 + +- [在线演示](https://fastmovie.ai) +- [后端仓库 (Gitee)](https://gitee.com/yc_open/ai-short-play) +- [前端仓库 (Gitee)](https://gitee.com/yc_open/ai-short-play-vue) +- [后端开发文档](./docs/backend-development.md) +- [前端开发文档](./docs/frontend-development.md) +- [问题反馈](https://gitee.com/yc_open/ai-short-play/issues) + +## ⚠️ 免责声明 + +本项目仅供学习和研究使用,使用本项目所产生的一切后果由使用者自行承担。请遵守相关法律法规,不得用于非法用途。 + +## ⚠️ 优秀开源项目推荐 + +- [imaiwork](https://gitee.com/imai-work/imaiwork) +- [BuildingAI](https://gitee.com/BidingCC/BuildingAI) +- [aiflowy](https://gitee.com/aiflowy/aiflowy) + +## 💬 联系方式 + +如有问题或建议,欢迎通过以下方式联系: + +- 提交 Issue: + - 后端问题: [ai-short-play Issues](https://gitee.com/yc_open/ai-short-play/issues) + - 前端问题: [ai-short-play-vue Issues](https://gitee.com/yc_open/ai-short-play-vue/issues) +- 邮箱: 416716328@qq.com +- 在线演示: [https://fastmovie.ai](https://fastmovie.ai) + +### 加入社区 + +
+ + + + + + +
+ 用户交流群 +
+ 用户交流群 +
+ 扫码加入,与创作者交流 +
+ 商务合作 +
+ 技术咨询 & 商务合作 +
+ 扫码联系,洽谈合作 +
+ +
+ +--- + +
+ +**如果这个项目对你有帮助,请给我们一个 ⭐️ Star!** + +Made with ❤️ by FastMovieAI Team + +
diff --git a/fastmovie-admin/.env.example b/fastmovie-admin/.env.example new file mode 100644 index 0000000..5a11d27 --- /dev/null +++ b/fastmovie-admin/.env.example @@ -0,0 +1,39 @@ +DEBUG = false + +SERVER_NAME = AiShortPlay +SERVER_PORT = 36300 +SERVER_ADMIN_PATH = admin + +DATABASE_HOST = 127.0.0.1 +DATABASE_PORT = 3306 +DATABASE_NAME = +DATABASE_USERNAME = +DATABASE_PASSWORD = +DATABASE_CHARSET = utf8mb4 +DATABASE_PREFIX = php_ +DATABASE_MAX_CONNECTIONS = 10 +DATABASE_MIN_CONNECTIONS = 1 +DATABASE_WAIT_TIMEOUT = 3 +DATABASE_IDLE_TIMEOUT = 60 +DATABASE_HEARTBEAT_INTERVAL = 50 +DATABASE_MAX_CONNECTIONS = 100 +DATABASE_MIN_CONNECTIONS = 20 +DATABASE_WAIT_TIMEOUT = 20 +DATABASE_IDLE_TIMEOUT = 60 +DATABASE_HEARTBEAT_INTERVAL = 50 + +REDIS_HOST = 127.0.0.1 +REDIS_PORT = 6379 +REDIS_PASSWORD = +REDIS_DATABASE = 4 +REDIS_MAX_CONNECTIONS = 100 +REDIS_MIN_CONNECTIONS = 20 +REDIS_WAIT_TIMEOUT = 20 +REDIS_IDLE_TIMEOUT = 60 +REDIS_HEARTBEAT_INTERVAL = 50 + + +PUSH_KEY = +PUSH_SCERET = +PUSH_API_PORT = 36301 +PUSH_WSS_PORT = 36302 diff --git a/fastmovie-admin/.gitignore b/fastmovie-admin/.gitignore new file mode 100644 index 0000000..caf93a9 --- /dev/null +++ b/fastmovie-admin/.gitignore @@ -0,0 +1,22 @@ +/runtime +/.idea +/.vscode +*.log +.env +/tests/tmp +/tests/.phpunit.result.cache +*.pem +/uploads +/public/uploads +.well-known +*.ini +/config/**/*.json +/update/VERSION +# Kiro IDE +.kiro +.vscode +/vendor +/public/drama +/public/install +/public/assets +/public/fastmovie diff --git a/fastmovie-admin/.htaccess b/fastmovie-admin/.htaccess new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/fastmovie-admin/.htaccess @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fastmovie-admin/404.html b/fastmovie-admin/404.html new file mode 100644 index 0000000..6096254 --- /dev/null +++ b/fastmovie-admin/404.html @@ -0,0 +1,16 @@ + + + + +404 Not Found + +

404 Not Found

+
+
Power by 堡塔 (免费,高效和安全的托管控制面板)
+ + \ No newline at end of file diff --git a/fastmovie-admin/Dockerfile b/fastmovie-admin/Dockerfile new file mode 100644 index 0000000..1071d24 --- /dev/null +++ b/fastmovie-admin/Dockerfile @@ -0,0 +1,22 @@ +FROM php:8.1-cli + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpng-dev libjpeg-dev libfreetype6-dev libzip-dev libonig-dev \ + libcurl4-openssl-dev libssl-dev unzip \ + && rm -rf /var/lib/apt/lists/* + +RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j$(nproc) pdo_mysql mysqli pcntl posix gd zip bcmath mbstring + +RUN pecl install redis && docker-php-ext-enable redis + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +COPY . /app +WORKDIR /app + +RUN composer install --no-dev --optimize-autoloader --no-interaction + +EXPOSE 36300 36301 36302 + +CMD ["php", "start.php", "start"] diff --git a/fastmovie-admin/LICENSE b/fastmovie-admin/LICENSE new file mode 100644 index 0000000..1d3fcf0 --- /dev/null +++ b/fastmovie-admin/LICENSE @@ -0,0 +1,30 @@ +LoongAdmin遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2024 by Renloong (https://www.renloong.com) +All rights reserved。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +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 OWNER 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. diff --git a/fastmovie-admin/README.md b/fastmovie-admin/README.md new file mode 100644 index 0000000..481c483 --- /dev/null +++ b/fastmovie-admin/README.md @@ -0,0 +1,50 @@ +# LoongAdmin + +#### 教程 + +```binsh +composer create-project loong/admin +``` + +```binsh +git clone https://github.com/RenLoong/loong-admin.git +``` + +手动导入根目录下的database.sql文件 + +将根目录的.env.example修改为.env + +```ini +; 进程名 +SERVER_NAME = LoongAdmin +; 监听端口号 +SERVER_PORT = 36999 +; 自定义后台入口 +SERVER_ADMIN_PATH = admin +; 数据库配置 +DATABASE_HOST = 127.0.0.1 +DATABASE_PORT = 3306 +DATABASE_NAME = +DATABASE_USERNAME = +DATABASE_PASSWORD = +DATABASE_CHARSET = utf8mb4 +DATABASE_PREFIX = php_ +; Redis配置 +REDIS_HOST = 127.0.0.1 +REDIS_PORT = 6379 +REDIS_PASSWORD = +REDIS_DATABASE = 2 +; webman/push插件配置 +PUSH_KEY = 32位字符串 +PUSH_SCERET = 32位字符串 +PUSH_API_PORT = 37000 +PUSH_WSS_PORT = 37001 +``` + +后台账号:admin +后台密码:123456 + +#### 联系我 + +- QQ群: +[826867889](https://h5.qun.qq.com/h5/qun-share-page/?_wv=1027&k=FhjcGB-hY66iJSPySAoJs-vxtT0zqbtR&authKey=qdPxxwMiApBT4tU8mk5dubgDODo8tSYemfW36kY6k0uT5lPFgS9nQCSQn4niYJlm&market_channel_source=826867889_1&noverify=0&group_code=826867889) \ No newline at end of file diff --git a/fastmovie-admin/VERSION b/fastmovie-admin/VERSION new file mode 100644 index 0000000..fe2daff --- /dev/null +++ b/fastmovie-admin/VERSION @@ -0,0 +1,2 @@ +1034 +0.0.34 diff --git a/fastmovie-admin/app/Basic.php b/fastmovie-admin/app/Basic.php new file mode 100644 index 0000000..ddb60d0 --- /dev/null +++ b/fastmovie-admin/app/Basic.php @@ -0,0 +1,50 @@ +post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } + /** + * 更新状态字段 + * @method POST + */ + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } +} diff --git a/fastmovie-admin/app/admin/admin.json b/fastmovie-admin/app/admin/admin.json new file mode 100644 index 0000000..4230f1b --- /dev/null +++ b/fastmovie-admin/app/admin/admin.json @@ -0,0 +1,235 @@ +[ + { + "title": "Dashboard", + "icon": "House", + "path": "Index", + "sort": 0, + "children": [ + { + "title": "Console", + "icon": "ElementPlus", + "path": "Index/control", + "component": "databoard" + } + ] + }, + { + "title": "Functional", + "icon": "Stopwatch", + "path": "Functional", + "children": [ + { + "title": "Administrator", + "icon": "User", + "path": "Admin/group", + "component": "table", + "children": [ + { + "title": "Staff List", + "path": "Admin/index", + "component": "table", + "children": [ + { + "title": "Create Administrator", + "path": "Admin/create", + "show": false, + "component": "form" + }, + { + "title": "Edit Administrator", + "show": false, + "path": "Admin/update", + "component": "form" + }, + { + "title": "Delete Administrator", + "show": false, + "path": "Admin/delete" + }, + { + "title": "Update Status", + "show": false, + "path": "Admin/indexUpdateState" + }, + { + "title": "Move Role", + "component": "form", + "show": false, + "path": "Admin/moveRole" + } + ] + } + ] + }, + { + "title": "Role Management", + "icon": "PriceTag", + "path": "AdminRole/group", + "component": "table", + "children": [ + { + "title": "Role List", + "path": "AdminRole/index", + "component": "table", + "children": [ + { + "title": "Create Role", + "path": "AdminRole/create", + "show": false, + "component": "form" + }, + { + "title": "Edit Role", + "show": false, + "path": "AdminRole/update", + "component": "form" + }, + { + "title": "Delete Role", + "show": false, + "path": "AdminRole/delete" + }, + { + "title": "Update Status", + "show": false, + "path": "AdminRole/indexUpdateState" + } + ] + } + ] + } + ] + }, + { + "title": "Systems", + "icon": "Setting", + "path": "System", + "children": [ + { + "title": "Basic Settings", + "icon": "Setting", + "path": "System/basic", + "component": "form" + }, + { + "title": "Upload Settings", + "icon": "UploadFilled", + "path": "System/upload", + "component": "form" + }, + { + "title": "SMS Settings", + "icon": "Message", + "path": "System/sms", + "component": "form" + }, + { + "title": "Payment Management", + "icon": "Wallet", + "path": "Payment/group", + "show": false, + "children": [ + { + "title": "Payment Settings", + "path": "Payment/index", + "show": false, + "component": "payment-form" + }, + { + "title": "Payment Template", + "path": "PaymentTemplate/index", + "component": "table", + "show": false, + "children": [ + { + "title": "Create Payment Template", + "path": "PaymentTemplate/create", + "show": false, + "component": "form" + }, + { + "title": "Edit Payment Template", + "path": "PaymentTemplate/update", + "show": false, + "component": "form" + }, + { + "title": "Delete Payment Template", + "path": "PaymentTemplate/delete", + "show": false, + "component": "" + } + ] + } + ] + } + ] + }, + { + "title": "Public Permissions", + "path": "Public", + "show": false, + "children": [ + { + "title": "Edit Profile", + "show": false, + "path": "Admin/updateSelf", + "component": "form" + }, + { + "title": "Get Profile", + "show": false, + "path": "Admin/getSelfInfo", + "component": "" + }, + { + "title": "Logout", + "show": false, + "path": "Public/outLogin", + "component": "" + }, + { + "title": "Get Upload Categories", + "path": "Uploads/getUploadClassify", + "show": false, + "component": "" + }, + { + "title": "Edit Upload Category", + "path": "Uploads/saveClassify", + "show": false, + "component": "" + }, + { + "title": "Delete Upload Category", + "path": "Uploads/deleteClassify", + "show": false, + "component": "" + }, + { + "title": "Upload File", + "path": "Uploads/upload", + "show": false, + "component": "" + }, + { + "title": "Delete Upload File", + "path": "Uploads/deleteUploads", + "show": false, + "component": "" + }, + { + "title": "Edit Upload File", + "path": "Uploads/updateUploads", + "show": false, + "component": "" + }, + { + "title": "Get Upload File List", + "path": "Uploads/getList", + "show": false, + "component": "" + } + ] + } +] \ No newline at end of file diff --git a/fastmovie-admin/app/admin/api/Install.php b/fastmovie-admin/app/admin/api/Install.php new file mode 100644 index 0000000..b695c87 --- /dev/null +++ b/fastmovie-admin/app/admin/api/Install.php @@ -0,0 +1,14 @@ +path = __DIR__; + } +} diff --git a/fastmovie-admin/app/admin/config/route.php b/fastmovie-admin/app/admin/config/route.php new file mode 100644 index 0000000..071fd81 --- /dev/null +++ b/fastmovie-admin/app/admin/config/route.php @@ -0,0 +1,73 @@ +isAbstract() || $reflection->isInterface()) { + continue; + } + + $actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); + $actionsLen = count($actions); + for ($n = 0; $n < $actionsLen; $n++) { + $name = $actions[$n]->name; + if (!str_starts_with($name, '__')) { + $routes["/{$controllerName}/{$name}"] = [$classStr, $name]; + } + } + } + $controllersVersionClass = glob(app_path('admin') . '/controller/**/*Controller.php'); + if (!empty($controllersVersionClass)) { + $len = count($controllersVersionClass); + for ($i = 0; $i < $len; $i++) { + $value = $controllersVersionClass[$i]; + $version=basename(dirname($value)); + $controllerName = str_replace('Controller', '', basename($value, '.php')); + $classStr = 'app' . str_replace([app_path(), '.php', '/'], ['', '', '\\'], $value); + $reflection = new \ReflectionClass('\\' . $classStr); + + // 忽略抽象类、接口 + if ($reflection->isAbstract() || $reflection->isInterface()) { + continue; + } + + $actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); + $actionsLen = count($actions); + for ($n = 0; $n < $actionsLen; $n++) { + $name = $actions[$n]->name; + + if (!str_starts_with($name, '__')) { + $routes["/{$version}/{$controllerName}/{$name}"] = [$classStr, $name]; + } + } + } + } + Route::group('/' . $admin_path, function ()use($routes) { + foreach ($routes as $key => $value) { + if(in_array($value[1],['indexUpdateField','indexUpdateState'])){ + Route::post($key,$value); + }else if(strpos($value[1],'GetTable')){ + Route::get($key,$value); + }else{ + Route::any($key,$value); + } + } + }); +} diff --git a/fastmovie-admin/app/admin/controller/AdminController.php b/fastmovie-admin/app/admin/controller/AdminController.php new file mode 100644 index 0000000..3fa7a25 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/AdminController.php @@ -0,0 +1,601 @@ +addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => 'Admin/update', + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{nickname}》管理员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => 'Admin/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{nickname}》管理员吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addFooter(); + $builder->addFooterAction('移动到', [ + 'model' => Action::DIALOG['value'], + 'path' => 'Admin/moveRole', + 'props' => [ + 'title' => '移动到', + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建管理员', [ + 'path' => 'Admin/create', + 'props' => [ + 'type' => 'success', + 'title' => '创建管理员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder; + $formBuilder->add('username', '账号', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('userinfo', '管理员', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ], + [ + 'field' => 'new_text1', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + 'tags' => [ + [ + 'field' => 'role_name', + 'props' => [ + 'type' => 'success' + ] + ] + ], + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ], + [ + 'field' => 'wx_openid', + 'label' => 'OpenID' + ] + ] + ] + ] + ]); + $builder->add('allow_week', '工作日', [ + 'component' => [ + 'name' => 'tag', + 'options' => Week::getOptions() + ], + 'props' => [ + 'width' => '240px' + ] + ]); + $builder->add('allow_work', '工作时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'danger' + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('online_time', '活动', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'online_time', + 'label' => '在线' + ], + [ + 'field' => 'login_time', + 'label' => '登录' + ], + [ + 'component' => 'tag', + 'field' => 'login_ip', + 'label' => '登录IP', + 'props' => [ + 'size' => 'small', + ] + ] + ] + ] + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => 'Admin/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $username = $request->get('username'); + if ($username) { + $where[] = ['username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['mobile', 'like', "%{$mobile}%"]; + } + $list = Admin::alias('a')->where($where) + ->join('admin_role b', 'b.id=a.role_id') + ->field('a.*,b.name as role_name,b.is_system') + ->order('a.id desc')->paginate($limit)->each(function ($item) { + $item->username = "UserName:" . $item->username; + $item->new_text = '新'; + $item->new_text1 = 'T'; + $allow_work = []; + $allow_work[] = $item->allow_time_start; + $allow_work[] = $item->allow_time_end; + $item->allow_work = $allow_work; + }); + return $this->resData($list); + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $Admin = Admin::where(['id' => $id])->find(); + if (!$Admin) { + return $this->fail('管理员不存在'); + } + $Role = AdminRole::where(['id' => $Admin->role_id])->find(); + if ($Role && $Role->is_system && !$value) { + return $this->fail('系统管理员不允许操作'); + } + $Admin->{$field} = $value; + if ($Admin->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $Admin = Admin::where(['id' => $id])->find(); + if (!$Admin) { + return $this->fail('管理员不存在'); + } + $Role = AdminRole::where(['id' => $Admin->role_id])->find(); + if ($Role && $Role->is_system) { + return $this->fail('系统管理员不允许操作'); + } + if ($Admin->delete()) { + return $this->success(); + } + return $this->fail('操作失败'); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $validate = new ValidateAdmin; + $validate->check($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + $Role = AdminRole::where(['id' => $D['role_id']])->find(); + if (!$Role) { + return $this->fail('角色不存在'); + } + $Admin = new Admin; + $Admin->role_id = $D['role_id']; + $Admin->username = $D['username']; + $Admin->nickname = $D['nickname']; + $Admin->headimg = $D['headimg']; + $Admin->mobile = $D['mobile']; + $Admin->email = $D['email']; + $Admin->wx_openid = $D['wx_openid']; + if ($D['password']) { + $Admin->password = $D['password']; + } + if ($Role->is_system) { + $Admin->allow_time_start = '00:00:00'; + $Admin->allow_time_end = '23:59:59'; + $Admin->allow_week = Week::getValues(); + } else { + $Admin->allow_time_start = $D['allow_time'][0]; + $Admin->allow_time_end = $D['allow_time'][1]; + $Admin->allow_week = $D['allow_week']; + } + if ($Admin->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $validate = new ValidateAdmin; + $validate->check($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + $Admin = Admin::where(['id' => $D['id']])->find(); + if (!$Admin) { + return $this->fail('管理员不存在'); + } + $Role = AdminRole::where(['id' => $D['role_id']])->find(); + if (!$Role) { + return $this->fail('角色不存在'); + } + $Admin->role_id = $D['role_id']; + $Admin->username = $D['username']; + $Admin->nickname = $D['nickname']; + $Admin->headimg = $D['headimg']; + $Admin->mobile = $D['mobile']; + $Admin->email = $D['email']; + $Admin->wx_openid = $D['wx_openid']; + if ($D['password']) { + $Admin->password = $D['password']; + } + $Admin->allow_time_start = $D['allow_time'][0]; + $Admin->allow_time_end = $D['allow_time'][1]; + $Admin->allow_week = $D['allow_week']; + if ($Admin->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $id = $request->get('id'); + $Admin = Admin::where(['id' => $id])->withoutField('password')->find(); + $Role = AdminRole::where(['id' => $Admin->role_id])->find(); + if (!$Role) { + $Admin->role_id = ''; + } + $is_system = $Role && $Role->is_system; + $builder = $this->getFormBuilder($is_system); + $Admin->allow_time = [ + $Admin->allow_time_start, + $Admin->allow_time_end + ]; + $builder->setData($Admin->toArray()); + return $this->resData($builder); + } + private function getFormBuilder($is_system = false) + { + $builder = new FormBuilder; + $builder->add('role_id', '所属角色', 'cascader', null, [ + 'required' => true, + 'props' => [ + 'options' => AdminRole::options(), + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $builder->add('username', '账号', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('password', '密码', 'input', '', [ + 'props' => [ + 'placeholder' => '不修改密码请留空', + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('nickname', '昵称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true + ]); + $builder->add('headimg', '头像', 'bundle', '', [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ]); + $builder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'maxlength' => 11, + 'show-word-limit' => true + ] + ]); + $builder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'maxlength' => 50, + 'show-word-limit' => true + ] + ]); + $SystemRoleId = AdminRole::systemRole('id'); + $builder->add('allow_time', '工作时间范围', 'time-picker', $is_system ? ['00:00:00', '23:59:59'] : ['08:00:00', '18:00:00'], [ + 'where' => [ + ['role_id', 'not in', $SystemRoleId] + ], + 'props' => [ + 'is-range' => true, + 'value-format' => 'HH:mm:ss', + 'disabled' => $is_system + ] + ]); + $builder->add('allow_week', '工作日', 'checkbox', [], [ + 'options' => Week::getOptions(), + 'where' => [ + ['role_id', 'not in', $SystemRoleId] + ], + 'subProps' => [ + 'border' => true, + 'disabled' => $is_system + ] + ]); + $builder->add('wx_openid', 'OpenID', 'input', '', []); + return $builder; + } + public function moveRole(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + if (!$D['role_id']) { + return $this->fail('请选择角色'); + } + $Role = AdminRole::where(['id' => $D['role_id']])->find(); + if (!$Role) { + return $this->fail('角色不存在'); + } + $ids = explode(',', $D['id']); + $Admin = Admin::whereIn('id', $ids)->select(); + Db::startTrans(); + try { + foreach ($Admin as $item) { + $item->role_id = $D['role_id']; + $item->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + $builder = new FormBuilder(null,null,[ + 'labelPosition' => 'right', + 'label-width' => "400px", + 'class' => 'w-80 mx-auto', + 'size' => 'large', + ]); + $builder->add('role_id', '所属角色', 'cascader', null, [ + 'required' => true, + 'props' => [ + 'options' => AdminRole::options(), + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + return $this->resData($builder); + } + public function updateSelf(Request $request) + { + $id = $request->admin_uid; + if ($request->method() === 'POST') { + $D = $request->post(); + $D['id'] = $id; + try { + $validate = new ValidateAdmin; + $validate->scene('self')->check($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + $Admin = Admin::where(['id' => $D['id']])->find(); + if (!$Admin) { + return $this->fail('管理员不存在'); + } + $Admin->username = $D['username']; + $Admin->nickname = $D['nickname']; + $Admin->headimg = $D['headimg']; + $Admin->mobile = $D['mobile']; + $Admin->email = $D['email']; + $Admin->wx_openid = $D['wx_openid']; + if ($D['password']) { + $Admin->password = $D['password']; + } + if ($Admin->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $Admin = Admin::where(['id' => $id])->withoutField('password')->find(); + if (!$Admin) { + return $this->fail('管理员不存在'); + } + $builder = $this->getFormBuilder(); + $builder->remove('role_id'); + $builder->remove('allow_time'); + $builder->remove('allow_week'); + $builder->setData($Admin->toArray()); + return $this->resData($builder); + } + public function getSelfInfo(Request $request) + { + $id = $request->admin_uid; + $Admin = Admin::where(['id' => $id])->withoutField('password')->find(); + if (!$Admin) { + return $this->fail('管理员不存在'); + } + return $this->resData(Admin::getTokenInfo($Admin)); + } +} diff --git a/fastmovie-admin/app/admin/controller/AdminRoleController.php b/fastmovie-admin/app/admin/controller/AdminRoleController.php new file mode 100644 index 0000000..0f7e369 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/AdminRoleController.php @@ -0,0 +1,294 @@ + 'id', + 'api' => 'AdminRole/index', + 'lazy' => true, + 'treeProps' => [ + 'children' => 'children', + 'hasChildren' => 'hasChildren' + ] + ]); + $builder->addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => 'AdminRole/update', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{name}》角色' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => 'AdminRole/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{name}》角色吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建角色', [ + 'path' => 'AdminRole/create', + 'props' => [ + 'type' => 'success', + 'title' => '创建角色' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('name', '角色名称'); + $builder->add('admin_num', '人数', [ + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('rule_num', '拥有权限数', [ + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => 'AdminRole/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $id = $request->get('id'); + if ($id) { + $where[] = ['pid', '=', $id]; + } else { + $where[] = ['pid', 'NULL', '']; + } + $list = AdminRole::where($where) + ->order('id asc')->paginate($limit)->each(function ($item) { + $item->admin_num = Admin::where(['role_id' => $item->id])->count(); + $item->rule_num = 0; + if ($item->is_system) { + $item->rule_num = '所有'; + } elseif ($item->rule) { + $item->rule_num = count($item->rule); + } + $item->hasChildren = AdminRole::where(['pid' => $item->id])->count() > 0; + }); + return $this->resData($list); + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $AdminRole = AdminRole::where(['id' => $id])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + if ($AdminRole->is_system) { + return $this->fail('系统角色不允许操作'); + } + $AdminRole->{$field} = $value; + if ($AdminRole->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $AdminRole = AdminRole::where(['id' => $id])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + if ($AdminRole->is_system) { + return $this->fail('系统角色不允许操作'); + } + if (AdminRole::where(['pid' => $id])->count() > 0) { + return $this->fail('请先删除子角色'); + } + if (Admin::where(['role_id' => $id])->count() > 0) { + return $this->fail('请先将拥有该角色的用户移除'); + } + if ($AdminRole->delete()) { + return $this->success(); + } + return $this->fail('操作失败'); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $AdminRole = new AdminRole; + if (!empty($D['pid'])) { + $AdminRole->pid = $D['pid']; + } + $AdminRole->name = $D['name']; + $AdminRole->rule = $D['rule']; + if ($AdminRole->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $AdminRole = AdminRole::where(['id' => $D['id']])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + $AdminRole->name = $D['name']; + $AdminRole->rule = $D['rule']; + if ($AdminRole->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $id = $request->get('id'); + $AdminRole = AdminRole::where(['id' => $id])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + $builder = $this->getFormBuilder(true); + if (!$AdminRole->rule) { + $AdminRole->rule = []; + } + $builder->setData($AdminRole->toArray()); + return $this->resData($builder); + } + private function getFormBuilder($update = false) + { + $builder = new FormBuilder; + $Component = new ComponentBuilder; + if (!$update) { + $builder->add('pid', '角色等级', 'cascader', null, [ + 'prompt' => [ + $Component->add('text', ['default' => '建议使用公司组织架构为角色等级'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'options' => AdminRole::options(null, ['is_system' => 0]), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + } + $builder->add('name', '角色名称', 'input', '', [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '建议使用公司组织架构为角色名称'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $Install = new \app\admin\api\Install; + $menus = new Menus($Install); + $builder->add('rule', '规则', 'admin-rule', [], [ + 'required' => true, + 'options' => $menus, + 'props' => [ + 'rightDefaultChecked' => [ + 'Index', + 'Index/index', + 'Index/control', + 'Admin/updateSelf', + 'Admin/getSelfInfo', + 'Public', + 'Public/outLogin' + ] + ] + ]); + return $builder; + } +} diff --git a/fastmovie-admin/app/admin/controller/CaptchaController.php b/fastmovie-admin/app/admin/controller/CaptchaController.php new file mode 100644 index 0000000..f1af967 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/CaptchaController.php @@ -0,0 +1,38 @@ +success(null, Captcha::captchaCode()); + } +} diff --git a/fastmovie-admin/app/admin/controller/IndexController.php b/fastmovie-admin/app/admin/controller/IndexController.php new file mode 100644 index 0000000..746d2be --- /dev/null +++ b/fastmovie-admin/app/admin/controller/IndexController.php @@ -0,0 +1,56 @@ +path(); + # 如果不是以/结尾的,就重定向到以/结尾的URL + if (substr($path, -1) != '/') { + return redirect($path . '/'); + } + return view(public_path('index.html')); + } + public function control(Request $request) + { + $builder = new DataboardBuilder([ + 'gutter' => 6 + ]); + $pluginConfig = glob(base_path("plugin/*/api/{$request->app}/IndexController.php")); + foreach ($pluginConfig as $path) { + $plugin_name = basename(dirname(dirname(dirname($path)))); + $class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\IndexController"; + if (!class_exists($class)) { + continue; + } + $plugin = new $class; + if (method_exists($plugin, 'control')) { + $plugin->control($builder); + } + } + foreach ($pluginConfig as $path) { + $plugin_name = basename(dirname(dirname(dirname($path)))); + $class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\IndexController"; + if (!class_exists($class)) { + continue; + } + $plugin = new $class; + if (method_exists($plugin, 'echarts')) { + $plugin->echarts($builder); + } + } + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/app/admin/controller/LoginController.php b/fastmovie-admin/app/admin/controller/LoginController.php new file mode 100644 index 0000000..a096647 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/LoginController.php @@ -0,0 +1,70 @@ +post(); + $captcha_state = Config::get('captcha', 'state'); + if ($captcha_state) { + if (!Captcha::check($D['captcha'], $D['token'])) { + throw new Exception("验证码不正确", ResponseCode::CAPTCHA); + } + } + $Admin = Admin::where(['username' => $D['username']])->find(); + if (!$Admin) { + throw new Exception('密码错误'); + } + if (!Password::verify($D['password'], $Admin->password)) { + throw new Exception('密码错误'); + } + if (!$Admin->state) { + throw new Exception('管理员账号异常'); + } + $AdminRole = AdminRole::where(['id' => $Admin->role_id])->find(); + if (!$AdminRole) { + throw new Exception('管理员角色异常'); + } + if (!$AdminRole->state) { + throw new Exception('管理员角色异常'); + } + if (!$AdminRole->is_system) { + $now = date('H:i:s'); + if (!($now >= $Admin->allow_time_start && $now <= $Admin->allow_time_end)) { + throw new Exception("当前不在工作时间"); + } + if (!in_array(date('w'), $Admin->allow_week)) { + throw new Exception("今日因该是休息日哦"); + } + } + $Admin->login_time = date('Y-m-d H:i:s'); + $Admin->login_ip = $request->getRealIp(true); + if ($Admin->save()) { + return $this->success('登录成功', Admin::getTokenInfo($Admin)); + } else { + throw new Exception("登录失败"); + } + } catch (\Throwable $th) { + return $this->exception($th); + } + } +} diff --git a/fastmovie-admin/app/admin/controller/PaymentController.php b/fastmovie-admin/app/admin/controller/PaymentController.php new file mode 100644 index 0000000..211bcf1 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/PaymentController.php @@ -0,0 +1,187 @@ +model = new PaymentConfig; + } + public function index(Request $request) + { + $tabs = []; + $PaymentConfig = PaymentConfig::where(['channels_uid' => null])->select(); + $Platform = Platform::getOptions(); + foreach ($Platform as $key => $item) { + $channels = []; + switch ($item['value']) { + case Platform::PC['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::ALIPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + case Platform::H5['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::ALIPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + case Platform::WECHAT_MINIAPP['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + case Platform::WECHAT_OFFICIAL_ACCOUNT['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + case Platform::APP['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::ALIPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + } + $tabs[] = $this->getFormBuilder($PaymentConfig, $item, $channels); + } + return $this->resData($tabs); + } + private function getFormBuilder($PaymentConfig, $Platform, $channels) + { + $builder = new TableBuilder([ + 'border' => false, + 'stripe' => false + ]); + $builder->add('channels', '支付方式', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels_text', + 'avatar' => [ + 'field' => 'channels_icon', + 'size' => 40 + ] + ] + ], + 'props' => [] + ]); + $builder->add('template_id', '支付模板', [ + 'component' => [ + 'name' => 'select', + 'api' => 'Payment/indexUpdateField', + 'options' => PaymentTemplate::options(['channels_uid' => null]), + 'props' => [ + 'clearable' => true + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('state', '是否启用', [ + 'component' => [ + 'name' => 'switch', + 'api' => 'Payment/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('is_default', '是否为默认支付', [ + 'component' => [ + 'name' => 'switch', + 'api' => 'Payment/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $tableData = []; + $exist = []; + foreach ($PaymentConfig as $item) { + if ($item->platform != $Platform['value']) continue; + $temp = $item->toArray(); + $PaymentChannels = PaymentChannels::get($item->channels); + if ($PaymentChannels) { + $temp['channels_text'] = $PaymentChannels['label']; + $temp['channels_icon'] = $PaymentChannels['icon']; + } + $exist[] = $item->channels; + $tableData[] = $temp; + } + foreach ($channels as $key => $channel) { + if (!in_array($channel['value'], $exist)) { + $model = new PaymentConfig; + $model->platform = $Platform['value']; + $model->channels = $channel['value']; + $model->state = State::NO['value']; + $model->save(); + $tableData[] = [ + 'id' => $model->id, + 'channels' => $channel['value'], + 'channels_text' => $channel['label'], + 'channels_icon' => $channel['icon'], + 'template_id' => null, + 'state' => State::NO['value'], + 'is_default' => State::NO['value'] + ]; + } + } + $data = [ + 'title' => $Platform['label'], + 'name' => $Platform['value'], + 'tips' => '在' . $Platform['label'] . '端付款时使用', + 'builder' => $builder, + 'data' => $tableData + ]; + return $data; + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $PaymentConfig = PaymentConfig::where(['id' => $id])->find(); + if (!$PaymentConfig) { + return $this->fail('数据不存在'); + } + if ($field == 'is_default' && $value == State::YES['value']) { + $PaymentConfig->where(['platform' => $PaymentConfig->platform])->update(['is_default' => State::NO['value']]); + } + $PaymentConfig->{$field} = $value; + if ($PaymentConfig->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } +} diff --git a/fastmovie-admin/app/admin/controller/PaymentTemplateController.php b/fastmovie-admin/app/admin/controller/PaymentTemplateController.php new file mode 100644 index 0000000..2f713c2 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/PaymentTemplateController.php @@ -0,0 +1,639 @@ +addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => 'PaymentTemplate/update', + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{title}》支付模板' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => 'PaymentTemplate/delete', + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{title}》支付模板吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('新增模板', [ + 'path' => 'PaymentTemplate/create', + 'props' => [ + 'type' => 'success', + 'title' => '新增支付模板' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success', + 'icon' => 'Plus' + ] + ] + ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '模板名称'); + $builder->add('channels', '支付方式', [ + 'component' => [ + 'name' => 'tag', + 'options' => PaymentChannels::getOptions() + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => 'PaymentTemplate/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', null]; + $list = PaymentTemplate::where($where)->withoutField('value') + ->order('id desc')->paginate($limit)->each(function ($item) { + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $model = new PaymentTemplate; + $model->title = $D['title']; + unset($D['title']); + $model->channels = $D['channels']; + unset($D['channels']); + $model->state = State::YES['value']; + $model->value = $this->getValue($model->channels, $D); + if ($model->save()) { + return $this->success('新增成功'); + } + return $this->fail('新增失败'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $model = PaymentTemplate::where(['id' => $D['id']])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + unset($D['id']); + $model->title = $D['title']; + unset($D['title']); + $model->channels = $D['channels']; + unset($D['channels']); + $model->state = State::YES['value']; + $model->value = $this->getValue($model->channels, $D); + if ($model->save()) { + return $this->success('更新成功'); + } + return $this->fail('更新失败'); + } + $id = $request->get('id'); + $model = PaymentTemplate::where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $builder = $this->getFormBuilder(true); + $data = [ + 'id' => $model->id, + 'title' => $model->title, + 'channels' => $model->channels + ]; + foreach ($model->value as $key => $value) { + $data[$key] = $value; + } + $builder->setData($data); + return $this->resData($builder); + } + private function getValue($channels, $D) + { + $value = []; + switch ($channels) { + case PaymentChannels::WXPAY['value']: + $value = [ + 'mch_type' => $D['mch_type'], + 'appid' => $D['appid'], + 'mch_id' => $D['mch_id'], + 'mch_key' => $D['mch_key'], + 'ssl_cert' => $D['ssl_cert'], + 'ssl_key' => $D['ssl_key'], + 'notify_url' => $D['notify_url'], + ]; + break; + case PaymentChannels::ALIPAY['value']: + $value = [ + 'appid' => $D['appid'], + 'ras_model' => $D['ras_model'], + 'app_public_cert' => $D['app_public_cert'], + 'alipay_public_cert' => $D['alipay_public_cert'], + 'alipay_root_cert' => $D['alipay_root_cert'], + 'alipay_public_key' => $D['alipay_public_key'], + 'private_key' => $D['private_key'], + 'notify_url' => $D['notify_url'], + ]; + break; + case PaymentChannels::INTEGRAL['value']: + $value = [ + 'display_name' => $D['display_name'], + 'proportion' => $D['proportion'], + 'is_integer' => $D['is_integer'], + 'integer' => $D['integer'] + ]; + break; + } + return $value; + } + private function getFormBuilder($update = false) + { + $builder = new FormBuilder(null, '', [ + 'labelWidth' => '300px', + 'labelPosition' => 'right' + ]); + $Component = new ComponentBuilder; + $builder->add('title', '模板名称', 'input', null, [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '仅用于后台管理使用,对前台用户不可见;例如:H5-支付宝支付;微信小程序-微信支付'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入模板名称' + ] + ]); + $builder->add('channels', '支付方式', 'radio', PaymentChannels::WXPAY['value'], [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '保存以后支付方式将不可修改,请谨慎操作,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信商户平台'], ['type' => 'success', 'href' => 'https://pay.weixin.qq.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small']) + ->add('text', ['default' => ','], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '支付宝开放平台'], ['type' => 'primary', 'href' => 'https://alipay.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small']) + ->builder() + ], + 'options' => PaymentChannels::getOptions(), + 'props' => [ + 'disabled' => $update + ], + 'subProps' => [ + 'border' => true + ] + ]); + + # 微信支付开始 + /* $builder->add('api_version', '微信支付接口版本', 'radio', WxpayVersion::V3['value'], [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']] + ], + 'options' => WxpayVersion::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); */ + $builder->add('mch_type', '微信商户号类型', 'radio', WxpayMchType::NORMAL['value'], [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']] + ], + 'options' => WxpayMchType::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('appid', '应用ID (AppID)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入应用ID (AppID)' + ] + ]); + $builder->add('mch_id', '微信商户号 (MchId)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入微信商户号 (MchId)' + ] + ]); + $builder->add('mch_key', '支付密钥 (APIKEY)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置APIv3密钥'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入支付密钥 (APIKEY)' + ] + ]); + $builder->add('ssl_cert', '证书文件 (CERT)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + $builder->add('ssl_key', '证书文件 (KEY)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + # 微信支付结束 + + # 微信支付服务商模式开始 + $builder->add('service_appid', '服务商应用ID (AppID)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '请填写微信支付服务商的AppID'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入服务商应用ID (AppID)' + ] + ]); + $builder->add('service_mch_id', '服务商户号 (MchId)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付服务商的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入服务商户号 (MchId)' + ] + ]); + $builder->add('service_mch_key', '服务商密钥 (APIKEY)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置API密钥'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入服务商密钥 (APIKEY)' + ] + ]); + $builder->add('appid', '子商户应用ID (AppID)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入子商户应用ID (AppID)' + ] + ]); + $builder->add('mch_id', '子商户号 (MchId)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入子商户号 (MchId)' + ] + ]); + $builder->add('service_ssl_cert', '服务商证书文件 (CERT)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + $builder->add('service_ssl_key', '服务商证书文件 (KEY)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'props' => [ + 'accept' => 'application/octet-stream', + 'view' => 'file', + 'multiple' => 1 + ] + ]); + # 微信支付服务商模式结束 + + # 支付宝支付开始 + $builder->add('appid', '支付宝应用 (AppID)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::ALIPAY['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '支付宝分配给开发者的应用ID;例如:2021072300007148'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入支付宝应用 (AppID)' + ] + ]); + $builder->add('ras_model', '加签模式', 'radio', AlipayRsaModel::PUBLIC_CERT['value'], [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::ALIPAY['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '如需使用资金支出类的接口,则必须使用公钥证书模式'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'options' => AlipayRsaModel::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('app_public_cert', '应用公钥证书', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::ALIPAY['value']], + ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '请上传 "appCertPublicKey_xxxxxxxx.crt" 文件'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + $builder->add('alipay_public_cert', '支付宝公钥证书', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::ALIPAY['value']], + ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '请上传 "alipayCertPublicKey_RSA2.crt" 文件'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + $builder->add('alipay_root_cert', '支付宝根证书', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::ALIPAY['value']], + ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '请上传 "alipayRootCert.crt" 文件'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + $builder->add('alipay_public_key', '支付宝公钥 (alipayPublicKey)', 'input', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::ALIPAY['value']], + ['ras_model', '=', AlipayRsaModel::PUBLIC_KEY['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '可在 "支付宝开放平台" - "应用信息" - "接口加签方式" - "支付宝公钥" 中复制'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 6 + ], + 'placeholder' => '请输入支付宝公钥 (alipayPublicKey)' + ] + ]); + $builder->add('private_key', '应用私钥 (privateKey)', 'input', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::ALIPAY['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '查看 "应用私钥RSA2048-敏感数据,请妥善保管.txt" 文件,将全部内容复制到此处'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 6 + ], + 'placeholder' => '请输入应用私钥 (privateKey)' + ] + ]); + $builder->add('display_name', '显示名称', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::INTEGRAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '在客户端显示的名称'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入显示名称' + ] + ]); + $builder->add('proportion', '充值比例', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::INTEGRAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '1元=多少积分'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'controls' => false, + 'min' => 1, + 'max' => 1000000 + ] + ]); + $builder->add('is_integer', '整数使用', 'switch', State::NO['value'], [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::INTEGRAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '开启后只能使用整数的倍数积分'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $builder->add('integer', '整数', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::INTEGRAL['value']], + ['is_integer', '=', State::YES['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '整数的倍数使用'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'controls' => false, + 'min' => 1, + 'max' => 1000000 + ] + ]); + $builder->add('notify_url', '支付通知', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', 'in', [PaymentChannels::WXPAY['value'],PaymentChannels::ALIPAY['value']]] + ], + 'prompt' => [ + $Component->add('text', ['default' => '只需要填写域名,不需要填写路径,如:https://www.baidu.com'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '支付通知接口域名' + ] + ]); + return $builder; + } +} diff --git a/fastmovie-admin/app/admin/controller/PlatformController.php b/fastmovie-admin/app/admin/controller/PlatformController.php new file mode 100644 index 0000000..c28f6d5 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/PlatformController.php @@ -0,0 +1,20 @@ +builder(); + } + public function wechat_official_account(Request $request) + { + return $this->builder(); + } +} diff --git a/fastmovie-admin/app/admin/controller/PublicController.php b/fastmovie-admin/app/admin/controller/PublicController.php new file mode 100644 index 0000000..1c54216 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/PublicController.php @@ -0,0 +1,211 @@ +lang; + $domain = $request->app; + $config = Config::get('basic'); + $config = new Web($config); + $captcha_state = Config::get('captcha', 'state'); + $config->useLogin([ + 'title' => trans('Login Title', [], $domain, $lang), + 'url' => 'Login/login', + 'user_agreement' => '', + 'captcha' => $captcha_state + ]); + $config->useApis([ + 'userinfo' => 'Admin/getSelfInfo', + 'lock' => 'Public/lock', + 'unlock' => 'Public/unlock', + 'menus' => 'Public/menus', + 'outLogin' => 'Public/outLogin' + ]); + $toolbar = new Action(); + $toolbar->add(EnumAction::LOCK['value'], [ + 'icon' => 'Lock', + 'tips' => trans('toolbar Lock', [], $domain, $lang), + ]); + $toolbar->add(EnumAction::SEARCH['value'], [ + 'icon' => 'Search', + 'tips' => trans('toolbar Search', [], $domain, $lang), + ]); + $toolbar->add(EnumAction::NOTIFICATION['value'], [ + 'icon' => 'Notification', + 'tips' => trans('toolbar Notification', [], $domain, $lang), + ]); + $toolbar->add(EnumAction::FULL_SCREEN['value'], [ + 'icon' => 'FullScreen', + 'tips' => trans('toolbar FullScreen', [], $domain, $lang), + ]); + // 读取update/VERSION文件和跟目录下的VERSION文件对比 + $updateVersionContent = file_get_contents(base_path('update/VERSION')); + $updateVersion = explode("\n", $updateVersionContent); + $updateVersion = (int)trim($updateVersion[0]); + $versionContent = file_get_contents(base_path('VERSION')); + $version = explode("\n", $versionContent); + $version = (int)trim($version[0]); + if ($updateVersion < $version) { + $toolbar->add(EnumAction::COMFIRM['value'], [ + 'path' => 'Public/syncSql', + 'icon' => 'Coin', + 'tips' => trans('同步SQL', [], $domain, $lang), + 'props' => [ + 'message' => '检测到有SQL未同步,确定要同步SQL吗?', + 'confirmButtonClass' => 'el-button--danger' + ] + ]); + } + $toolbar->add(EnumAction::LINK['value'], [ + 'icon' => 'House', + 'tips' => trans('toolbar House', [], $domain, $lang), + 'props' => [ + 'url' => '/', + 'target' => '_blank' + ] + ]); + $toolbar->add(EnumAction::LINK['value'], [ + 'icon' => 'ElementPlus', + 'tips' => trans('toolbar Control', [], $domain, $lang), + 'props' => [ + 'url' => '/control/', + 'target' => '_blank' + ] + ]); + $config->useToolbar($toolbar->toArray()); + $userDropdownMenu = new Action(); + $userDropdownMenu->add(EnumAction::DIALOG['value'], [ + 'path' => 'Admin/updateSelf', + 'label' => trans('userDropdownMenu updateSelf', [], $domain, $lang), + 'icon' => 'User', + 'props' => [ + 'title' => trans('userDropdownMenu updateSelf', [], $domain, $lang) + ] + ]); + $config->useUserDropdownMenu($userDropdownMenu->toArray()); + $config->storage = Filesystem::getOptions(); + $pluginConfig = glob(base_path("plugin/*/api/{$request->app}/PublicController.php")); + foreach ($pluginConfig as $path) { + $plugin_name = basename(dirname(dirname(dirname($path)))); + $class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\PublicController"; + if (!class_exists($class)) { + continue; + } + $plugin = new $class; + if (method_exists($plugin, 'config')) { + $plugin->config($config); + } + } + $updateVersionContent = file_get_contents(base_path('update/VERSION')); + $updateVersionArr = explode("\n", $updateVersionContent); + $config->version_name = $updateVersionArr[1]; + $config->version = $updateVersionArr[0]; + return $this->resData($config); + } + public function menus(Request $request) + { + $Install = new \app\admin\api\Install; + $menus = new Menus($Install); + return $this->resData($menus); + } + public function outLogin(Request $request) + { + $token = $request->header('Authorization'); + if ($token) { + try { + Auth::setPrefix('ADMIN')->delete($token); + } catch (\Throwable $th) { + } + } + return $this->success(trans('Logout Success', [], $request->app, $request->lang)); + } + public function lock(Request $request) + { + try { + $password = $request->post('password'); + if (!$password) { + return $this->fail(trans('PIN Code Cannot Be Empty', [], $request->app, $request->lang)); + } + if (mb_strlen($password) != 6) { + return $this->fail(trans('Please Enter 6 Digits PIN Code', [], $request->app, $request->lang)); + } + $token = $request->header('Authorization'); + Auth::setPrefix('CONTROL')->lock($token, $password); + return $this->event(ResponseEvent::UPDATE_USERINFO, trans('Lock Success', [], $request->app, $request->lang)); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + public function unlock(Request $request) + { + $password = $request->post('password'); + try { + $token = $request->header('Authorization'); + Auth::setPrefix('CONTROL')->unlock($token, $password); + return $this->success(trans('Unlock Success', [], $request->app, $request->lang)); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + public function syncSql(Request $request) + { + $updateVersionContent = file_get_contents(base_path('update/VERSION')); + $updateVersionArr = explode("\n", $updateVersionContent); + $updateVersion = (int)trim($updateVersionArr[0]); + $versionContent = file_get_contents(base_path('VERSION')); + $versionArr = explode("\n", $versionContent); + $version = (int)trim($versionArr[0]); + if ($updateVersion >= $version) { + return $this->fail('已是最新版本'); + } + $sqlSum = 0; + $sqlSuccessSum = 0; + $sqlErrorSum = 0; + for ($i = $updateVersion + 1; $i <= $version; $i++) { + $sqlFile = glob(base_path("update/{$i}/*.sql")); + if (empty($sqlFile)) { + continue; + } + foreach ($sqlFile as $file) { + $sqlSum++; + Db::startTrans(); + try { + $sql = file_get_contents($file); + $prefix = config('thinkorm.connections.mysql.prefix'); + $sql = str_replace('`php_', "`$prefix", $sql); + $sql = str_replace('INTO php_', "INTO $prefix", $sql); + Db::execute($sql); + Db::commit(); + $sqlSuccessSum++; + } catch (\Throwable $th) { + Db::rollback(); + $sqlErrorSum++; + } + } + } + file_put_contents(base_path('update/VERSION'), $versionContent); + return $this->success('同步SQL成功,共执行' . $sqlSum . '条SQL,成功' . $sqlSuccessSum . '条,失败' . $sqlErrorSum . '条,已更新到:' . $versionArr[1]); + } +} diff --git a/fastmovie-admin/app/admin/controller/SystemController.php b/fastmovie-admin/app/admin/controller/SystemController.php new file mode 100644 index 0000000..ceffc8f --- /dev/null +++ b/fastmovie-admin/app/admin/controller/SystemController.php @@ -0,0 +1,228 @@ +builder(); + } + public function payment(Request $request) + { + return $this->builder(); + } + public function sms(Request $request) + { + return $this->tabsBuilder(); + } + public function upload(Request $request) + { + $request = request(); + if ($request->method() === 'POST') { + $D = $request->post(); + $config = []; + $config['default'] = $D['default']; + $config['max_size'] = (int)$D['max_size']; + $config['storage']['public']['url'] = $D['public']['url']; + $config['storage']['local']['url'] = $D['local']['url']; + foreach ($D['ftp'] as $key => $value) { + $config['storage']['ftp'][$key] = $value; + } + foreach ($D['s3'] as $key => $value) { + if (in_array($key, ['key', 'secret'])) { + $config['storage']['s3']['credentials'][$key] = $value; + } else { + $config['storage']['s3'][$key] = $value; + } + } + foreach ($D['minio'] as $key => $value) { + if (in_array($key, ['key', 'secret'])) { + $config['storage']['minio']['credentials'][$key] = $value; + } else { + $config['storage']['minio'][$key] = $value; + } + } + foreach ($D['oss'] as $key => $value) { + $config['storage']['oss'][$key] = $value; + } + foreach ($D['qiniu'] as $key => $value) { + $config['storage']['qiniu'][$key] = $value; + } + foreach ($D['cos'] as $key => $value) { + $config['storage']['cos'][$key] = $value; + } + $content = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $user = config_path("plugin/shopwwi/filesystem/app.json"); + $res = file_put_contents($user, $content); + if ($res) { + return $this->success('保存成功,重启服务生效'); + } + return $this->fail('保存失败'); + } + $config = config('plugin.shopwwi.filesystem.app'); + $builder = new FormBuilder(null, null, [ + 'translations'=>true, + 'submitEvent' => SubmitEvent::SILENT + ]); + $defaultOptions = Filesystem::getOptions(); + $builder->add('default', '默认存储渠道商', 'select', $config['default'], [ + 'options' => $defaultOptions + ]); + $builder->add('max_size', '单个文件大小(字节)', 'input-number', $config['max_size'], [ + 'props' => [ + 'min' => 0, + 'controls' => false, + 'style' => [ + 'width' => '200px' + ] + ] + ]); + $Component = new ComponentBuilder; + $subBuilder = new FormBuilder('public', '本地存储(外部)'); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['public']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('local', '本地存储(内部)'); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['local']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('ftp', 'FTP存储'); + $subBuilder->add('host', 'HOST', 'input', $config['storage']['ftp']['host']); + $subBuilder->add('username', 'UserName', 'input', $config['storage']['ftp']['username']); + $subBuilder->add('password', 'Password', 'input', $config['storage']['ftp']['password']); + $subBuilder->add('port', 'Port', 'input', $config['storage']['ftp']['port']); + $subBuilder->add('root', '目录', 'input', $config['storage']['ftp']['root']); + $subBuilder->add('passive', '被动模式', 'switch', $config['storage']['ftp']['passive']); + $subBuilder->add('ssl', 'SSL', 'switch', $config['storage']['ftp']['ssl']); + $subBuilder->add('timeout', '超时', 'input-number', $config['storage']['ftp']['timeout']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['ftp']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('s3', 'S3存储'); + $subBuilder->add('key', 'KEY', 'input', $config['storage']['s3']['credentials']['key']); + $subBuilder->add('secret', 'Secret', 'input', $config['storage']['s3']['credentials']['secret']); + $subBuilder->add('region', 'Region', 'input', $config['storage']['s3']['region']); + $subBuilder->add('version', 'Version', 'input', $config['storage']['s3']['version']); + $subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['s3']['bucket_endpoint']); + $subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['s3']['use_path_style_endpoint']); + $subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['s3']['endpoint']); + $subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['s3']['bucket_name']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['s3']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('minio', 'Minio存储'); + $subBuilder->add('key', 'KEY', 'input', $config['storage']['minio']['credentials']['key']); + $subBuilder->add('secret', 'Secret', 'input', $config['storage']['minio']['credentials']['secret']); + $subBuilder->add('region', 'Region', 'input', $config['storage']['minio']['region']); + $subBuilder->add('version', 'Version', 'input', $config['storage']['minio']['version']); + $subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['minio']['bucket_endpoint']); + $subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['minio']['use_path_style_endpoint']); + $subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['minio']['endpoint']); + $subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['minio']['bucket_name']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['minio']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('oss', '阿里云存储'); + $subBuilder->add('accessId', 'AccessId', 'input', $config['storage']['oss']['accessId']); + $subBuilder->add('accessSecret', 'AccessSecret', 'input', $config['storage']['oss']['accessSecret']); + $subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['oss']['bucket']); + $subBuilder->add('endpoint', 'Bucket域名', 'input', $config['storage']['oss']['endpoint']); + $subBuilder->add('isCName', '私有空间', 'switch', $config['storage']['oss']['isCName']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['oss']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('qiniu', '七牛存储'); + $subBuilder->add('accessKey', 'AccessKey', 'input', $config['storage']['qiniu']['accessKey']); + $subBuilder->add('secretKey', 'SecretKey', 'input', $config['storage']['qiniu']['secretKey']); + $subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['qiniu']['bucket']); + $subBuilder->add('domain', 'Domain', 'input', $config['storage']['qiniu']['domain']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['qiniu']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('cos', '腾讯云存储'); + $subBuilder->add('app_id', 'Appid', 'input', $config['storage']['cos']['app_id']); + $subBuilder->add('secret_id', 'SecretId', 'input', $config['storage']['cos']['secret_id']); + $subBuilder->add('secret_key', 'SecretKey', 'input', $config['storage']['cos']['secret_key']); + $subBuilder->add('region', 'Region', 'input', $config['storage']['cos']['region']); + $subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['cos']['bucket']); + $subBuilder->add('read_from_cdn', '从CDN读取', 'switch', $config['storage']['cos']['read_from_cdn']); + $subBuilder->add('signed_url', '私有空间', 'switch', $config['storage']['cos']['signed_url']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['cos']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + return $this->resData($builder); + } +} \ No newline at end of file diff --git a/fastmovie-admin/app/admin/controller/UploadsController.php b/fastmovie-admin/app/admin/controller/UploadsController.php new file mode 100644 index 0000000..eaff914 --- /dev/null +++ b/fastmovie-admin/app/admin/controller/UploadsController.php @@ -0,0 +1,16 @@ +admin_uid = $request->admin_uid; + } +} diff --git a/fastmovie-admin/app/admin/middleware/Auth.php b/fastmovie-admin/app/admin/middleware/Auth.php new file mode 100644 index 0000000..e29879f --- /dev/null +++ b/fastmovie-admin/app/admin/middleware/Auth.php @@ -0,0 +1,9 @@ +toArray()); + } + public function fastmovie(Request $request) + { + $config = new Config('basic'); + $templateFile = public_path('template/fastmovie.html'); + if (!file_exists($templateFile)) { + return '请检查 /public/template/fastmovie.html 是否存在。'; + } + return view($templateFile, $config->toArray()); + } + public function test(Request $request) + { + //当前连接 + $connect = $request->connection; + //设置连接头 + $connect->send(new Response(200, [ + 'Content-Type' => 'text/event-stream', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Credentials' => 'true', + 'Connection' => 'keep-alive', + ], "\r\n")); + $content = file_get_contents(runtime_path('story_stream_project_27_20251111_110715.txt')); + $streamData = explode("\r\n", $content); + foreach ($streamData as $line) { + if (empty($line)) { + continue; + } + $connect->send("id: " . uniqid() . "\n", true); + $connect->send($line . "\n\n", true); + } + $connect->send("data: [DONE]\n\n", true); + $connect->close(); + } +} diff --git a/fastmovie-admin/app/controller/NotifyController.php b/fastmovie-admin/app/controller/NotifyController.php new file mode 100644 index 0000000..16fd7a2 --- /dev/null +++ b/fastmovie-admin/app/controller/NotifyController.php @@ -0,0 +1,113 @@ +post(); + Log::error('微信支付回调1111', $data); + try { + $D = $request->post(); + if (PaymentNotifyWechat::where(['notify_id' => $D['id']])->count()) { + return $this->code('SUCCESS', '成功'); + } + $PaymentTemplate = PaymentTemplate::where(['id' => $template_id])->find(); + if (!$PaymentTemplate) { + throw new Exception('支付模板不存在'); + } + $Payment = $PaymentTemplate->value; + $config = [ + 'wechat' => [ + 'default' => [ + // 必填-商户号 + 'mch_id' => $Payment['mch_id'], + // 选填-v2商户私钥 + 'mch_secret_key_v2' => '', + // 必填-v3商户秘钥 + 'mch_secret_key' => $Payment['mch_key'], + // 必填-商户私钥 字符串或路径 + 'mch_secret_cert' => base_path(Uploads::path($Payment['ssl_key'])), + // 必填-商户公钥证书路径 + 'mch_public_cert_path' => base_path(Uploads::path($Payment['ssl_cert'])), + // 必填 + 'notify_url' => $Payment['notify_url'], + // 选填-公众号 的 app_id + 'mp_app_id' => $Payment['appid'], + // 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE + 'mode' => Pay::MODE_NORMAL, + ] + ], + 'logger' => [ + 'enable' => true, + 'file' => runtime_path('logs/wechat-notify-' . date('Y-m-d') . '.log'), + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + ], + ]; + $wxpay = Pay::wechat($config); + $data = $wxpay->callback($D); + $obj = null; + Log::error('微信支付回调222', (array)$data); + Db::startTrans(); + try { + $PaymentNotifyWechat = new PaymentNotifyWechat; + $PaymentNotifyWechat->notify_id = $D['id']; + $PaymentNotifyWechat->resource_type = $D['resource_type']; + $PaymentNotifyWechat->event_type = $D['event_type']; + $PaymentNotifyWechat->summary = $D['summary']; + $PaymentNotifyWechat->resource_original_type = $D['resource']['original_type']; + $PaymentNotifyWechat->resource_algorithm = $D['resource']['algorithm']; + $PaymentNotifyWechat->resource_ciphertext = $D['resource']['ciphertext']; + $PaymentNotifyWechat->resource_associated_data = $D['resource']['associated_data']; + $PaymentNotifyWechat->resource_nonce = $D['resource']['nonce']; + $PaymentNotifyWechat->plugin = $plugin; + $PaymentNotifyWechat->template_id = $template_id; + $PaymentNotifyWechat->save(); + $class = "plugin\\{$plugin}\\api\\notify\\Wechat"; + if (class_exists($class)) { + $obj = new $class; + $obj->{$data->resource['original_type']}($data->resource['ciphertext']); + } else { + Event::emit(EventName::ORDERS_PAY['value'], [ + 'data' => $data->resource['ciphertext'], + 'payment_channel' => PaymentChannels::WXPAY['value'], + 'plugin' => $plugin, + 'template_id' => $template_id + ]); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + if ($obj && method_exists($obj, 'finish')) { + $obj->finish(); + } + } catch (\Throwable $th) { + Log::error('微信支付回调错误:' . $th->getMessage(), $th->getTrace()); + return $this->json(['code' => 'FAIL', 'message' => $th->getMessage()], JSON_UNESCAPED_UNICODE, 400); + } + return $this->code('SUCCESS', '成功'); + } +} diff --git a/fastmovie-admin/app/controller/WechatOfficialAccountController.php b/fastmovie-admin/app/controller/WechatOfficialAccountController.php new file mode 100644 index 0000000..579adae --- /dev/null +++ b/fastmovie-admin/app/controller/WechatOfficialAccountController.php @@ -0,0 +1,32 @@ +checkSignature($request, $config); + if ($request->method() === 'GET') { + return $request->get('echostr'); + } + } catch (\Throwable $th) { + if ($request->method() === 'GET') { + return $th->getMessage(); + } else { + } + } + return $OfficialAccount->handle($request,$config); + } +} diff --git a/fastmovie-admin/app/exception/Handler.php b/fastmovie-admin/app/exception/Handler.php new file mode 100644 index 0000000..554dffb --- /dev/null +++ b/fastmovie-admin/app/exception/Handler.php @@ -0,0 +1,30 @@ +expectsJson()) { + return $this->exception($exception); + } + if (config('app.debug')) { + Log::error($exception->getTraceAsString()); + return parent::render($request, $exception); + } + return new Response(500, [], $exception->getMessage()); + } +} diff --git a/fastmovie-admin/app/expose/api/Install.php b/fastmovie-admin/app/expose/api/Install.php new file mode 100644 index 0000000..b44ab5f --- /dev/null +++ b/fastmovie-admin/app/expose/api/Install.php @@ -0,0 +1,76 @@ +path . '/../admin.json')) { + $content = file_get_contents($menu_file); + if (!$content) { + return false; + } + $data = json_decode($content, true); + return $data; + } + return false; + } + public function getPlugin() + { + return $this->plugin; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/ActionBuilder.php b/fastmovie-admin/app/expose/build/builder/ActionBuilder.php new file mode 100644 index 0000000..0cee755 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/ActionBuilder.php @@ -0,0 +1,72 @@ +data = [ + 'label' => $label, + 'extra' => [ + 'group' => [], + 'props' => $props, + ] + ]; + } + /** + * 向操作按钮中添加字段 + * + * @param string $label 操作按钮标题 + * @param mixed $extra 额外参数 + * @param string $extra.path 路径, 例如: '/app/pluginExample/admin/Form/index' + * @param string $extra.model 操作模式, 详见 \app\expose\enum\Action::class,例如:Action::COMFIRM['value'] + * @param mixed $extra.where 显示条件, 例如: [['is_system', '!=', 1]] + * @param mixed $extra.params 额外传参, 例如: [['id' => 1]] + * @param string|array $extra.field 指定提交字段, 例如: *或['id'=>'uid'],id:数据集中字段名,uid:提交数据中字段名 + * @param mixed $extra.props 操作属性, 例如: ['type' => 'primary', 'title' => '编辑《{nickname}》'] + * @param mixed $extra.component 操作展示组件 + * @param string $extra.component.name 组件名, 例如: 'button' + * @param mixed $extra.component.props 组件属性, 例如: ['type' => 'primary', 'size' => 'small'] + * @return ActionBuilder + */ + public function add(string $label, mixed $extra = []) + { + $this->data['extra']['group'][] = [ + 'label' => $label, + 'extra' => array_merge([ + 'loading' => false, + ], $extra) + ]; + return $this; + } + /** + * 设置数据 + * + * @param array $data 数据 + * @return ActionBuilder + */ + public function setData(array $data) + { + $this->data['data'] = $data; + return $this; + } + /** + * 设置数据操作 + * + * @param string $action 操作,append:追加合并数据 + * @return ActionBuilder + */ + public function setDataAction(string $action) + { + $this->data['callback'] = $action; + return $this; + } + public function builder() + { + return $this->data; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/AppsBuilder.php b/fastmovie-admin/app/expose/build/builder/AppsBuilder.php new file mode 100644 index 0000000..ae03708 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/AppsBuilder.php @@ -0,0 +1,48 @@ +data['props'] = array_merge([], $props); + $this->data['columns'] = []; + } + public function add(string $prop, string $label, mixed $extra = []) + { + $this->data['columns'][] = [ + 'prop' => $prop, + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addAction(string $label, mixed $extra = []) + { + $this->data['action'][] = [ + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addTabs(string $label, string $name, mixed $extra) + { + $this->data['tabs'][] = [ + 'label' => $label, + 'name' => $name, + 'extra' => $extra + ]; + return $this; + } + public function addScreen() + { + } + public function builder() + { + return $this->data; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/ComponentBuilder.php b/fastmovie-admin/app/expose/build/builder/ComponentBuilder.php new file mode 100644 index 0000000..792a252 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/ComponentBuilder.php @@ -0,0 +1,25 @@ +data[] = [ + 'component' => $component, + 'children' => $children, + 'props' => $props + ]; + return $this; + } + public function builder() + { + $result = $this->data; + $this->data = []; + return $result; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/DataboardBuilder.php b/fastmovie-admin/app/expose/build/builder/DataboardBuilder.php new file mode 100644 index 0000000..67fae9e --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/DataboardBuilder.php @@ -0,0 +1,27 @@ +data['props'] = array_merge([ + 'gutter' => 0 + ], $props); + $this->data['rows'] = []; + } + public function add(Basic $component, array $props = [], array $row = []) + { + $this->data['rows'][] = [ + 'props' => $props, + 'row' => $row, + 'component' => $component + ]; + return $this; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/ExamineBuilder.php b/fastmovie-admin/app/expose/build/builder/ExamineBuilder.php new file mode 100644 index 0000000..6781118 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/ExamineBuilder.php @@ -0,0 +1,117 @@ +translations=$props['translations']; + unset($props['translations']); + } + $this->data['props'] = $props; + $this->data['rule'] = []; + $this->data['form'] = []; + } + public function setTranslations(bool $state=true){ + $this->translations=$state; + return $this; + } + /** + * 向审核表单中添加字段 + * + * @param string $field 字段名 + * @param string $title 字段标题 + * @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库 + * @param mixed $extra 额外参数 + * @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]] + * @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容'] + * @param mixed $extra.subProps 子组件选项属性,和$extra.props类似 + * @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ] + * @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder + * @return ExamineBuilder + */ + public function add(string $field, string $title, string $component, mixed $extra = []): ExamineBuilder + { + $title = $this->trans($title); + $this->data['rule'][] = [ + 'field' => $field, + 'title' => $title, + 'component' => $component, + 'extra' => $extra + ]; + return $this; + } + /** + * 从审核表单中移除字段 + * + * @param string $field 字段名 + * @return ExamineBuilder + */ + public function remove(string $field): ExamineBuilder + { + $rule = array_filter($this->data['rule'], function ($item) use ($field) { + return $item['field'] != $field; + }); + $this->data['rule'] = array_values($rule); + return $this; + } + /** + * 设置审核原始对比数据 + * + * @param array $data 数据 + * @return ExamineBuilder + */ + public function setOldData(array $data): ExamineBuilder + { + $this->data['old']=$data; + return $this; + } + /** + * 设置审核最新对比数据 + * + * @param array $data 数据 + * @return ExamineBuilder + */ + public function setNewData(array $data): ExamineBuilder + { + $this->data['new']=$data; + return $this; + } + public function addForm(FormBuilder $builder) + { + $this->data['form']=$builder; + } + public function trans(string|null $val,array $parameters =[]) + { + if(!$val||!$this->translations){ + return $val; + } + $request=request(); + $lang = ''; + $domain=null; + if ($request && $request->lang) { + $lang = $request->lang; + $domain=$request->app; + } + return trans($val,$parameters,$domain,$lang); + } +} diff --git a/fastmovie-admin/app/expose/build/builder/FormBuilder.php b/fastmovie-admin/app/expose/build/builder/FormBuilder.php new file mode 100644 index 0000000..3154c51 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/FormBuilder.php @@ -0,0 +1,172 @@ +translations=$props['translations']; + unset($props['translations']); + } + $this->data['props'] = $props; + $this->data['name'] = $formName ?? 'basic'; + $this->data['title'] = $this->trans($formTitle) ?? $this->trans('form_basic_title'); + $this->data['rule'] = []; + $this->data['form'] = []; + $this->data['group'] = []; + $this->data['url'] = $this->url; + } + public function setTranslations(bool $state=true){ + $this->translations=$state; + return $this; + } + /** + * 向表单中添加字段 + * + * @param string $field 字段名 + * @param string $title 字段标题 + * @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库 + * @param mixed|null $value 默认值 + * @param mixed $extra 额外参数 + * @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]] + * @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容'] + * @param mixed $extra.subProps 子组件选项属性,和$extra.props类似 + * @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ] + * @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder + * @param ActionBuilder|null $action 操作按钮, 详见 \app\expose\build\builder\ActionBuilder + * @return FormBuilder + */ + public function add(string $field, string $title, string $component, mixed $value = null, mixed $extra = [],?ActionBuilder $action = null): FormBuilder + { + $title = $this->trans($title); + $this->data['rule'][] = [ + 'field' => $field, + 'title' => $title, + 'component' => $component, + 'extra' => $extra, + 'action' => $action ? $action->builder() : null + ]; + $this->addValue($field, $value); + return $this; + } + /** + * 从表单中移除字段 + * + * @param string $field 字段名 + * @return FormBuilder + */ + public function remove(string $field): FormBuilder + { + $rule = array_filter($this->data['rule'], function ($item) use ($field) { + return $item['field'] != $field; + }); + $this->data['rule'] = array_values($rule); + return $this; + } + /** + * 向表单中添加数据 + * + * @param string $field 字段名 + * @param mixed $value 默认值 + * @return FormBuilder + */ + public function addValue(string $field, mixed $value): FormBuilder + { + $this->data['form'][$field] = $value; + return $this; + } + /** + * 设置表单数据 + * + * @param array $data 数据 + * @return FormBuilder + */ + public function setData(array $data): FormBuilder + { + foreach ($data as $key => $value) { + $this->addValue($key, $value); + } + return $this; + } + /** + * 获取表单名 + * + * @return string + */ + public function getFormName(): string + { + return $this->data['name']; + } + /** + * 获取表单标题 + * + * @return string + */ + public function getFormTitle(): string + { + return $this->data['title']; + } + /** + * 向表单中添加分组 + * + * @param FormBuilder $builder 分组表单 + * @return FormBuilder + */ + public function addGroupForm(FormBuilder $builder): FormBuilder + { + $formName = $builder->getFormName(); + $temp = $builder->toArray(); + $this->data['group'][] = $temp; + $this->addValue($formName, $temp['form']); + return $this; + } + /** + * 设置表单提交地址 + * + * @param string $url 地址 + * @return FormBuilder + */ + public function setUrl(string $url): FormBuilder + { + $this->url = $url; + $this->data['url'] = $url; + return $this; + } + public function trans(string|null $val,array $parameters =[]) + { + if(!$val||!$this->translations){ + return $val; + } + $request=request(); + $lang = ''; + $domain=null; + if ($request && $request->lang) { + $lang = $request->lang; + $domain=$request->app; + } + return trans($val,$parameters,$domain,$lang); + } +} diff --git a/fastmovie-admin/app/expose/build/builder/ImagesBuilder.php b/fastmovie-admin/app/expose/build/builder/ImagesBuilder.php new file mode 100644 index 0000000..d0605b5 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/ImagesBuilder.php @@ -0,0 +1,96 @@ +data['props'] = array_merge([ + 'image'=>'url', + 'title'=>'title', + 'info'=>'info' + ], $props); + } + public function addSelection(string $prop = '', string $label = '', mixed $extra = []) + { + if (!isset($extra['props'])) { + $extra['props'] = [ + 'width' => '50px', + 'type' => 'selection' + ]; + } + $extra['props']['type'] = 'selection'; + $this->data['selection'] = true; + return $this; + } + public function addAction(string|ActionBuilder $label, mixed $extra = []) + { + if ($label instanceof ActionBuilder) { + $this->data['action'] = $label->builder(); + return $this; + } + if (!isset($this->data['action'])) { + $this->data['action'] = []; + } + $this->data['action'][] = [ + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addFooter(mixed $props = []) + { + if (!isset($this->data['selection'])) { + $this->addSelection(); + } + $this->data['footer'] = [ + 'extra' => [ + 'group' => [], + 'props' => $props, + ] + ]; + return $this; + } + public function addFooterAction(string $label, mixed $extra = []) + { + $this->data['footer']['extra']['group'][] = [ + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addHeader(array|ActionBuilder $props = []) + { + if ($props instanceof ActionBuilder) { + $this->data['header'] = $props->builder(); + return $this; + } + $this->data['header'] = [ + 'extra' => [ + 'group' => [], + 'props' => $props, + ] + ]; + return $this; + } + public function addHeaderAction(string $label, mixed $extra = []) + { + $this->data['header']['extra']['group'][] = [ + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addScreen(FormBuilder $builder) + { + $this->data['screen'] = $builder; + } + public function builder() + { + return $this->data; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/InfoBuilder.php b/fastmovie-admin/app/expose/build/builder/InfoBuilder.php new file mode 100644 index 0000000..386b5b6 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/InfoBuilder.php @@ -0,0 +1,154 @@ +translations = $props['translations']; + unset($props['translations']); + } + $this->data['props'] = $props; + $this->data['name'] = $formName ?? 'basic'; + $this->data['title'] = $this->trans($formTitle) ?? $this->trans('form_basic_title'); + $this->data['rule'] = []; + $this->data['group'] = []; + $this->data['data'] = []; + } + public function setTranslations(bool $state = true) + { + $this->translations = $state; + return $this; + } + /** + * 向信息展示中添加字段 + * + * @param string $field 字段名 + * @param string $title 字段标题 + * @param string $component 组件名,ElementUI不含"el-"前缀组件名,例如: input,自定义组件:bundle:附件库 + * @param mixed $extra 额外参数 + * @param mixed $extra.options 子选项数据, 例如: [['label' => '选项1', 'value' => 1], ['label' => '选项2', 'value' => 2]] + * @param mixed $extra.props 组件属性, 例如: ['placeholder' => '请输入内容'] + * @param mixed $extra.subProps 子组件选项属性,和$extra.props类似 + * @param mixed $extra.children 插槽内容,['default' => '内容']或['default' => ComponentBuilder ] + * @param mixed $extra.prompt 提示信息, 详见 \app\expose\build\builder\ComponentBuilder + * @param ActionBuilder|null $action 操作按钮, 详见 \app\expose\build\builder\ActionBuilder + * @return InfoBuilder + */ + public function add(string $field, string $title, string $component, mixed $extra = [], ?ActionBuilder $action = null): InfoBuilder + { + $title = $this->trans($title); + $this->data['rule'][] = [ + 'field' => $field, + 'title' => $title, + 'component' => $component, + 'extra' => $extra, + 'action' => $action ? $action->builder() : null + ]; + return $this; + } + /** + * 从信息展示中移除字段 + * + * @param string $field 字段名 + * @return InfoBuilder + */ + public function remove(string $field): InfoBuilder + { + $rule = array_filter($this->data['rule'], function ($item) use ($field) { + return $item['field'] != $field; + }); + $this->data['rule'] = array_values($rule); + return $this; + } + /** + * 向表单中添加数据 + * + * @param string $field 字段名 + * @param mixed $value 默认值 + * @return InfoBuilder + */ + public function addValue(string $field, mixed $value): InfoBuilder + { + $this->data['data'][$field] = $value; + return $this; + } + /** + * 设置表单数据 + * + * @param array $data 数据 + * @return InfoBuilder + */ + public function setData(array $data): InfoBuilder + { + foreach ($data as $key => $value) { + $this->addValue($key, $value); + } + return $this; + } + /** + * 获取表单名 + * + * @return string + */ + public function getInfoName(): string + { + return $this->data['name']; + } + /** + * 获取表单标题 + * + * @return string + */ + public function getInfoTitle(): string + { + return $this->data['title']; + } + /** + * 向表单中添加分组 + * + * @param InfoBuilder $builder 分组表单 + * @return InfoBuilder + */ + public function addGroupInfo(InfoBuilder $builder): InfoBuilder + { + $infoName = $builder->getInfoName(); + $temp = $builder->toArray(); + $this->data['group'][] = $temp; + $this->addValue($infoName, $temp['data']); + return $this; + } + public function trans(string|null $val, array $parameters = []) + { + if (!$val || !$this->translations) { + return $val; + } + $request = request(); + $lang = ''; + $domain = null; + if ($request && $request->lang) { + $lang = $request->lang; + $domain = $request->app; + } + return trans($val, $parameters, $domain, $lang); + } +} diff --git a/fastmovie-admin/app/expose/build/builder/RechargeBuilder.php b/fastmovie-admin/app/expose/build/builder/RechargeBuilder.php new file mode 100644 index 0000000..1936a42 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/RechargeBuilder.php @@ -0,0 +1,60 @@ +data['props'] = array_merge([], $props); + $this->data['list'] = []; + $this->data['prompt']=[]; + } + public function addPayment(mixed $data) + { + $this->data['payment']=$data; + return $this; + } + public function addList(mixed $data) + { + $this->data['list']=$data; + return $this; + } + public function addQrcodePrompt(ComponentBuilder $builder) + { + $this->data['prompt']['qrcode'][] = $builder->builder(); + return $this; + } + public function addAgreementPrompt(ComponentBuilder $builder) + { + $this->data['prompt']['agreement'][] = $builder->builder(); + return $this; + } + public function addFooterPrompt(ComponentBuilder $builder) + { + $this->data['prompt']['footer'][] = $builder->builder(); + return $this; + } + public function addSwiperPrompt(ComponentBuilder $builder) + { + $this->data['prompt']['swiper'][] = $builder->builder(); + return $this; + } + public function setCreateOrdersApi(string $api) + { + $this->data['create_orders']=$api; + return $this; + } + public function setGetOrdersStateApi(string $api) + { + $this->data['get_orders_state']=$api; + return $this; + } + public function builder() + { + return $this->data; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/SoltBuilder.php b/fastmovie-admin/app/expose/build/builder/SoltBuilder.php new file mode 100644 index 0000000..2864fb0 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/SoltBuilder.php @@ -0,0 +1,31 @@ +name = $name; + } + public static function solt(string $solt = 'default') + { + $self = new self($solt); + return $self; + } + public function text(string $text) + { + return [$this->name => $text]; + } + public function add(mixed $component, mixed $props = [], mixed $children = null) + { + return [ + $this->name => [ + 'component' => $component, + 'props' => $props, + 'children' => $children + ] + ]; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/TableBuilder.php b/fastmovie-admin/app/expose/build/builder/TableBuilder.php new file mode 100644 index 0000000..59cf524 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/TableBuilder.php @@ -0,0 +1,118 @@ +data['props'] = array_merge([ + 'border' => true, + 'stripe' => true, + 'size' => 'large', + 'header-row-style' => [ + '--el-table-header-bg-color' => 'var(--el-bg-color-page)' + ] + ], $props); + $this->data['columns'] = []; + } + public function add(string $prop, string $label, mixed $extra = []) + { + $this->data['columns'][] = [ + 'prop' => $prop, + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addSelection(string $prop = '', string $label = '', mixed $extra = []) + { + if (!isset($extra['props'])) { + $extra['props'] = [ + 'width' => '50px', + 'type' => 'selection' + ]; + } + $extra['props']['type'] = 'selection'; + $this->add($prop, $label, $extra); + $this->data['selection'] = true; + return $this; + } + public function addAction(string|ActionBuilder $label, mixed $props = []) + { + if ($label instanceof ActionBuilder) { + $this->data['action'] = $label->builder(); + return $this; + } + $this->data['action'] = [ + 'label' => $label, + 'extra' => [ + 'group' => [], + 'props' => $props, + ] + ]; + return $this; + } + public function addTableAction(string $label, mixed $extra = []) + { + $this->data['action']['extra']['group'][] = [ + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addFooter(mixed $props = []) + { + if (!isset($this->data['selection'])) { + $this->addSelection(); + } + $this->data['footer'] = [ + 'extra' => [ + 'group' => [], + 'props' => $props, + ] + ]; + return $this; + } + public function addFooterAction(string $label, mixed $extra = []) + { + $this->data['footer']['extra']['group'][] = [ + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addHeader(array|ActionBuilder $props = []) + { + if ($props instanceof ActionBuilder) { + $this->data['header'] = $props->builder(); + return $this; + } + $this->data['header'] = [ + 'extra' => [ + 'group' => [], + 'props' => $props, + ] + ]; + return $this; + } + public function addHeaderAction(string $label, mixed $extra = []) + { + $this->data['header']['extra']['group'][] = [ + 'label' => $label, + 'extra' => $extra + ]; + return $this; + } + public function addScreen(FormBuilder $builder) + { + $this->data['screen'] = $builder; + } + public function builder() + { + return $this->data; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/databoardBuilder/Basic.php b/fastmovie-admin/app/expose/build/builder/databoardBuilder/Basic.php new file mode 100644 index 0000000..cf35814 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/databoardBuilder/Basic.php @@ -0,0 +1,9 @@ +data['name'] = 'apply'; + $this->data['props'] = []; + } + public function __call($name, $arguments) + { + $name = lcfirst(str_replace('set', '', $name)); + $name = Str::snake($name, '_'); + $this->data['props'][$name] = $arguments[0]; + return $this; + } + /** + * 设置数据 + * + * @param mixed $data + * @return Apply + */ + public function setData(mixed $data): Apply + { + $this->data['props']['data'] = $data; + return $this; + } + public function setApi(string $api): Apply + { + $this->data['props']['api'] = $api; + return $this; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Domain.php b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Domain.php new file mode 100644 index 0000000..63d4af9 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Domain.php @@ -0,0 +1,50 @@ +data['name'] = 'domain'; + $this->data['props'] = []; + } + public function __call($name, $arguments) + { + $name = lcfirst(str_replace('set', '', $name)); + $name = Str::snake($name, '_'); + $this->data['props'][$name] = $arguments[0]; + return $this; + } + /** + * 设置数据 + * + * @param mixed $data + * @return Domain + */ + public function setData(mixed $data): Domain + { + $this->data['props']['data'] = $data; + return $this; + } + public function setApi(string $api): Domain + { + $this->data['props']['api'] = $api; + return $this; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Echarts.php b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Echarts.php new file mode 100644 index 0000000..a0d781b --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Echarts.php @@ -0,0 +1,40 @@ +data['name'] = 'echarts'; + $this->data['props'] = []; + } + public function __call($name, $arguments) + { + $name = lcfirst(str_replace('set', '', $name)); + $name = Str::snake($name, '_'); + $this->data['props'][$name] = $arguments[0]; + return $this; + } + /** + * 设置数据 + * + * @param mixed $data + * @return echarts + */ + public function setData(mixed $data): echarts + { + $this->data['props']['data'] = $data; + return $this; + } + public function setApi(string $api): echarts + { + $this->data['props']['api'] = $api; + return $this; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Placeholder.php b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Placeholder.php new file mode 100644 index 0000000..5f991e1 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Placeholder.php @@ -0,0 +1,33 @@ +data['name'] = 'div'; + $this->data['props'] = []; + } + public function __call($name, $arguments) + { + $name = lcfirst(str_replace('set', '', $name)); + $name = Str::snake($name, '_'); + $this->data['props'][$name] = $arguments[0]; + return $this; + } + public function setData(mixed $data): Placeholder + { + $this->data['props']['data'] = $data; + return $this; + } + public function setApi(string $api): Placeholder + { + $this->data['props']['api'] = $api; + return $this; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Statistic.php b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Statistic.php new file mode 100644 index 0000000..8361c40 --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/databoardBuilder/component/Statistic.php @@ -0,0 +1,56 @@ +data['name'] = 'statistic'; + $this->data['props'] = [ + 'yesterday_label'=>'昨日', + ]; + } + public function __call($name, $arguments) + { + $name = lcfirst(str_replace('set', '', $name)); + $name = Str::snake($name, '_'); + $this->data['props'][$name] = $arguments[0]; + return $this; + } + /** + * 设置数据 + * + * @param mixed $data + * @param number $data.today 今日数据 + * @param number $data.yesterday 昨日数据 + * @param number $data.growth_rate 增长率 + * @return statistic + */ + public function setData(mixed $data): statistic + { + $this->data['props']['data'] = $data; + return $this; + } + public function setApi(string $api): statistic + { + $this->data['props']['api'] = $api; + return $this; + } +} diff --git a/fastmovie-admin/app/expose/build/builder/databoardBuilder/interface/DataboardBuilderInterface.php b/fastmovie-admin/app/expose/build/builder/databoardBuilder/interface/DataboardBuilderInterface.php new file mode 100644 index 0000000..22f361c --- /dev/null +++ b/fastmovie-admin/app/expose/build/builder/databoardBuilder/interface/DataboardBuilderInterface.php @@ -0,0 +1,11 @@ +data = array_merge($this->data, $data); + } + /** + * 添加一个操作 + * + * @param string $model 查看 \app\expose\enum\Action::class, 例如 \app\expose\enum\Action::LOCK['value'] + * @param array $props ['icon' => 'Lock','label' => '锁屏'] + * @return Action + */ + public function add($model, $props = []): Action + { + $this->data[] = [ + 'model' => $model, + 'props' => $props + ]; + return $this; + } +} diff --git a/fastmovie-admin/app/expose/build/config/Apis.php b/fastmovie-admin/app/expose/build/config/Apis.php new file mode 100644 index 0000000..19c5e4f --- /dev/null +++ b/fastmovie-admin/app/expose/build/config/Apis.php @@ -0,0 +1,16 @@ + 'User/getUserInfo', + ]; + public function __construct(array $data = []) + { + $this->data = array_merge($this->data, $data); + } +} diff --git a/fastmovie-admin/app/expose/build/config/Login.php b/fastmovie-admin/app/expose/build/config/Login.php new file mode 100644 index 0000000..ae6caf5 --- /dev/null +++ b/fastmovie-admin/app/expose/build/config/Login.php @@ -0,0 +1,38 @@ + true, + # 登录标题 + 'title' => '账号密码登录', + # 登录请求地址 + 'url' => 'Login/login', + # 检测登录状态地址 + 'check' => 'Login/check', + # 登录页背景,auto:自动,off:关闭,url:自定义,url[]:多张图片 + 'bg_image' => 'auto', + # 登录页展示图片 + 'image' => '', + # 开启图形验证码,在短信登录时默认开启 + 'captcha' => true, + # 跳转链接文案 + 'link_text' => '', + # 用户协议链接 + 'user_agreement' => '/article/user_agreement.html', + # 用户协议自定义配置 + /* 'user_agreement_config'=>[ + 'title'=>'《用户协议》', + 'label'=>'点击"登录"即代表你已阅读并同意' + ] */ + ]; + public function __construct(array $data = []) + { + $this->data = array_merge($this->data, $data); + } +} diff --git a/fastmovie-admin/app/expose/build/config/Web.php b/fastmovie-admin/app/expose/build/config/Web.php new file mode 100644 index 0000000..a51daa8 --- /dev/null +++ b/fastmovie-admin/app/expose/build/config/Web.php @@ -0,0 +1,90 @@ + 'Loong', + # LOGO展示方式 + 'logo_use' => 'svg', + # 应用浅色LOGO + 'web_logo_light' => '', + # 应用深色LOGO + 'web_logo_dark' => '', + # ICP备案 + 'web_icp' => '', + # 公安备案号 + 'web_mps' => '', + # 公安备案文案 + 'web_mps_text' => '', + # 网站标题 + 'web_title' => '', + # 版本名称 + 'version_name' => 'v1.0.0', + # 版本号 + 'version' => 10000, + # 版权信息 + 'copyright' => 'copyright © 2020 RenLoong All Rights Reserved', + # 微信客服链接 + 'wechat_url' => '', + # 客服二维码 + 'wechat_qrcode_url' => '', + # 页面布局,control: 控制台布局,admin: 后台布局 + 'layout' => 'control', + ]; + public function __construct(array $data = []) + { + $request = request(); + $this->data['layout'] = $request->app; + $this->data = array_merge($this->data, $data); + } + public function useLogin(array $data = []) + { + $this->data['login'] = new Login($data); + return $this; + } + public function useVcode(array $data = []) + { + $this->data['vcode'] = new Login($data); + return $this; + } + public function useRegister(array $data = []) + { + $this->data['register'] = new Login($data); + return $this; + } + public function useQrcodeLogin(array $data = []) + { + $this->data['qrcode_login'] = new Login($data); + return $this; + } + public function useApis(array $data = []) + { + $apis = new Apis($data); + if (!isset($this->data['apis'])) { + $this->data['apis'] = []; + } + $this->data['apis'] = array_merge($this->data['apis'], $apis->toArray()); + return $this; + } + public function useToolbar(array $data = []) + { + if (!isset($this->data['toolbar'])) { + $this->data['toolbar'] = []; + } + $this->data['toolbar'] = array_merge($this->data['toolbar'], $data); + return $this; + } + public function useUserDropdownMenu(array $data = []) + { + if (!isset($this->data['user_dropdown_menu'])) { + $this->data['user_dropdown_menu'] = []; + } + $this->data['user_dropdown_menu'] = array_merge($this->data['user_dropdown_menu'], $data); + return $this; + } +} diff --git a/fastmovie-admin/app/expose/enum/Action.php b/fastmovie-admin/app/expose/enum/Action.php new file mode 100644 index 0000000..717d6a3 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/Action.php @@ -0,0 +1,53 @@ + '页面跳转', + 'value' => '' + ]; + const REDIRECT = [ + 'label' => '重定向', + 'value' => 'redirect' + ]; + const LINK= [ + 'label' => '链接跳转', + 'value' => 'link' + ]; + const COMFIRM = [ + 'label' => '确认操作', + 'value' => 'comfirm' + ]; + const DIALOG = [ + 'label' => '弹窗操作', + 'value' => 'dialog' + ]; + const REQUEST = [ + 'label' => '请求操作', + 'value' => 'request' + ]; + const LOCK = [ + 'label' => '锁屏', + 'value' => 'Lock' + ]; + const SEARCH = [ + 'label' => '搜索', + 'value' => 'Search' + ]; + const NOTIFICATION = [ + 'label' => '通知', + 'value' => 'Notification' + ]; + const FULL_SCREEN = [ + 'label' => '全屏', + 'value' => 'FullScreen' + ]; + const OUT_LOGIN = [ + 'label' => '退出登录', + 'value' => 'OutLogin' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/AlipayRsaModel.php b/fastmovie-admin/app/expose/enum/AlipayRsaModel.php new file mode 100644 index 0000000..492e9a1 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/AlipayRsaModel.php @@ -0,0 +1,17 @@ + '公钥证书', + 'value' => 'public_cert', + ]; + const PUBLIC_KEY = [ + 'label' => '公钥', + 'value' => 'public_key', + ]; +} diff --git a/fastmovie-admin/app/expose/enum/EventName.php b/fastmovie-admin/app/expose/enum/EventName.php new file mode 100644 index 0000000..9b46cc0 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/EventName.php @@ -0,0 +1,114 @@ + '注册', + 'value' => 'USER.REGISTER', + 'props' => [ + 'type' => 'primary' + ] + ]; + const USER_LOGIN = [ + 'label' => '登录', + 'value' => 'USER.LOGIN', + 'props' => [ + 'type' => 'success' + ] + ]; + const USER_LOGOUT = [ + 'label' => '退出', + 'value' => 'USER.LOGOUT', + 'props' => [ + 'type' => 'warning' + ] + ]; + const USER_UPDATE = [ + 'label' => '更新用户信息', + 'value' => 'USER.UPDATE', + 'props' => [ + 'type' => 'info' + ] + ]; + const ORDERS_CREATE = [ + 'label' => '创建订单', + 'value' => 'ORDERS.CREATE', + 'props' => [ + 'type' => 'info' + ] + ]; + const ORDERS_PAY = [ + 'label' => '支付订单', + 'value' => 'ORDERS.PAY', + 'props' => [ + 'type' => 'danger' + ] + ]; + const ORDERS_PAY_SUCCESS = [ + 'label' => '支付成功', + 'value' => 'ORDERS.PAY_SUCCESS', + 'props' => [ + 'type' => 'success' + ] + ]; + const ORDERS_CANCEL = [ + 'label' => '取消订单', + 'value' => 'ORDERS.CANCEL', + 'props' => [ + 'type' => 'info' + ] + ]; + const ORDERS_REFUND = [ + 'label' => '退款订单', + 'value' => 'ORDERS.REFUND', + 'props' => [ + 'type' => 'warning' + ] + ]; + const ORDERS_FINISH = [ + 'label' => '完成订单', + 'value' => 'ORDERS.FINISH', + 'props' => [ + 'type' => 'success' + ] + ]; + const WECHAT_OFFICIAL_ACCOUNT_SCAN = [ + 'label' => '微信公众号扫码', + 'value' => 'WECHAT_OFFICIAL_ACCOUNT.SCAN', + 'props' => [ + 'type' => 'primary' + ] + ]; + const WECHAT_OFFICIAL_ACCOUNT_SUBSCRLBE=[ + 'label' => '微信公众号关注', + 'value' => 'WECHAT_OFFICIAL_ACCOUNT.SUBSCRLBE', + 'props' => [ + 'type' => 'primary' + ] + ]; + const WECHAT_OFFICIAL_ACCOUNT_UNSUBSCRLBE=[ + 'label' => '微信公众号取关', + 'value' => 'WECHAT_OFFICIAL_ACCOUNT.UNSUBSCRLBE', + 'props' => [ + 'type' => 'primary' + ] + ]; + const NOTIFICATION_LIMIT= [ + 'label' => '有限通知', + 'value' => 'NOTIFICATION.LIMIT', + 'props' => [ + 'type' => 'info' + ] + ]; + const NOTIFICATION_UNLIMIT= [ + 'label' => '无限通知', + 'value' => 'NOTIFICATION.UNLIMIT', + 'props' => [ + 'type' => 'info' + ] + ]; +} diff --git a/fastmovie-admin/app/expose/enum/Examine.php b/fastmovie-admin/app/expose/enum/Examine.php new file mode 100644 index 0000000..5042e2b --- /dev/null +++ b/fastmovie-admin/app/expose/enum/Examine.php @@ -0,0 +1,30 @@ + '待审核', + 'value' => 0, + 'props'=>[ + 'type'=>'primary' + ] + ]; + const PASS = [ + 'label' => '通过', + 'value' => 1, + 'props'=>[ + 'type'=>'success' + ] + ]; + const REJECT = [ + 'label' => '驳回', + 'value' => 2, + 'props'=>[ + 'type'=>'danger' + ] + ]; +} diff --git a/fastmovie-admin/app/expose/enum/Filesystem.php b/fastmovie-admin/app/expose/enum/Filesystem.php new file mode 100644 index 0000000..751b355 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/Filesystem.php @@ -0,0 +1,41 @@ + '本地存储(外部)', + 'value' => 'public' + ]; + const LOCAL = [ + 'label' => '本地存储(内部)', + 'value' => 'local' + ]; + const OSS = [ + 'label' => '阿里云存储', + 'value' => 'oss' + ]; + const COS = [ + 'label' => '腾讯云存储', + 'value' => 'cos' + ]; + const QINIU = [ + 'label' => '七牛存储', + 'value' => 'qiniu' + ]; + const FTP = [ + 'label' => 'FTP存储', + 'value' => 'ftp' + ]; + const S3 = [ + 'label' => 'S3存储', + 'value' => 's3' + ]; + const MINIO = [ + 'label' => 'Minio存储', + 'value' => 'minio' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/Methods.php b/fastmovie-admin/app/expose/enum/Methods.php new file mode 100644 index 0000000..f9ec228 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/Methods.php @@ -0,0 +1,29 @@ + 'GET', + 'value' => 'GET' + ]; + const POST = [ + 'label' => 'POST', + 'value' => 'POST' + ]; + const PUT = [ + 'label' => 'PUT', + 'value' => 'PUT' + ]; + const DELETE = [ + 'label' => 'DELETE', + 'value' => 'DELETE' + ]; + const OPTIONS = [ + 'label' => 'OPTIONS', + 'value' => 'OPTIONS' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/PaymentChannels.php b/fastmovie-admin/app/expose/enum/PaymentChannels.php new file mode 100644 index 0000000..9294621 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/PaymentChannels.php @@ -0,0 +1,73 @@ + '微信支付', + 'value' => 'wxpay', + 'icon' => '/static/image/wxpay.png', + 'props' => [ + 'type' => 'success' + ] + ]; + const ALIPAY = [ + 'label' => '支付宝支付', + 'value' => 'alipay', + 'icon' => '/static/image/alipay.png', + 'props' => [ + 'type' => 'primary' + ] + ]; + const BALANCE = [ + 'label' => '余额支付', + 'value' => 'balance', + 'icon' => '/static/image/balance.png', + 'props' => [ + 'type' => 'info' + ] + ]; + const INTEGRAL = [ + 'label' => '积分支付', + 'value' => 'integral', + 'icon' => '/static/image/integral.png', + 'props' => [ + 'type' => 'warning' + ] + ]; + const CASH = [ + 'label' => '现金支付', + 'value' => 'cash', + 'icon' => '/static/image/cash.png', + 'props' => [ + 'type' => 'danger' + ] + ]; + const COUPON = [ + 'label' => '优惠券支付', + 'value' => 'coupon', + 'icon' => '/static/image/coupon.png', + 'props' => [ + 'type' => 'info' + ] + ]; + const FREE = [ + 'label' => '免费', + 'value' => 'free', + 'icon' => '/static/image/free.png', + 'props' => [ + 'type' => 'success' + ] + ]; + const ADMIN = [ + 'label' => '管理员操作', + 'value' => 'admin', + 'icon' => '/static/image/admin.png', + 'props' => [ + 'type' => 'warning' + ] + ]; +} diff --git a/fastmovie-admin/app/expose/enum/Permission.php b/fastmovie-admin/app/expose/enum/Permission.php new file mode 100644 index 0000000..68c283c --- /dev/null +++ b/fastmovie-admin/app/expose/enum/Permission.php @@ -0,0 +1,25 @@ + '创建', + 'value' => 'Create' + ]; + const DELETE = [ + 'label' => '删除', + 'value' => 'Delete' + ]; + const UPDATE = [ + 'label' => '更新', + 'value' => 'Update' + ]; + const UPDATE_STATE = [ + 'label' => '更新状态', + 'value' => 'UpdateState' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/Platform.php b/fastmovie-admin/app/expose/enum/Platform.php new file mode 100644 index 0000000..e6aa33c --- /dev/null +++ b/fastmovie-admin/app/expose/enum/Platform.php @@ -0,0 +1,29 @@ + 'PC', + 'value' => 'pc' + ]; + const H5 = [ + 'label' => 'H5', + 'value' => 'h5' + ]; + const WECHAT_MINIAPP = [ + 'label' => '微信小程序', + 'value' => 'wechat_miniapp' + ]; + const WECHAT_OFFICIAL_ACCOUNT = [ + 'label' => '微信公众号', + 'value' => 'wechat_official_account' + ]; + const APP = [ + 'label' => 'APP', + 'value' => 'app' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/ResponseCode.php b/fastmovie-admin/app/expose/enum/ResponseCode.php new file mode 100644 index 0000000..59b7a78 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/ResponseCode.php @@ -0,0 +1,86 @@ + '阿里云短信', + 'value' => 'aliyun' + ]; + const TENCENT = [ + 'label' => '腾讯云短信', + 'value' => 'tencent' + ]; + const SMSBAO = [ + 'label' => '短信宝', + 'value' => 'smsbao' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/State.php b/fastmovie-admin/app/expose/enum/State.php new file mode 100644 index 0000000..ce13395 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/State.php @@ -0,0 +1,17 @@ + '正常', + 'value' => 1 + ]; + const NO = [ + 'label' => '禁用', + 'value' => 0 + ]; +} diff --git a/fastmovie-admin/app/expose/enum/SubmitEvent.php b/fastmovie-admin/app/expose/enum/SubmitEvent.php new file mode 100644 index 0000000..0d0ee32 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/SubmitEvent.php @@ -0,0 +1,18 @@ + '周日', + 'value' => 0, + 'props' => [ + 'type' => 'info' + ] + ]; + const MONDAY = [ + 'label' => '周一', + 'value' => 1, + 'props' => [ + 'type' => 'success' + ] + ]; + const TUESDAY = [ + 'label' => '周二', + 'value' => 2, + 'props' => [ + 'type' => 'success' + ] + ]; + const WEDNESDAY = [ + 'label' => '周三', + 'value' => 3, + 'props' => [ + 'type' => 'success' + ] + ]; + const THURSDAY = [ + 'label' => '周四', + 'value' => 4, + 'props' => [ + 'type' => 'success' + ] + ]; + const FRIDAY = [ + 'label' => '周五', + 'value' => 5, + 'props' => [ + 'type' => 'success' + ] + ]; + const SATURDAY = [ + 'label' => '周六', + 'value' => 6, + 'props' => [ + 'type' => 'info' + ] + ]; +} diff --git a/fastmovie-admin/app/expose/enum/WxpayMchType.php b/fastmovie-admin/app/expose/enum/WxpayMchType.php new file mode 100644 index 0000000..380ebb8 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/WxpayMchType.php @@ -0,0 +1,17 @@ + '普通商户', + 'value' => 'normal' + ]; + const SERVICE = [ + 'label' => '子商户 (服务商模式)', + 'value' => 'service' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/WxpayVersion.php b/fastmovie-admin/app/expose/enum/WxpayVersion.php new file mode 100644 index 0000000..a04f7c8 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/WxpayVersion.php @@ -0,0 +1,17 @@ + 'V3', + 'value' => 'v3' + ]; + const V2 = [ + 'label' => 'V2', + 'value' => 'v2' + ]; +} diff --git a/fastmovie-admin/app/expose/enum/builder/Enum.php b/fastmovie-admin/app/expose/enum/builder/Enum.php new file mode 100644 index 0000000..98f2058 --- /dev/null +++ b/fastmovie-admin/app/expose/enum/builder/Enum.php @@ -0,0 +1,100 @@ +getConstants() as $key => $value) { + if ($filter) { + if ($filter($value)) { + if (is_array($value)) { + $options[] = $value; + } else { + $options[] = [ + 'label' => $key, + 'value' => $value + ]; + } + } + } else { + if (is_array($value)) { + $options[] = $value; + } else { + $options[] = [ + 'label' => $key, + 'value' => $value + ]; + } + } + } + return $options; + } + /** + * 获取枚举文本 + * @param mixed $value 值 + * @return string 文本 + */ + public static function getText($value) + { + $options = static::getOptions(); + foreach ($options as $option) { + if ($option['value'] == $value) { + return $option['label']; + } + } + return ''; + } + /** + * 获取枚举选项 + * @param mixed $value 值 + * @return array|null 选项 + */ + public static function get($value, $key = null) + { + $options = static::getOptions(); + foreach ($options as $option) { + if ($option['value'] == $value) { + return array_key_exists($key, $option) ? $option[$key] : $option; + } + } + return null; + } + /** + * 获取枚举值 + * @param callable|null $filter 过滤函数 + * @return array 值数组 + */ + public static function getValues(?callable $filter = null) + { + $options = static::getOptions($filter); + $values = []; + foreach ($options as $option) { + $values[] = $option['value']; + } + return $values; + } + /** + * 判断是否存在 + * @param mixed $value 值 + * @return bool 是否存在 + */ + public static function has($value) + { + $options = static::getOptions(); + foreach ($options as $option) { + if ($option['value'] == $value) { + return true; + } + } + return false; + } +} diff --git a/fastmovie-admin/app/expose/exception/Exception.php b/fastmovie-admin/app/expose/exception/Exception.php new file mode 100644 index 0000000..831fbfe --- /dev/null +++ b/fastmovie-admin/app/expose/exception/Exception.php @@ -0,0 +1,19 @@ +data = $data; + } + + public function getData() + { + return $this->data; + } +} \ No newline at end of file diff --git a/fastmovie-admin/app/expose/helper/Captcha.php b/fastmovie-admin/app/expose/helper/Captcha.php new file mode 100644 index 0000000..943d820 --- /dev/null +++ b/fastmovie-admin/app/expose/helper/Captcha.php @@ -0,0 +1,109 @@ +sessionId($token); + } + $captchaData = $request->session()->get('captcha'); + if (!$captchaData) { + throw new Exception('图像验证码不存在'); + } + if ($captchaData['expire'] < time()) { + throw new Exception('图像验证码已过期'); + } + // 对比session中的captcha值 + if (strtolower($captcha) !== $captchaData['captcha']) { + throw new Exception('图像验证码不正确'); + } + return true; + } + public static function create() + { + $request = request(); + $bg = $request->get('bg'); + $length = $request->get('length', 4); + if ($length < 4) { + $length = 4; + } + if ($length > 6) { + $length = 6; + } + $defaultBg = '255,255,255'; + if ($bg) { + $defaultBg = $bg; + } + $width = (int)$request->get('w', 150); + $height = (int)$request->get('h', 40); + // 初始化验证码类 + $builder = new CaptchaBuilder((int)$length); + $bgArr = explode(',', $defaultBg); + $builder->setBackgroundColor($bgArr[0], $bgArr[1], $bgArr[2]); + $builder->setDistortion(false); + $builder->setInterpolation(false); + $builder->setTextColor(255 - $bgArr[0], 255 - $bgArr[1], 255 - $bgArr[2]); + // 生成验证码 + $builder->build($width, $height); + return $builder; + } + + /** + * 生成图像验证码 + * @return Response + */ + public static function captcha(): Response + { + $request = request(); + $builder = self::create(); + $request->session()->set('captcha', [ + 'captcha' => strtolower($builder->getPhrase()), + 'expire' => time() + 60 * 5 + ]); + $img_content = $builder->inline(); + return response($img_content, 200); + } + + + /** + * 图像验证码 + */ + public static function captchaCode(): array + { + $request = request(); + $builder = self::create(); + $request->session()->set('captcha', [ + 'captcha' => strtolower($builder->getPhrase()), + 'expire' => time() + 60 * 5 + ]); + $img_content = $builder->inline(); + return ['base64' => $img_content, 'token' => $request->sessionId()]; + } + + + + +} diff --git a/fastmovie-admin/app/expose/helper/Config.php b/fastmovie-admin/app/expose/helper/Config.php new file mode 100644 index 0000000..360af55 --- /dev/null +++ b/fastmovie-admin/app/expose/helper/Config.php @@ -0,0 +1,142 @@ +plugin; + } + if ($request && $request->channels_uid && $channels_uid === null) { + $this->channels_uid = $request->channels_uid; + } elseif ($channels_uid) { + $this->channels_uid = $channels_uid; + } + $this->group = $plugin ? $plugin . '.' . $group : $group; + $this->configData = config('settings'); + $this->groupData = $this->configData[$this->group] ?? []; + $this->builder(); + } + /** + * 获取当前应用指定配置,如需获取全局配置请new Config('group',''); + * + * @param string $group 配置分组,settings目录下的文件名 + * @param string|null $field 获取自定字段配置 + * @param mixed $default 默认数据 + * @return array|mixed + */ + public static function get(string $group, string|null $field = null, mixed $default = null) + { + $self = new self($group); + if ($field) { + return isset($self->{$field}) ? $self->{$field} : $default; + } + return $self->toArray(); + } + public function builder() + { + $d = []; + $request = request(); + $domain = ''; + $host = ''; + if ($request) { + $domain = $request->header('x-forwarded-proto') . '://' . $request->host(); + $host = $request->host(); + } + $replaceData = [ + '{DOMAIN}' => $domain, + '{HOST}' => $host, + '{CHANNELS_UID}' => $this->channels_uid + ]; + if (!empty($this->groupData)) { + foreach ($this->groupData as $key => $item) { + $has = false; + if (is_string($item['value']) && strpos($item['value'], '{DOMAIN}') !== false && $domain) { + $has = true; + } elseif (is_string($item['value']) && strpos($item['value'], '{HOST}') !== false && $host) { + $has = true; + } elseif (is_string($item['value']) && strpos($item['value'], '{CHANNELS_UID}') !== false && $this->channels_uid) { + $has = true; + } + if ($has) { + $d[$item['field']] = str_replace(array_keys($replaceData), array_values($replaceData), $item['value']); + } else { + $d[$item['field']] = $item['value']; + } + } + } + $ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find(); + if ($ConfigModel) { + foreach ($ConfigModel->value as $field => $value) { + $has = false; + if (is_string($value) && strpos($value, '{DOMAIN}') !== false && $domain) { + $has = true; + } elseif (is_string($value) && strpos($value, '{HOST}') !== false && $host) { + $has = true; + } elseif (is_string($value) && strpos($value, '{CHANNELS_UID}') !== false && $this->channels_uid) { + $has = true; + } + if ($has) { + $d[$field] = str_replace(array_keys($replaceData), array_values($replaceData), $value); + } else { + $d[$field] = $value; + } + } + } + $this->data = $d; + } + public function getGroupData() + { + return $this->groupData; + } + public function getGrop() + { + return $this->group; + } + public static function set($group, $field, $value) + { + $self = new self($group); + $self->{$field} = $value; + $ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find(); + if (!$ConfigModel) { + $ConfigModel = new ModelConfig; + $ConfigModel->group = $group; + $ConfigModel->channels_uid = $self->channels_uid; + } + $ConfigModel->value = $self->toArray(); + $ConfigModel->save(); + } + public static function formBuilder($group, $plugin = null, $channels_uid = null) + { + $self = new self($group, $plugin, $channels_uid); + $builder = new FormBuilder(null, null, [ + 'submitEvent' => SubmitEvent::SILENT + ]); + $builder->setTranslations(); + $groupData = $self->getGroupData(); + foreach ($groupData as $key => $item) { + $builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']); + } + $builder->setData($self->toArray()); + return $builder; + } +} diff --git a/fastmovie-admin/app/expose/helper/ConfigGroup.php b/fastmovie-admin/app/expose/helper/ConfigGroup.php new file mode 100644 index 0000000..cc9c93f --- /dev/null +++ b/fastmovie-admin/app/expose/helper/ConfigGroup.php @@ -0,0 +1,110 @@ +plugin; + } + if ($request && $request->channels_uid && $channels_uid === null) { + $this->channels_uid = $request->channels_uid; + } elseif ($channels_uid) { + $this->channels_uid = $channels_uid; + } + $this->group = $plugin ? $plugin . '.' . $group : $group; + $this->configData = config('settings-tabs'); + $this->groupData = $this->configData[$this->group] ?? []; + $this->builder(); + } + public function builder() + { + $d = []; + if (!empty($this->groupData)) { + foreach ($this->groupData as $key => $item) { + if ($key != 'group') { + $d[$item['field']] = $item['value']; + } else { + foreach ($item as $group) { + $d[$group['name']] = []; + foreach ($group['children'] as $child) { + $d[$group['name']][$child['field']] = $child['value']; + } + } + } + } + $ConfigModel = ModelConfig::where(['group' => $this->group, 'channels_uid' => $this->channels_uid])->find(); + if ($ConfigModel) { + foreach ($ConfigModel->value as $field => $value) { + $d[$field] = $value; + } + } + } + $this->data = $d; + } + public function getGroupData() + { + return $this->groupData; + } + public function getGrop() + { + return $this->group; + } + public static function get($group, $field = null, $default = null) + { + $self = new self($group); + if ($field) { + return isset($self->{$field}) ? $self->{$field} : $default; + } + return $self->toArray(); + } + public static function set($group, $field, $value) + { + $self = new self($group); + $self->{$field} = $value; + $ConfigModel = ModelConfig::where(['group' => $group, 'channels_uid' => $self->channels_uid])->find(); + if (!$ConfigModel) { + $ConfigModel = new ModelConfig; + $ConfigModel->group = $group; + $ConfigModel->channels_uid = $self->channels_uid; + } + $ConfigModel->value = $self->toArray(); + $ConfigModel->save(); + } + public static function formBuilder($group, $plugin = null, $channels_uid = null) + { + $self = new self($group, $plugin, $channels_uid); + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'submitEvent' => SubmitEvent::SILENT + ]); + $groupData = $self->getGroupData(); + foreach ($groupData as $key => $item) { + if ($key != 'group') { + $builder->add($item['field'], $item['title'], $item['component'], $item['value'], $item['extra']); + } + } + foreach ($groupData['group'] as $key => $item) { + $subBuilder = new FormBuilder($item['name'], $item['title']); + foreach ($item['children'] as $child) { + $subBuilder->add($child['field'], $child['title'], $child['component'], $child['value'], $child['extra']); + } + $builder->addGroupForm($subBuilder); + } + $builder->setData($self->toArray()); + return $builder; + } +} diff --git a/fastmovie-admin/app/expose/helper/Menus.php b/fastmovie-admin/app/expose/helper/Menus.php new file mode 100644 index 0000000..5d32746 --- /dev/null +++ b/fastmovie-admin/app/expose/helper/Menus.php @@ -0,0 +1,116 @@ + "Title", + # 菜单图标,element-plus中的Icon名称,如:ElementPlus + "icon" => "", + # 路由路径 + "path" => "", + # 使用组件,table:表格列表,form:表单,images:图片列表 + "component" => "default", + # 请求方式,GET,POST,PUT,DELETE + "methods" => ["GET"], + # 是否显示,0:隐藏,1:显示 + "show" => 1, + # 排序,正序 + "sort" => 9999, + # 携带地址栏参数 + "query" => [], + # 携带post参数 + "params" => [], + # 路由meta + "meta" => [ + "login" => true, + ], + # 子级和父级参数一致 + "children" => [] + ]; + /** + * 构造函数 + * + * @param object $Install 需实现 getMenus 方法的类返回菜单数据 + */ + public function __construct($Install) + { + $request = request(); + $lang = null; + if ($request && $request->lang) { + $lang = $request->lang; + } + $data = $Install->getMenus(); + if ($lang) { + $this->translateChildren($data, $request->app,$lang); + } + foreach (glob(base_path('plugin/*')) as $path) { + $plugin_name = basename($path); + $class = 'plugin\\' . $plugin_name . '\\api\\Install'; + $plugin = new $class; + $menus = $plugin->getMenus(); + if ($menus) { + if ($lang&&$plugin->getPlugin()) { + $request->plugin = $plugin_name; + $this->translateChildren($menus, $request->app,$lang); + } + $data[] = $menus; + } + } + $this->builder($data); + } + public function translateChildren(&$data, $domain,$lang) + { + if (is_array($data) && isset($data[0])) { + foreach ($data as $key => &$item) { + if (isset($item['title'])) { + $data[$key]['title'] = trans($item['title'], [], $domain, $lang); + } + if (!empty($item['children'])) { + $this->translateChildren($item['children'],$domain, $lang); + } + } + } else { + if (isset($data['title'])) { + $data['title'] = trans($data['title'], [], $domain, $lang); + } + if (!empty($data['children'])) { + $this->translateChildren($data['children'],$domain, $lang); + } + } + } + /** + * 递归合并 + * + * @param array $data + * @return $data + */ + public function mergeMenus(array $data): array + { + $result = []; + foreach ($data as $key => $item) { + $temp = array_merge($this->itemData, $item); + if (!empty($temp['children'])) { + $temp['children'] = $this->mergeMenus($temp['children']); + } + $result[] = $temp; + } + return $result; + } + /** + * 构建菜单 + * + * @param array $data + * @return Menus + */ + public function builder(array $data) + { + $this->data = $this->mergeMenus($data); + return $this; + } +} diff --git a/fastmovie-admin/app/expose/helper/Payment.php b/fastmovie-admin/app/expose/helper/Payment.php new file mode 100644 index 0000000..a377190 --- /dev/null +++ b/fastmovie-admin/app/expose/helper/Payment.php @@ -0,0 +1,188 @@ +find(); + if (!$PaymentConfig) { + throw new Exception('支付配置不存在'); + } + $PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id, 'channels_uid' => $channels_uid])->find(); + if (!$PaymentTemplate) { + throw new Exception('支付模板不存在'); + } + return $PaymentTemplate->value; + } + public static function platform(int|null $channels_uid = null) + { + $platform = request()->platform; + $where = []; + $where[] = ['platform', '=', $platform]; + $where[] = ['state', '=', State::YES['value']]; + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $PaymentConfig = PaymentConfig::where($where)->select(); + $data = []; + foreach ($PaymentConfig as $item) { + $enum = PaymentChannels::get($item->channels); + $temp = [ + 'id' => $item->id, + 'label' => $enum['label'], + 'enum' => $enum, + ]; + if ($item->is_default == State::YES['value']) { + $temp['default'] = 1; + } + $data[] = $temp; + } + return $data; + } + public static function getIntegralName() + { + $integral = '积分'; + try { + $platform = request()->platform; + if (!$platform) { + $platform = Platform::PC['value']; + } + $Payment = self::get($platform, PaymentChannels::INTEGRAL['value']); + if (isset($Payment['display_name'])) { + $integral = $Payment['display_name']; + } + } catch (\Throwable $th) { + } + return $integral; + } + public static function createPayment($model, $id) + { + $PaymentConfig = PaymentConfig::where(['id' => $id])->find(); + if (!$PaymentConfig) { + throw new Exception('支付配置不存在'); + } + switch ($PaymentConfig->channels) { + case PaymentChannels::WXPAY['value']: + return self::wxPay($model, $PaymentConfig); + case PaymentChannels::ALIPAY['value']: + break; + case PaymentChannels::INTEGRAL['value']: + break; + case PaymentChannels::BALANCE['value']: + break; + } + throw new \Exception('未知支付方式'); + } + public static function wxPay($model, $PaymentConfig) + { + switch ($PaymentConfig->platform) { + case Platform::PC['value']: + return self::wxPayPc($model, $PaymentConfig); + case Platform::H5['value']: + return self::wxPayH5($model, $PaymentConfig); + case Platform::WECHAT_OFFICIAL_ACCOUNT['value']: + return self::wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig); + case Platform::APP['value']: + return self::wxPayApp($model, $PaymentConfig); + case Platform::WECHAT_MINIAPP['value']: + return self::wxPayMiniapp($model, $PaymentConfig); + } + } + public static function wxPayPc($model, $PaymentConfig) + { + $PaymentTemplate = PaymentTemplate::where(['id' => $PaymentConfig->template_id])->find(); + if (!$PaymentTemplate) { + throw new Exception('支付模板不存在'); + } + $Payment = $PaymentTemplate->value; + $notify_url = $Payment['notify_url']; + # 判断是否为“/”结尾 + if (substr($notify_url, -1) == '/') { + $notify_url = substr($notify_url, 0, -1); + } + $notify_url .= '/notify/wechat/' . $model->plugin . '/' . $PaymentTemplate->id; + $mch_secret_cert = null; + if ($PaymentTemplate->channels_uid) { + $mch_secret_cert = Uploads::downloadTemp($Payment['ssl_key'], 'mch_secret_cert_' . $PaymentTemplate->id); + } else { + $mch_secret_cert = base_path(HelperUploads::path($Payment['ssl_key'])); + } + $mch_public_cert_path = null; + if ($PaymentTemplate->channels_uid) { + $mch_public_cert_path = Uploads::downloadTemp($Payment['ssl_cert'], 'mch_public_cert_path_' . $PaymentTemplate->id); + } else { + $mch_public_cert_path = base_path(HelperUploads::path($Payment['ssl_cert'])); + } + $config = [ + 'wechat' => [ + 'default' => [ + // 必填-商户号 + 'mch_id' => $Payment['mch_id'], + // 选填-v2商户私钥 + 'mch_secret_key_v2' => '', + // 必填-v3商户秘钥 + 'mch_secret_key' => $Payment['mch_key'], + // 必填-商户私钥 字符串或路径 + 'mch_secret_cert' => $mch_secret_cert, + // 必填-商户公钥证书路径 + 'mch_public_cert_path' => $mch_public_cert_path, + // 必填 + 'notify_url' => $notify_url, + // 选填-公众号 的 app_id + 'mp_app_id' => $Payment['appid'], + // 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE + 'mode' => Pay::MODE_NORMAL, + ] + ], + 'logger' => [ + 'enable' => true, + 'file' => runtime_path('logs/wechat-pay-' . date('Y-m-d') . '.log'), + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + ], + ]; + $order = [ + 'out_trade_no' => $model->trade, + 'amount' => [ + 'total' => getenv('DEV') === 'true' ? 1 : $model->price * 100, + ], + 'description' => $model->title, + 'time_expire' => date('c', strtotime($model->expire_time)), + ]; + Log::info('wxPayPc', ['order' => $order, 'config' => $config]); + $Pay = Pay::wechat($config)->scan($order); + return [ + 'plugin' => $model->plugin, + 'trade' => $model->trade, + 'qrcode' => $Pay->code_url, + ]; + } + public static function wxPayH5($model, $PaymentConfig) {} + public static function wxPayOFFICIAL_ACCOUNT($model, $PaymentConfig) {} + public static function wxPayApp($model, $PaymentConfig) {} + public static function wxPayMiniapp($model, $PaymentConfig) {} +} diff --git a/fastmovie-admin/app/expose/helper/Uploads.php b/fastmovie-admin/app/expose/helper/Uploads.php new file mode 100644 index 0000000..8ba02a3 --- /dev/null +++ b/fastmovie-admin/app/expose/helper/Uploads.php @@ -0,0 +1,368 @@ +channels = $key; + $model->path = substr($path, strpos($path, ']') + 1); + }else{ + $model = ModelUploads::where(['path' => $path])->find(); + if (!$model) { + return $path; + } + } + return Storage::adapter($model->channels)->url($model->path); + } + /** + * 获取文件在本地存储的完整路径 + * @param string|array|null $path 文件路径 + * @return string|array + */ + public static function local(string|array|null $path) + { + if (empty($path)) { + return $path; + } + if (is_array($path)) { + $data = []; + foreach ($path as $value) { + $data[] = self::local($value); + } + return $data; + } + if (strpos($path, '[') === 0) { + $key = substr($path, 1, strpos($path, ']') - 1); + $model = new \stdClass; + $model->channels = $key; + $model->path = substr($path, strpos($path, ']') + 1); + }else{ + $model = ModelUploads::where(['path' => $path])->find(); + if (!$model) { + return $path; + } + } + $filesystem = FilesystemFactory::get($model->channels); + $has = $filesystem->has($model->path); + if ($has) { + if (in_array($model->channels, [Filesystem::LOCAL['value'], Filesystem::PUBLIC['value']])) { + $config = config('plugin.shopwwi.filesystem.app.storage.' . $model->channels); + return $config['root'] . $model->path; + } else { + return self::downloadTemp(Storage::adapter($model->channels)->url($model->path)); + } + } + return $path; + } + /** + * 获取文件路径 + * @param string|array|null $url URL地址 + * @return string|array + */ + public static function path(string|array|null $url) + { + if (empty($url)) { + return ''; + } + if (is_array($url)) { + $data = []; + if (count($url) === 1) { + return self::path(current($url)); + } + $config = config('plugin.shopwwi.filesystem.app.storage'); + $urls = []; + foreach ($config as $key => $value) { + if (empty($value['url'])) { + continue; + } + $urls[$key] = $value['url']; + } + foreach ($url as $value) { + if (filter_var($value, FILTER_SANITIZE_URL) === false) { + throw new Exception('URL地址不合法'); + } + $parseUrl = parse_url($value); + $domain = $parseUrl['scheme'] . '://' . $parseUrl['host']; + $key = array_search($domain, $urls); + if ($key) { + $data[] = '[' . $key . ']' . ltrim($parseUrl['path'], '/'); + } else { + $data[] = ltrim($parseUrl['path'], '/'); + } + } + return $data; + } else { + if (filter_var($url, FILTER_SANITIZE_URL) === false) { + throw new Exception('URL地址不合法'); + } + $parseUrl = parse_url($url); + $config = config('plugin.shopwwi.filesystem.app.storage'); + $urls = []; + foreach ($config as $key => $value) { + if (empty($value['url'])) { + continue; + } + $urls[$key] = $value['url']; + } + $domain = $parseUrl['scheme'] . '://' . $parseUrl['host']; + $key = array_search($domain, $urls); + if ($key) { + $data = '[' . $key . ']' . ltrim($parseUrl['path'], '/'); + } else { + $data = ltrim($parseUrl['path'], '/'); + } + return $data; + } + } + /** + * 保存文件 + * @param string $path 文件路径 + * @param string $channels 文件存储通道 + * @param string $dir_name 文件存储目录 + * @param string $title 文件分类标题 + * @return array + */ + public static function save(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save', $title = '本地保存') + { + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels])->find(); + if (!$UploadsClassify) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = $title; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = $channels; + $UploadsClassify->sort = 0; + $UploadsClassify->is_system = 1; + $UploadsClassify->save(); + } + $date_path = date('Ymd'); + $originName = basename($path); + //单文件上传 + $file = new UploadFile($path, $originName, mime_content_type($path), filesize($path)); + $result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + $Uploads = new ModelUploads; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->save(); + return [ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]; + } + /** + * 远程下载文件 + * @param string $url 文件URL + * @param string $channels 文件存储通道 + * @return string + */ + public static function download(string $url, $channels = Filesystem::PUBLIC['value']) + { + $dir_name = 'uploads/remote'; + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'is_system' => 1])->find(); + if (!$UploadsClassify) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = '远程下载'; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = $channels; + $UploadsClassify->sort = 0; + $UploadsClassify->is_system = 1; + $UploadsClassify->save(); + } + $date_path = date('Ymd'); + $client = new Client(); + $response = $client->get($url); + $body = $response->getBody(); + $file = $body->getContents(); + $urlPath = parse_url($url, PHP_URL_PATH); + $ext = pathinfo($urlPath, PATHINFO_EXTENSION); + $fileName = uniqid() . '.' . $ext; + $temp = tempnam(sys_get_temp_dir(), '') . $fileName; + file_put_contents($temp, $file); + $file = new UploadFile($temp, $temp, $response->getHeaderLine('Content-Type'), 0); + $result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + \unlink($temp); + $Uploads = new ModelUploads; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->save(); + return $result->file_name; + } + /** + * 下载文件到临时文件 + * @param string $url 文件URL + * @return string 临时文件路径 + */ + public static function downloadTemp(string $url) + { + $client = new Client(); + $response = $client->get($url); + $body = $response->getBody(); + $file = $body->getContents(); + $urlPath = parse_url($url, PHP_URL_PATH); + $ext = pathinfo($urlPath, PATHINFO_EXTENSION); + $fileName = uniqid() . '.' . $ext; + $temp = runtime_path('temp/' . $fileName); + file_put_contents($temp, $file); + return $temp; + } + /** + * 上传文件 + * @param string $path 文件路径 + * @param string $channels 文件存储通道 + * @param string $dir_name 文件存储目录 + * @return array + */ + public static function upload(string $path, $channels = Filesystem::PUBLIC['value'], $dir_name = 'uploads/save') + { + $date_path = date('Ymd'); + $originName = basename($path); + //单文件上传 + $file = new UploadFile($path, $originName, mime_content_type($path), filesize($path)); + $result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + $Uploads = new ModelUploads; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->save(); + return [ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]; + } + /** + * 删除文件 + * @param string $path 文件路径 + * @return bool + */ + public static function delete(string $path) + { + $model = ModelUploads::where(['path' => $path])->find(); + if (!$model) { + return true; + } + $filesystem = FilesystemFactory::get($model->channels); + $filesystem->delete($model->path); + $model->delete(); + return true; + } + /** + * 重命名文件 + * @param string $path 文件路径 + * @param string $newName 新文件名 + * @return bool + */ + public static function rename(string $path, string $newName) + { + $model = ModelUploads::where(['path' => $path])->find(); + if (!$model) { + return false; + } + $filesystem = FilesystemFactory::get($model->channels); + $filesystem->move($model->path, $newName); + $model->path = $newName; + $model->save(); + return true; + } + /** + * 判断文件是否存在 + * @param string $path 文件路径 + * @return bool + */ + public static function has(string $path) + { + $model = ModelUploads::where(['path' => $path])->find(); + if (!$model) { + return false; + } + $filesystem = FilesystemFactory::get($model->channels); + return $filesystem->has($model->path); + } + /** + * 复制文件 + * @param string $path 文件路径 + * @param string $newName 新文件名 + * @return bool + */ + public static function copy(string $path, string $newName) + { + $model = ModelUploads::where(['path' => $path])->find(); + if (!$model) { + return false; + } + $filesystem = FilesystemFactory::get($model->channels); + $filesystem->copy($model->path, $newName); + $model = new ModelUploads; + $model->classify_id = $model->classify_id; + $model->filename = $model->filename; + $model->path = $newName; + $model->ext = $model->ext; + $model->mime = $model->mime; + $model->size = $model->size; + $model->path = $newName; + $model->save(); + return true; + } + /** + * 获取文件列表 + * @param string $path 文件路径 + * @param bool $recursive 是否递归 + * @return array + */ + public static function listContents(string $path, $recursive = false) + { + $model = ModelUploads::where(['path' => $path])->find(); + if (!$model) { + return false; + } + $filesystem = FilesystemFactory::get($model->channels); + return $filesystem->listContents($path, $recursive); + } +} diff --git a/fastmovie-admin/app/expose/helper/Vcode.php b/fastmovie-admin/app/expose/helper/Vcode.php new file mode 100644 index 0000000..7128e03 --- /dev/null +++ b/fastmovie-admin/app/expose/helper/Vcode.php @@ -0,0 +1,79 @@ +sessionId($token); + } + $vcodeData = $request->session()->get('vcode:' . $scene . ':' . $username); + if (empty($vcodeData)) { + throw new \Exception('验证码不存在'); + } + if ($vcodeData['vcode'] != $vcode) { + throw new \Exception('验证码错误'); + } + if ($vcodeData['expire'] < time()) { + throw new \Exception('验证码已过期'); + } + return true; + } + public static function send($username, $scene, $token = null) + { + $request = request(); + if (!empty($token)) { + $request->sessionId($token); + } + $vcode = Str::random(6, 1); + if (filter_var($username, FILTER_VALIDATE_EMAIL)) { + $Email = new Email; + $Email->toemail = $username; + $Email->setTemplate(EmailVcode::class); + $Email->setData(['vcode' => $vcode]); + $Email->send(); + } else { + $request->session()->set('vcode:' . $scene . ':' . $username, [ + 'vcode' => $vcode, + 'expire' => time() + 60 * 5 + ]); + $config = new ConfigGroup('sms', ''); + foreach ($config['channels'] as $channel) { + $item = $config[$channel]; + if ($item['enable'] == State::NO['value']) { + continue; + } + try { + if (empty($item['vcode_template_' . $scene])) { + continue; + } + $SmsVcode = new SmsVcode(); + $SmsVcode->channel = $channel; + $SmsVcode->config = $item; + $SmsVcode->template_code = $item['vcode_template_' . $scene]; + $Sms = new Sms; + $Sms->mobile = $username; + $Sms->setTemplate($SmsVcode); + $Sms->setData(['code' => $vcode]); + $Sms->send(); + return true; + } catch (\Throwable $th) { + Log::error("发送短信失败:" . $th->getMessage(), $th->getTrace()); + continue; + } + } + throw new \Exception('发送失败'); + } + } +} diff --git a/fastmovie-admin/app/expose/helper/Wechat.php b/fastmovie-admin/app/expose/helper/Wechat.php new file mode 100644 index 0000000..0a5ed35 --- /dev/null +++ b/fastmovie-admin/app/expose/helper/Wechat.php @@ -0,0 +1,126 @@ +get($url); + $data = json_decode($response->getBody()->getContents(), true); + if (isset($data['errcode'])) { + throw new \Exception($data['errmsg']); + } + Redis::set('wechat_official_account_access_token', $data['access_token'], 'EX', $data['expires_in']); + return $data['access_token']; + } catch (\Throwable $th) { + throw $th; + } + } + /** + * 创建公众号二维码 + * + * @param string $action_name QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值 + * @param int $expire 二维码有效时间,以秒为单位。最大不超过2592000(即30天) + * @param array $eventData 回调事件数据 + * @param Class $eventData[0] 回调事件类 + * @param string $eventData[1] 回调事件方法 + * @param array $eventData[2] 回调事件参数,会在回调事件方法中$data.params原样传入 + * @return array $data + * @return string $data['url'] 二维码图片地址 + * @return string $data['id'] 二维码id,在回调事件中$data['EventKey'] + * @return int $data['expire_seconds'] 二维码有效时间 + * @throws \Exception + */ + public static function createQrCode($action_name, $expire, $eventData) + { + $request = request(); + $id = $request->sessionId(); + if(count($eventData)<2){ + throw new \Exception('回调事件数据不完整'); + } + $access_token = self::getAccessToken(); + $url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $access_token; + $client = new Client(); + $data = [ + 'expire_seconds' => $expire, + 'action_name' => $action_name, + 'action_info' => [ + 'scene' => [] + ] + ]; + if (in_array($action_name, [self::QR_SCENE, self::QR_LIMIT_SCENE])) { + $data['action_info']['scene'] = ['scene_id' => $id]; + } else { + $data['action_info']['scene'] = ['scene_str' => $id]; + } + Redis::set($id, json_encode($eventData, JSON_UNESCAPED_UNICODE), 'EX', $expire + 60); + $response = $client->post($url, [ + 'json' => $data + ]); + $res = json_decode($response->getBody()->getContents(), true); + if (isset($res['errcode'])) { + throw new \Exception($res['errmsg']); + } + $res['id'] = $id; + return $res; + } + /** + * 发送模板消息 + * + * @param mixed $params 模板消息参数 + * @return string + */ + public static function sendTemplate($params) + { + $access_token = self::getAccessToken(); + $url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $access_token; + $client = new Client([ + 'content_type' => 'application/json', + ]); + $response = $client->post($url, ['body' => json_encode($params, JSON_UNESCAPED_UNICODE)]); + $data = json_decode($response->getBody()->getContents(), true); + if (empty($data['msgid'])) { + throw new \Exception("[{$data['errcode']}]" . $data['errmsg']); + } + return $data['msgid']; + } +} diff --git a/fastmovie-admin/app/expose/middleware/AdminAuth.php b/fastmovie-admin/app/expose/middleware/AdminAuth.php new file mode 100644 index 0000000..4388059 --- /dev/null +++ b/fastmovie-admin/app/expose/middleware/AdminAuth.php @@ -0,0 +1,142 @@ +method() == 'OPTIONS') { + return response('', 204); + } + # 微信浏览器 + $this->isWehcatBrowser($request); + // 鉴权检测 + try { + $this->Authorization($request); + $response = $next($request); + } catch (\Throwable $th) { + if ($request->expectsJson()) { + $response = $this->exception($th); + } else { + if (config('app.debug')) { + Log::error($th->getTraceAsString()); + throw $th; + } else { + $response = response($th->getMessage(), 500); + } + } + } + return $response; + } + public function isWehcatBrowser($request) + { + $request->isWehcatBrowser = strpos(strtolower($request->header('user-agent') ?? ''), 'micromessenger') !== false; + } + + /** + * 业务逻辑 + * @author 贵州猿创科技有限公司 + * @Email 416716328@qq.com + * @DateTime 2023-03-11 + */ + public function Authorization(Request $request) + { + # 无控制器地址 + if (!$request->controller) { + return true; + } + # 获取控制器鉴权信息 + $controller = new \ReflectionClass($request->controller); + $properties = $controller->getDefaultProperties(); + # 无需登录方法 + $notNeedLogin = $properties['notNeedLogin'] ?? []; + # 无需鉴权方法 + $notNeedAuth = $properties['notNeedAuth'] ?? []; + # 是否强制登录 + $isForceLogin = true; + if (in_array($request->action, $notNeedLogin)) { + $isForceLogin = false; + } + try { + # 令牌验证 + $token = $request->header('Authorization'); + if (!$token) { + throw new Exception('请先登录', ResponseCode::NEED_LOGIN); + } + $user = Auth::setPrefix('ADMIN')->decrypt($token); + if (!$user) { + throw new TokenExpireException(); + } + $request->admin_uid = $user['uid']; + } catch (TokenExpireException $e) { + if ($isForceLogin) { + throw new Exception('登录已过期,请重新登录', ResponseCode::NEED_LOGIN); + } + } catch (LockException $e) { + if ($isForceLogin) { + throw new Exception($e->getMessage(), ResponseCode::LOCK); + } + } catch (\Throwable $th) { + if ($isForceLogin) { + throw new \Exception($th->getMessage(), ResponseCode::NEED_LOGIN); + } + } + if ($request->admin_uid) { + if ($user['is_system']) { + return true; + } else { + $now = date('H:i:s'); + if (!($now >= $user['allow_time_start'] && $now <= $user['allow_time_end'])) { + throw new Exception("当前不在工作时间"); + } + if (!in_array(date('w'), $user['allow_week'])) { + throw new Exception("今日因该是休息日哦"); + } + $Admin = Admin::where('id', $user['uid'])->find(); + if ($Admin->state != State::YES['value']) { + throw new Exception("您的账号已被禁用", ResponseCode::NEED_LOGIN); + } + } + } + # 不需要鉴权 + if (in_array($request->action, $notNeedAuth)) { + return true; + } + if (empty($user)) { + throw new Exception('请先登录', ResponseCode::NEED_LOGIN); + } + if (empty($user['permissions'])) { + throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION); + } + $plugin = $request->plugin; + // plugin\api\app\admin\controller\IndexController,只取Index + $controllers = explode('\\', $request->controller); + $controller = str_replace('Controller', '', end($controllers)); + $action = str_replace(['GetTable', 'GetGrid'], ['', ''], $request->action); + $rule = $controller . '/' . $action; + if ($plugin) { + $app = $request->app; + $rule = "/app/{$plugin}/{$app}/{$rule}"; + } + if (!in_array($rule, $user['permissions'])) { + throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION); + } + } +} diff --git a/fastmovie-admin/app/expose/middleware/Platform.php b/fastmovie-admin/app/expose/middleware/Platform.php new file mode 100644 index 0000000..1f08ad3 --- /dev/null +++ b/fastmovie-admin/app/expose/middleware/Platform.php @@ -0,0 +1,59 @@ +header('x-platform'); + if ($platform &&EnumPlatform::has($platform)) { + $request->platform = $platform; + } + $this->isMobile($request); + if ($request->isMobile) { + $request->platform = EnumPlatform::H5['value']; + } + # 微信浏览器 + $this->isWehcatBrowser($request); + # 检测多语言 + $this->languare($request); + $response = $next($request); + return $response; + } + public function isWehcatBrowser($request) + { + $request->isWehcatBrowser = strpos(strtolower($request->header('user-agent')??''), 'micromessenger') !== false; + if($request->isWehcatBrowser&&$request->platform==EnumPlatform::H5['value']){ + $request->platform = EnumPlatform::WECHAT_OFFICIAL_ACCOUNT['value']; + } + } + public function isMobile($request) + { + $request->isMobile = strpos(strtolower($request->header('user-agent')??''), 'mobile') !== false; + } + public function languare($request) + { + $lang=$request->header('lang'); + if(!$lang){ + $AcceptLanguage=$request->header('accept-language'); + if($AcceptLanguage){ + $AcceptLanguageArr=explode(',',$AcceptLanguage); + if(isset($AcceptLanguageArr[0])){ + $lang=$AcceptLanguageArr[0]; + } + } + } + $request->lang='zh-CN'; + if($lang){ + $request->lang=$lang; + } + } +} diff --git a/fastmovie-admin/app/expose/middleware/Template.php b/fastmovie-admin/app/expose/middleware/Template.php new file mode 100644 index 0000000..06e64f6 --- /dev/null +++ b/fastmovie-admin/app/expose/middleware/Template.php @@ -0,0 +1,27 @@ + 'Webman', + 'keywords' => 'Webman,PHP', + 'description' => 'Webman is a high-performance, component-based PHP framework for web applications.', + ]; + View::assign('web', $webInfo); + /** @var Response $response */ + $response = $next($request); + return $response; + } +} diff --git a/fastmovie-admin/app/expose/template/email/Vcode.php b/fastmovie-admin/app/expose/template/email/Vcode.php new file mode 100644 index 0000000..99853de --- /dev/null +++ b/fastmovie-admin/app/expose/template/email/Vcode.php @@ -0,0 +1,13 @@ +data['channels'] = $channels; + $this->data['config'] = $config[$channels]; + } + } +} diff --git a/fastmovie-admin/app/expose/template/sms/Vcode.php b/fastmovie-admin/app/expose/template/sms/Vcode.php new file mode 100644 index 0000000..ef3e2b7 --- /dev/null +++ b/fastmovie-admin/app/expose/template/sms/Vcode.php @@ -0,0 +1,10 @@ +method() === 'POST') { + return $this->update($callback); + } + $builder = HelperConfig::formBuilder($request->action, null, $this->channels_uid); + return $this->resData($builder); + } + private function tabsBuilder($callback = null) + { + $request = request(); + if ($request->method() === 'POST') { + return $this->update($callback); + } + $builder = ConfigGroup::formBuilder($request->action, null, $this->channels_uid); + return $this->resData($builder); + } + private function update($callback = null) + { + $request = request(); + $HelperConfig = new HelperConfig($request->action, null, $this->channels_uid); + $group = $HelperConfig->getGrop(); + $D = $request->post(); + $where = []; + $where[] = ['group', '=', $group]; + if ($this->channels_uid) { + $where[] = ['channels_uid', '=', $this->channels_uid]; + } + $ConfigModel = ModelConfig::where($where)->find(); + if (!$ConfigModel) { + $ConfigModel = new ModelConfig; + $ConfigModel->group = $group; + if ($this->channels_uid) { + $ConfigModel->channels_uid = $this->channels_uid; + } + } + $ConfigModel->value = $D; + if ($ConfigModel->save()) { + if ($callback) { + $callback($D); + } + return $this->event(ResponseEvent::UPDATE_WEBCONFIG, '保存成功'); + } + return $this->fail('保存失败'); + } +} diff --git a/fastmovie-admin/app/expose/trait/Json.php b/fastmovie-admin/app/expose/trait/Json.php new file mode 100644 index 0000000..28abc71 --- /dev/null +++ b/fastmovie-admin/app/expose/trait/Json.php @@ -0,0 +1,111 @@ + ResponseCode::SUCCESS, 'msg' => $msg, 'data' => $data]); + } + /** + * 返回失败JSON + * + * @param string $msg 消息 + * @param mixed $data 数据 + * @return Response + */ + protected static function fail(string $msg = 'fail', $data = []) + { + return self::json(['code' => ResponseCode::FAIL, 'msg' => $msg, 'data' => $data]); + } + /** + * 返回数据JSON + * + * @param mixed $data 数据 + * @return Response + */ + protected static function resData($data) + { + return self::json(['code' => ResponseCode::SUCCESS, 'msg' => 'success', 'data' => $data]); + } + protected static function event($event, $msg = 'success') + { + return self::code(ResponseCode::SUCCESS_EVENT_PUSH, $msg, [ + 'event' => $event + ]); + } + /** + * 返回自定义JSON + * + * @param int $code 状态码 + * @param string $msg 消息 + * @param mixed $data 数据 + * @return Response + */ + protected static function code($code, $msg = null, $data = []) + { + return self::json(['code' => $code, 'msg' => $msg, 'data' => $data]); + } + /** + * 返回异常消息 + * + * @param \Throwable $th 捕获的异常 + * @return Response + */ + protected static function exception($th) + { + $data = []; + if (config('app.debug')) { + $data = [ + 'file' => $th->getFile(), + 'line' => $th->getLine(), + 'trace' => $th->getTrace(), + ]; + } + if ($th instanceof Exception) { + $data = array_merge($data, $th->getData()); + } + return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => $data]); + } + /** + * 响应失败 + * + * @param \Throwable $th 捕获的异常 + * @return Response + */ + protected static function server($th, $http_code = 500) + { + return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => ['file' => $th->getFile(), 'line' => $th->getLine()]], JSON_UNESCAPED_UNICODE, $http_code); + } + /** + * 返回JSON + * + * @param mixed $data JSON数据 + * @param int|null $options JSON编码 + * @param int $http_code 服务器响应代码 + * @return Response + */ + protected static function json($data, $options = JSON_UNESCAPED_UNICODE, $http_code = 200) + { + $request = request(); + if ($request && $request->lang) { + $data['msg'] = trans($data['msg'], [], null, $request->lang); + } + return new Response($http_code, ['Content-Type' => 'application/json'], json_encode($data, $options)); + } +} diff --git a/fastmovie-admin/app/expose/trait/Uploads.php b/fastmovie-admin/app/expose/trait/Uploads.php new file mode 100644 index 0000000..57d23bb --- /dev/null +++ b/fastmovie-admin/app/expose/trait/Uploads.php @@ -0,0 +1,277 @@ +get('limit', 10); + $dir_name = $request->get('dir_name', 'all'); + $where = []; + if ($dir_name != 'all') { + $where[] = ['c.dir_name', '=', $dir_name]; + } + $accept = $request->get('accept'); + if ($accept) { + # 判断是否包含*号 + if (strpos($accept, '*') !== false) { + $accept = str_replace('*', '', $accept); + $where[] = ['u.mime', 'like', "%{$accept}%"]; + } elseif(strpos($accept, '.') !== false){ + $accept = str_replace('.', '', $accept); + if(strpos($accept, ',') !== false){ + $accept = explode(',', $accept); + $where[] = ['u.ext', 'in', $accept]; + } else { + $where[] = ['u.ext', '=', $accept]; + } + } else { + $where[] = ['u.mime', 'in', explode(',', $accept)]; + } + } + if ($this->uid) { + $where[] = ['u.uid', '=', $this->uid]; + } + if ($this->admin_uid) { + $where[] = ['u.admin_uid', '=', $this->admin_uid]; + } + $ModelUploads = ModelUploads::alias('u')->where($where) + ->join('uploads_classify c', 'u.classify_id=c.id') + ->field('u.*,c.title as classify_title') + ->order('u.id desc') + ->paginate($limit)->each(function ($item) { + $item->url = Storage::adapter($item->channels)->url($item->path); + return $item; + }); + return $this->resData($ModelUploads); + } + public function upload(Request $request) + { + $dir_name = $request->post('dir_name'); + $dir_title = $request->post('dir_title'); + $UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find(); + if (!$UploadsClassify) { + $UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->find(); + if (!$UploadsClassify) { + if (empty($dir_title)) { + return $this->fail('分类不存在'); + } + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = $dir_title; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = config('plugin.shortplay.filesystem.app.default'); + $UploadsClassify->save(); + } + } + $channels = $request->post('channels'); + if (!$channels) { + $channels = $UploadsClassify->channels; + } + $date_path = date('Ymd'); + //单文件上传 + $file = $request->file('file'); + try { + $result = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + } catch (\Throwable $th) { + return $this->exception($th); + } + try { + $Uploads = new ModelUploads; + $Uploads->uid = $this->uid; + $Uploads->admin_uid = $this->admin_uid; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->resData([ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]); + } + public function uploads(Request $request) + { + $dir_name = $request->post('dir_name'); + $dir_title = $request->post('dir_title'); + $UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find(); + if (!$UploadsClassify) { + $UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->find(); + if (!$UploadsClassify) { + if (empty($dir_title)) { + return $this->fail('分类不存在'); + } + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = $dir_title; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = config('plugin.shortplay.filesystem.app.default'); + $UploadsClassify->save(); + } + } + $channels = $request->post('channels'); + if (!$channels) { + $channels = $UploadsClassify->channels; + } + $date_path = date('Ymd'); + $files = $request->file(); + try { + $results = Storage::adapter($channels)->path($dir_name . '/' . $date_path)->uploads($files); + } catch (\Throwable $th) { + return $this->exception($th); + } + $resData = []; + try { + foreach ($results as $result) { + $Uploads = new ModelUploads; + $Uploads->uid = $this->uid; + $Uploads->admin_uid = $this->admin_uid; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->save(); + $resData[] = [ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]; + } + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->resData($resData); + } + public function deleteUploads(Request $request) + { + $ids = $request->post('ids'); + $data = ModelUploads::whereIn('id', $ids)->select(); + foreach ($data as $item) { + if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) { + continue; + } + try { + $filesystem = FilesystemFactory::get($item->channels); + $filesystem->delete($item->path); + } catch (\Throwable $th) { + Log::error('删除文件"'.$item->path.'"失败:'.$th->getMessage(),$th->getTrace()); + } + $item->delete(); + } + return $this->success('删除成功'); + } + public function updateUploads(Request $request) + { + $ids = $request->post('ids'); + $dir_name = $request->post('dir_name'); + $UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find(); + if (!$UploadsClassify) { + return $this->fail('分类不存在'); + } + $data = ModelUploads::whereIn('id', $ids)->select(); + foreach ($data as $item) { + if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) { + continue; + } + $item->classify_id = $UploadsClassify->id; + $item->save(); + } + return $this->success('修改成功'); + } + public function getUploadClassify(Request $request) + { + if (!UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1])->count()) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = '默认分类'; + $UploadsClassify->dir_name = 'uploads/default'; + $UploadsClassify->channels = Filesystem::PUBLIC['value']; + $UploadsClassify->sort = 0; + $UploadsClassify->is_system = 1; + $UploadsClassify->save(); + } + $UploadsClassify = UploadsClassify::order('sort asc,id asc')->select()->toArray(); + $all = [ + 'title' => '全部', + 'dir_name' => 'all', + 'is_system' => 1, + ]; + array_unshift($UploadsClassify, $all); + return $this->resData($UploadsClassify); + } + public function saveClassify(Request $request) + { + $data = $request->post(); + if (empty($data['title'])) { + return $this->fail('标题不能为空'); + } + if (empty($data['channels'])) { + return $this->fail('请选择储存渠道'); + } + if (empty($data['dir_name'])) { + return $this->fail('目录名不能为空'); + } + $UploadsClassify = UploadsClassify::where('dir_name', $data['dir_name'])->find(); + if (!$UploadsClassify) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->dir_name = 'uploads/' . $data['dir_name']; + } + $UploadsClassify->title = $data['title']; + $UploadsClassify->channels = $data['channels']; + if (!empty($data['sort'])) { + $UploadsClassify->sort = $data['sort']; + } + if ($UploadsClassify->save()) { + return $this->success('创建成功'); + } else { + return $this->fail('创建失败'); + } + } + public function deleteClassify(Request $request) + { + $dir_name = $request->post('dir_name'); + if (empty($dir_name)) { + return $this->fail('目录名不能为空'); + } + if ($dir_name == 'all') { + return $this->fail('全部目录不能删除'); + } + $UploadsClassify = UploadsClassify::where('dir_name', $dir_name)->find(); + if (!$UploadsClassify) { + return $this->fail('目录不存在'); + } + if ($UploadsClassify->is_system) { + return $this->fail('系统分类,无法删除'); + } + if (ModelUploads::where(['classify_id' => $UploadsClassify->id])->count() > 0) { + return $this->fail('目录分类下有文件不能删除'); + } + if ($UploadsClassify->delete()) { + return $this->success('删除成功'); + } else { + return $this->fail('删除失败'); + } + } +} diff --git a/fastmovie-admin/app/expose/utils/DataModel.php b/fastmovie-admin/app/expose/utils/DataModel.php new file mode 100644 index 0000000..e950c5f --- /dev/null +++ b/fastmovie-admin/app/expose/utils/DataModel.php @@ -0,0 +1,86 @@ +data=$data; + } + public function __get($name) + { + return $this->data[$name] ?? null; + } + public function __set($name, $value) + { + $this->data[$name] = $value; + } + public function __isset($name) + { + return isset($this->data[$name]); + } + public function __unset($name) + { + unset($this->data[$name]); + } + public function __toString() + { + return json_encode($this->data, JSON_UNESCAPED_UNICODE); + } + public function offsetGet(mixed $offset):mixed + { + return $this->data[$offset] ?? null; + } + public function offsetSet(mixed $offset, mixed $value): void + { + if (is_null($offset)) { + $this->data[] = $value; + } else { + $this->data[$offset] = $value; + } + } + public function offsetExists(mixed $offset): bool + { + return $this->__isset($offset); + } + public function offsetUnset(mixed $offset): void + { + $this->__unset($offset); + } + public function toArray() + { + return $this->data; + } + public function toJson() + { + return json_encode($this->data, JSON_UNESCAPED_UNICODE); + } + public function jsonSerialize(): array + { + return $this->data; + } +} diff --git a/fastmovie-admin/app/expose/utils/Email.php b/fastmovie-admin/app/expose/utils/Email.php new file mode 100644 index 0000000..85cb6c6 --- /dev/null +++ b/fastmovie-admin/app/expose/utils/Email.php @@ -0,0 +1,27 @@ +template = new $template; + return $this; + } + public function setData($data) + { + $this->data = $data; + return $this; + } + public function send() + { + $content = $this->template->builder($this->toemail, $this->data); + p($content); + return true; + } +} diff --git a/fastmovie-admin/app/expose/utils/Json.php b/fastmovie-admin/app/expose/utils/Json.php new file mode 100644 index 0000000..58b442b --- /dev/null +++ b/fastmovie-admin/app/expose/utils/Json.php @@ -0,0 +1,108 @@ + ResponseCode::SUCCESS, 'msg' => $msg, 'data' => $data]); + } + /** + * 返回失败JSON + * + * @param string $msg 消息 + * @param mixed $data 数据 + * @return Response + */ + protected static function fail(string $msg = 'fail', $data = []) + { + return self::json(['code' => ResponseCode::FAIL, 'msg' => $msg, 'data' => $data]); + } + /** + * 返回数据JSON + * + * @param mixed $data 数据 + * @return Response + */ + protected static function resData($data) + { + return self::json(['code' => ResponseCode::SUCCESS, 'msg' => 'success', 'data' => $data]); + } + protected static function event($event, $msg = 'success') + { + return self::code(ResponseCode::SUCCESS_EVENT_PUSH, $msg, [ + 'event' => $event + ]); + } + /** + * 返回自定义JSON + * + * @param int $code 状态码 + * @param string $msg 消息 + * @param mixed $data 数据 + * @return Response + */ + protected static function code($code, $msg = null, $data = []) + { + return self::json(['code' => $code, 'msg' => $msg, 'data' => $data]); + } + /** + * 返回异常消息 + * + * @param \Throwable $th 捕获的异常 + * @return Response + */ + protected static function exception($th) + { + $data = []; + if (config('app.debug')) { + $data = [ + 'file' => $th->getFile(), + 'line' => $th->getLine(), + 'trace' => $th->getTrace(), + ]; + } + return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => $data]); + } + /** + * 响应失败 + * + * @param \Throwable $th 捕获的异常 + * @return Response + */ + protected static function server($th, $http_code = 500) + { + return self::json(['code' => $th->getCode() ? $th->getCode() : ResponseCode::FAIL, 'msg' => $th->getMessage(), 'data' => ['file' => $th->getFile(), 'line' => $th->getLine()]], JSON_UNESCAPED_UNICODE, $http_code); + } + /** + * 返回JSON + * + * @param mixed $data JSON数据 + * @param int|null $options JSON编码 + * @param int $http_code 服务器响应代码 + * @return Response + */ + protected static function json($data, $options = JSON_UNESCAPED_UNICODE, $http_code = 200) + { + $request=request(); + if($request&&$request->lang){ + $data['msg']=trans($data['msg'],[], null, $request->lang); + } + return new Response($http_code, ['Content-Type' => 'application/json'], json_encode($data, $options)); + } +} diff --git a/fastmovie-admin/app/expose/utils/Password.php b/fastmovie-admin/app/expose/utils/Password.php new file mode 100644 index 0000000..9d031b1 --- /dev/null +++ b/fastmovie-admin/app/expose/utils/Password.php @@ -0,0 +1,30 @@ + 2048, + "private_key_type" => OPENSSL_KEYTYPE_RSA + ]); + openssl_pkey_export($res, $privKey); + $pubKey = openssl_pkey_get_details($res); + return [ + 'privatekey' => $privKey, + 'publickey' => $pubKey["key"] + ]; + } + # 以下是数字加密解密 + const PASSWORD = 'MAW512P89WIAUW9G7OHQWVGAPX51WPSN'; + /** + * 数字加密函数 + * + * @param int $number + * @param string $password + * @return string + */ + public static function encryptNumber(int $number, string $password = '') + { + // 生成随机的初始向量 + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); + $password = $password ?: self::PASSWORD; + // 加密数字 + $encrypted = openssl_encrypt($number, 'aes-256-cbc', $password, 0, $iv); + + // 将初始向量和加密后的数据进行编码并拼接起来 + $encoded = base64_encode($iv . $encrypted); + + return $encoded; + } + /** + * 数字解密函数 + * + * @param string $encrypted + * @param string $password + * @return int + */ + public static function decryptNumber(string $encrypted, string $password = '') + { + // 解码加密后的数据 + $decoded = base64_decode($encrypted); + + // 提取初始向量和加密后的数据 + $ivLength = openssl_cipher_iv_length('aes-256-cbc'); + $iv = substr($decoded, 0, $ivLength); + $encryptedData = substr($decoded, $ivLength); + + $password = $password ?: self::PASSWORD; + + // 解密数据 + $decrypted = openssl_decrypt($encryptedData, 'aes-256-cbc', $password, 0, $iv); + + return $decrypted; + } +} diff --git a/fastmovie-admin/app/expose/utils/Sms.php b/fastmovie-admin/app/expose/utils/Sms.php new file mode 100644 index 0000000..016da9b --- /dev/null +++ b/fastmovie-admin/app/expose/utils/Sms.php @@ -0,0 +1,89 @@ +template = $template; + return $this; + } + $this->template = new $template; + return $this; + } + public function setData($data) + { + $this->data = $data; + return $this; + } + public function send() + { + $this->template->builder($this->mobile, $this->data); + switch ($this->template->channel) { + case SmsChannels::ALIYUN['value']: + $this->sendAliyun(); + break; + case SmsChannels::TENCENT['value']: + $this->sendTencent(); + break; + case SmsChannels::SMSBAO['value']: + $this->sendSmsbao(); + break; + default: + throw new Exception("未知的短信服务商"); + } + } + public function sendAliyun() + { + if (empty($this->template->config)) { + throw new Exception("阿里云短信服务商配置不完整"); + } + $config = new Config([ + "accessKeyId" => $this->template->config['access_key_id'], + "accessKeySecret" => $this->template->config['access_secret'], + ]); + $config->endpoint = "dysmsapi.aliyuncs.com"; + $client = new Dysmsapi($config); + $request = new SendSmsRequest([ + "phoneNumbers" => $this->mobile, + "templateCode" => $this->template->template_code, + "templateParam" => json_encode($this->data, JSON_UNESCAPED_UNICODE), + "signName" => $this->template->config['sign_name'] + ]); + $runtime = new RuntimeOptions(); + $runtime->maxIdleConns = 3; + $runtime->connectTimeout = 10000; + $runtime->readTimeout = 10000; + // 复制代码运行请自行打印 API 的返回值 + $res = $client->sendSms($request, $runtime); + if ($res->body->code == 'OK') { + return true; + } + if ($res->body->message) { + throw new Exception($res->body->message); + } + throw new Exception("发送失败"); + } + public function sendTencent() + { + throw new Exception("error"); + } + public function sendSmsbao() + { + throw new Exception("error"); + } +} diff --git a/fastmovie-admin/app/expose/utils/Str.php b/fastmovie-admin/app/expose/utils/Str.php new file mode 100644 index 0000000..85c93a3 --- /dev/null +++ b/fastmovie-admin/app/expose/utils/Str.php @@ -0,0 +1,262 @@ + +// +---------------------------------------------------------------------- + +namespace app\expose\utils; + +class Str +{ + + protected static $snakeCache = []; + + protected static $camelCache = []; + + protected static $studlyCache = []; + + /** + * 检查字符串中是否包含某些字符串 + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) !== false) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串结尾 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function endsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ((string) $needle === static::substr($haystack, -static::length($needle))) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串开头 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) === 0) { + return true; + } + } + + return false; + } + + /** + * 获取指定长度的随机字母数字组合的字符串 + * + * @param int $length + * @param int $type + * @param string $addChars + * @return string + */ + public static function random(int $length = 6, int $type = null, string $addChars = ''): string + { + $str = ''; + switch ($type) { + case 0: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 1: + $chars = str_repeat('0123456789', 3); + break; + case 2: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars; + break; + case 3: + $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 4: + $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars; + break; + default: + $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars; + break; + } + if ($length > 10) { + $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5); + } + if ($type != 4) { + $chars = str_shuffle($chars); + $str = substr($chars, 0, $length); + } else { + for ($i = 0; $i < $length; $i++) { + $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1); + } + } + return $str; + } + + /** + * 字符串转小写 + * + * @param string $value + * @return string + */ + public static function lower(string $value): string + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * 字符串转大写 + * + * @param string $value + * @return string + */ + public static function upper(string $value): string + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * 获取字符串的长度 + * + * @param string $value + * @return int + */ + public static function length(string $value): int + { + return mb_strlen($value); + } + + /** + * 截取字符串 + * + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + public static function substr(string $string, int $start, int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * 驼峰转下划线 + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake(string $value, string $delimiter = '_'): string + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (!ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', ucwords($value)); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * 下划线转驼峰(首字母小写) + * + * @param string $value + * @return string + */ + public static function camel(string $value): string + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * 下划线转驼峰(首字母大写) + * + * @param string $value + * @return string + */ + public static function studly(string $value): string + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } + + /** + * 转为首字母大写的标题格式 + * + * @param string $value + * @return string + */ + public static function title(string $value): string + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + /** + * 手机号,邮箱,脱敏 + * @param string|null $value + * @return string|null + */ + public static function mask(?string $value): ?string + { + if (empty($value)) { + return null; + } + if (filter_var($value, FILTER_VALIDATE_EMAIL)) { + $arr = explode('@', $value); + $name = $arr[0]; + $domain = $arr[1]; + + // 如果长度小于等于4,就直接显示 + if (strlen($name) <= 4) { + $masked = $name; + } else { + $masked = substr($name, 0, 2) . '****' . substr($name, -2); + } + + return $masked . '@' . $domain; + } else { + return substr($value, 0, 3) . '****' . substr($value, -4); + } + } +} diff --git a/fastmovie-admin/app/expose/utils/TreeModel.php b/fastmovie-admin/app/expose/utils/TreeModel.php new file mode 100644 index 0000000..296439e --- /dev/null +++ b/fastmovie-admin/app/expose/utils/TreeModel.php @@ -0,0 +1,36 @@ + $record) { + if ($record[$pidKey]) { + $organized[$record[$pidKey]]['children'][] = &$organized[$id]; + } else { + $tree[] = &$organized[$id]; + } + } + return $tree; + } + public function toTree($idKey='id',$pidKey='pid') + { + $organized=$this->_organizeRecords($this->data,$idKey); + $data=$this->_buildTree($organized,$pidKey); + return $data; + } +} \ No newline at end of file diff --git a/fastmovie-admin/app/expose/utils/TwofaAuthenticator.php b/fastmovie-admin/app/expose/utils/TwofaAuthenticator.php new file mode 100644 index 0000000..f7ecb3d --- /dev/null +++ b/fastmovie-admin/app/expose/utils/TwofaAuthenticator.php @@ -0,0 +1,251 @@ +_getBase32LookupTable(); + + // Valid secret lengths are 80 to 640 bits + if ($secretLength < 16 || $secretLength > 128) { + throw new Exception('Bad secret length'); + } + $secret = ''; + $rnd = false; + if (function_exists('random_bytes')) { + $rnd = random_bytes($secretLength); + } elseif (function_exists('mcrypt_create_iv')) { + $rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong); + if (!$cryptoStrong) { + $rnd = false; + } + } + if ($rnd !== false) { + for ($i = 0; $i < $secretLength; ++$i) { + $secret .= $validChars[ord($rnd[$i]) & 31]; + } + } else { + throw new Exception('No source of secure random'); + } + + return $secret; + } + + /** + * Calculate the code, with given secret and point in time. + * + * @param string $secret + * @param int|null $timeSlice + * + * @return string + */ + public function getCode($secret, $timeSlice = null) + { + if ($timeSlice === null) { + $timeSlice = floor(time() / 30); + } + + $secretkey = $this->_base32Decode($secret); + + // Pack time into binary string + $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); + // Hash it with users secret key + $hm = hash_hmac('SHA1', $time, $secretkey, true); + // Use last nipple of result as index/offset + $offset = ord(substr($hm, -1)) & 0x0F; + // grab 4 bytes of the result + $hashpart = substr($hm, $offset, 4); + + // Unpak binary value + $value = unpack('N', $hashpart); + $value = $value[1]; + // Only 32 bits + $value = $value & 0x7FFFFFFF; + + $modulo = pow(10, $this->_codeLength); + + return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); + } + + /** + * Get QR-Code URL for image, from google charts. + * + * @param string $name + * @param string $secret + * @param string $title + * @param array $params + * + * @return string + */ + public function getQRCode($name, $secret, $title = null) + { + $urlencoded = 'otpauth://totp/'.$name.'?secret='.$secret; + if (isset($title)) { + $urlencoded .= '&issuer='.urlencode($title); + } + return $urlencoded; + } + + /** + * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now. + * + * @param string $secret + * @param string $code + * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) + * @param int|null $currentTimeSlice time slice if we want use other that time() + * + * @return bool + */ + public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) + { + if ($currentTimeSlice === null) { + $currentTimeSlice = floor(time() / 30); + } + + if (strlen($code) != 6) { + return false; + } + + for ($i = -$discrepancy; $i <= $discrepancy; ++$i) { + $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); + if ($this->timingSafeEquals($calculatedCode, $code)) { + return true; + } + } + + return false; + } + + /** + * Set the code length, should be >=6. + * + * @param int $length + * + * @return PHPGangsta_GoogleAuthenticator + */ + public function setCodeLength($length) + { + $this->_codeLength = $length; + + return $this; + } + + /** + * Helper class to decode base32. + * + * @param $secret + * + * @return bool|string + */ + protected function _base32Decode($secret) + { + if (empty($secret)) { + return ''; + } + + $base32chars = $this->_getBase32LookupTable(); + $base32charsFlipped = array_flip($base32chars); + + $paddingCharCount = substr_count($secret, $base32chars[32]); + $allowedValues = array(6, 4, 3, 1, 0); + if (!in_array($paddingCharCount, $allowedValues)) { + return false; + } + for ($i = 0; $i < 4; ++$i) { + if ($paddingCharCount == $allowedValues[$i] && + substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) { + return false; + } + } + $secret = str_replace('=', '', $secret); + $secret = str_split($secret); + $binaryString = ''; + for ($i = 0; $i < count($secret); $i = $i + 8) { + $x = ''; + if (!in_array($secret[$i], $base32chars)) { + return false; + } + for ($j = 0; $j < 8; ++$j) { + $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); + } + $eightBits = str_split($x, 8); + for ($z = 0; $z < count($eightBits); ++$z) { + $binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : ''; + } + } + + return $binaryString; + } + + /** + * Get array with all 32 characters for decoding from/encoding to base32. + * + * @return array + */ + protected function _getBase32LookupTable() + { + return array( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 + 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 + '=', // padding char + ); + } + + /** + * A timing safe equals comparison + * more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html. + * + * @param string $safeString The internal (safe) value to be checked + * @param string $userString The user submitted (unsafe) value + * + * @return bool True if the two strings are identical + */ + private function timingSafeEquals($safeString, $userString) + { + if (function_exists('hash_equals')) { + return hash_equals($safeString, $userString); + } + $safeLen = strlen($safeString); + $userLen = strlen($userString); + + if ($userLen != $safeLen) { + return false; + } + + $result = 0; + + for ($i = 0; $i < $userLen; ++$i) { + $result |= (ord($safeString[$i]) ^ ord($userString[$i])); + } + + // They are only identical strings if $result is exactly 0... + return $result === 0; + } +} \ No newline at end of file diff --git a/fastmovie-admin/app/expose/utils/wechat/OfficialAccount.php b/fastmovie-admin/app/expose/utils/wechat/OfficialAccount.php new file mode 100644 index 0000000..216a541 --- /dev/null +++ b/fastmovie-admin/app/expose/utils/wechat/OfficialAccount.php @@ -0,0 +1,57 @@ +getData($request, $config); + if ($data['MsgType'] === 'event') { + return $this->handleEvent($data); + } else { + return $this->replyText($data, 'hello world'); + } + } + public function checkSignature($request, $config) + { + $signature = $request->get("signature"); + $timestamp = $request->get("timestamp"); + $nonce = $request->get("nonce"); + $token = $config['token']; + $tmpArr = [$token, $timestamp, $nonce]; + sort($tmpArr, SORT_STRING); + $tmpStr = implode($tmpArr); + $tmpStr = sha1($tmpStr); + if ($tmpStr == $signature) { + return true; + } + throw new \Exception('signature error'); + } + private function getData($request, $config) + { + $xml = $request->rawBody(); + $json = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); + $data = json_decode(json_encode($json), true); + if ($request->get('encrypt_type') === 'aes') { + $Encrypt = base64_decode($data['Encrypt']); + $aes_key = $config['aes_key']; + $aes_key = base64_decode($aes_key . '='); + $iv = substr($aes_key, 0, 16); + $xml = openssl_decrypt($Encrypt, 'aes-256-cbc', $aes_key, OPENSSL_RAW_DATA, $iv); + $filterHeader = substr($xml, 20); + $xml = preg_replace('/' . $config['app_id'] . '/', '', $filterHeader); + $json = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); + $data = json_decode(json_encode($json), true); + return $data; + } else { + return $data; + } + } +} diff --git a/fastmovie-admin/app/expose/utils/wechat/modules/Event.php b/fastmovie-admin/app/expose/utils/wechat/modules/Event.php new file mode 100644 index 0000000..3fa70eb --- /dev/null +++ b/fastmovie-admin/app/expose/utils/wechat/modules/Event.php @@ -0,0 +1,73 @@ +replyText($data,'欢迎关注'); + // case 'unsubscribe': + // EventEvent::emit(EventName::WECHAT_OFFICIAL_ACCOUNT_UNSUBSCRLBE['value'],$data); + // return $this->replyText($data,'路远,再会。'); + case 'subscribe': + case 'unsubscribe': + case 'SCAN': + $scene = str_replace('qrscene_', '', $data['EventKey']); + $Scene = Redis::get($scene); + if ($Scene) { + $Scene = json_decode($Scene, true); + if (count($Scene) < 2) { + return $this->replyText($data, '二维码参数错误'); + } + $class = $Scene[0]; + $method = $Scene[1]; + if (class_exists($class)) { + $class = new $class(); + $class->FromUserName = $data['FromUserName']; + $class->ToUserName = $data['ToUserName']; + $class->MsgType = $data['MsgType']; + $class->Event = $data['Event']; + $class->EventKey = $scene; + $class->data = $data; + if (method_exists($class, $method)) { + $params = null; + if (isset($Scene[2])) { + $params = $Scene[2]; + } + return $this->replyText($data, $class->$method($params)); + } + } + return $this->replyText($data, '扫码成功,但未找到对应处理类或方法'); + } + return $this->replyText($data, '二维码已过期'); + case 'LOCATION': + return $this->replyText($data, '上报地理位置'); + case 'CLICK': + return $this->replyText($data, '点击菜单'); + case 'VIEW': + return $this->replyText($data, '点击菜单跳转链接'); + case 'scancode_push': + return $this->replyText($data, '扫码推事件'); + case 'scancode_waitmsg': + return $this->replyText($data, '扫码推事件且弹出“消息接收中”提示框'); + case 'pic_sysphoto': + return $this->replyText($data, '弹出系统拍照发图'); + case 'pic_photo_or_album': + return $this->replyText($data, '弹出拍照或者相册发图'); + case 'pic_weixin': + return $this->replyText($data, '弹出微信相册发图器'); + case 'location_select': + return $this->replyText($data, '弹出地理位置选择器'); + case 'view_miniprogram': + return $this->replyText($data, '点击菜单跳转小程序'); + } + } +} diff --git a/fastmovie-admin/app/expose/utils/wechat/modules/Reply.php b/fastmovie-admin/app/expose/utils/wechat/modules/Reply.php new file mode 100644 index 0000000..d5f7495 --- /dev/null +++ b/fastmovie-admin/app/expose/utils/wechat/modules/Reply.php @@ -0,0 +1,94 @@ + "", + 'FromUserName' => "", + 'CreateTime' => time(), + 'MsgType' => '', + 'Content' => "" + ]; + return xml(arrayToXml($result)); + } + private function replyImage($data,$media_id) + { + $result=[ + 'ToUserName' => "", + 'FromUserName' => "", + 'CreateTime' => time(), + 'MsgType' => '', + 'Image' => [ + 'MediaId' => "" + ] + ]; + return xml(arrayToXml($result)); + } + private function replyVoice($data,$media_id) + { + $result=[ + 'ToUserName' => "", + 'FromUserName' => "", + 'CreateTime' => time(), + 'MsgType' => '', + 'Voice' => [ + 'MediaId' => "" + ] + ]; + return xml(arrayToXml($result)); + } + private function replyVideo($data,$media_id,$title='',$description='') + { + $result=[ + 'ToUserName' => "", + 'FromUserName' => "", + 'CreateTime' => time(), + 'MsgType' => '', + 'Video' => [ + 'MediaId' => "", + 'Title' => "", + 'Description' => "" + ] + ]; + return xml(arrayToXml($result)); + } + private function replyMusic($data,$thumb_media_id,$title='',$description='',$music_url='',$hq_music_url='') + { + $result=[ + 'ToUserName' => "", + 'FromUserName' => "", + 'CreateTime' => time(), + 'MsgType' => '', + 'Music' => [ + 'Title' => "", + 'Description' => "", + 'MusicUrl' => "", + 'HQMusicUrl' => "", + 'ThumbMediaId' => "" + ] + ]; + return xml(arrayToXml($result)); + } + private function replyNews($data,$articles) + { + $result=[ + 'ToUserName' => "", + 'FromUserName' => "", + 'CreateTime' => time(), + 'MsgType' => '', + 'ArticleCount' => count($articles), + 'Articles' => [] + ]; + foreach ($articles as $article) { + $result['Articles'][]=[ + 'Title' => "", + 'Description' => "", + 'PicUrl' => "", + 'Url' => "" + ]; + } + return xml(arrayToXml($result)); + } +} \ No newline at end of file diff --git a/fastmovie-admin/app/expose/validate/Validate.php b/fastmovie-admin/app/expose/validate/Validate.php new file mode 100644 index 0000000..b2a5cf8 --- /dev/null +++ b/fastmovie-admin/app/expose/validate/Validate.php @@ -0,0 +1,64 @@ +db = $db; + } + public function unique($value, $rule, array $data = [], string $field = ''): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + $this->setDb(new Db); //设置Db对象 + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + $db = $this->db::name($rule[0]); + } + $map = []; + if (isset($data[$field])) { + $map[] = [$field, '=', $data[$field]]; + } + if (!empty($rule[1]) && strpos($rule[1], '^')) { + // 支持多个字段验证 + $fields = explode('^', $rule[1]); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[] = [$key, '=', $data[$key]]; + } + } + } + + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + if (is_string($pk)) { + if (isset($rule[2])) { + $map[] = [$pk, '<>', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[] = [$pk, '<>', $data[$pk]]; + } + } + + if ($db->where($map)->field($pk)->count()) { + return false; + } + + return true; + } +} diff --git a/fastmovie-admin/app/functions.php b/fastmovie-admin/app/functions.php new file mode 100644 index 0000000..f58bd93 --- /dev/null +++ b/fastmovie-admin/app/functions.php @@ -0,0 +1,37 @@ +"; + print_r($data); + echo ''; +} +function arrayToXml($data, $root = 'xml') +{ + $xml = "<{$root}>"; + foreach ($data as $key => $value) { + if (is_array($value)) { + $xml .= arrayToXml($value, $key); + } else { + $xml .= "<{$key}>{$value}"; + } + } + $xml .= ""; + return $xml; +} +function growth_rate($a, $b) +{ + if (!$a || !$b || ($a - $b == 0)) { + return 0; + } + return round(($a - $b) / $b * 100, 2); +} diff --git a/fastmovie-admin/app/middleware/Access.php b/fastmovie-admin/app/middleware/Access.php new file mode 100644 index 0000000..40dc7fe --- /dev/null +++ b/fastmovie-admin/app/middleware/Access.php @@ -0,0 +1,28 @@ +withHeader('Access-Control-Allow-Origin', '*'); + // $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + // $response->withHeader('Access-Control-Allow-Headers', 'x-requested-with, Content-Type, Accept, Origin, Authorization'); + // $response->withHeader('Access-Control-Allow-Credentials', 'true'); + // return $response; + $response = $next($request); + if (config('app.debug')) { + $response->withHeader('Access-Control-Allow-Origin', '*'); + $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + $response->withHeader('Access-Control-Allow-Headers', 'x-requested-with, Content-Type, Accept, Origin, Authorization'); + $response->withHeader('Access-Control-Allow-Credentials', 'true'); + } + return $response; + } +} diff --git a/fastmovie-admin/app/middleware/Platform.php b/fastmovie-admin/app/middleware/Platform.php new file mode 100644 index 0000000..823a5d5 --- /dev/null +++ b/fastmovie-admin/app/middleware/Platform.php @@ -0,0 +1,9 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace app\middleware; + +use Webman\MiddlewareInterface; +use Webman\Http\Response; +use Webman\Http\Request; + +/** + * Class StaticFile + * @package app\middleware + */ +class StaticFile implements MiddlewareInterface +{ + public function process(Request $request, callable $next): Response + { + // Access to files beginning with. Is prohibited + if (strpos($request->path(), '/.') !== false) { + return response('

403 forbidden

', 403); + } + /** @var Response $response */ + $response = $next($request); + // Add cross domain HTTP header + /*$response->withHeaders([ + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Credentials' => 'true', + ]);*/ + return $response; + } +} diff --git a/fastmovie-admin/app/middleware/Template.php b/fastmovie-admin/app/middleware/Template.php new file mode 100644 index 0000000..7eb5fa2 --- /dev/null +++ b/fastmovie-admin/app/middleware/Template.php @@ -0,0 +1,9 @@ + [ + // 设置JSON字段的类型 + 'allow_week' => 'json' + ] + ]; + } + public function getHeadimgAttr($value) + { + return Uploads::url($value); + } + public function setHeadimgAttr($value) + { + return Uploads::path($value); + } + public function getNicknameAttr($value) + { + return base64_decode($value); + } + public function setNicknameAttr($value) + { + return base64_encode($value); + } + public function setPasswordAttr($value) + { + return Password::encrypt($value); + } + public static function getTokenInfo(Admin $Admin) + { + /* 重组用户信息 */ + $AdminUser = new \stdClass; + $AdminUser->nickname = $Admin->nickname; + $AdminUser->headimg = $Admin->headimg; + $AdminUser->username = $Admin->username; + /* 生成token */ + $data = new \stdClass; + $data->uid = $Admin->id; + $data->role_id = $Admin->role_id; + $data->username = $Admin->username; + $data->mobile = $Admin->mobile; + $data->email = $Admin->email; + $data->allow_time_start = $Admin->allow_time_start; + $data->allow_time_end = $Admin->allow_time_end; + $data->allow_week = $Admin->allow_week; + $AdminRole = AdminRole::where(['id' => $Admin->role_id])->find(); + $data->is_system = $AdminRole->is_system; + $data->permissions = $AdminRole->rule; + $AdminUser->role_name = $AdminRole['name']; + $AdminUser->is_system = $AdminRole->is_system; + $AdminUser->permissions = $AdminRole->rule; + $AdminUser->token = Auth::setPrefix('ADMIN')->encrypt($data); + return $AdminUser; + } +} diff --git a/fastmovie-admin/app/model/AdminRole.php b/fastmovie-admin/app/model/AdminRole.php new file mode 100644 index 0000000..da56ce8 --- /dev/null +++ b/fastmovie-admin/app/model/AdminRole.php @@ -0,0 +1,37 @@ +where(['pid' => $pid])->select(); + } else { + $data = self::where($where)->whereNull('pid')->select(); + } + $options = []; + if ($data->isEmpty()) { + return $options; + } + foreach ($data as $item) { + $temp = [ + 'value' => $item->id, + 'label' => $item['name'] + ]; + $children = self::options($item->id, $where); + if (!empty($children)) { + $temp['children'] = $children; + } + $options[] = $temp; + } + return $options; + } + public static function systemRole($column = '*') + { + return self::where('is_system', 1)->column($column); + } +} diff --git a/fastmovie-admin/app/model/Basic.php b/fastmovie-admin/app/model/Basic.php new file mode 100644 index 0000000..41a8b80 --- /dev/null +++ b/fastmovie-admin/app/model/Basic.php @@ -0,0 +1,10 @@ + [ + // 设置JSON字段的类型 + 'value' => 'json' + ] + ]; + } +} diff --git a/fastmovie-admin/app/model/PaymentConfig.php b/fastmovie-admin/app/model/PaymentConfig.php new file mode 100644 index 0000000..24908f2 --- /dev/null +++ b/fastmovie-admin/app/model/PaymentConfig.php @@ -0,0 +1,7 @@ + [ + // 设置JSON字段的类型 + 'value' => 'json' + ] + ]; + } + public static function options($where = []) + { + $options = []; + $where[] = ['state', '=', State::YES['value']]; + $templates = self::where($where)->select(); + foreach ($templates as $template) { + $channel = PaymentChannels::getText($template->channels); + $options[] = [ + 'label' => "{$channel} - {$template->title}", + 'value' => $template->id, + 'extra' => [ + 'where' => [ + ['channels', '=', $template->channels] + ] + ], + ]; + } + return $options; + } +} diff --git a/fastmovie-admin/app/model/Uploads.php b/fastmovie-admin/app/model/Uploads.php new file mode 100644 index 0000000..34fab82 --- /dev/null +++ b/fastmovie-admin/app/model/Uploads.php @@ -0,0 +1,7 @@ +whereTime('auto_delete_time','<',date('Y-m-d H:i:s'))->select(); + foreach ($models as $model) { + try { + $filesystem = FilesystemFactory::get($model->channels); + $filesystem->delete($model->path); + $model->delete(); + } catch (\Throwable $th) { + Log::error('删除文件"'.$model->path.'"失败:'.$th->getMessage(),$th->getTrace()); + } + } + }); + } +} \ No newline at end of file diff --git a/fastmovie-admin/app/process/Http.php b/fastmovie-admin/app/process/Http.php new file mode 100644 index 0000000..f462c3a --- /dev/null +++ b/fastmovie-admin/app/process/Http.php @@ -0,0 +1,10 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace app\process; + +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; +use Workerman\Timer; +use Workerman\Worker; + +/** + * Class FileMonitor + * @package process + */ +class Monitor +{ + /** + * @var array + */ + protected $paths = []; + + /** + * @var array + */ + protected $extensions = []; + + /** + * @var array + */ + protected $loadedFiles = []; + + /** + * Pause monitor + * @return void + */ + public static function pause() + { + file_put_contents(static::lockFile(), time()); + } + + /** + * Resume monitor + * @return void + */ + public static function resume(): void + { + clearstatcache(); + if (is_file(static::lockFile())) { + unlink(static::lockFile()); + } + } + + /** + * Whether monitor is paused + * @return bool + */ + public static function isPaused(): bool + { + clearstatcache(); + return file_exists(static::lockFile()); + } + + /** + * Lock file + * @return string + */ + protected static function lockFile(): string + { + return runtime_path('monitor.lock'); + } + + /** + * FileMonitor constructor. + * @param $monitorDir + * @param $monitorExtensions + * @param array $options + */ + public function __construct($monitorDir, $monitorExtensions, array $options = []) + { + static::resume(); + $this->paths = (array)$monitorDir; + $this->extensions = $monitorExtensions; + foreach (get_included_files() as $index => $file) { + $this->loadedFiles[$file] = $index; + if (strpos($file, 'webman-framework/src/support/App.php')) { + break; + } + } + if (!Worker::getAllWorkers()) { + return; + } + $disableFunctions = explode(',', ini_get('disable_functions')); + if (in_array('exec', $disableFunctions, true)) { + echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n"; + } else { + if ($options['enable_file_monitor'] ?? true) { + Timer::add(1, function () { + $this->checkAllFilesChange(); + }); + } + } + + $memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null); + if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) { + Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]); + } + } + + /** + * @param $monitorDir + * @return bool + */ + public function checkFilesChange($monitorDir): bool + { + static $lastMtime, $tooManyFilesCheck; + if (!$lastMtime) { + $lastMtime = time(); + } + clearstatcache(); + if (!is_dir($monitorDir)) { + if (!is_file($monitorDir)) { + return false; + } + $iterator = [new SplFileInfo($monitorDir)]; + } else { + // recursive traversal directory + $dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new RecursiveIteratorIterator($dirIterator); + } + $count = 0; + foreach ($iterator as $file) { + $count ++; + /** var SplFileInfo $file */ + if (is_dir($file->getRealPath())) { + continue; + } + // check mtime + if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) { + $lastMtime = $file->getMTime(); + if (DIRECTORY_SEPARATOR === '/' && isset($this->loadedFiles[$file->getRealPath()])) { + echo "$file updated but cannot be reloaded because only auto-loaded files support reload.\n"; + continue; + } + $var = 0; + exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var); + if ($var) { + continue; + } + echo $file . " updated and reload\n"; + // send SIGUSR1 signal to master process for reload + if (DIRECTORY_SEPARATOR === '/') { + posix_kill(posix_getppid(), SIGUSR1); + } else { + return true; + } + break; + } + } + if (!$tooManyFilesCheck && $count > 1000) { + echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n"; + $tooManyFilesCheck = 1; + } + return false; + } + + /** + * @return bool + */ + public function checkAllFilesChange(): bool + { + if (static::isPaused()) { + return false; + } + foreach ($this->paths as $path) { + if ($this->checkFilesChange($path)) { + return true; + } + } + return false; + } + + /** + * @param $memoryLimit + * @return void + */ + public function checkMemory($memoryLimit) + { + if (static::isPaused() || $memoryLimit <= 0) { + return; + } + $ppid = posix_getppid(); + $childrenFile = "/proc/$ppid/task/$ppid/children"; + if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) { + return; + } + foreach (explode(' ', $children) as $pid) { + $pid = (int)$pid; + $statusFile = "/proc/$pid/status"; + if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) { + continue; + } + $mem = 0; + if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) { + $mem = $match[1]; + } + $mem = (int)($mem / 1024); + if ($mem >= $memoryLimit) { + posix_kill($pid, SIGINT); + } + } + } + + /** + * Get memory limit + * @return float + */ + protected function getMemoryLimit($memoryLimit) + { + if ($memoryLimit === 0) { + return 0; + } + $usePhpIni = false; + if (!$memoryLimit) { + $memoryLimit = ini_get('memory_limit'); + $usePhpIni = true; + } + + if ($memoryLimit == -1) { + return 0; + } + $unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]); + if ($unit === 'g') { + $memoryLimit = 1024 * (int)$memoryLimit; + } else if ($unit === 'm') { + $memoryLimit = (int)$memoryLimit; + } else if ($unit === 'k') { + $memoryLimit = ((int)$memoryLimit / 1024); + } else { + $memoryLimit = ((int)$memoryLimit / (1024 * 1024)); + } + if ($memoryLimit < 30) { + $memoryLimit = 30; + } + if ($usePhpIni) { + $memoryLimit = (int)(0.8 * $memoryLimit); + } + return $memoryLimit; + } +} diff --git a/fastmovie-admin/app/validate/Admin.php b/fastmovie-admin/app/validate/Admin.php new file mode 100644 index 0000000..3b8ee0b --- /dev/null +++ b/fastmovie-admin/app/validate/Admin.php @@ -0,0 +1,33 @@ + 'require', + 'username' => 'require|length:4,30|alphaDash|unique:admin', + 'nickname' => 'require|length:2,30', + 'password' => 'length:5,30', + 'mobile' => 'mobile', + 'email' => 'email' + ]; + + protected $message = [ + 'role_id.require' => '请选择角色', + 'username.require' => '用户名不能为空', + 'username.length' => '用户名长度为4-30位', + 'username.alphaDash' => '用户名只能包含字母、数字、下划线和破折号', + 'username.unique' => '用户名已存在', + 'nickname.require' => '昵称不能为空', + 'nickname.length' => '昵称长度为2-30位', + 'password.length' => '密码长度为5-30位', + 'mobile' => '手机号格式错误', + 'email' => '邮箱格式错误' + ]; + protected $scene = [ + 'self' => ['username', 'nickname', 'password', 'mobile', 'email'] + ]; +} diff --git a/fastmovie-admin/app/validate/User.php b/fastmovie-admin/app/validate/User.php new file mode 100644 index 0000000..9d69d74 --- /dev/null +++ b/fastmovie-admin/app/validate/User.php @@ -0,0 +1,31 @@ + 'length:4,30|alphaDash|unique:user', + 'nickname' => 'require|length:2,30', + 'password' => 'length:5,30', + 'mobile' => 'mobile', + 'email' => 'email' + ]; + + protected $message = [ + 'username.require' => '用户名不能为空', + 'username.length' => '用户名长度为4-30位', + 'username.alphaDash' => '用户名只能包含字母、数字、下划线和破折号', + 'username.unique' => '用户名已存在', + 'nickname.require' => '昵称不能为空', + 'nickname.length' => '昵称长度为2-30位', + 'password.length' => '密码长度为5-30位', + 'mobile' => '手机号格式错误', + 'email' => '邮箱格式错误' + ]; + protected $scene = [ + 'self' => ['username', 'nickname', 'password', 'mobile', 'email'] + ]; +} diff --git a/fastmovie-admin/app/view/index/index.html b/fastmovie-admin/app/view/index/index.html new file mode 100644 index 0000000..6f01218 --- /dev/null +++ b/fastmovie-admin/app/view/index/index.html @@ -0,0 +1,5 @@ +{extend name="main"/} +{block name="body"} +

Hello, webman!

+

Welcome to use webman framework.

+{/block} \ No newline at end of file diff --git a/fastmovie-admin/app/view/main.html b/fastmovie-admin/app/view/main.html new file mode 100644 index 0000000..d9a8ee0 --- /dev/null +++ b/fastmovie-admin/app/view/main.html @@ -0,0 +1,19 @@ + + + + + + + {block name="title"}{$web.title}{/block} + + + + + {block name="head"}{/block} + + + + {block name="body"}{/block} + + + \ No newline at end of file diff --git a/fastmovie-admin/composer.json b/fastmovie-admin/composer.json new file mode 100644 index 0000000..de09b63 --- /dev/null +++ b/fastmovie-admin/composer.json @@ -0,0 +1,95 @@ +{ + "name": "loong/admin", + "type": "project", + "keywords": [ + "快速开发", + "后台管理", + "后端渲染" + ], + "homepage": "https://github.com/RenLoong/loong-admin.git", + "license": "MIT", + "description": "一款让开发者专注后端PHP业务开发的高性能框架,基于Webman框架开发", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + }, + { + "name": "余心", + "email": "1016683587@qq.com", + "homepage": "https://www.renloong.com", + "role": "Developer" + } + ], + "repositories": { + "packagist": { + "type": "composer", + "url": "https://mirrors.aliyun.com/composer/" + } + }, + "support": { + "email": "1016683587@qq.com", + "issues": "https://github.com/RenLoong/loong-admin/issues", + "forum": "https://wenda.workerman.net/", + "wiki": "https://workerman.net/doc/webman", + "source": "https://github.com/RenLoong/loong-admin" + }, + "require": { + "php": ">=8.1", + "workerman/webman-framework": "~v2.1", + "monolog/monolog": "^3.8", + "webman/think-orm": "^2.1", + "vlucas/phpdotenv": "^5.6", + "topthink/think-template": "^3.0", + "illuminate/redis": "^10.48", + "illuminate/events": "^10.48", + "topthink/think-validate": "^3.0", + "webman/captcha": "^1.0", + "workerman/crontab": "^1.0", + "webman/console": "^2.1", + "shopwwi/webman-filesystem": "^1.1", + "shopwwi/filesystem-oss": "^1.0", + "league/flysystem-aws-s3-v3": "^3.29", + "overtrue/flysystem-qiniu": "^3.2", + "overtrue/flysystem-cos": "^5.1", + "loong/oauth": "^1.1", + "alibabacloud/dysmsapi-20170525": "^3.1", + "yansongda/pay": "^3.7", + "webman/event": "^1.0", + "php-ffmpeg/php-ffmpeg": "^1.3", + "workerman/workerman": "~v5.1", + "webman/redis": "^2.1", + "webman/cache": "^2.1", + "textalk/websocket": "^1.5", + "webman/push": "^1.1", + "symfony/translation": "^6.4", + "tencentcloud/sms": "^3.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "": "./", + "app\\": "./app", + "App\\": "./app", + "app\\View\\Components\\": "./app/view/components" + }, + "files": [ + "./support/helpers.php" + ] + }, + "scripts": { + "post-package-install": [ + "support\\Plugin::install" + ], + "post-package-update": [ + "support\\Plugin::install" + ], + "pre-package-uninstall": [ + "support\\Plugin::uninstall" + ] + } +} diff --git a/fastmovie-admin/composer.lock b/fastmovie-admin/composer.lock new file mode 100644 index 0000000..65e8ba1 --- /dev/null +++ b/fastmovie-admin/composer.lock @@ -0,0 +1,6881 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "be15daa665bc88e290a35baeb00da3d7", + "packages": [ + { + "name": "adbario/php-dot-notation", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/adbario/php-dot-notation.git", + "reference": "081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adbario/php-dot-notation/zipball/081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae", + "reference": "081e2cca50c84bfeeea2e3ef9b2c8d206d80ccae", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^5.5 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7|^6.6|^7.5|^8.5|^9.5", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Adbar\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Riku Särkinen", + "email": "riku@adbar.io" + } + ], + "description": "PHP dot notation access to arrays", + "homepage": "https://github.com/adbario/php-dot-notation", + "keywords": [ + "ArrayAccess", + "dotnotation" + ], + "support": { + "issues": "https://github.com/adbario/php-dot-notation/issues", + "source": "https://github.com/adbario/php-dot-notation/tree/2.5.0" + }, + "time": "2022-10-14T20:31:46+00:00" + }, + { + "name": "alibabacloud/credentials", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/aliyun/credentials-php.git", + "reference": "f6d1986e7b7be8da781d0b99f24c92d9860ba0c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/credentials-php/zipball/f6d1986e7b7be8da781d0b99f24c92d9860ba0c1", + "reference": "f6d1986e7b7be8da781d0b99f24c92d9860ba0c1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "adbario/php-dot-notation": "^2.2", + "alibabacloud/tea": "^3.0", + "ext-curl": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "ext-xmlwriter": "*", + "guzzlehttp/guzzle": "^6.3|^7.0", + "php": ">=5.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "drupal/coder": "^8.3", + "ext-dom": "*", + "ext-pcre": "*", + "ext-sockets": "*", + "ext-spl": "*", + "mikey179/vfsstream": "^1.6", + "monolog/monolog": "^1.24", + "phpunit/phpunit": "^5.7|^6.6|^9.3", + "psr/cache": "^1.0", + "symfony/dotenv": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "suggest": { + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "autoload": { + "psr-4": { + "AlibabaCloud\\Credentials\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com", + "homepage": "http://www.alibabacloud.com" + } + ], + "description": "Alibaba Cloud Credentials for PHP", + "homepage": "https://www.alibabacloud.com/", + "keywords": [ + "alibaba", + "alibabacloud", + "aliyun", + "client", + "cloud", + "credentials", + "library", + "sdk", + "tool" + ], + "support": { + "issues": "https://github.com/aliyun/credentials-php/issues", + "source": "https://github.com/aliyun/credentials-php" + }, + "time": "2025-04-18T09:09:46+00:00" + }, + { + "name": "alibabacloud/darabonba-openapi", + "version": "0.2.15", + "source": { + "type": "git", + "url": "https://github.com/alibabacloud-sdk-php/darabonba-openapi.git", + "reference": "de1a60831d74b0ddf78c16f6d31be57d95b7d49e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alibabacloud-sdk-php/darabonba-openapi/zipball/de1a60831d74b0ddf78c16f6d31be57d95b7d49e", + "reference": "de1a60831d74b0ddf78c16f6d31be57d95b7d49e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "alibabacloud/credentials": "^1.2.2", + "alibabacloud/gateway-spi": "^1", + "alibabacloud/openapi-util": "^0.1.10|^0.2.1", + "alibabacloud/tea-utils": "^0.2.21", + "alibabacloud/tea-xml": "^0.2", + "php": ">5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Darabonba\\OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com" + } + ], + "description": "Alibaba Cloud OpenApi Client", + "support": { + "issues": "https://github.com/alibabacloud-sdk-php/darabonba-openapi/issues", + "source": "https://github.com/alibabacloud-sdk-php/darabonba-openapi/tree/0.2.15" + }, + "time": "2025-04-15T11:59:13+00:00" + }, + { + "name": "alibabacloud/dysmsapi-20170525", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/alibabacloud-sdk-php/Dysmsapi-20170525.git", + "reference": "5a301a076b29449a488607cbfbb7f12b8cc50c1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alibabacloud-sdk-php/Dysmsapi-20170525/zipball/5a301a076b29449a488607cbfbb7f12b8cc50c1f", + "reference": "5a301a076b29449a488607cbfbb7f12b8cc50c1f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "alibabacloud/darabonba-openapi": "^0.2.13", + "alibabacloud/endpoint-util": "^0.1.0", + "alibabacloud/openapi-util": "^0.1.10|^0.2.1", + "alibabacloud/tea-utils": "^0.2.21", + "php": ">5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "AlibabaCloud\\SDK\\Dysmsapi\\V20170525\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com" + } + ], + "description": "Alibaba Cloud Dysmsapi (20170525) SDK Library for PHP", + "support": { + "source": "https://github.com/alibabacloud-sdk-php/Dysmsapi-20170525/tree/3.1.1" + }, + "time": "2025-01-03T17:14:56+00:00" + }, + { + "name": "alibabacloud/endpoint-util", + "version": "0.1.1", + "source": { + "type": "git", + "url": "https://github.com/alibabacloud-sdk-php/endpoint-util.git", + "reference": "f3fe88a25d8df4faa3b0ae14ff202a9cc094e6c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alibabacloud-sdk-php/endpoint-util/zipball/f3fe88a25d8df4faa3b0ae14ff202a9cc094e6c5", + "reference": "f3fe88a25d8df4faa3b0ae14ff202a9cc094e6c5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "AlibabaCloud\\Endpoint\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com" + } + ], + "description": "Alibaba Cloud Endpoint Library for PHP", + "support": { + "source": "https://github.com/alibabacloud-sdk-php/endpoint-util/tree/0.1.1" + }, + "time": "2020-06-04T10:57:15+00:00" + }, + { + "name": "alibabacloud/gateway-spi", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/alibabacloud-sdk-php/alibabacloud-gateway-spi.git", + "reference": "7440f77750c329d8ab252db1d1d967314ccd1fcb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alibabacloud-sdk-php/alibabacloud-gateway-spi/zipball/7440f77750c329d8ab252db1d1d967314ccd1fcb", + "reference": "7440f77750c329d8ab252db1d1d967314ccd1fcb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "alibabacloud/credentials": "^1.1", + "php": ">5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Darabonba\\GatewaySpi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com" + } + ], + "description": "Alibaba Cloud Gateway SPI Client", + "support": { + "source": "https://github.com/alibabacloud-sdk-php/alibabacloud-gateway-spi/tree/1.0.0" + }, + "time": "2022-07-14T05:31:35+00:00" + }, + { + "name": "alibabacloud/openapi-util", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/alibabacloud-sdk-php/openapi-util.git", + "reference": "f31f7bcd835e08ca24b6b8ba33637eb4eceb093a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alibabacloud-sdk-php/openapi-util/zipball/f31f7bcd835e08ca24b6b8ba33637eb4eceb093a", + "reference": "f31f7bcd835e08ca24b6b8ba33637eb4eceb093a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "alibabacloud/tea": "^3.1", + "alibabacloud/tea-utils": "^0.2", + "lizhichao/one-sm": "^1.5", + "php": ">5.5" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "AlibabaCloud\\OpenApiUtil\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com" + } + ], + "description": "Alibaba Cloud OpenApi Util", + "support": { + "issues": "https://github.com/alibabacloud-sdk-php/openapi-util/issues", + "source": "https://github.com/alibabacloud-sdk-php/openapi-util/tree/0.2.1" + }, + "time": "2023-01-10T09:10:10+00:00" + }, + { + "name": "alibabacloud/tea", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/aliyun/tea-php.git", + "reference": "1619cb96c158384f72b873e1f85de8b299c9c367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/tea-php/zipball/1619cb96c158384f72b873e1f85de8b299c9c367", + "reference": "1619cb96c158384f72b873e1f85de8b299c9c367", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "adbario/php-dot-notation": "^2.4", + "ext-curl": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "ext-xmlwriter": "*", + "guzzlehttp/guzzle": "^6.3|^7.0", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "*", + "symfony/dotenv": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "suggest": { + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "autoload": { + "psr-4": { + "AlibabaCloud\\Tea\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com", + "homepage": "http://www.alibabacloud.com" + } + ], + "description": "Client of Tea for PHP", + "homepage": "https://www.alibabacloud.com/", + "keywords": [ + "alibabacloud", + "client", + "cloud", + "tea" + ], + "support": { + "issues": "https://github.com/aliyun/tea-php/issues", + "source": "https://github.com/aliyun/tea-php" + }, + "time": "2023-05-16T06:43:41+00:00" + }, + { + "name": "alibabacloud/tea-utils", + "version": "0.2.21", + "source": { + "type": "git", + "url": "https://github.com/alibabacloud-sdk-php/tea-utils.git", + "reference": "5039e45714c6456186d267f5d81a4b260a652495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alibabacloud-sdk-php/tea-utils/zipball/5039e45714c6456186d267f5d81a4b260a652495", + "reference": "5039e45714c6456186d267f5d81a4b260a652495", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "alibabacloud/tea": "^3.1", + "php": ">5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "AlibabaCloud\\Tea\\Utils\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com" + } + ], + "description": "Alibaba Cloud Tea Utils for PHP", + "support": { + "issues": "https://github.com/aliyun/tea-util/issues", + "source": "https://github.com/aliyun/tea-util" + }, + "time": "2024-07-05T06:05:54+00:00" + }, + { + "name": "alibabacloud/tea-xml", + "version": "0.2.4", + "source": { + "type": "git", + "url": "https://github.com/alibabacloud-sdk-php/tea-xml.git", + "reference": "3e0c000bf536224eebbac913c371bef174c0a16a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alibabacloud-sdk-php/tea-xml/zipball/3e0c000bf536224eebbac913c371bef174c0a16a", + "reference": "3e0c000bf536224eebbac913c371bef174c0a16a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">5.5" + }, + "require-dev": { + "phpunit/phpunit": "*", + "symfony/var-dumper": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "AlibabaCloud\\Tea\\XML\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com" + } + ], + "description": "Alibaba Cloud Tea XML Library for PHP", + "support": { + "source": "https://github.com/alibabacloud-sdk-php/tea-xml/tree/0.2.4" + }, + "time": "2022-08-02T04:12:58+00:00" + }, + { + "name": "aliyuncs/oss-sdk-php", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "php-coveralls/php-coveralls": "*", + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "OSS\\": "src/OSS" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aliyuncs", + "homepage": "http://www.aliyun.com" + } + ], + "description": "Aliyun OSS SDK for PHP", + "homepage": "http://www.aliyun.com/product/oss/", + "support": { + "issues": "https://github.com/aliyun/aliyun-oss-php-sdk/issues", + "source": "https://github.com/aliyun/aliyun-oss-php-sdk/tree/v2.7.2" + }, + "time": "2024-10-28T10:41:12+00:00" + }, + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.337.3", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/06dfc8f76423b49aaa181debd25bbdc724c346d6", + "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "aws/aws-crt-php": "^1.2.3", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + }, + "exclude-from-classmap": [ + "src/data/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.337.3" + }, + "time": "2025-01-21T19:10:05+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.10" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2024-02-18T20:23:39+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:16:48+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-24T11:22:20+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:27:01+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-03-27T12:30:47+00:00" + }, + { + "name": "illuminate/bus", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "c66d57011eec385055e1426d026c270aeecb05aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/c66d57011eec385055e1426d026c270aeecb05aa", + "reference": "c66d57011eec385055e1426d026c270aeecb05aa", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/collections": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/pipeline": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/collections", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "48de3d6bc6aa779112ddcb608a3a96fc975d89d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/48de3d6bc6aa779112ddcb608a3a96fc975d89d8", + "reference": "48de3d6bc6aa779112ddcb608a3a96fc975d89d8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "php": "^8.1" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^6.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "3ee34ac306fafc2a6f19cd7cd68c9af389e432a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/3ee34ac306fafc2a6f19cd7cd68c9af389e432a5", + "reference": "3ee34ac306fafc2a6f19cd7cd68c9af389e432a5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/container", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "ed6253f7dd3a67d468b2cc7a69a657e1f14c7ba3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/ed6253f7dd3a67d468b2cc7a69a657e1f14c7ba3", + "reference": "ed6253f7dd3a67d468b2cc7a69a657e1f14c7ba3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^10.0", + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "f90663a69f926105a70b78060a31f3c64e2d1c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/f90663a69f926105a70b78060a31f3c64e2d1c74", + "reference": "f90663a69f926105a70b78060a31f3c64e2d1c74", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/events", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "3edcdad2f2fe6da6802afb0a256b0f7ee00d72e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/3edcdad2f2fe6da6802afb0a256b0f7ee00d72e9", + "reference": "3edcdad2f2fe6da6802afb0a256b0f7ee00d72e9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/bus": "^10.0", + "illuminate/collections": "^10.0", + "illuminate/container": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2023-06-05T12:46:42+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "3030a131e5e9cb18c9a826428fcffc076df9dcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/3030a131e5e9cb18c9a826428fcffc076df9dcd7", + "reference": "3030a131e5e9cb18c9a826428fcffc076df9dcd7", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/redis", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/redis.git", + "reference": "446d36aeb21fd2b6719293a8d930ae9ac8135be0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/redis/zipball/446d36aeb21fd2b6719293a8d930ae9ac8135be0", + "reference": "446d36aeb21fd2b6719293a8d930ae9ac8135be0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/collections": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "suggest": { + "ext-redis": "Required to use the phpredis connector (^4.0|^5.0).", + "predis/predis": "Required to use the predis connector (^2.0.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Redis package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-01-21T16:06:22+00:00" + }, + { + "name": "illuminate/support", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "6d09b480d34846245d9288f4dcefb17a73ce6e6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/6d09b480d34846245d9288f4dcefb17a73ce6e6a", + "reference": "6d09b480d34846245d9288f4dcefb17a73ce6e6a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^10.0", + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "nesbot/carbon": "^2.67", + "php": "^8.1", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^10.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the composer class (^6.2).", + "symfony/uid": "Required to use Str::ulid() (^6.2).", + "symfony/var-dumper": "Required to use the dd function (^6.2).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-12-10T14:47:55+00:00" + }, + { + "name": "league/flysystem", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.29.0" + }, + "time": "2024-09-29T11:59:11+00:00" + }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9", + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "aws/aws-sdk-php": "^3.295.10", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0" + }, + "time": "2024-08-17T13:10:48+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + }, + "time": "2024-08-09T21:24:39+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-01-28T23:22:08+00:00" + }, + { + "name": "lizhichao/one-sm", + "version": "1.10", + "source": { + "type": "git", + "url": "https://github.com/lizhichao/sm.git", + "reference": "687a012a44a5bfd4d9143a0234e1060543be455a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lizhichao/sm/zipball/687a012a44a5bfd4d9143a0234e1060543be455a", + "reference": "687a012a44a5bfd4d9143a0234e1060543be455a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "OneSm\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "tanszhe", + "email": "1018595261@qq.com" + } + ], + "description": "国密sm3", + "keywords": [ + "php", + "sm3" + ], + "support": { + "issues": "https://github.com/lizhichao/sm/issues", + "source": "https://github.com/lizhichao/sm/tree/1.10" + }, + "funding": [ + { + "url": "https://www.vicsdf.com/img/w.jpg", + "type": "custom" + }, + { + "url": "https://www.vicsdf.com/img/z.jpg", + "type": "custom" + } + ], + "time": "2021-05-26T06:19:22+00:00" + }, + { + "name": "loong/oauth", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/RenLoong/oauth.git", + "reference": "9588637bb93d5b42877539e6b622afc1ef0709a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RenLoong/oauth/zipball/9588637bb93d5b42877539e6b622afc1ef0709a5", + "reference": "9588637bb93d5b42877539e6b622afc1ef0709a5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "loong\\oauth\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Unknown", + "email": "1016683587@qq.com" + } + ], + "description": "基于TP8, 不同系统之间安全传递信息, 用于用户登录验证, 信息传递等", + "support": { + "issues": "https://github.com/RenLoong/oauth/issues", + "source": "https://github.com/RenLoong/oauth/tree/1.1.3" + }, + "time": "2025-11-14T11:03:20+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2025-03-24T10:02:05+00:00" + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" + }, + "time": "2023-08-25T10:54:48+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2022-08-04T09:53:51+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.73.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "<6", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-01-08T20:10:23+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "overtrue/flysystem-cos", + "version": "5.1.11", + "source": { + "type": "git", + "url": "https://github.com/overtrue/flysystem-cos.git", + "reference": "a26a672262aa69006334f9d5730b46adeb7574a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/flysystem-cos/zipball/a26a672262aa69006334f9d5730b46adeb7574a5", + "reference": "a26a672262aa69006334f9d5730b46adeb7574a5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^3.0", + "overtrue/qcloud-cos-client": "^2.1.4", + "php": ">=8.0.2" + }, + "require-dev": { + "laravel/pint": "^1.6", + "league/flysystem-adapter-test-utilities": "^3.0", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Overtrue\\Flysystem\\Cos\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "i@overtrue.me" + } + ], + "description": "Flysystem adapter for the QCloud COS storage.", + "support": { + "issues": "https://github.com/overtrue/flysystem-cos/issues", + "source": "https://github.com/overtrue/flysystem-cos/tree/5.1.11" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2024-09-18T09:27:12+00:00" + }, + { + "name": "overtrue/flysystem-qiniu", + "version": "3.2.5", + "source": { + "type": "git", + "url": "https://github.com/overtrue/flysystem-qiniu.git", + "reference": "49c7eaa22aa24d68c8b24b01a6a93ff7ccbe980a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/flysystem-qiniu/zipball/49c7eaa22aa24d68c8b24b01a6a93ff7ccbe980a", + "reference": "49c7eaa22aa24d68c8b24b01a6a93ff7ccbe980a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^3.0", + "qiniu/php-sdk": "^7.2" + }, + "require-dev": { + "mockery/mockery": "^1.4", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "hooks": { + "pre-push": [ + "composer test", + "composer check-style" + ], + "pre-commit": [ + "composer test", + "composer check-style" + ] + } + }, + "autoload": { + "psr-4": { + "Overtrue\\Flysystem\\Qiniu\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "i@overtrue.me" + } + ], + "description": "Flysystem adapter for the Qiniu storage.", + "support": { + "issues": "https://github.com/overtrue/flysystem-qiniu/issues", + "source": "https://github.com/overtrue/flysystem-qiniu/tree/3.2.5" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2025-05-22T10:16:24+00:00" + }, + { + "name": "overtrue/qcloud-cos-client", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/overtrue/qcloud-cos-client.git", + "reference": "0fef6153fdb37fc7fbb3b6c29cc746310c662361" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/qcloud-cos-client/zipball/0fef6153fdb37fc7fbb3b6c29cc746310c662361", + "reference": "0fef6153fdb37fc7fbb3b6c29cc746310c662361", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^7.4", + "php": ">=8.0.2", + "psr/http-message": "^1.0|^2.0", + "thenorthmemory/xml": "^1.0" + }, + "require-dev": { + "brainmaestro/composer-git-hooks": "^2.8", + "friendsofphp/php-cs-fixer": "^3.5", + "laravel/pint": "^1.2", + "mockery/mockery": "^1.0", + "monolog/monolog": "^2.5", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "hooks": { + "pre-push": [ + "composer test", + "composer check-style" + ], + "pre-commit": [ + "composer test", + "composer check-style" + ] + } + }, + "autoload": { + "psr-4": { + "Overtrue\\CosClient\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "Client of QCloud.com COS", + "support": { + "issues": "https://github.com/overtrue/qcloud-cos-client/issues", + "source": "https://github.com/overtrue/qcloud-cos-client/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2025-05-16T08:35:45+00:00" + }, + { + "name": "php-ffmpeg/php-ffmpeg", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git", + "reference": "8e74bdc07ad200da7a6cfb21ec2652875e4368e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/8e74bdc07ad200da7a6cfb21ec2652875e4368e0", + "reference": "8e74bdc07ad200da7a6cfb21ec2652875e4368e0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "evenement/evenement": "^3.0", + "php": "^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "spatie/temporary-directory": "^2.0", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "phpunit/phpunit": "^9.5.10 || ^10.0" + }, + "suggest": { + "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg" + }, + "type": "library", + "autoload": { + "psr-4": { + "FFMpeg\\": "src/FFMpeg", + "Alchemy\\BinaryDriver\\": "src/Alchemy/BinaryDriver" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Neutron", + "email": "imprec@gmail.com", + "homepage": "http://www.lickmychip.com/" + }, + { + "name": "Phraseanet Team", + "email": "info@alchemy.fr", + "homepage": "http://www.phraseanet.com/" + }, + { + "name": "Patrik Karisch", + "email": "patrik@karisch.guru", + "homepage": "http://www.karisch.guru" + }, + { + "name": "Romain Biard", + "email": "romain.biard@gmail.com", + "homepage": "https://www.strime.io/" + }, + { + "name": "Jens Hausdorf", + "email": "hello@jens-hausdorf.de", + "homepage": "https://jens-hausdorf.de" + }, + { + "name": "Pascal Baljet", + "email": "pascal@protone.media", + "homepage": "https://protone.media" + } + ], + "description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg", + "keywords": [ + "audio", + "audio processing", + "avconv", + "avprobe", + "ffmpeg", + "ffprobe", + "video", + "video processing" + ], + "support": { + "issues": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/issues", + "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.3.2" + }, + "time": "2025-04-01T20:36:46+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, + { + "name": "phrity/net-uri", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-net-uri.git", + "reference": "3f458e0c4d1ddc0e218d7a5b9420127c63925f43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-net-uri/zipball/3f458e0c4d1ddc0e218d7a5b9420127c63925f43", + "reference": "3f458e0c4d1ddc0e218d7a5b9420127c63925f43", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.4 | ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 | ^2.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Net\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "PSR-7 Uri and PSR-17 UriFactory implementation", + "homepage": "https://phrity.sirn.se/net-uri", + "keywords": [ + "psr-17", + "psr-7", + "uri", + "uri factory" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-net-uri/issues", + "source": "https://github.com/sirn-se/phrity-net-uri/tree/1.3.0" + }, + "time": "2023-08-21T10:33:06+00:00" + }, + { + "name": "phrity/util-errorhandler", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sirn-se/phrity-util-errorhandler.git", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/483228156e06673963902b1cc1e6bd9541ab4d5e", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.4 | ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phrity\\Util\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sören Jensen", + "email": "sirn@sirn.se", + "homepage": "https://phrity.sirn.se" + } + ], + "description": "Inline error handler; catch and resolve errors for code block.", + "homepage": "https://phrity.sirn.se/util-errorhandler", + "keywords": [ + "error", + "warning" + ], + "support": { + "issues": "https://github.com/sirn-se/phrity-util-errorhandler/issues", + "source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.1.1" + }, + "time": "2024-09-12T06:49:16+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "79dff0b268932c640297f5208d6298f71855c03e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", + "reference": "79dff0b268932c640297f5208d6298f71855c03e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.1" + }, + "time": "2024-08-21T13:31:24+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "qiniu/php-sdk", + "version": "v7.14.0", + "source": { + "type": "git", + "url": "https://github.com/qiniu/php-sdk.git", + "reference": "ee752ffa7263ce99fca0bd7340cf13c486a3516c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/qiniu/php-sdk/zipball/ee752ffa7263ce99fca0bd7340cf13c486a3516c", + "reference": "ee752ffa7263ce99fca0bd7340cf13c486a3516c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-curl": "*", + "ext-xml": "*", + "myclabs/php-enum": "~1.5.2 || ~1.6.6 || ~1.7.7 || ~1.8.4", + "php": ">=5.3.3" + }, + "require-dev": { + "paragonie/random_compat": ">=2", + "phpunit/phpunit": "^4.8 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4", + "squizlabs/php_codesniffer": "^2.3 || ~3.6" + }, + "type": "library", + "autoload": { + "files": [ + "src/Qiniu/functions.php", + "src/Qiniu/Http/Middleware/Middleware.php" + ], + "psr-4": { + "Qiniu\\": "src/Qiniu" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Qiniu", + "email": "sdk@qiniu.com", + "homepage": "http://www.qiniu.com" + } + ], + "description": "Qiniu Resource (Cloud) Storage SDK for PHP", + "homepage": "http://developer.qiniu.com/", + "keywords": [ + "cloud", + "qiniu", + "sdk", + "storage" + ], + "support": { + "issues": "https://github.com/qiniu/php-sdk/issues", + "source": "https://github.com/qiniu/php-sdk/tree/v7.14.0" + }, + "time": "2024-10-25T08:39:01+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "shopwwi/filesystem-oss", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/filesystem-oss.git", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/filesystem-oss/zipball/85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "aliyuncs/oss-sdk-php": "^2.4", + "league/flysystem": "^2.1|^3.0", + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\FilesystemOss\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "AliYun OSS adapter for flysystem V2. aliyuncs/oss-sdk-php ~2.4", + "homepage": "https://github.com/phcent/filesystem-oss", + "keywords": [ + "Flysystem", + "aliyun-oss", + "flysystem-aliyun-oss" + ], + "support": { + "issues": "https://github.com/shopwwi/filesystem-oss/issues", + "source": "https://github.com/shopwwi/filesystem-oss/tree/v1.0.3" + }, + "time": "2022-10-01T03:00:18+00:00" + }, + { + "name": "shopwwi/webman-filesystem", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/webman-filesystem.git", + "reference": "40d85f01d29513299c1d9d129e29d57624bebeae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/webman-filesystem/zipball/40d85f01d29513299c1d9d129e29d57624bebeae", + "reference": "40d85f01d29513299c1d9d129e29d57624bebeae", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^2.3|^3.0", + "php": ">=7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\WebmanFilesystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "Flysystem V2 adapter for the webman", + "keywords": [ + "cos", + "filesystem", + "oss", + "qiniu", + "webman" + ], + "support": { + "issues": "https://github.com/shopwwi/webman-filesystem/issues", + "source": "https://github.com/shopwwi/webman-filesystem/tree/v1.1.1" + }, + "time": "2024-04-29T12:40:45+00:00" + }, + { + "name": "spatie/temporary-directory", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-01-13T13:04:43+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.4.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "95af448bb7c3d8db02f7b4f5cbf3cb7a6ff1e432" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/95af448bb7c3d8db02f7b4f5cbf3cb7a6ff1e432", + "reference": "95af448bb7c3d8db02f7b4f5cbf3cb7a6ff1e432", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.3.6|^7.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.4.20" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-08T15:51:34+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a3011c7b7adb58d89f6c0d822abb641d7a5f9719", + "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-07T15:42:41+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.20" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-10T17:11:00+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-18T15:23:29+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "bb92ea5588396b319ba43283a5a3087a034cb29c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/bb92ea5588396b319ba43283a5a3087a034cb29c", + "reference": "bb92ea5588396b319ba43283a5a3087a034cb29c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-07T19:02:30+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "717e7544aa99752c54ecba5c0e17459c48317472" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/717e7544aa99752c54ecba5c0e17459c48317472", + "reference": "717e7544aa99752c54ecba5c0e17459c48317472", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-27T21:06:26+00:00" + }, + { + "name": "tencentcloud/common", + "version": "3.0.1339", + "source": { + "type": "git", + "url": "https://github.com/tencentcloud-sdk-php/common.git", + "reference": "97fea23b090ac99798050a3dff636182ba1f9a2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tencentcloud-sdk-php/common/zipball/97fea23b090ac99798050a3dff636182ba1f9a2c", + "reference": "97fea23b090ac99798050a3dff636182ba1f9a2c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "guzzlehttp/guzzle": "^6.3||^7.0", + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TencentCloud\\": "./src/TencentCloud" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "tencentcloudapi", + "email": "tencentcloudapi@tencent.com", + "homepage": "https://cloud.tencent.com/document/sdk/PHP", + "role": "Developer" + } + ], + "description": "TencentCloudApi php sdk", + "homepage": "https://github.com/tencentcloud-sdk-php/common", + "support": { + "issues": "https://github.com/tencentcloud-sdk-php/common/issues", + "source": "https://github.com/tencentcloud-sdk-php/common/tree/3.0.1339" + }, + "time": "2025-03-14T10:52:02+00:00" + }, + { + "name": "tencentcloud/sms", + "version": "3.0.1339", + "source": { + "type": "git", + "url": "https://github.com/tencentcloud-sdk-php/sms.git", + "reference": "94f5f648582c9482092aae3dd04cd868c0347248" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tencentcloud-sdk-php/sms/zipball/94f5f648582c9482092aae3dd04cd868c0347248", + "reference": "94f5f648582c9482092aae3dd04cd868c0347248", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "tencentcloud/common": "3.0.1339" + }, + "type": "library", + "autoload": { + "psr-4": { + "TencentCloud\\": "./src/TencentCloud" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "tencentcloudapi", + "email": "tencentcloudapi@tencent.com", + "homepage": "https://github.com/tencentcloud-sdk-php/sms", + "role": "Developer" + } + ], + "description": "TencentCloudApi php sdk sms", + "homepage": "https://github.com/tencentcloud-sdk-php/sms", + "support": { + "issues": "https://github.com/tencentcloud-sdk-php/sms/issues", + "source": "https://github.com/tencentcloud-sdk-php/sms/tree/3.0.1339" + }, + "time": "2025-03-14T11:30:53+00:00" + }, + { + "name": "textalk/websocket", + "version": "1.6.3", + "source": { + "type": "git", + "url": "https://github.com/Textalk/websocket-php.git", + "reference": "67de79745b1a357caf812bfc44e0abf481cee012" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Textalk/websocket-php/zipball/67de79745b1a357caf812bfc44e0abf481cee012", + "reference": "67de79745b1a357caf812bfc44e0abf481cee012", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.4 | ^8.0", + "phrity/net-uri": "^1.0", + "phrity/util-errorhandler": "^1.0", + "psr/http-message": "^1.0", + "psr/log": "^1.0 | ^2.0 | ^3.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "WebSocket\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Fredrik Liljegren" + }, + { + "name": "Sören Jensen" + } + ], + "description": "WebSocket client and server", + "support": { + "issues": "https://github.com/Textalk/websocket-php/issues", + "source": "https://github.com/Textalk/websocket-php/tree/1.6.3" + }, + "time": "2022-11-07T18:59:33+00:00" + }, + { + "name": "thenorthmemory/xml", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/TheNorthMemory/xml.git", + "reference": "6f50c63450a0b098772423f8bdc3c4ad2c4c30bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheNorthMemory/xml/zipball/6f50c63450a0b098772423f8bdc3c4ad2c4c30bb", + "reference": "6f50c63450a0b098772423f8bdc3c4ad2c4c30bb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-libxml": "*", + "ext-simplexml": "*", + "php": ">=7.1.2" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.89 || ^1.0", + "phpunit/phpunit": "^7.5 || ^8.5.16 || ^9.3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "TheNorthMemory\\Xml\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "James ZHANG", + "homepage": "https://github.com/TheNorthMemory" + } + ], + "description": "A wrapper of the XML parser and builder", + "homepage": "https://github.com/TheNorthMemory/xml", + "keywords": [ + "xml-builder", + "xml-parser" + ], + "support": { + "issues": "https://github.com/TheNorthMemory/xml/issues", + "source": "https://github.com/TheNorthMemory/xml/tree/1.1.1" + }, + "time": "2023-01-15T06:01:13+00:00" + }, + { + "name": "topthink/think-container", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-container.git", + "reference": "a24d442a02fb2a4716de232ff1a4f006c178a370" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-container/zipball/a24d442a02fb2a4716de232ff1a4f006c178a370", + "reference": "a24d442a02fb2a4716de232ff1a4f006c178a370", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0", + "psr/container": "^2.0", + "topthink/think-helper": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "PHP Container & Facade Manager", + "support": { + "issues": "https://github.com/top-think/think-container/issues", + "source": "https://github.com/top-think/think-container/tree/v3.0.1" + }, + "time": "2025-01-07T08:19:23+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.11", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.11" + }, + "time": "2025-04-07T06:55:59+00:00" + }, + { + "name": "topthink/think-orm", + "version": "v4.0.40", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "1637860ff736859058f0a5003c7cc719c2068dbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/1637860ff736859058f0a5003c7cc719c2068dbc", + "reference": "1637860ff736859058f0a5003c7cc719c2068dbc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-pdo": "*", + "php": ">=8.0.0", + "psr/log": ">=1.0", + "psr/simple-cache": ">=1.0", + "topthink/think-helper": "^3.1", + "topthink/think-validate": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6|^10" + }, + "suggest": { + "ext-mongodb": "provide mongodb support" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php", + "stubs/load_stubs.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the PHP Database&ORM Framework", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v4.0.40" + }, + "time": "2025-05-27T03:32:05+00:00" + }, + { + "name": "topthink/think-template", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-template.git", + "reference": "0b88bd449f0f7626dd75b05f557c8bc208c08b0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-template/zipball/0b88bd449f0f7626dd75b05f557c8bc208c08b0c", + "reference": "0b88bd449f0f7626dd75b05f557c8bc208c08b0c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0.0", + "psr/simple-cache": ">=1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the php template engine", + "support": { + "issues": "https://github.com/top-think/think-template/issues", + "source": "https://github.com/top-think/think-template/tree/v3.0.2" + }, + "time": "2024-10-16T03:41:06+00:00" + }, + { + "name": "topthink/think-validate", + "version": "v3.0.6", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-validate.git", + "reference": "c6a7704fc991d3689408299822dbc2ba4ee3f79a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-validate/zipball/c6a7704fc991d3689408299822dbc2ba4ee3f79a", + "reference": "c6a7704fc991d3689408299822dbc2ba4ee3f79a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0", + "topthink/think-container": ">=3.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think validate", + "support": { + "issues": "https://github.com/top-think/think-validate/issues", + "source": "https://github.com/top-think/think-validate/tree/v3.0.6" + }, + "time": "2025-05-23T04:10:45+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:43:29+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "16c17671a804bb92602822113dd91fbc8a35d2af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/16c17671a804bb92602822113dd91fbc8a35d2af", + "reference": "16c17671a804bb92602822113dd91fbc8a35d2af", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T00:49:12+00:00" + }, + { + "name": "webman/cache", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/webman-php/cache.git", + "reference": "66a5461ea51d23364403b54ee218b736d26bb03f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/cache/zipball/66a5461ea51d23364403b54ee218b736d26bb03f", + "reference": "66a5461ea51d23364403b54ee218b736d26bb03f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "psr/simple-cache": "^3.0", + "symfony/cache": "^6.0 || ^7.0", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "support\\": "src/support", + "Webman\\Cache\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/webman-php/cache/issues", + "source": "https://github.com/webman-php/cache/tree/v2.1.2" + }, + "time": "2025-04-11T01:23:50+00:00" + }, + { + "name": "webman/captcha", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/webman-php/captcha.git", + "reference": "44dc1e74b23e180e1dcb361c11b2ca8bc2e6b32c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/captcha/zipball/44dc1e74b23e180e1dcb361c11b2ca8bc2e6b32c", + "reference": "44dc1e74b23e180e1dcb361c11b2ca8bc2e6b32c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Captcha\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net" + }, + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "support": { + "source": "https://github.com/webman-php/captcha/tree/v1.0.4" + }, + "time": "2024-05-14T02:56:38+00:00" + }, + { + "name": "webman/console", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/webman-php/console.git", + "reference": "bbee274a5f091eaf90e7a257dd0fbfef47da9a17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/console/zipball/bbee274a5f091eaf90e7a257dd0fbfef47da9a17", + "reference": "bbee274a5f091eaf90e7a257dd0fbfef47da9a17", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/inflector": "^2.0", + "symfony/console": ">=5.0" + }, + "require-dev": { + "workerman/webman": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Console\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "Webman console", + "homepage": "http://www.workerman.net", + "keywords": [ + "webman console" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://www.workerman.net/questions", + "issues": "https://github.com/webman-php/console/issues", + "source": "https://github.com/webman-php/console", + "wiki": "http://www.workerman.net/doc/webman" + }, + "time": "2025-03-06T08:23:07+00:00" + }, + { + "name": "webman/event", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/webman-php/event.git", + "reference": "f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/event/zipball/f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7", + "reference": "f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Event\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman event plugin", + "support": { + "issues": "https://github.com/webman-php/event/issues", + "source": "https://github.com/webman-php/event/tree/v1.0.4" + }, + "time": "2023-03-28T04:01:23+00:00" + }, + { + "name": "webman/push", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/webman-php/push.git", + "reference": "b9e51d39a6ae232eab6b3f5c48a918857976add8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/push/zipball/b9e51d39a6ae232eab6b3f5c48a918857976add8", + "reference": "b9e51d39a6ae232eab6b3f5c48a918857976add8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Push\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/webman-php/push/issues", + "source": "https://github.com/webman-php/push/tree/v1.1.1" + }, + "time": "2025-05-15T14:32:34+00:00" + }, + { + "name": "webman/redis", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/webman-php/redis.git", + "reference": "83893f0931e07906ba9dbf976126ad8338f40624" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/redis/zipball/83893f0931e07906ba9dbf976126ad8338f40624", + "reference": "83893f0931e07906ba9dbf976126ad8338f40624", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/redis": "^10.0 || ^11.0", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "support\\": "src/support", + "Webman\\Redis\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman redis", + "support": { + "issues": "https://github.com/webman-php/redis/issues", + "source": "https://github.com/webman-php/redis/tree/v2.1.2" + }, + "time": "2025-02-14T11:36:24+00:00" + }, + { + "name": "webman/think-orm", + "version": "v2.1.6", + "source": { + "type": "git", + "url": "https://github.com/webman-php/think-orm.git", + "reference": "af4a2586177a333983e0da1bf6512ec12f894b29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/think-orm/zipball/af4a2586177a333983e0da1bf6512ec12f894b29", + "reference": "af4a2586177a333983e0da1bf6512ec12f894b29", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1", + "topthink/think-container": "^2.0|^3.0", + "topthink/think-orm": "^2.0.53 || ^3.0.0 || ^4.0.30 || dev-master", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "support\\": "src/support", + "Webman\\ThinkOrm\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/webman-php/think-orm/issues", + "source": "https://github.com/webman-php/think-orm/tree/v2.1.6" + }, + "time": "2025-05-27T13:15:25+00:00" + }, + { + "name": "workerman/coroutine", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/workerman-php/coroutine.git", + "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/df8fc428967d512a74a8a7d80355c1d40228c9fa", + "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "psr/log": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "src", + "Workerman\\Coroutine\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Workerman coroutine", + "support": { + "issues": "https://github.com/workerman-php/coroutine/issues", + "source": "https://github.com/workerman-php/coroutine/tree/v1.1.3" + }, + "time": "2025-02-17T03:34:21+00:00" + }, + { + "name": "workerman/crontab", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/walkor/crontab.git", + "reference": "74f51ca8204e8eb628e57bc0e640561d570da2cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/crontab/zipball/74f51ca8204e8eb628e57bc0e640561d570da2cb", + "reference": "74f51ca8204e8eb628e57bc0e640561d570da2cb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0", + "workerman/workerman": ">=4.0.20" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\Crontab\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "A crontab written in PHP based on workerman", + "homepage": "http://www.workerman.net", + "keywords": [ + "crontab" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/crontab", + "wiki": "http://doc.workerman.net/" + }, + "time": "2025-01-15T07:20:50+00:00" + }, + { + "name": "workerman/webman-framework", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "f803bd867f07bb0929faef060b59a19a44186bfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/f803bd867f07bb0929faef060b59a19a44186bfc", + "reference": "f803bd867f07bb0929faef060b59a19a44186bfc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": ">=8.1", + "psr/container": ">=1.0", + "psr/log": "^3.0", + "workerman/workerman": "^5.1 || dev-master" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "files": [ + "./src/support/helpers.php" + ], + "psr-4": { + "Webman\\": "./src", + "Support\\": "./src/support", + "support\\": "./src/support", + "Support\\View\\": "./src/support/view", + "Support\\Bootstrap\\": "./src/support/bootstrap", + "Support\\Exception\\": "./src/support/exception" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "description": "High performance HTTP Service Framework.", + "homepage": "https://www.workerman.net", + "keywords": [ + "High Performance", + "http service" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman-framework", + "wiki": "https://doc.workerman.net/" + }, + "time": "2025-03-10T11:52:22+00:00" + }, + { + "name": "workerman/workerman", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "5ce17bc71dfdad0e9eba8cd658342da793d2f581" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/5ce17bc71dfdad0e9eba8cd658342da793d2f581", + "reference": "5ce17bc71dfdad0e9eba8cd658342da793d2f581", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": ">=8.1", + "workerman/coroutine": "^1.1 || dev-main" + }, + "conflict": { + "ext-swow": "=8.0", + "psr/container": "^1.1 || ^2.0", + "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "yansongda/supports": "~4.0.10" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.44", + "guzzlehttp/guzzle": "^7.0", + "hyperf/pimple": "^2.2", + "mockery/mockery": "^1.4", + "monolog/monolog": "^2.2", + "phpstan/phpstan": "^1.0.0 || ^2.0.0", + "phpunit/phpunit": "^9.0", + "symfony/event-dispatcher": "^5.2.0", + "symfony/http-foundation": "^5.2.0", + "symfony/psr-http-message-bridge": "^2.1", + "symfony/var-dumper": "^5.1" + }, + "suggest": { + "hyperf/pimple": "其它/无框架下使用 SDK,请安装,任选其一", + "illuminate/container": "其它/无框架下使用 SDK,请安装,任选其一" + }, + "type": "library", + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Yansongda\\Artful\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "Artful 是一个简单易用的 API 请求框架 PHP Api RequesT Framwork U Like。", + "keywords": [ + "api", + "artful", + "framework", + "request" + ], + "support": { + "homepage": "https://artful.yansongda.cn", + "issues": "https://github.com/yansongda/artful/issues", + "source": "https://github.com/yansongda/artful" + }, + "time": "2025-01-09T12:39:39+00:00" + }, + { + "name": "yansongda/pay", + "version": "v3.7.11", + "source": { + "type": "git", + "url": "https://github.com/yansongda/pay.git", + "reference": "0c8bcb8f67b56fae6c852a5b3ec0a1793f5c8ddb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yansongda/pay/zipball/0c8bcb8f67b56fae6c852a5b3ec0a1793f5c8ddb", + "reference": "0c8bcb8f67b56fae6c852a5b3ec0a1793f5c8ddb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-bcmath": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "php": ">=8.0", + "yansongda/artful": "~1.1.1", + "yansongda/supports": "~4.0.10" + }, + "conflict": { + "hyperf/framework": "<3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.44", + "guzzlehttp/guzzle": "^7.0", + "hyperf/pimple": "^2.2", + "jetbrains/phpstorm-attributes": "^1.1", + "mockery/mockery": "^1.4", + "monolog/monolog": "^2.2", + "phpstan/phpstan": "^1.0.0", + "phpunit/phpunit": "^9.0", + "symfony/event-dispatcher": "^5.2.0", + "symfony/http-foundation": "^5.2.0", + "symfony/psr-http-message-bridge": "^2.1", + "symfony/var-dumper": "^5.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Yansongda\\Pay\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "可能是我用过的最优雅的 Alipay 和 WeChat 的支付 SDK 扩展包了", + "keywords": [ + "alipay", + "pay", + "wechat" + ], + "support": { + "homepage": "https://pay.yansongda.cn", + "issues": "https://github.com/yansongda/pay/issues", + "source": "https://github.com/yansongda/pay" + }, + "time": "2024-11-23T03:29:59+00:00" + }, + { + "name": "yansongda/supports", + "version": "v4.0.10", + "source": { + "type": "git", + "url": "https://github.com/yansongda/supports.git", + "reference": "11cc73776e6d4d763a84c8c733f64820abdc44e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yansongda/supports/zipball/11cc73776e6d4d763a84c8c733f64820abdc44e5", + "reference": "11cc73776e6d4d763a84c8c733f64820abdc44e5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "mockery/mockery": "^1.4", + "phpstan/phpstan": "^1.1.0", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "monolog/monolog": "Use logger", + "symfony/console": "Use stdout logger" + }, + "type": "library", + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Yansongda\\Supports\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "common components", + "keywords": [ + "array", + "collection", + "config", + "support" + ], + "support": { + "issues": "https://github.com/yansongda/supports/issues", + "source": "https://github.com/yansongda/supports" + }, + "time": "2024-06-09T15:49:21+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/fastmovie-admin/config/app.php b/fastmovie-admin/config/app.php new file mode 100644 index 0000000..ee8725f --- /dev/null +++ b/fastmovie-admin/config/app.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Request; + +return [ + 'debug' => getenv('DEBUG') === 'true', + 'error_reporting' => E_ALL, + 'default_timezone' => 'Asia/Shanghai', + 'request_class' => Request::class, + 'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public', + 'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime', + 'controller_suffix' => 'Controller', + 'controller_reuse' => false, +]; diff --git a/fastmovie-admin/config/autoload.php b/fastmovie-admin/config/autoload.php new file mode 100644 index 0000000..69a8135 --- /dev/null +++ b/fastmovie-admin/config/autoload.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'files' => [ + base_path() . '/app/functions.php', + base_path() . '/support/Request.php', + base_path() . '/support/Response.php', + ] +]; diff --git a/fastmovie-admin/config/bootstrap.php b/fastmovie-admin/config/bootstrap.php new file mode 100644 index 0000000..d5b7ccd --- /dev/null +++ b/fastmovie-admin/config/bootstrap.php @@ -0,0 +1,19 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + support\bootstrap\Session::class, + Webman\ThinkOrm\ThinkOrm::class, + support\Event::class, +]; diff --git a/fastmovie-admin/config/cache.php b/fastmovie-admin/config/cache.php new file mode 100644 index 0000000..d2aa8d7 --- /dev/null +++ b/fastmovie-admin/config/cache.php @@ -0,0 +1,31 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => 'redis', + 'stores' => [ + 'file' => [ + 'driver' => 'file', + 'path' => runtime_path('cache') + ], + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default' + ], + 'array' => [ + 'driver' => 'array' + ] + ] +]; \ No newline at end of file diff --git a/fastmovie-admin/config/container.php b/fastmovie-admin/config/container.php new file mode 100644 index 0000000..106b7b4 --- /dev/null +++ b/fastmovie-admin/config/container.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return new Webman\Container; \ No newline at end of file diff --git a/fastmovie-admin/config/database.php b/fastmovie-admin/config/database.php new file mode 100644 index 0000000..7dc463a --- /dev/null +++ b/fastmovie-admin/config/database.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; diff --git a/fastmovie-admin/config/dependence.php b/fastmovie-admin/config/dependence.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/fastmovie-admin/config/dependence.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/fastmovie-admin/config/event.php b/fastmovie-admin/config/event.php new file mode 100644 index 0000000..aef6ac3 --- /dev/null +++ b/fastmovie-admin/config/event.php @@ -0,0 +1,5 @@ + app\exception\Handler::class, + 'control' => app\exception\Handler::class, +]; diff --git a/fastmovie-admin/config/log.php b/fastmovie-admin/config/log.php new file mode 100644 index 0000000..887b5ec --- /dev/null +++ b/fastmovie-admin/config/log.php @@ -0,0 +1,32 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/webman.log', + 7, //$maxFiles + getenv('DEBUG') === 'true'?\Monolog\Level::Debug:\Monolog\Level::Warning, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/fastmovie-admin/config/middleware.php b/fastmovie-admin/config/middleware.php new file mode 100644 index 0000000..b1fdb5e --- /dev/null +++ b/fastmovie-admin/config/middleware.php @@ -0,0 +1,10 @@ + [ + app\middleware\Template::class, + app\middleware\Platform::class + ], + 'admin' => [ + app\admin\middleware\Auth::class + ] +]; diff --git a/fastmovie-admin/config/oauth.php b/fastmovie-admin/config/oauth.php new file mode 100644 index 0000000..d833b6a --- /dev/null +++ b/fastmovie-admin/config/oauth.php @@ -0,0 +1,16 @@ + config_path('certs'), + 'rsa_privatekey' => config_path('certs') . '/rsa_private.pem', + 'rsa_publickey' => config_path('certs') . '/rsa_public.pem', + 'prefix' => 'oauth', + 'expire' => 3600*24*30, + 'single' => false, + 'redis' => [ + 'host' => getenv('REDIS_HOST'), + 'password' => getenv('REDIS_PASSWORD'), + 'port' => getenv('REDIS_PORT'), + 'select' => getenv('REDIS_DATABASE'), + ], +]; diff --git a/fastmovie-admin/config/plugin/shopwwi/filesystem/app.php b/fastmovie-admin/config/plugin/shopwwi/filesystem/app.php new file mode 100644 index 0000000..e6de608 --- /dev/null +++ b/fastmovie-admin/config/plugin/shopwwi/filesystem/app.php @@ -0,0 +1,118 @@ + true, + 'default' => 'local', + 'max_size' => 1024 * 1024 * 10, //单个文件大小10M + 'ext_yes' => [], //允许上传文件类型 为空则为允许所有 + 'ext_no' => [], // 不允许上传文件类型 为空则不限制 + 'storage' => [ + 'public' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => public_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'local' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => base_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'ftp' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class, + 'host' => 'ftp.example.com', + 'username' => 'username', + 'password' => 'password', + 'url' => '', // 静态文件访问域名 + 'port' => 21, + 'root' => '/path/to/root', + 'passive' => true, + 'ssl' => true, + 'timeout' => 30, + 'ignorePassiveAddress' => false, + 'timestampsOnUnixListingsEnabled' => true, + ], + 'memory' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\MemoryAdapterFactory::class, + ], + 's3' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => false, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'minio' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'oss' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class, + 'accessId' => 'OSS_ACCESS_ID', + 'accessSecret' => 'OSS_ACCESS_SECRET', + 'bucket' => 'OSS_BUCKET', + 'endpoint' => 'OSS_ENDPOINT', + 'url' => '', // 静态文件访问域名 + 'timeout' => 3600, + 'connectTimeout' => 10, + 'isCName' => false, + 'token' => null, + 'proxy' => null, + ], + 'qiniu' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class, + 'accessKey' => 'QINIU_ACCESS_KEY', + 'secretKey' => 'QINIU_SECRET_KEY', + 'bucket' => 'QINIU_BUCKET', + 'domain' => 'QINBIU_DOMAIN', + 'url' => '' // 静态文件访问域名 + ], + 'cos' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class, + 'region' => 'COS_REGION', + 'app_id' => 'COS_APPID', + 'secret_id' => 'COS_SECRET_ID', + 'secret_key' => 'COS_SECRET_KEY', + // 可选,如果 bucket 为私有访问请打开此项 + 'signed_url' => false, + 'bucket' => 'COS_BUCKET', + 'read_from_cdn' => false, + 'url' => '' // 静态文件访问域名 + // 'timeout' => 60, + // 'connect_timeout' => 60, + // 'cdn' => '', + // 'scheme' => 'https', + ], + ], +]; +$filePath = config_path("plugin/shopwwi/filesystem/app.json"); +if (file_exists($filePath)) { + $user = file_get_contents($filePath); + $user = json_decode($user, true); + foreach ($user as $key => $value) { + if ($key !== 'storage') { + $origin[$key] = $value; + } + } + foreach ($origin['storage'] as $key => $value) { + if (isset($user['storage'][$key])) { + $origin['storage'][$key] = array_merge($origin['storage'][$key], $user['storage'][$key]); + } + } +} +return $origin; diff --git a/fastmovie-admin/config/plugin/webman/console/app.php b/fastmovie-admin/config/plugin/webman/console/app.php new file mode 100644 index 0000000..05ef286 --- /dev/null +++ b/fastmovie-admin/config/plugin/webman/console/app.php @@ -0,0 +1,22 @@ + true, + + 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build', + + 'phar_filename' => 'webman.phar', + + 'bin_filename' => 'webman.bin', + + 'signature_algorithm' => Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL. + + 'private_key_file' => '', // The file path for certificate or OpenSSL private key file. + + 'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#', + + 'exclude_files' => [ + '.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin' + ], + + 'custom_ini' => 'memory_limit = 256M', +]; diff --git a/fastmovie-admin/config/plugin/webman/event/app.php b/fastmovie-admin/config/plugin/webman/event/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/fastmovie-admin/config/plugin/webman/event/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/fastmovie-admin/config/plugin/webman/event/bootstrap.php b/fastmovie-admin/config/plugin/webman/event/bootstrap.php new file mode 100644 index 0000000..e5b09ba --- /dev/null +++ b/fastmovie-admin/config/plugin/webman/event/bootstrap.php @@ -0,0 +1,17 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + Webman\Event\BootStrap::class, +]; diff --git a/fastmovie-admin/config/plugin/webman/event/command.php b/fastmovie-admin/config/plugin/webman/event/command.php new file mode 100644 index 0000000..e860cf7 --- /dev/null +++ b/fastmovie-admin/config/plugin/webman/event/command.php @@ -0,0 +1,7 @@ + $enable, + 'websocket' => 'websocket://0.0.0.0:'.getenv('PUSH_WSS_PORT'), + 'api' => 'http://0.0.0.0:'.getenv('PUSH_API_PORT'), + 'app_key' => getenv('PUSH_KEY'), + 'app_secret' => getenv('PUSH_SCERET'), + 'channel_hook' => 'http://127.0.0.1:'.getenv('SERVER_PORT').'/app/notification/push/hook', + 'auth' => '/app/notification/api/push/auth' +]; +return $d; \ No newline at end of file diff --git a/fastmovie-admin/config/plugin/webman/push/process.php b/fastmovie-admin/config/plugin/webman/push/process.php new file mode 100644 index 0000000..faa8817 --- /dev/null +++ b/fastmovie-admin/config/plugin/webman/push/process.php @@ -0,0 +1,21 @@ + [ + 'enable' => $enable, + 'handler' => Server::class, + 'listen' => 'websocket://0.0.0.0:'.getenv('PUSH_WSS_PORT'), + 'count' => 1, + 'constructor' => [ + 'api_listen' => 'http://0.0.0.0:'.getenv('PUSH_API_PORT'), + 'app_info' => [ + getenv('PUSH_KEY') => [ + 'channel_hook' => 'http://127.0.0.1:'.getenv('SERVER_PORT').'/app/notification/push/hook', + 'app_secret' => getenv('PUSH_SCERET'), + ], + ], + ] + ], +]; diff --git a/fastmovie-admin/config/plugin/webman/push/route.php b/fastmovie-admin/config/plugin/webman/push/route.php new file mode 100644 index 0000000..c95f6c3 --- /dev/null +++ b/fastmovie-admin/config/plugin/webman/push/route.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Request; +use Webman\Route; + +$enable=file_exists(base_path('plugin/notification')); +if($enable){ + /** + * 推送js客户端文件 + */ + Route::get('/plugin/webman/push/push.js', function (Request $request) { + return response()->file(base_path().'/vendor/webman/push/src/push.js'); + }); +} \ No newline at end of file diff --git a/fastmovie-admin/config/process.php b/fastmovie-admin/config/process.php new file mode 100644 index 0000000..918b2a8 --- /dev/null +++ b/fastmovie-admin/config/process.php @@ -0,0 +1,73 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use app\process\AutoDeleteUpload; +use support\Log; +use support\Request; +use app\process\Http; +use app\process\Monitor; +use Workerman\Events\Select; + +global $argv; + +return [ + 'webman' => [ + 'handler' => Http::class, + 'listen' => 'http://0.0.0.0:' . getenv('SERVER_PORT'), + 'count' => getenv('DEBUG') == 'true' ? 1 : cpu_count(), + 'user' => '', + 'group' => '', + 'reusePort' => false, + 'eventLoop' => Select::class, + 'context' => [], + 'constructor' => [ + 'requestClass' => Request::class, + 'logger' => Log::channel('default'), + 'appPath' => app_path(), + 'publicPath' => public_path() + ] + ], + // File update detection and automatic reload + 'monitor' => [ + 'handler' => Monitor::class, + 'reloadable' => false, + 'constructor' => [ + // Monitor these directories + 'monitorDir' => array_merge([ + app_path(), + config_path(), + base_path() . '/process', + base_path() . '/support', + base_path() . '/resource', + base_path() . '/.env', + ], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')), + // Files with these suffixes will be monitored + 'monitorExtensions' => [ + 'php', + 'html', + 'htm', + 'env' + ], + 'options' => [ + 'enable_file_monitor' => !in_array('-d', $argv) && getenv('DEBUG') === 'true' && DIRECTORY_SEPARATOR === '/', + 'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/', + ] + ] + ], + 'AutoDeleteUpload' => [ + 'handler' => AutoDeleteUpload::class, + 'eventLoop' => Select::class, + ] +]; diff --git a/fastmovie-admin/config/redis.php b/fastmovie-admin/config/redis.php new file mode 100644 index 0000000..9c4ac06 --- /dev/null +++ b/fastmovie-admin/config/redis.php @@ -0,0 +1,29 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'host' => getenv('REDIS_HOST'), + 'password' => getenv('REDIS_PASSWORD'), + 'port' => getenv('REDIS_PORT'), + 'database' => getenv('REDIS_DATABASE'), + 'pool' => [ // 连接池配置,仅支持swoole/swow驱动 + 'max_connections' => (int)getenv('REDIS_MAX_CONNECTIONS'), // 最大连接数 + 'min_connections' => (int)getenv('REDIS_MIN_CONNECTIONS'), // 最小连接数 + 'wait_timeout' => (int)getenv('REDIS_WAIT_TIMEOUT'), // 从连接池获取连接等待的最大时间,超时后会抛出异常 + 'idle_timeout' => (int)getenv('REDIS_IDLE_TIMEOUT'), // 连接池中连接最大空闲时间,超时后会关闭回收,直到连接数为min_connections + 'heartbeat_interval' => (int)getenv('REDIS_HEARTBEAT_INTERVAL'), // 连接池心跳检测时间,单位秒,建议小于60秒 + ], + ], +]; diff --git a/fastmovie-admin/config/route.php b/fastmovie-admin/config/route.php new file mode 100644 index 0000000..6102c4e --- /dev/null +++ b/fastmovie-admin/config/route.php @@ -0,0 +1,8 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'stop_timeout' => 2, + 'pid_file' => runtime_path() . '/webman.pid', + 'status_file' => runtime_path() . '/webman.status', + 'stdout_file' => runtime_path() . '/logs/stdout.log', + 'log_file' => runtime_path() . '/logs/workerman.log', + 'max_package_size' => 100 * 1024 * 1024 +]; diff --git a/fastmovie-admin/config/session.php b/fastmovie-admin/config/session.php new file mode 100644 index 0000000..043f8c4 --- /dev/null +++ b/fastmovie-admin/config/session.php @@ -0,0 +1,65 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Session\FileSessionHandler; +use Webman\Session\RedisSessionHandler; +use Webman\Session\RedisClusterSessionHandler; + +return [ + + 'type' => 'file', // or redis or redis_cluster + + 'handler' => FileSessionHandler::class, + + 'config' => [ + 'file' => [ + 'save_path' => runtime_path() . '/sessions', + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'auth' => '', + 'timeout' => 2, + 'database' => '', + 'prefix' => 'redis_session_', + ], + 'redis_cluster' => [ + 'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'], + 'timeout' => 2, + 'auth' => '', + 'prefix' => 'redis_session_', + ] + ], + + 'session_name' => 'PHPSID', + + 'auto_update_timestamp' => false, + + 'lifetime' => 7*24*60*60, + + 'cookie_lifetime' => 365*24*60*60, + + 'cookie_path' => '/', + + 'domain' => '', + + 'http_only' => true, + + 'secure' => false, + + 'same_site' => '', + + 'gc_probability' => [1, 1000], + +]; diff --git a/fastmovie-admin/config/settings-tabs.php b/fastmovie-admin/config/settings-tabs.php new file mode 100644 index 0000000..3bb9dd6 --- /dev/null +++ b/fastmovie-admin/config/settings-tabs.php @@ -0,0 +1,15 @@ + $value) { + $prompt=$Component->add("text", ["default" => "短信模板ID,变量:"], ["type" => "info", "size" => "small"]); + foreach ($value['variable'] as $k => $v) { + $prompt->add("tag", ["default" => $v.':'.$k], ["type" => "info", "size" => "small"]); + } + $vcode_templates[]=[ + 'title' => $value['label'].'短信模板ID', + 'field' => 'vcode_template_'.$value['value'], + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $prompt->builder() + ] + ] + ]; +} +return [ + [ + 'title' => '服务商', + 'field' => 'channels', + 'value' => [SmsChannels::ALIYUN['value'], SmsChannels::TENCENT['value'], SmsChannels::SMSBAO['value']], + 'component' => 'drag-sort', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '短信服务商优先级,当发送失败时,会自动切换到下一个服务商'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'options' => SmsChannels::getOptions(), + ] + ], + 'group' => [ + [ + + 'title' => SmsChannels::ALIYUN['label'], + 'name' => SmsChannels::ALIYUN['value'], + 'children' => [ + [ + 'title' => '开关', + 'field' => 'enable', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '是否启用阿里云短信服务'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'AccessKey ID', + 'field' => 'access_key_id', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '阿里云-AccessKey管理-AccessKey ID,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '阿里云AccessKey'], ['target' => '_blank', 'href' => 'https://ram.console.aliyun.com/manage/ak', 'type' => 'info', 'underline' => 'never', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'AccessKey Secret', + 'field' => 'access_secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '阿里云-AccessKey管理-AccessKey Secret'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => '短信签名', + 'field' => 'sign_name', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '阿里云-短信服务-国内消息-签名管理,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '阿里云短信签名'], ['target' => '_blank', 'href' => 'https://dysms.console.aliyun.com/domestic/text', 'type' => 'info', 'underline' => 'never', 'size' => 'small']) + ->builder() + ] + ] + ], + ...$vcode_templates + ] + ], + [ + "title" => SmsChannels::TENCENT['label'], + "name" => SmsChannels::TENCENT['value'], + "children" => [ + [ + 'title' => '开关', + 'field' => 'enable', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '是否启用腾讯云短信服务'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + "title" => "SecretId", + "field" => "secret_id", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-访问管理-访问密钥-AccessKey ID,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云SecretId"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/cam/capi", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + [ + "title" => "SecretKey", + "field" => "secret_key", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-访问管理-访问密钥-AccessKey Secret,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云SecretKey"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/cam/capi", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + [ + "title" => "应用 ID", + "field" => "appid", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-短信-应用管理-应用 ID,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云应用 ID"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/smsv2/app-manage", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + [ + "title" => "短信签名", + "field" => "sign_name", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-短信-国内短信-签名管理,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云短信签名"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/smsv2/csms-sign", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + ...$vcode_templates + ] + ], + [ + "title" => SmsChannels::SMSBAO['label'], + "name" => SmsChannels::SMSBAO['value'], + "children" => [ + [ + 'title' => '开关', + 'field' => 'enable', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '是否启用短信宝短信服务'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'UserName', + 'field' => 'key', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '短信宝-用户名'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'API Key或密码', + 'field' => 'secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '短信宝-API Key或密码'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => '短信签名', + 'field' => 'sign_name', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '短信宝-短信签名'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + ...$vcode_templates + ] + ] + ], +]; diff --git a/fastmovie-admin/config/settings.php b/fastmovie-admin/config/settings.php new file mode 100644 index 0000000..cd8f1c9 --- /dev/null +++ b/fastmovie-admin/config/settings.php @@ -0,0 +1,15 @@ + 'Settings_basic_web_name', + 'field' => 'web_name', + 'value' => 'FastMovie', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '应用名称并非网页标题'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入应用名称' + ] + ] + ], + [ + 'title' => '网站标题', + 'field' => 'web_title', + 'value' => 'FastMovie', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '网页标题,显示于浏览器tab标题'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入网站标题' + ] + ] + ], + [ + 'title' => 'LOGO展示方式', + 'field' => 'logo_use', + 'value' => 'svg', + 'component' => 'radio', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '应用名称将会显示在页面标题中'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'options' => [ + ['label' => '默认', 'value' => 'svg'], + ['label' => '图片', 'value' => 'image'] + ] + ] + ], + [ + 'title' => 'LOGO(浅色)', + 'field' => 'web_logo_light', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'where' => [ + ['logo_use', '=', 'image'] + ], + 'prompt' => [ + $Component->add('text', ['default' => '浅色LOGO,用于深色背景'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ], + [ + 'title' => 'LOGO(深色)', + 'field' => 'web_logo_dark', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'where' => [ + ['logo_use', '=', 'image'] + ], + 'prompt' => [ + $Component->add('text', ['default' => '深色LOGO,用于浅色背景'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ], + [ + 'title' => 'ICP备案号', + 'field' => 'web_icp', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入ICP备案号' + ] + ] + ], + [ + 'title' => '公安备案号', + 'field' => 'web_mps', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '用于跳转至公安备案系统携带的备案号'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入公安备案号' + ], + 'children' => SoltBuilder::solt('prepend')->add('el-image', ['style' => 'display:flex;', 'src' => '/static/beian.gov.png', 'width' => '15px', 'height' => '15px']) + ] + ], + [ + 'title' => '公安备案号文本', + 'field' => 'web_mps_text', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '展示在网站底部公安备案号'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入公安备案号文本' + ], + 'children' => SoltBuilder::solt('prepend')->add('el-image', ['style' => 'display:flex;', 'src' => '/static/beian.gov.png', 'width' => '15px', 'height' => '15px']) + ] + ], + [ + 'title' => '版权信息', + 'field' => 'copyright', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入版权信息' + ], + 'children' => SoltBuilder::solt('prepend')->text('©') + ] + ], + [ + 'title' => '客服链接', + 'field' => 'wechat_url', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入客服链接链接' + ] + ] + ], + [ + 'title' => '客服二维码', + 'field' => 'wechat_qrcode_url', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ] +]; diff --git a/fastmovie-admin/config/settings/wechat_miniproject.php b/fastmovie-admin/config/settings/wechat_miniproject.php new file mode 100644 index 0000000..e5616ac --- /dev/null +++ b/fastmovie-admin/config/settings/wechat_miniproject.php @@ -0,0 +1,138 @@ + '开关', + 'field' => 'state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '是否开启微信小程序'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [] + ] + ], + [ + 'title' => 'AppID', + 'field' => 'app_id', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序AppID(小程序ID),'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppID' + ] + ] + ], + [ + 'title' => 'AppSecret', + 'field' => 'app_secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序AppSecret(小程序密钥) ,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppSecret' + ] + ] + ], + [ + 'title' => '小程序代码上传密钥', + 'field' => 'code_upload_key', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序代码上传密钥 ,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'view' => 'file' + ] + ] + ], + [ + 'title' => 'request合法域名', + 'field' => 'request_domain', + 'value' => '{DOMAIN}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序request合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-request合法域名'], ['type' => 'warning', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入request合法域名' + ] + ] + ], + [ + 'title' => 'uploadFile合法域名', + 'field' => 'upload_file_domain', + 'value' => '{DOMAIN}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序uploadFile合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-uploadFile合法域名'], ['type' => 'warning', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入uploadFile合法域名' + ] + ] + ], + [ + 'title' => 'downloadFile合法域名', + 'field' => 'download_file_domain', + 'value' => '{DOMAIN}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序downloadFile合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-downloadFile合法域名,'], ['type' => 'warning', 'size' => 'small']) + ->add('text', ['default' => '注:如若该域名与本站系统-上传配置-中储存域名不一致时,以上传配置中使用的域名为准'], ['type' => 'danger', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入downloadFile合法域名' + ] + ] + ], +]; diff --git a/fastmovie-admin/config/settings/wechat_official_account.php b/fastmovie-admin/config/settings/wechat_official_account.php new file mode 100644 index 0000000..ec46dae --- /dev/null +++ b/fastmovie-admin/config/settings/wechat_official_account.php @@ -0,0 +1,142 @@ + '开关', + 'field' => 'state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '是否开启微信公众号'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + ] + ] + ], + [ + 'title' => 'AppID', + 'field' => 'app_id', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号开发者ID(AppID),'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppID' + ] + ] + ], + [ + 'title' => 'AppSecret', + 'field' => 'app_secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号开发者密码(AppSecret),'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppSecret' + ] + ] + ], + [ + 'title' => '接收公众号消息通知状态', + 'field' => 'message_state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '是否开启接收公众号消息通知'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + ] + ] + ], + [ + 'title' => '服务器地址', + 'field' => 'url', + 'value' => '{DOMAIN}/WechatOfficialAccount/message', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true], + ['message_state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号服务器地址(URL),请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-服务器地址(URL)'], ['type' => 'warning', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入服务器地址' + ] + ] + ], + [ + 'title' => '令牌', + 'field' => 'token', + 'value' => Str::random(32), + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true], + ['message_state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号令牌(Token),请将此Token填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-令牌(Token)'], ['type' => 'warning', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入令牌' + ] + ] + ], + [ + 'title' => '消息加解密密钥', + 'field' => 'aes_key', + 'value' => Str::random(43), + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true], + ['message_state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号消息加解密密钥(AESKey),请将此AESKey填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-消息加解密方式(AESKey)'], ['type' => 'warning', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入消息加解密密钥' + ] + ] + ] +]; diff --git a/fastmovie-admin/config/static.php b/fastmovie-admin/config/static.php new file mode 100644 index 0000000..6313679 --- /dev/null +++ b/fastmovie-admin/config/static.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Static file settings + */ +return [ + 'enable' => true, + 'middleware' => [ // Static file Middleware + //app\middleware\StaticFile::class, + ], +]; \ No newline at end of file diff --git a/fastmovie-admin/config/thinkorm.php b/fastmovie-admin/config/thinkorm.php new file mode 100644 index 0000000..42be11e --- /dev/null +++ b/fastmovie-admin/config/thinkorm.php @@ -0,0 +1,43 @@ + 'mysql', + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => getenv('DATABASE_HOST'), + // 数据库名 + 'database' => getenv('DATABASE_NAME'), + // 数据库用户名 + 'username' => getenv('DATABASE_USERNAME'), + // 数据库密码 + 'password' => getenv('DATABASE_PASSWORD'), + // 数据库连接端口 + 'hostport' => getenv('DATABASE_PORT'), + // 数据库连接参数 + 'params' => [ + // 连接超时3秒 + \PDO::ATTR_TIMEOUT => 3, + ], + // 数据库编码默认采用utf8 + 'charset' => getenv('DATABASE_CHARSET'), + // 数据库表前缀 + 'prefix' => getenv('DATABASE_PREFIX'), + // 断线重连 + 'break_reconnect' => true, + // 关闭SQL监听日志 + 'trigger_sql' => false, + // 自定义分页类 + 'bootstrap' => '', + 'pool' => [ // 连接池配置,仅支持swoole/swow驱动 + 'max_connections' => (int)getenv('DATABASE_MAX_CONNECTIONS'), // 最大连接数 + 'min_connections' => (int)getenv('DATABASE_MIN_CONNECTIONS'), // 最小连接数 + 'wait_timeout' => (int)getenv('DATABASE_WAIT_TIMEOUT'), // 从连接池获取连接等待的最大时间,超时后会抛出异常 + 'idle_timeout' => (int)getenv('DATABASE_IDLE_TIMEOUT'), // 连接池中连接最大空闲时间,超时后会关闭回收,直到连接数为min_connections + 'heartbeat_interval' => (int)getenv('DATABASE_HEARTBEAT_INTERVAL'), // 连接池心跳检测时间,单位秒,建议小于60秒 + ], + ], + ], +]; diff --git a/fastmovie-admin/config/translation.php b/fastmovie-admin/config/translation.php new file mode 100644 index 0000000..96589b2 --- /dev/null +++ b/fastmovie-admin/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/config/view.php b/fastmovie-admin/config/view.php new file mode 100644 index 0000000..fe78682 --- /dev/null +++ b/fastmovie-admin/config/view.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +// use support\view\ThinkPHP; +use support\ThinkPHP; + +return [ + 'handler' => ThinkPHP::class, +]; diff --git a/fastmovie-admin/database.sql b/fastmovie-admin/database.sql new file mode 100644 index 0000000..7461871 --- /dev/null +++ b/fastmovie-admin/database.sql @@ -0,0 +1,1286 @@ +/* + Navicat Premium Data Transfer + + Source Server : 公司服务器 + Source Server Type : MySQL + Source Server Version : 80036 (8.0.36) + Source Host : 127.0.0.1:3306 + Source Schema : ai_short_play + + Target Server Type : MySQL + Target Server Version : 80036 (8.0.36) + File Encoding : 65001 + + Date: 23/01/2026 16:47:51 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for php_admin +-- ---------------------------- +DROP TABLE IF EXISTS `php_admin`; +CREATE TABLE `php_admin` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `role_id` int NULL DEFAULT NULL, + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `state` tinyint(1) NULL DEFAULT 1, + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `login_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `login_time` datetime NULL DEFAULT NULL, + `online_time` datetime NULL DEFAULT NULL, + `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `allow_time_start` time NULL DEFAULT '08:00:00', + `allow_time_end` time NULL DEFAULT '18:00:00', + `allow_week` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '[0,1,2,3,4,5,6]', + `wx_openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_admin_role +-- ---------------------------- +DROP TABLE IF EXISTS `php_admin_role`; +CREATE TABLE `php_admin_role` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `pid` int NULL DEFAULT NULL, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `rule` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + `state` tinyint(1) NULL DEFAULT 1, + `is_system` tinyint NULL DEFAULT 0, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '管理员-职权' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_admin_role +-- ---------------------------- +INSERT INTO `php_admin_role` VALUES (1, NULL, '系统管理员', NULL, 1, 1, '2024-03-08 16:25:29', '2024-03-08 16:25:31'); + +-- ---------------------------- +-- Table structure for php_config +-- ---------------------------- +DROP TABLE IF EXISTS `php_config`; +CREATE TABLE `php_config` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `group` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `group`(`group` ASC) USING BTREE, + INDEX `channelsgroup`(`channels_uid` ASC, `group` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; +-- ---------------------------- +-- Table structure for php_payment_config +-- ---------------------------- +DROP TABLE IF EXISTS `php_payment_config`; +CREATE TABLE `php_payment_config` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `platform` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `channels` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `template_id` int NULL DEFAULT NULL, + `state` tinyint NULL DEFAULT 1, + `is_default` tinyint NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '微信支付方式绑定' ROW_FORMAT = DYNAMIC; +-- ---------------------------- +-- Table structure for php_payment_notify_wechat +-- ---------------------------- +DROP TABLE IF EXISTS `php_payment_notify_wechat`; +CREATE TABLE `php_payment_notify_wechat` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `notify_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `resource_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `event_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `summary` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `resource_original_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `resource_algorithm` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `resource_ciphertext` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + `resource_associated_data` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `resource_nonce` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `plugin` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `template_id` int NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '微信支付通知' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_payment_template +-- ---------------------------- +DROP TABLE IF EXISTS `php_payment_template`; +CREATE TABLE `php_payment_template` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `state` tinyint NULL DEFAULT 1, + `channels` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '微信支付模板' ROW_FORMAT = DYNAMIC; +-- ---------------------------- +-- Table structure for php_plugin_article +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_article`; +CREATE TABLE `php_plugin_article` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL COMMENT '为空则为系统级', + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `release_time` datetime NULL DEFAULT NULL, + `alias` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文章别名', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文章标题', + `subtitle` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `keywords` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `state` tinyint(1) NULL DEFAULT 1, + `examine` tinyint(1) NULL DEFAULT NULL, + `view` int NULL DEFAULT 0, + `thumb` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '文章封面', + `class` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '', + `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '', + `description` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '', + `classify_id` int NULL DEFAULT NULL, + `sort` tinyint NULL DEFAULT 99, + `delete_time` datetime NULL DEFAULT NULL, + `push_state` tinyint NULL DEFAULT 0 COMMENT '是否需要推送', + `push_crowd` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '目标推送人群', + `push_crowd_uids` json NULL COMMENT '渠道用户uid或用户uid数组', + `push_last_id` bigint NULL DEFAULT NULL COMMENT '最后推送uid', + `push_people_num` int NULL DEFAULT 0 COMMENT '已推送人数', + `push_tarage_people_num` int NULL DEFAULT 0 COMMENT '目标推送人数', + `last_heartbeat` datetime NULL DEFAULT NULL COMMENT '执行时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `cid`(`classify_id` ASC) USING BTREE, + INDEX `alias`(`alias` ASC, `channels_uid` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文章' ROW_FORMAT = COMPACT; +-- ---------------------------- +-- Table structure for php_plugin_article_classify +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_article_classify`; +CREATE TABLE `php_plugin_article_classify` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL COMMENT '为空则为系统级', + `create_time` datetime NULL DEFAULT NULL COMMENT '分类创建时间', + `update_time` datetime NULL DEFAULT NULL, + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分类名称', + `alias` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '别名', + `pid` int NULL DEFAULT NULL COMMENT '父级ID', + `sort` int NULL DEFAULT 99 COMMENT '排序', + `state` tinyint(1) NULL DEFAULT 1, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `alias`(`alias` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文章分类数据' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of php_plugin_article_classify +-- ---------------------------- +INSERT INTO `php_plugin_article_classify` VALUES (2, NULL, '2025-12-26 10:46:22', '2025-12-30 16:06:40', '公告', NULL, 2, 99, 1); +INSERT INTO `php_plugin_article_classify` VALUES (4, NULL, '2026-01-09 14:51:19', '2026-01-09 15:09:05', '教程', NULL, 4, 99, 1); +INSERT INTO `php_plugin_article_classify` VALUES (9, NULL, '2026-01-09 15:13:39', '2026-01-09 15:17:41', '短剧制作流程', NULL, 11, 99, 1); +INSERT INTO `php_plugin_article_classify` VALUES (11, NULL, '2026-01-09 15:14:10', '2026-01-09 15:14:38', '教程', '', NULL, 99, 1); +INSERT INTO `php_plugin_article_classify` VALUES (13, NULL, '2026-01-09 15:17:55', '2026-01-09 15:17:55', '公告', NULL, NULL, 99, 1); +INSERT INTO `php_plugin_article_classify` VALUES (14, NULL, '2026-01-09 15:18:07', '2026-01-09 15:18:07', '行业', NULL, NULL, 99, 1); + +-- ---------------------------- +-- Table structure for php_plugin_article_content +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_article_content`; +CREATE TABLE `php_plugin_article_content` ( + `article_id` int UNSIGNED NOT NULL, + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`article_id`) USING BTREE, + UNIQUE INDEX `article`(`article_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文章内容表' ROW_FORMAT = DYNAMIC; +-- ---------------------------- +-- Table structure for php_plugin_channels_domain +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_channels_domain`; +CREATE TABLE `php_plugin_channels_domain` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '绑定的域名,不带协议,如:app.example.com', + `channels_uid` bigint UNSIGNED NOT NULL COMMENT '绑定到的用户ID', + `state` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态:1正常,0禁用', + `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uniq_domain`(`domain` ASC) USING BTREE, + INDEX `idx_user_id`(`channels_uid` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '域名绑定用户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_channels_role +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_channels_role`; +CREATE TABLE `php_plugin_channels_role` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `pid` int NULL DEFAULT NULL, + `channels_uid` int NULL DEFAULT NULL, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `rule` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + `state` tinyint(1) NULL DEFAULT 1, + `is_system` int NULL DEFAULT 1, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '管理员-职权' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_channels_user +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_channels_user`; +CREATE TABLE `php_plugin_channels_user` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `activation_time` datetime NULL DEFAULT NULL COMMENT '激活时间', + `channels_uid` int NULL DEFAULT NULL, + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '账号', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码', + `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号', + `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱', + `state` tinyint NULL DEFAULT 1 COMMENT '状态', + `state_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态原因', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称', + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像', + `role_id` int NULL DEFAULT 0, + `login_ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登录IP', + `is_system` int NULL DEFAULT 1, + `login_time` datetime NULL DEFAULT NULL COMMENT '登录时间', + `expire_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1656 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '渠道商' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_channels_wechat +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_channels_wechat`; +CREATE TABLE `php_plugin_channels_wechat` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` bigint NULL DEFAULT NULL COMMENT '所属用户', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称', + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像', + `openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `unionid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `mp_openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `subscribe` tinyint(1) NULL DEFAULT 0, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uid`(`uid` ASC) USING BTREE, + UNIQUE INDEX `openid`(`openid` ASC) USING BTREE, + UNIQUE INDEX `unid`(`unionid` ASC) USING BTREE, + UNIQUE INDEX `mini`(`mp_openid` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '微信用户' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_channels_wechat +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_finance_orders +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_finance_orders`; +CREATE TABLE `php_plugin_finance_orders` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `payment_id` int NULL DEFAULT NULL COMMENT 'payment_config_id', + `template_id` int NULL DEFAULT NULL COMMENT 'payment_template_id', + `trade_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '订单号', + `alias_id` int NULL DEFAULT NULL COMMENT '关联ID', + `uid` bigint NULL DEFAULT NULL COMMENT '用户id', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '订单标题', + `origin_money` decimal(10, 2) NULL DEFAULT NULL COMMENT '原价', + `unit_money` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '单价', + `money` decimal(10, 2) NULL DEFAULT NULL COMMENT '需要支付价格', + `system_money` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '平台优惠金额', + `num` int NULL DEFAULT 1 COMMENT '数量', + `pay_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '支付方式', + `state` int NOT NULL DEFAULT 0 COMMENT '0 待付款 1已完成 2已退款 3已作废 99已取消', + `pay_time` datetime NULL DEFAULT NULL COMMENT '支付时间', + `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '订单类型', + `coupon` json NULL COMMENT '使用的优惠券列表', + `plugin` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `cancel_time` datetime NULL DEFAULT NULL COMMENT '取消时间', + `expire_time` datetime NULL DEFAULT NULL COMMENT '过期时间', + `transaction_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '商户支付流水号', + `finish_time` datetime NULL DEFAULT NULL COMMENT '完成时间', + `comment_time` datetime NULL DEFAULT NULL COMMENT '评价时间', + `delete_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `trade`(`trade_no` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单' ROW_FORMAT = DYNAMIC; +-- ---------------------------- +-- Table structure for php_plugin_finance_orders_log +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_finance_orders_log`; +CREATE TABLE `php_plugin_finance_orders_log` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `level` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '日志等级', + `orders_id` int NULL DEFAULT NULL COMMENT '订单ID', + `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '日志内容', + `admin_id` int NULL DEFAULT NULL COMMENT '操作管理员ID或为空', + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `orders`(`orders_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 80 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单操作日志' ROW_FORMAT = COMPACT; +-- ---------------------------- +-- Table structure for php_plugin_finance_payment_notify +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_finance_payment_notify`; +CREATE TABLE `php_plugin_finance_payment_notify` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `mchid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `appid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `out_trade_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `transaction_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `trade_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `trade_state` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `trade_state_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `bank_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `attach` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `success_time` datetime NULL DEFAULT NULL, + `payer_openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `amount_total` int NULL DEFAULT NULL, + `amount_payer_total` int NULL DEFAULT NULL, + `amount_currency` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `amount_payer_currency` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `state` tinyint NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '微信支付通知' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_finance_payment_notify +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_finance_wallet +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_finance_wallet`; +CREATE TABLE `php_plugin_finance_wallet` ( + `uid` bigint NOT NULL, + `channels_uid` int NOT NULL, + `balance` decimal(12, 4) UNSIGNED NOT NULL DEFAULT 0.0000 COMMENT '可用余额', + `balance_sum` decimal(12, 4) UNSIGNED NOT NULL DEFAULT 0.0000 COMMENT '累计总收入', + `balance_used` decimal(12, 4) UNSIGNED NOT NULL DEFAULT 0.0000 COMMENT '累计支出', + `points` int NULL DEFAULT 0 COMMENT '可用积分', + `points_sum` int NULL DEFAULT 0 COMMENT '累计总积分', + `points_used` int NULL DEFAULT 0 COMMENT '累计支出', + `tmp_points` int NULL DEFAULT 0 COMMENT '临时积分', + `tmp_points_sum` int NULL DEFAULT 0 COMMENT '临时积分累计', + `tmp_points_used` int NULL DEFAULT 0 COMMENT '临时积分使用', + PRIMARY KEY (`uid`, `channels_uid`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户钱包' ROW_FORMAT = DYNAMIC; +-- ---------------------------- +-- Table structure for php_plugin_marketing_coupon +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_marketing_coupon`; +CREATE TABLE `php_plugin_marketing_coupon` ( + `id` int NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `create_time` datetime NOT NULL, + `update_time` datetime NOT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '优惠券名', + `coupon_rule` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '优惠券规则', + `discount` decimal(3, 2) NULL DEFAULT 0.00 COMMENT '折扣', + `full_price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '满', + `money` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '减', + `state` tinyint(1) NULL DEFAULT 1 COMMENT '状态', + `show_list` tinyint(1) NULL DEFAULT 1 COMMENT '是否在前台优惠列表中显示', + `receive_type` enum('user_only','repeat_day','repeat_week','repeat_month') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'user_only' COMMENT '领取规则', + `use_type` enum('first_order','first_plugin','','unlimited') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'unlimited' COMMENT '使用规则', + `sum` int NULL DEFAULT 0 COMMENT '发行数量', + `num` int NULL DEFAULT 0 COMMENT '可领取数量', + `use_num` int NULL DEFAULT 0 COMMENT '使用数量', + `expire_num` int NULL DEFAULT 0 COMMENT '过期数量', + `receive_num` int NULL DEFAULT 0 COMMENT '领取数量', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 139 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '优惠券-规则' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_marketing_coupon +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_marketing_coupon_code +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_marketing_coupon_code`; +CREATE TABLE `php_plugin_marketing_coupon_code` ( + `id` int NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `create_time` datetime NOT NULL, + `update_time` datetime NOT NULL, + `uid` bigint NULL DEFAULT NULL COMMENT '拥有者ID', + `coupon_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '券码', + `coupon_id` int NOT NULL COMMENT '优惠券ID', + `receive_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '领取规则', + `coupon_rule` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '优惠券规则', + `discount` decimal(5, 2) NOT NULL DEFAULT 0.00 COMMENT '优惠券折扣', + `full_price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '满', + `money` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '减', + `state` tinyint(1) NULL DEFAULT 0 COMMENT '状态', + `start_time` datetime NULL DEFAULT NULL COMMENT '开始使用时间', + `end_time` datetime NULL DEFAULT NULL COMMENT '结束使用时间', + `day` int NULL DEFAULT 30 COMMENT '领取后有效期', + `expire_time` datetime NULL DEFAULT NULL COMMENT '过期时间', + `receive_time` datetime NULL DEFAULT NULL COMMENT '领取时间', + `use_time` datetime NULL DEFAULT NULL COMMENT '使用时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `coupon_code`(`coupon_code` ASC) USING BTREE, + INDEX `apps_available`(`uid` ASC, `state` ASC, `start_time` ASC, `end_time` ASC, `expire_time` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 175 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '优惠券-券码' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_marketing_coupon_code +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_marketing_coupon_password +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_marketing_coupon_password`; +CREATE TABLE `php_plugin_marketing_coupon_password` ( + `coupon_id` int NOT NULL, + `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '优惠券码', + UNIQUE INDEX `password`(`password` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '自定义优惠券码' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_marketing_coupon_password +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_marketing_coupon_server +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_marketing_coupon_server`; +CREATE TABLE `php_plugin_marketing_coupon_server` ( + `coupon_id` int NOT NULL, + `server` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '可使用服务类型' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '优惠券可使用服务' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_marketing_coupon_server +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_marketing_plan +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_marketing_plan`; +CREATE TABLE `php_plugin_marketing_plan` ( + `id` int NOT NULL AUTO_INCREMENT, + `key` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'basic 基础 pro 高级', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `state` tinyint(1) NULL DEFAULT NULL COMMENT '状态', + `channels_uid` int NULL DEFAULT NULL, + `sort` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '会员套餐' ROW_FORMAT = Dynamic; +-- ---------------------------- +-- Table structure for php_plugin_marketing_plan_price +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_marketing_plan_price`; +CREATE TABLE `php_plugin_marketing_plan_price` ( + `id` int NOT NULL AUTO_INCREMENT, + `plan_id` int NULL DEFAULT 0, + `price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '价格', + `billing_cycle` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '计费周期', + `original_price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '划线价', + `points` int NULL DEFAULT 0 COMMENT '积分', + `give` int NULL DEFAULT 0 COMMENT '赠送积分', + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '会员套餐价格' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_marketing_points +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_marketing_points`; +CREATE TABLE `php_plugin_marketing_points` ( + `id` int NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '套餐名称', + `points` int NULL DEFAULT 0 COMMENT '购买积分点数', + `desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `give` int NULL DEFAULT 0 COMMENT '赠送点数', + `original_price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '划线价', + `price` decimal(10, 2) NULL DEFAULT NULL COMMENT '价格', + `state` tinyint(1) NULL DEFAULT 1 COMMENT '状态', + `discount` decimal(4, 2) NULL DEFAULT 0.00 COMMENT '折扣', + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '积分套餐' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_model +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_model`; +CREATE TABLE `php_plugin_model` ( + `id` int NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '模型名称', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `model_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型类型', + `model_id` int NOT NULL COMMENT '模型ID', + `model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `assistant_id` int NOT NULL COMMENT '助手ID', + `assistant_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `scene` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '场景', + `point` int NULL DEFAULT NULL COMMENT '计费', + `point_type` char(20) NULL DEFAULT 'times' COMMENT '计费类型', + `state` tinyint(1) NULL DEFAULT 1 COMMENT '状态', + `sort` int NULL DEFAULT 0, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `form` json NULL COMMENT '表单', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '模型' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_model_task +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_model_task`; +CREATE TABLE `php_plugin_model_task` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `model_id` int NULL DEFAULT NULL COMMENT '模型ID', + `model_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型类型', + `pre_task_id` bigint NULL DEFAULT NULL COMMENT '前置任务ID', + `pre_task_status` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '前置任务状态', + `alias_id` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '关联数据ID', + `task_id` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '任务ID', + `scene` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '场景', + `status` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + `execution_count` int NULL DEFAULT NULL, + `success_execution_count` int NULL DEFAULT 0, + `expectation_execution_count` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `last_heartbeat` datetime NULL DEFAULT NULL, + `consume_ids` json NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `task_id`(`task_id` ASC, `model_type` ASC) USING BTREE, + INDEX `alias`(`alias_id` ASC, `scene` ASC, `status` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1625 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '模型任务列表' ROW_FORMAT = Dynamic; + + +-- ---------------------------- +-- Table structure for php_plugin_model_task_result +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_model_task_result`; +CREATE TABLE `php_plugin_model_task_result` ( + `task_id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `params` json NULL COMMENT '传参', + `result` json NULL COMMENT '结果', + `image` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图片地址', + `video` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '视频地址', + `audio` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '音频地址', + `video_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '本地路径', + `image_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '本地路径', + `audio_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '本地路径', + `message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '错误消息', + PRIMARY KEY (`task_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1625 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '模型任务结果' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_notification_message +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_notification_message`; +CREATE TABLE `php_plugin_notification_message` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL COMMENT '所属用户', + `form_uid` bigint NULL DEFAULT NULL COMMENT '所属管理员', + `form_id` bigint NULL DEFAULT NULL COMMENT '来源ID', + `scene` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '场景', + `alias_id` int NULL DEFAULT NULL COMMENT '关联操作ID', + `state` tinyint NULL DEFAULT 0 COMMENT '状态', + `read_state` tinyint NULL DEFAULT 0 COMMENT '已读状态', + `extra` json NULL COMMENT '扩展信息', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '消息标题', + `subtitle` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `effect` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '消息情景', + `push_state` tinyint NULL DEFAULT 0, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 87 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通知消息' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_notification_message_content +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_notification_message_content`; +CREATE TABLE `php_plugin_notification_message_content` ( + `message_id` int NOT NULL, + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`message_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_notification_online +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_notification_online`; +CREATE TABLE `php_plugin_notification_online` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` bigint NULL DEFAULT NULL COMMENT '所属用户', + `channels_uid` int NULL DEFAULT NULL, + `channel` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '渠道id', + `event` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '监听事件', + `hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `uid`(`uid` ASC, `event` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 227 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '在线用户' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_actor +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_actor`; +CREATE TABLE `php_plugin_shortplay_actor` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL COMMENT '所属用户', + `drama_id` bigint NULL DEFAULT NULL COMMENT '所属剧本,可空,空=公共', + `episode_id` bigint NULL DEFAULT NULL COMMENT '所属分集,可空', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名称', + `actor_id` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色ID', + `species_type` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '物种类别', + `gender` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别', + `age` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '年龄', + `remarks` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '角色描述', + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色形象', + `three_view_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '三视图', + `status` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `voice` json NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 850 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色列表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_actor_character_look +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_actor_character_look`; +CREATE TABLE `php_plugin_shortplay_actor_character_look` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint UNSIGNED NOT NULL, + `actor_id` bigint UNSIGNED NOT NULL, + `status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `character_look_id` bigint NULL DEFAULT NULL, + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `three_view_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `upadte_time` datetime NULL DEFAULT NULL, + `drama_id` bigint NULL DEFAULT NULL, + `episode_id` bigint NULL DEFAULT NULL, + `storyboard_id` bigint NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 622 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '分集角色引用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_character_look +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_character_look`; +CREATE TABLE `php_plugin_shortplay_character_look` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `drama_id` bigint UNSIGNED NOT NULL COMMENT '剧集ID', + `episode_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '集ID', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `overall_style` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '整体风格(如:日常、精致、狼狈、正式)', + `makeup` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '妆容描述(淡妆/浓妆/素颜感等)', + `hair_style` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发型描述', + `costume` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '服饰描述(颜色+款式+搭配)', + `costume_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '服饰URL', + `status_note` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态补充(凌乱、破损、是否呼应情绪)', + `status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '妆容服饰设定表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama`; +CREATE TABLE `php_plugin_shortplay_drama` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint UNSIGNED NOT NULL, + `model_id` int NULL DEFAULT NULL, + `style_id` int NULL DEFAULT NULL, + `script` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `aspect_ratio` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `episode_num` int NULL DEFAULT 0, + `episode_sum` int NULL DEFAULT 0, + `episode_duration` int NULL DEFAULT 0, + `state` tinyint(1) NULL DEFAULT 1, + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `background_description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `outline` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `overall_hook` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `core_catharsis_mechanism` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `main_conflict` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `relationship_mainline` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `voice` json NULL, + `prosody_speed` double NULL DEFAULT 1, + `prosody_volume` int NULL DEFAULT 50, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1000018 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '剧本' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_actor +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_actor`; +CREATE TABLE `php_plugin_shortplay_drama_actor` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `drama_id` bigint UNSIGNED NOT NULL, + `actor_id` bigint UNSIGNED NOT NULL, + `sort` tinyint NULL DEFAULT 99, + `character_look_id` bigint NULL DEFAULT NULL, + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `three_view_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `voice` json NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `actorList`(`drama_id` ASC, `actor_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '剧本角色引用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_episode +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_episode`; +CREATE TABLE `php_plugin_shortplay_drama_episode` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `drama_id` bigint UNSIGNED NOT NULL, + `episode_no` int NOT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `outline` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + `video` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `video_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1000212 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '剧本分集' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_episode_actor +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_episode_actor`; +CREATE TABLE `php_plugin_shortplay_drama_episode_actor` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `drama_id` bigint NULL DEFAULT NULL, + `episode_id` bigint UNSIGNED NOT NULL, + `actor_id` bigint UNSIGNED NOT NULL, + `character_look_id` bigint NULL DEFAULT NULL, + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `three_view_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `voice` json NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `actorList`(`drama_id` ASC, `episode_id` ASC, `actor_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 892 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '分集角色引用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_scene +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_scene`; +CREATE TABLE `php_plugin_shortplay_drama_scene` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `drama_id` int NULL DEFAULT NULL, + `episode_id` int NULL DEFAULT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `scene_space` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `scene_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `scene_time` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `scene_weather` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `atmosphere` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `sort` int NULL DEFAULT 0, + `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 164 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '场景' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_storyboard +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_storyboard`; +CREATE TABLE `php_plugin_shortplay_drama_storyboard` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `drama_id` bigint NOT NULL, + `episode_id` bigint UNSIGNED NOT NULL, + `scene_id` bigint NULL DEFAULT NULL, + `sort` int NULL DEFAULT NULL, + `description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分镜概述', + `image_prompt` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分镜生图提示词', + `video_prompt` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分镜合成视频提示词', + `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分镜图片', + `video` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分镜视频', + `shot_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '镜头类型', + `shot_angle` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '镜头视角', + `shot_motion` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '镜头运动', + `sfx` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '音效指令', + `sfx_audio` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '音效音频', + `duration` int NULL DEFAULT 0 COMMENT '分镜时长', + `narration` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '旁白', + `narration_audio` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '旁白音频', + `use_material_type` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'image', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 517 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '分镜' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_storyboard_actor +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_storyboard_actor`; +CREATE TABLE `php_plugin_shortplay_drama_storyboard_actor` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `drama_id` bigint NULL DEFAULT NULL, + `episode_id` bigint NULL DEFAULT NULL, + `storyboard_id` bigint UNSIGNED NOT NULL, + `actor_id` bigint UNSIGNED NOT NULL, + `character_look_id` bigint NULL DEFAULT NULL, + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `three_view_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `voice` json NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `actorList`(`drama_id` ASC, `episode_id` ASC, `storyboard_id` ASC, `actor_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 682 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '分镜角色引用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_storyboard_dialogue +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_storyboard_dialogue`; +CREATE TABLE `php_plugin_shortplay_drama_storyboard_dialogue` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `storyboard_id` bigint UNSIGNED NOT NULL, + `actor_id` bigint UNSIGNED NOT NULL, + `prosody_speed` double NULL DEFAULT 1 COMMENT '语速倍率。有效范围:0.5 至 2.0。1.0 = 正常速度,0.5 = 半速,2.0 = 两倍速度。', + `prosody_volume` int NULL DEFAULT 50 COMMENT '音量,0:静音,50:正常,100:最大音量', + `emotion` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '情感:neutral、fearful、angry、sad、surprised、happy、disgusted', + `start_time` int NULL DEFAULT 0 COMMENT '台词出现时间:毫秒', + `end_time` int NULL DEFAULT 0 COMMENT '台词隐藏时间:毫秒', + `inner_monologue` tinyint NULL DEFAULT 0 COMMENT '内心独白或画外音', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '台词内容', + `audio` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '生成的音频', + `voice` json NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 346 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '分镜角色引用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_drama_storyboard_prop +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_drama_storyboard_prop`; +CREATE TABLE `php_plugin_shortplay_drama_storyboard_prop` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `storyboard_id` bigint UNSIGNED NOT NULL, + `prop_id` bigint UNSIGNED NOT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 755 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '分镜物品引用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_prop +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_prop`; +CREATE TABLE `php_plugin_shortplay_prop` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint UNSIGNED NULL DEFAULT NULL, + `drama_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属剧本,可空', + `episode_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '所属分集,可空', + `prop_id` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '物品名称', + `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图片', + `three_view_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '三视图', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '描述', + `status` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 446 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '物品(公共+剧本+分集)' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_share +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_share`; +CREATE TABLE `php_plugin_shortplay_share` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `drama_id` bigint NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `likes` int NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_share_episode +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_share_episode`; +CREATE TABLE `php_plugin_shortplay_share_episode` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `share_id` bigint NULL DEFAULT NULL, + `drama_id` bigint NULL DEFAULT NULL, + `episode_id` bigint NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_share_likes +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_share_likes`; +CREATE TABLE `php_plugin_shortplay_share_likes` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `share_id` bigint NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_shortplay_style +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_shortplay_style`; +CREATE TABLE `php_plugin_shortplay_style` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `classify` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '风格名称', + `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '风格封面', + `prompts` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '风格指令', + `state` tinyint NULL DEFAULT 0 COMMENT '状态', + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '风格' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_user +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user`; +CREATE TABLE `php_plugin_user` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `puid` bigint NULL DEFAULT NULL COMMENT '上级ID', + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `activation_time` datetime NULL DEFAULT NULL COMMENT '激活时间', + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '账号', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码', + `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号', + `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱', + `state` tinyint NULL DEFAULT 1 COMMENT '状态', + `state_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态原因', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称', + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像', + `login_ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登录IP', + `login_time` datetime NULL DEFAULT NULL COMMENT '登录时间', + `twofa_state` tinyint NULL DEFAULT 0 COMMENT '是否开启二次验证', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1690 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_user_authentication +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_authentication`; +CREATE TABLE `php_plugin_user_authentication` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `type` enum('personal','enterprise') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'personal' COMMENT '证件类型', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '证件名称', + `id_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '证件号码', + `img1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '证件图片', + `img2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '证件图片', + `img3` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '证件图片', + `img4` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '证件图片', + `examine` tinyint(1) NULL DEFAULT 0 COMMENT '审核状态', + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uid`(`uid` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户认证' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_user_authentication +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_user_authentication_log +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_authentication_log`; +CREATE TABLE `php_plugin_user_authentication_log` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `auth_id` bigint NULL DEFAULT NULL, + `state` tinyint(1) NULL DEFAULT NULL COMMENT '认证结果', + `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '认证日志内容', + `admin_id` int NULL DEFAULT NULL COMMENT '操作管理员', + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户认证日志' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_user_authentication_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_user_bill +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_bill`; +CREATE TABLE `php_plugin_user_bill` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL COMMENT '账单时间', + `uid` bigint NULL DEFAULT NULL COMMENT '所属用户', + `form_id` bigint NULL DEFAULT NULL, + `num` decimal(12, 4) NULL DEFAULT 0.0000 COMMENT '本次变动余额', + `before` decimal(12, 4) NULL DEFAULT 0.0000 COMMENT '变动前余额', + `after` decimal(12, 4) NULL DEFAULT 0.0000 COMMENT '变动后余额', + `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '变动理由', + `action` enum('increase','decrease') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '账单类型:0减少,1增加,2余额无变动', + `scene` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `refunded` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '已退费数量', + `is_sum` tinyint(1) NULL DEFAULT 1 COMMENT '是否计入累计 1累计 0不累计', + `type` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-账单流水' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_user_invitation_code +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_invitation_code`; +CREATE TABLE `php_plugin_user_invitation_code` ( + `id` int NOT NULL AUTO_INCREMENT, + `uid` int NULL DEFAULT NULL, + `channels_uid` int NULL DEFAULT NULL, + `code` char(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `state` tinyint(1) NULL DEFAULT 1, + `points` int NULL DEFAULT 0, + `status` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'unused', + `use_uid` int NULL DEFAULT NULL, + `use_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 81 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_user_points +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_points`; +CREATE TABLE `php_plugin_user_points` ( + `id` int NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` int NULL DEFAULT NULL, + `total_points` int NULL DEFAULT 0, + `points` int NULL DEFAULT 0 COMMENT '点数', + `used_points` int NULL DEFAULT 0 COMMENT '已使用', + `valid_time` datetime NULL DEFAULT NULL COMMENT '有效期', + `extended_time` datetime NULL DEFAULT NULL COMMENT '延长有效期', + `state` tinyint(1) NULL DEFAULT 1 COMMENT '无剩余', + `scene` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'vip' COMMENT '来源', + `source_id` int NULL DEFAULT NULL COMMENT '来源ID', + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_user_points_bill +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_points_bill`; +CREATE TABLE `php_plugin_user_points_bill` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL COMMENT '账单时间', + `uid` bigint NULL DEFAULT NULL COMMENT '所属用户', + `form_id` bigint NULL DEFAULT NULL, + `source_id` int NULL DEFAULT 0, + `num` int NULL DEFAULT 0 COMMENT '本次变动数量', + `before` int NULL DEFAULT 0 COMMENT '变动前', + `after` int NULL DEFAULT 0 COMMENT '变动后', + `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '变动理由', + `action` enum('increase','decrease') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '账单类型', + `scene` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `refunded` int NULL DEFAULT 0 COMMENT '已退费数量', + `is_sum` tinyint(1) NULL DEFAULT 1 COMMENT '是否计入累计 1累计 0不累计', + `type` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '积分类型', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 236 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-积分流水' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_plugin_user_twofa_secret +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_twofa_secret`; +CREATE TABLE `php_plugin_user_twofa_secret` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NOT NULL COMMENT '所属用户', + `totp_app` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '二次验证APP', + `secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密钥', + `is_default` tinyint NULL DEFAULT 0 COMMENT '是否为默认', + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '二次验证密钥' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of php_plugin_user_twofa_secret +-- ---------------------------- + +-- ---------------------------- +-- Table structure for php_plugin_user_vip +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_vip`; +CREATE TABLE `php_plugin_user_vip` ( + `id` int NOT NULL AUTO_INCREMENT, + `uid` int NULL DEFAULT NULL, + `channels_uid` int NULL DEFAULT NULL, + `plan_id` int NULL DEFAULT NULL, + `plan_price_id` int NULL DEFAULT NULL, + `type` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `num` int NULL DEFAULT NULL, + `key` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `creat_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for php_plugin_user_wechat +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_user_wechat`; +CREATE TABLE `php_plugin_user_wechat` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL COMMENT '所属用户', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称', + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像', + `openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `unionid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `mp_openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `subscribe` tinyint(1) NULL DEFAULT 0, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uid`(`uid` ASC) USING BTREE, + UNIQUE INDEX `openid`(`openid` ASC) USING BTREE, + UNIQUE INDEX `unid`(`unionid` ASC) USING BTREE, + UNIQUE INDEX `mini`(`mp_openid` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '微信用户' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_uploads +-- ---------------------------- +DROP TABLE IF EXISTS `php_uploads`; +CREATE TABLE `php_uploads` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `admin_uid` int NULL DEFAULT NULL, + `classify_id` int NULL DEFAULT NULL, + `filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `ext` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `mime` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `size` double NULL DEFAULT 0, + `channels` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'local', + `auto_delete_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1426 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for php_uploads_classify +-- ---------------------------- +DROP TABLE IF EXISTS `php_uploads_classify`; +CREATE TABLE `php_uploads_classify` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `uid` bigint NULL DEFAULT NULL, + `admin_uid` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `dir_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `sort` tinyint NULL DEFAULT 99, + `channels` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `is_system` tinyint NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +CREATE TABLE `fastmovie`.`php_plugin_model_voice` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` bigint NULL DEFAULT NULL, + `channels_uid` int NULL DEFAULT NULL, + `name` char(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `headimg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `voice_id` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `audio` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `task_id` bigint NULL DEFAULT NULL, + `status` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `model_id` int NULL DEFAULT NULL, + `message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `text_id` int NULL DEFAULT NULL, + `gender` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `age` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +CREATE TABLE `fastmovie`.`php_plugin_model_voice_text` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `channels_uid` int NULL DEFAULT NULL, + `text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `state` tinyint NULL DEFAULT 1, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/fastmovie-admin/index.html b/fastmovie-admin/index.html new file mode 100644 index 0000000..86aeca2 --- /dev/null +++ b/fastmovie-admin/index.html @@ -0,0 +1,39 @@ + + + + + 恭喜,站点创建成功! + + + +
+

恭喜, 站点创建成功!

+

这是默认index.html,本页面由系统自动生成

+
    +
  • 本页面在FTP根目录下的index.html
  • +
  • 您可以修改、删除或覆盖本页面
  • +
  • FTP相关信息,请到“面板系统后台 > FTP” 查看
  • +
+
+ + \ No newline at end of file diff --git a/fastmovie-admin/lua/module/config.lua b/fastmovie-admin/lua/module/config.lua new file mode 100644 index 0000000..9e0fc74 --- /dev/null +++ b/fastmovie-admin/lua/module/config.lua @@ -0,0 +1,41 @@ + +local _M = { + root_path = nil +} + + +local function get_script_dir() + local lfs_ok, lfs = pcall(require, "lfs") + + local source + if debug and debug.getinfo then + -- OpenResty 或标准 Lua 都适用 + source = debug.getinfo(2, "S").source:sub(2) + elseif arg and arg[0] then + source = arg[0] + else + return nil + end + + -- 如果是相对路径,转换为绝对路径 + if not source:match("^/") then + if lfs_ok then + source = lfs.currentdir() .. "/" .. source + else + -- 没有 lfs,使用 io.popen("pwd") 获取当前目录 + local handle = io.popen("pwd") + local cwd = handle:read("*l") + handle:close() + source = cwd .. "/" .. source + end + end + + -- 提取目录 + local dir = source:match("(.*/)") + -- 降末尾的/lua/module/去掉 + return dir:sub(1,-13) +end + +_M.root_path = get_script_dir() +print(_M.root_path) +return _M \ No newline at end of file diff --git a/fastmovie-admin/lua/module/load_env.lua b/fastmovie-admin/lua/module/load_env.lua new file mode 100644 index 0000000..07a24ae --- /dev/null +++ b/fastmovie-admin/lua/module/load_env.lua @@ -0,0 +1,122 @@ +--[[ +load_env.lua +功能: + 自动读取 .env 文件(优先当前目录,其次模块上一级目录),解析为键值表。 + 提供安全的 env.get(key, default, type) 方法: + - 不存在 .env 文件或 key 时返回 default + - 支持类型转换(string / number / boolean) +使用示例: + local env = require("load_env") + local port = env.get("SERVER_PORT", 8080, "number") + local debug = env.get("DEBUG", false, "boolean") +]] +local config = require "config" +local M = {} +local env_cache = nil + +------------------------------------------------------------ +-- 工具函数 +------------------------------------------------------------ + +-- 读取文件内容 +local function read_file(path) + local file = io.open(path, "r") + if not file then return nil end + local content = file:read("*a") + file:close() + -- 去掉 UTF-8 BOM + if content:sub(1,3) == "\239\187\191" then + content = content:sub(4) + end + return content +end + +-- 解析 .env 文件为表 +local function parse_env(content) + local env = {} + if not content then return env end + for line in content:gmatch("[^\r\n]+") do + if not line:match("^%s*#") and line:match("%S") then + local key, value = line:match("^%s*([%w_]+)%s*=%s*(.-)%s*$") + if key then + value = value:gsub("^['\"](.-)['\"]$", "%1") + env[key] = value + end + end + end + return env +end + +-- 自动搜索 .env 文件路径 +local function find_env() + local dirs = { + config.root_path .. "/lua/", -- Lua目录 + config.root_path .. "/" -- 项目跟目录 + } + for _, dir in ipairs(dirs) do + local path = dir .. ".env" + local f = io.open(path, "r") + if f then f:close() return path end + end + return nil +end + + +------------------------------------------------------------ +-- 主逻辑 +------------------------------------------------------------ + +local function load_env() + if env_cache then return env_cache end + local env_path = find_env() + if not env_path then + print("未找到 .env 文件,返回空表") + env_cache = {} + return env_cache + end + print("加载 .env 文件路径: " .. env_path) + local content = read_file(env_path) + env_cache = parse_env(content) + return env_cache +end + +-- 类型转换 +local function convert_type(value, expect_type) + if expect_type == "number" then + return tonumber(value) + elseif expect_type == "boolean" then + local v = tostring(value):lower() + return (v == "true" or v == "1" or v == "yes" or v == "on") + else + return tostring(value) + end +end + +------------------------------------------------------------ +-- 导出接口 +------------------------------------------------------------ + +--- 获取单个键值 +-- @param key string 键名 +-- @param default any 默认值 +-- @param expect_type string 期望类型: "string" | "number" | "boolean" +function M.get(key, default, expect_type) + expect_type = expect_type or "string" + local env = load_env() + local val = env[key] + if val == nil or val == "" then + return default + end + local converted = convert_type(val, expect_type) + if converted == nil then + return default + end + return converted +end + +-- 获取完整表 +function M.all() + return load_env() +end + +return M diff --git a/fastmovie-admin/lua/qps.lua b/fastmovie-admin/lua/qps.lua new file mode 100644 index 0000000..0b0bb05 --- /dev/null +++ b/fastmovie-admin/lua/qps.lua @@ -0,0 +1,121 @@ +local function get_script_dir() + local lfs_ok, lfs = pcall(require, "lfs") + + local source + if debug and debug.getinfo then + -- OpenResty 或标准 Lua 都适用 + source = debug.getinfo(2, "S").source:sub(2) + elseif arg and arg[0] then + source = arg[0] + else + return nil + end + + -- 如果是相对路径,转换为绝对路径 + if not source:match("^/") then + if lfs_ok then + source = lfs.currentdir() .. "/" .. source + else + -- 没有 lfs,使用 io.popen("pwd") 获取当前目录 + local handle = io.popen("pwd") + local cwd = handle:read("*l") + handle:close() + source = cwd .. "/" .. source + end + end + + -- 提取目录 + local dir = source:match("(.*/)") + return dir +end + +package.path = package.path .. ";"..get_script_dir().."module/?.lua;" +-- 如过不是以/app/开头的请求,不进行QPS控制 +local request_uri = ngx.var.request_uri +if not string.match(request_uri, "^/app/") then + return +end + +-- 获取请求方法 +local method = ngx.req.get_method() +method=tostring(method) + +-- 获取请求路径 +local path = string.match(request_uri, "([^?]*)") +path=tostring(path) + +local env = require("load_env") +-- 连接Redis +local redis = require "resty.redis" +local red = redis:new() +red:set_timeout(1000) -- 1 秒超时 + +local ok, err = red:connect(env.get("REDIS_HOST","127.0.0.1"), env.get("REDIS_PORT",6379,"number")) +if not ok then + ngx.log(ngx.ERR,"Connection to Redis failed: ", err) + return +end + +local database = env.get("REDIS_DATABASE",2,"number") + -- 选择数据库 +local ok,err = red:select(database) +if not ok then + ngx.log(ngx.ERR,"Failed to select Redis database ",database,": ", err) + return +end + +-- 获取请求限制 +local limit_key="QPS:"..method..":"..path +local limit, err = red:get(limit_key) +limit = tonumber(limit) + +-- 如果没有设置限制,则不进行QPS控制 +if not limit then + return +end + +-- 获取请求头中的Authorization +local authorization=ngx.var.http_authorization +if not authorization then + ngx.log(ngx.ERR,"The request header does not include Authorization: ",authorization) + return ngx.exit(401) +end + +-- 拼接用户key +authorization=tostring(authorization) +local user_key = limit_key..":"..authorization + +-- 获取用户请求次数 +local requests, err = red:get(user_key) +requests = tonumber(requests) + +if not requests then + requests = 0 +end + +-- 如果请求次数超过限制,则返回503 +if requests > limit then + ngx.log(ngx.ERR,limit,"qps/s, Authorization: ",authorization) + return ngx.exit(429) +end + + +-- 原子增加请求次数 +local ok, err = red:incr(user_key) +if not ok then + ngx.log(ngx.ERR,"Redis increment operation failed: ", err) + return +end + +local ok, err = red:expire(user_key, 1) +if not ok then + ngx.log(ngx.ERR,"Failed to set expiration for Redis key: ", err) + return +end + +-- 释放连接(连接池实现) +local ok, err = red:set_keepalive(10000, 100) +if not ok then + ngx.log(ngx.ERR,"Failed to set keepalive: ", err) + return +end \ No newline at end of file diff --git a/fastmovie-admin/lua/test.lua b/fastmovie-admin/lua/test.lua new file mode 100644 index 0000000..9706429 --- /dev/null +++ b/fastmovie-admin/lua/test.lua @@ -0,0 +1,38 @@ +local function get_script_dir() + local lfs_ok, lfs = pcall(require, "lfs") + + local source + if debug and debug.getinfo then + -- OpenResty 或标准 Lua 都适用 + source = debug.getinfo(2, "S").source:sub(2) + elseif arg and arg[0] then + source = arg[0] + else + return nil + end + + -- 如果是相对路径,转换为绝对路径 + if not source:match("^/") then + if lfs_ok then + source = lfs.currentdir() .. "/" .. source + else + -- 没有 lfs,使用 io.popen("pwd") 获取当前目录 + local handle = io.popen("pwd") + local cwd = handle:read("*l") + handle:close() + source = cwd .. "/" .. source + end + end + + -- 提取目录 + local dir = source:match("(.*/)") + return dir +end + +package.path = package.path .. ";"..get_script_dir().."module/?.lua;" +local env = require("load_env") +print("ENV文件加载成功,服务名称:"..env.get("SERVER_NAME")) + + +-- 测试 +print(get_script_dir()) diff --git a/fastmovie-admin/menu.example b/fastmovie-admin/menu.example new file mode 100644 index 0000000..8b0bb89 --- /dev/null +++ b/fastmovie-admin/menu.example @@ -0,0 +1,22 @@ +[ + { + "title": "控制台", + "icon": "ElementPlus",//填入element-plus中的Icon名称,如:ElementPlus + "path": "Index/console", + "component":"",//table:表格列表,form:表单,images:图片列表 + "show": 1, + "sort":9999, + "query": { + //携带地址栏参数 + "id":1 + }, + "params":{ + //携带post参数 + }, + "children":[ + { + ...参数与父级一致 + } + ] + } +] \ No newline at end of file diff --git a/fastmovie-admin/nginx.example b/fastmovie-admin/nginx.example new file mode 100644 index 0000000..b7965c8 --- /dev/null +++ b/fastmovie-admin/nginx.example @@ -0,0 +1,39 @@ +# 站内信推送 +location /app/PUSH_KEY { + proxy_pass http://127.0.0.1:36302; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header X-Real-IP $remote_addr; +} + +# 将请求转发到webman +location ^~ / { + # 接口QPS插件,nginx版本需要选择:nginx openresty + # 如需使用接口QPS插件,取消注释 + # access_by_lua_file $document_root/lua/qps.lua; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_http_version 1.1; + proxy_set_header Connection ""; + if (!-f $request_filename){ + proxy_pass http://127.0.0.1:36300; + } +} + +# 拒绝访问所有以 .php 结尾的文件 +location ~ \.php$ { + return 404; +} + +# 允许访问 .well-known 目录 +location ~ ^/\.well-known/ { + allow all; +} + +# 拒绝访问所有以 . 开头的文件或目录 +location ~ /\. { + return 404; +} \ No newline at end of file diff --git a/fastmovie-admin/package.json b/fastmovie-admin/package.json new file mode 100644 index 0000000..4256a05 --- /dev/null +++ b/fastmovie-admin/package.json @@ -0,0 +1,13 @@ +{ + "name": "loong-admin", + "private": true, + "version": "0.0.8", + "type": "module", + "scripts": { + "dev": "php webman start", + "start": "php webman start -d", + "stop": "php webman stop", + "restart": "php webman restart", + "status": "php webman status" + } +} diff --git a/fastmovie-admin/plugin/article/admin.json b/fastmovie-admin/plugin/article/admin.json new file mode 100644 index 0000000..e432610 --- /dev/null +++ b/fastmovie-admin/plugin/article/admin.json @@ -0,0 +1,92 @@ +{ + "title":"文章", + "icon":"Notebook", + "path":"/app/article/admin", + "sort":4999, + "children":[ + { + "title":"公告", + "icon":"BellFilled", + "path":"/app/article/admin/Announcement/index", + "component":"table" + }, + { + "title":"文章列表", + "icon":"Notebook", + "path":"/app/article/admin/Index/index", + "show": false, + "component":"table" + }, + { + "title":"添加文章", + "path":"/app/article/admin/Index/create", + "component":"form", + "show":false + }, + { + "title":"编辑文章", + "path":"/app/article/admin/Index/update", + "component":"form", + "show":false + }, + { + "title":"审核文章", + "path":"/app/article/admin/Index/examine", + "component":"form", + "show":false + }, + { + "title":"更新文章状态", + "path":"/app/article/admin/Index/indexUpdateState", + "show":false + }, + { + "title":"更新文章排序", + "path":"/app/article/admin/Index/indexUpdateField", + "show":false + }, + { + "title":"删除文章", + "path":"/app/article/admin/Index/delete", + "show":false + }, + { + "title":"缓存文章", + "path":"/app/article/admin/Index/cache", + "show":false + }, + { + "title":"文章分类", + "icon":"FolderOpened", + "path":"/app/article/admin/Classify/index", + "component":"table" + }, + { + "title":"添加分类", + "path":"/app/article/admin/Classify/create", + "component":"form", + "show":false + }, + { + "title":"编辑分类", + "path":"/app/article/admin/Classify/update", + "component":"form", + "show":false + }, + { + "title":"更新分类状态", + "path":"/app/article/admin/Classify/indexUpdateState", + "show":false + }, + { + "title":"更新分类排序", + "path":"/app/article/admin/Classify/indexUpdateField", + "show":false + }, + { + "title":"删除分类", + "path":"/app/article/admin/Classify/delete", + "show":false + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/api/Control.php b/fastmovie-admin/plugin/article/api/Control.php new file mode 100644 index 0000000..4f97f18 --- /dev/null +++ b/fastmovie-admin/plugin/article/api/Control.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'article'; + } +} diff --git a/fastmovie-admin/plugin/article/api/Install.php b/fastmovie-admin/plugin/article/api/Install.php new file mode 100644 index 0000000..ec735f7 --- /dev/null +++ b/fastmovie-admin/plugin/article/api/Install.php @@ -0,0 +1,14 @@ +path = __DIR__; + } +} diff --git a/fastmovie-admin/plugin/article/api/Install.sql b/fastmovie-admin/plugin/article/api/Install.sql new file mode 100644 index 0000000..c7f3dcb --- /dev/null +++ b/fastmovie-admin/plugin/article/api/Install.sql @@ -0,0 +1,76 @@ +/* + Navicat Premium Data Transfer + + Source Server : Loong + Source Server Type : MySQL + Source Server Version : 80024 (8.0.24) + Source Host : 127.0.0.1:3306 + Source Schema : cloud_xhadmin_cn + + Target Server Type : MySQL + Target Server Version : 80024 (8.0.24) + File Encoding : 65001 + + Date: 15/05/2024 18:50:00 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for php_plugin_article +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_article`; +CREATE TABLE `php_plugin_article` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `release_time` datetime NULL DEFAULT NULL, + `alias` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文章别名', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文章标题', + `subtitle` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `keywords` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `state` tinyint(1) NULL DEFAULT 1, + `examine` tinyint(1) NULL DEFAULT NULL, + `view` int NULL DEFAULT 0, + `thumb` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文章封面', + `class` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `description` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `classify_id` int NULL DEFAULT NULL, + `sort` tinyint NULL DEFAULT 99, + `delete_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `alias`(`alias` ASC) USING BTREE, + INDEX `cid`(`classify_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Table structure for php_plugin_article_classify +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_article_classify`; +CREATE TABLE `php_plugin_article_classify` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `create_time` datetime NULL DEFAULT NULL COMMENT '分类创建时间', + `update_time` datetime NULL DEFAULT NULL, + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类名称', + `alias` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '别名', + `pid` int NULL DEFAULT NULL COMMENT '父级ID', + `sort` int NULL DEFAULT 99 COMMENT '排序', + `state` tinyint(1) NULL DEFAULT 1, + PRIMARY KEY (`id`) USING BTREE, + INDEX `alias`(`alias` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章分类数据' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Table structure for php_plugin_article_content +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_article_content`; +CREATE TABLE `php_plugin_article_content` ( + `article_id` int UNSIGNED NOT NULL, + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, + PRIMARY KEY (`article_id`) USING BTREE, + UNIQUE INDEX `article`(`article_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章内容表' ROW_FORMAT = DYNAMIC; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/fastmovie-admin/plugin/article/app/Basic.php b/fastmovie-admin/plugin/article/app/Basic.php new file mode 100644 index 0000000..1345a5a --- /dev/null +++ b/fastmovie-admin/plugin/article/app/Basic.php @@ -0,0 +1,10 @@ +model = new PluginArticle(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '220px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => $this->plugin . 'admin/Announcement/update', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('缓存', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'admin/Announcement/cache', + 'props' => [ + 'type' => 'warning', + 'title' => '确定要重新缓存《{title}》公告吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'admin/Announcement/delete', + 'props' => [ + 'type' => 'danger', + 'title' => '确定要删除《{title}》公告吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('发布公告', [ + 'path' => $this->plugin . 'admin/Announcement/create', + 'props' => [ + 'type' => 'success', + 'title' => '发布公告' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('title', '标题', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '标题搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '标题', [ + 'props' => [ + 'minWidth' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'title', + 'label' => '标题', + 'component' => 'text', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ], + [ + 'field' => 'subtitle', + 'label' => '副标题', + 'component' => 'text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ] + ]); + $builder->add('alias', '别名', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('view', '浏览量', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('source', '来源', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => $this->plugin . 'admin/Announcement/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('sort', '排序', [ + 'component' => [ + 'name' => 'input-number', + 'api' => $this->plugin . 'admin/Announcement/indexUpdateField', + 'props' => [ + 'min' => 0, + 'size' => 'small' + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ], + [ + 'field' => 'release_time', + 'label' => '发布' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['a.title', 'like', "%{$title}%"]; + } + $where[] = ['a.alias', '=', 'announcement']; + $state = $request->get('state'); + if ($state) { + $where[] = ['a.state', '=', $state]; + } + $where[] = ['a.examine', '=', Examine::PASS['value']]; + $list = PluginArticle::alias('a')->where($where) + ->join('plugin_article_classify c', 'c.id = a.classify_id', 'LEFT') + ->field('a.*,c.title as classify_title') + ->order('a.id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $validate = new Article; + try { + $validate->scene('create')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = new PluginArticle; + $PluginArticle->pid = $D['pid']; + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + $PluginArticle->keywords = $D['keywords']; + $PluginArticle->description = $D['description']; + $PluginArticle->alias = 'announcement'; + $PluginArticle->view = $D['view']; + $PluginArticle->sort = $D['sort']; + $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + $PluginArticle->examine = Examine::PASS['value']; + if ($D['release_time']) { + $PluginArticle->release_time = $D['release_time']; + } else { + $PluginArticle->release_time = date('Y-m-d H:i:s'); + } + $PluginArticle->save(); + $PluginArticleContent = new PluginArticleContent; + $PluginArticleContent->article_id = $PluginArticle->id; + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $this->cacheHtml($PluginArticle->id); + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $validate = new Article; + try { + $validate->scene('update')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = PluginArticle::where(['id' => $D['id']])->find(); + $PluginArticle->pid = $D['pid']; + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + $PluginArticle->keywords = $D['keywords']; + $PluginArticle->description = $D['description']; + $PluginArticle->view = $D['view']; + $PluginArticle->sort = $D['sort']; + $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + $PluginArticle->release_time = $D['release_time']; + $PluginArticle->save(); + $PluginArticleContent = PluginArticleContent::where(['article_id' => $D['id']])->find(); + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('编辑成功'); + } + $id = $request->get('id'); + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c', 'c.article_id=a.id')->field('a.*,c.content')->find(); + $builder = $this->getFormBuilder(); + $builder->setData($PluginArticle->toArray()); + return $this->resData($builder); + } + public function getFormBuilder() + { + $builder = new FormBuilder(null, null, ['size' => 'large']); + $Component = new ComponentBuilder; + $builder->add('classify_id', '所属分类', 'cascader', '', [ + 'required' => true, + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('subtitle', '副标题', 'input', '', [ + 'props' => [ + 'maxlength' => 200, + 'show-word-limit' => true + ] + ]); + $builder->add('thumb', '封面', 'bundle', [], [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 5 + ] + ]); + $builder->add('keywords', '关键词', 'input', '', [ + 'props' => [ + 'type' => 'textarea', + 'maxlength' => 200, + 'show-word-limit' => true, + 'autosize' => [ + 'minRows' => 2, + 'maxRows' => 4 + ] + ] + ]); + $builder->add('description', '描述', 'input', '', [ + 'props' => [ + 'type' => 'textarea', + 'maxlength' => 300, + 'show-word-limit' => true, + 'autosize' => [ + 'minRows' => 3, + 'maxRows' => 6 + ] + ] + ]); + $builder->add('alias', '别名', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'md' => 12 + ], + 'prompt' => [ + $Component->add('text', ['default' => '设置别名后可通过别名访问公告'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('view', '浏览量', 'input-number', 0, [ + 'col' => [ + 'xs' => 24, + 'md' => 6 + ], + 'props' => [ + 'min' => 0, + 'controls' => false + ] + ]); + $builder->add('sort', '排序', 'input-number', 99, [ + 'col' => [ + 'xs' => 24, + 'md' => 6 + ], + 'props' => [ + 'min' => 0, + 'controls' => false + ] + ]); + $builder->add('source', '来源名称', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '为空则自动读取网站应用名称'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('release_time', '发布时间', 'date-picker', null, [ + 'prompt' => [ + $Component->add('text', ['default' => '为空则自动写入当前时间'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'type' => 'datetime', + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'value-format' => 'YYYY-MM-DD HH:mm:ss', + 'placeholder' => '发布时间' + ] + ]); + $builder->add('content', '内容', 'wangeditor', '', [ + 'required' => true, + 'props' => [ + 'class' => 'vh-60' + ] + ]); + return $builder; + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + try { + if ($value === State::NO['value']) { + $this->clearHtml($id); + } else { + $this->cacheHtml($id); + } + } catch (\Throwable $th) { + } + return $this->success(); + } + return $this->fail('操作失败'); + } + public function clearHtml($id) + { + $PluginArticle = PluginArticle::where(['id' => $id])->find(); + $path = public_path('article/' . $id . '.html'); + if (file_exists($path)) { + unlink($path); + } + if ($PluginArticle->alias) { + $path = public_path('article/' . $PluginArticle->alias . '.html'); + if (file_exists($path)) { + unlink($path); + } + } + } + public function cacheHtml($id) + { + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c', 'c.article_id=a.id')->field('a.*,c.content')->find(); + $config = new Config('basic', ''); + if (!$PluginArticle->source) { + $PluginArticle->source = $config['web_name']; + } + if ($config['logo_use'] == 'image') { + $PluginArticle->web_logo = $config['web_logo_light']; + } else { + $PluginArticle->web_logo = '/vite.svg'; + } + $PluginArticle->web_title = "{$PluginArticle->title} - {$config['web_title']}"; + $html = view(base_path('plugin/article/public/template.html'), $PluginArticle->toArray(), null, '')->rawBody(); + $path = public_path('article/' . $id . '.html'); + file_put_contents($path, $html); + if ($PluginArticle->alias) { + $path = public_path('article/' . $PluginArticle->alias . '.html'); + file_put_contents($path, $html); + } + } + public function cache(Request $request) + { + $id = $request->post('id'); + $this->cacheHtml($id); + return $this->success('缓存成功'); + } +} diff --git a/fastmovie-admin/plugin/article/app/admin/controller/ClassifyController.php b/fastmovie-admin/plugin/article/app/admin/controller/ClassifyController.php new file mode 100644 index 0000000..4ab168e --- /dev/null +++ b/fastmovie-admin/plugin/article/app/admin/controller/ClassifyController.php @@ -0,0 +1,301 @@ +model = new PluginArticleClassify(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder([ + 'rowKey' => 'id', + 'api' => $this->plugin.'admin/Classify/index', + 'lazy' => true, + 'treeProps' => [ + 'children' => 'children', + 'hasChildren' => 'hasChildren' + ] + ]); + $builder->addAction('操作', [ + 'width' => '160px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => $this->plugin . 'admin/Classify/update', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'admin/Classify/delete', + 'props' => [ + 'type' => 'danger', + 'title' => '确定要删除《{title}》分类吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('新建分类', [ + 'path' => $this->plugin . 'admin/Classify/create', + 'props' => [ + 'type' => 'success', + 'title' => '新建分类' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('title', '标题', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '标题搜索', + 'clearable' => true + ] + ]); + + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '标题', [ + 'props' => [ + 'minWidth' => '200px' + ] + ]); + $builder->add('alias', '别名', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => $this->plugin . 'admin/Classify/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('sort', '排序', [ + 'component' => [ + 'name' => 'input-number', + 'api' => $this->plugin . 'admin/Classify/indexUpdateField', + 'props' => [ + 'min' => 0, + 'size'=>'small' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $id = $request->get('id'); + if ($id) { + $where[] = ['pid', '=', $id]; + } else { + $where[] = ['pid', '=', NULL]; + } + $state = $request->get('state'); + if ($state) { + $where[] = ['state', '=', $state]; + } + $list = PluginArticleClassify::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) { + $item->hasChildren = PluginArticleClassify::where(['pid' => $item->id])->count() > 0; + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + Db::startTrans(); + try { + $PluginArticleClassify = new PluginArticleClassify; + if($D['pid']){ + $PluginArticleClassify->pid = $D['pid']; + } + $PluginArticleClassify->title = $D['title']; + $PluginArticleClassify->alias = $D['alias']; + $PluginArticleClassify->sort = $D['sort']; + $PluginArticleClassify->state = $D['state']; + $PluginArticleClassify->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + Db::startTrans(); + try { + $PluginArticleClassify = PluginArticleClassify::where(['id' => $D['id']])->find(); + if($D['pid']){ + $PluginArticleClassify->pid = $D['pid']; + }else{ + $PluginArticleClassify->pid = null; + } + $PluginArticleClassify->title = $D['title']; + $PluginArticleClassify->alias = $D['alias']; + $PluginArticleClassify->sort = $D['sort']; + $PluginArticleClassify->state = $D['state']; + $PluginArticleClassify->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('编辑成功'); + } + $id = $request->get('id'); + $PluginArticleClassify = PluginArticleClassify::where(['id' => $id])->find(); + $builder = $this->getFormBuilder(); + $builder->setData($PluginArticleClassify->toArray()); + return $this->resData($builder); + } + public function getFormBuilder() + { + $builder = new FormBuilder(null,null,['size'=>'large']); + $builder->add('pid', '分类等级', 'cascader', '', [ + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('alias', '别名', 'input', '', [ + 'col'=>[ + 'xs'=>24, + 'md'=>12 + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('sort', '排序', 'input-number', 99, [ + 'required' => true, + 'col'=>[ + 'xs'=>24, + 'md'=>12 + ], + 'props' => [ + 'min' => 0, + 'controls'=>false + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + return $builder; + } + public function delete(Request $request) + { + $id=$request->post('id'); + $PluginArticleClassify = PluginArticleClassify::where(['id' => $id])->find(); + if(!$PluginArticleClassify){ + return $this->fail('分类不存在'); + } + if(PluginArticleClassify::where(['pid' => $id])->count()){ + return $this->fail('请先删除子分类'); + } + if(PluginArticle::where(['classify_id' => $id])->count()){ + return $this->fail('请先删除该分类下的文章'); + } + if($PluginArticleClassify->delete()){ + return $this->success('删除成功'); + } + return $this->fail('删除失败'); + } +} diff --git a/fastmovie-admin/plugin/article/app/admin/controller/IndexController.php b/fastmovie-admin/plugin/article/app/admin/controller/IndexController.php new file mode 100644 index 0000000..5193438 --- /dev/null +++ b/fastmovie-admin/plugin/article/app/admin/controller/IndexController.php @@ -0,0 +1,544 @@ +model = new PluginArticle(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '220px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => $this->plugin . 'admin/Index/update', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('缓存', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'admin/Index/cache', + 'props' => [ + 'type' => 'warning', + 'title' => '确定要重新缓存《{title}》文章吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'admin/Index/delete', + 'props' => [ + 'type' => 'danger', + 'title' => '确定要删除《{title}》文章吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('发布文章', [ + 'path' => $this->plugin . 'admin/Index/create', + 'props' => [ + 'type' => 'success', + 'title' => '发布文章' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('title', '标题', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '标题搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('classify_id', '分类等级', 'cascader', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $formBuilder->add('state', '状态', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('classify_title', '分类', [ + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('title', '标题', [ + 'props' => [ + 'minWidth' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'title', + 'label' => '标题', + 'component' => 'text', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ], + [ + 'field' => 'subtitle', + 'label' => '副标题', + 'component' => 'text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ] + ]); + $builder->add('alias', '别名', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('view', '浏览量', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('source', '来源', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => $this->plugin . 'admin/Index/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('sort', '排序', [ + 'component' => [ + 'name' => 'input-number', + 'api' => $this->plugin . 'admin/Index/indexUpdateField', + 'props' => [ + 'min' => 0, + 'size'=>'small' + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ], + [ + 'field' => 'release_time', + 'label' => '发布' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['a.title', 'like', "%{$title}%"]; + } + $classify_id = $request->get('classify_id'); + if ($classify_id) { + $where[] = ['a.classify_id', '=', $classify_id]; + } + $state = $request->get('state'); + if ($state) { + $where[] = ['a.state', '=', $state]; + } + $where[] = ['a.examine', '=', Examine::PASS['value']]; + $list = PluginArticle::alias('a')->where($where) + ->join('plugin_article_classify c', 'c.id = a.classify_id','LEFT') + ->field('a.*,c.title as classify_title') + ->order('a.id desc')->paginate($limit)->each(function ($item) { + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $validate = new Article; + try { + $validate->scene('create')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = new PluginArticle; + $PluginArticle->pid = $D['pid']; + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + $PluginArticle->keywords = $D['keywords']; + $PluginArticle->description = $D['description']; + $PluginArticle->alias = $D['alias']; + $PluginArticle->view = $D['view']; + $PluginArticle->sort = $D['sort']; + $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + $PluginArticle->examine = Examine::PASS['value']; + if($D['release_time']){ + $PluginArticle->release_time = $D['release_time']; + }else{ + $PluginArticle->release_time = date('Y-m-d H:i:s'); + } + $PluginArticle->save(); + $PluginArticleContent = new PluginArticleContent; + $PluginArticleContent->article_id = $PluginArticle->id; + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $this->cacheHtml($PluginArticle->id); + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $validate = new Article; + try { + $validate->scene('update')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = PluginArticle::where(['id' => $D['id']])->find(); + $PluginArticle->pid = $D['pid']; + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + $PluginArticle->keywords = $D['keywords']; + $PluginArticle->description = $D['description']; + $PluginArticle->alias = $D['alias']; + $PluginArticle->view = $D['view']; + $PluginArticle->sort = $D['sort']; + $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + $PluginArticle->release_time = $D['release_time']; + $PluginArticle->save(); + $PluginArticleContent = PluginArticleContent::where(['article_id' => $D['id']])->find(); + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('编辑成功'); + } + $id = $request->get('id'); + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c','c.article_id=a.id')->field('a.*,c.content')->find(); + $builder = $this->getFormBuilder(); + $builder->setData($PluginArticle->toArray()); + return $this->resData($builder); + } + public function getFormBuilder() + { + $builder = new FormBuilder(null,null,['size'=>'large']); + $Component = new ComponentBuilder; + $builder->add('classify_id', '所属分类', 'cascader', '', [ + 'required' => true, + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('subtitle', '副标题', 'input', '', [ + 'props' => [ + 'maxlength' => 200, + 'show-word-limit' => true + ] + ]); + $builder->add('thumb', '封面', 'bundle', [], [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 5 + ] + ]); + $builder->add('keywords', '关键词', 'input', '', [ + 'props' => [ + 'type' => 'textarea', + 'maxlength' => 200, + 'show-word-limit' => true, + 'autosize' => [ + 'minRows' => 2, + 'maxRows' => 4 + ] + ] + ]); + $builder->add('description', '描述', 'input', '', [ + 'props' => [ + 'type' => 'textarea', + 'maxlength' => 300, + 'show-word-limit' => true, + 'autosize' => [ + 'minRows' => 3, + 'maxRows' => 6 + ] + ] + ]); + $builder->add('alias', '别名', 'input', '', [ + 'col'=>[ + 'xs'=>24, + 'md'=>12 + ], + 'prompt'=>[ + $Component->add('text',['default'=>'设置别名后可通过别名访问文章'],['type'=>'info','size'=>'small'])->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('view', '浏览量', 'input-number', 0, [ + 'col'=>[ + 'xs'=>24, + 'md'=>6 + ], + 'props' => [ + 'min' => 0, + 'controls'=>false + ] + ]); + $builder->add('sort', '排序', 'input-number', 99, [ + 'col'=>[ + 'xs'=>24, + 'md'=>6 + ], + 'props' => [ + 'min' => 0, + 'controls'=>false + ] + ]); + $builder->add('source', '来源名称', 'input', '', [ + 'prompt'=>[ + $Component->add('text',['default'=>'为空则自动读取网站应用名称'],['type'=>'info','size'=>'small'])->builder() + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps'=>[ + 'border'=>true + ] + ]); + $builder->add('release_time', '发布时间', 'date-picker', null, [ + 'prompt' => [ + $Component->add('text',['default'=>'为空则自动写入当前时间'],['type'=>'info','size'=>'small'])->builder() + ], + 'props' => [ + 'type' => 'datetime', + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'value-format' => 'YYYY-MM-DD HH:mm:ss', + 'placeholder' => '发布时间' + ] + ]); + $builder->add('content', '内容', 'wangeditor', '', [ + 'required' => true, + 'props' => [ + 'class' => 'vh-60' + ] + ]); + return $builder; + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + try { + if($value===State::NO['value']){ + $this->clearHtml($id); + }else{ + $this->cacheHtml($id); + } + } catch (\Throwable $th) { + } + return $this->success(); + } + return $this->fail('操作失败'); + } + public function clearHtml($id) + { + $PluginArticle=PluginArticle::where(['id'=>$id])->find(); + $path = public_path('article/'.$id.'.html'); + if(file_exists($path)){ + unlink($path); + } + if($PluginArticle->alias){ + $path = public_path('article/'.$PluginArticle->alias.'.html'); + if(file_exists($path)){ + unlink($path); + } + } + } + public function cacheHtml($id) + { + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c','c.article_id=a.id')->field('a.*,c.content')->find(); + $config=new Config('basic',''); + if(!$PluginArticle->source){ + $PluginArticle->source = $config['web_name']; + } + if($config['logo_use']=='image'){ + $PluginArticle->web_logo=$config['web_logo_light']; + }else{ + $PluginArticle->web_logo='/vite.svg'; + } + $PluginArticle->web_title="{$PluginArticle->title} - {$config['web_title']}"; + $html=view(base_path('plugin/article/public/template.html'),$PluginArticle->toArray(),null,'')->rawBody(); + $path = public_path('article/'.$id.'.html'); + file_put_contents($path, $html); + if($PluginArticle->alias){ + $path = public_path('article/'.$PluginArticle->alias.'.html'); + file_put_contents($path, $html); + } + } + public function cache(Request $request) + { + $id = $request->post('id'); + $this->cacheHtml($id); + return $this->success('缓存成功'); + } +} diff --git a/fastmovie-admin/plugin/article/app/admin/middleware/Auth.php b/fastmovie-admin/plugin/article/app/admin/middleware/Auth.php new file mode 100644 index 0000000..dbae0cc --- /dev/null +++ b/fastmovie-admin/plugin/article/app/admin/middleware/Auth.php @@ -0,0 +1,9 @@ +get('key'); + $article = PluginArticle::where(['alias' => $alias, 'channels_uid' => $request->channels_uid])->find(); + if (!$article) { + return $this->resData('文章不存在'); + } + $content = PluginArticleContent::where(['article_id' => $article->id])->find(); + return $this->success('获取成功', $content->content); + } + public function details(Request $request) + { + $id = $request->get('id'); + if (!$id) { + return $this->fail('文章ID不能为空'); + } + $article = PluginArticle::where(['id' => $id, 'channels_uid' => $request->channels_uid])->with('content')->find(); + if (!$article) { + $article = PluginArticle::where(['alias' => $id, 'channels_uid' => $request->channels_uid])->with('content')->find(); + } + if (!$article) { + return $this->fail('文章不存在'); + } + return $this->success('获取成功', $article); + } +} diff --git a/fastmovie-admin/plugin/article/app/control/controller/AgreementController.php b/fastmovie-admin/plugin/article/app/control/controller/AgreementController.php new file mode 100644 index 0000000..d6ff1a9 --- /dev/null +++ b/fastmovie-admin/plugin/article/app/control/controller/AgreementController.php @@ -0,0 +1,177 @@ +method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + + return $this->showAgreementForm($alias); + } + public function user(Request $request) + { + $alias = 'user'; + if ($request->method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + + return $this->showAgreementForm($alias); + } + public function guide(Request $request) + { + $alias = 'guide'; + if ($request->method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + return $this->showAgreementForm($alias); + } + public function help(Request $request) + { + $alias = 'help'; + if ($request->method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + + return $this->showAgreementForm($alias); + } + public function points(Request $request) + { + $alias = 'points'; + if ($request->method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + return $this->showAgreementForm($alias); + } + public function terms(Request $request) + { + $alias = 'terms'; + if ($request->method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + + return $this->showAgreementForm($alias); + } + public function about(Request $request) + { + $alias = 'about'; + if ($request->method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + + return $this->showAgreementForm($alias); + } + public function cloning(Request $request) + { + $alias = 'cloning'; + if ($request->method() === 'POST') { + return $this->saveAgreement($request->post(), $alias, $request->channels_uid); + } + return $this->showAgreementForm($alias); + } + + protected function saveAgreement(array $data, string $alias, $channels_uid) + { + $article = PluginArticle::where(['alias' => $alias])->find(); + if (!$article) { + $article = new PluginArticle; + $article->alias = $alias; + $article->channels_uid = $channels_uid; + } + $article->title = $data['title']; + $article->save(); + + $content = PluginArticleContent::where(['article_id' => $article->id])->find(); + if (!$content) { + $content = new PluginArticleContent; + $content->article_id = $article->id; + } + $content->content = $data['content']; + $result = $content->save(); + $this->cacheHtml($article->id); + if ($result) { + return $this->success('保存成功1'); + } else { + return $this->fail('保存失败'); + } + } + + protected function showAgreementForm(string $alias) + { + $builder = $this->getFormBuilder($alias); + + $article = PluginArticle::where('alias', $alias)->find(); + if ($article) { + $content = PluginArticleContent::where('article_id', $article->id)->find(); + $article->content = $content->content ?? ''; + $builder->setData($article->toArray()); + } + + return $this->resData($builder); + } + + protected function getFormBuilder(string $alias) + { + $builder = new FormBuilder(null, null, ['size' => 'large','submitEvent' => SubmitEvent::SILENT]); + + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + + $builder->add('alias', '名称', 'input', $alias, [ + 'props' => [ + 'disabled' => true + ] + ]); + + $builder->add('content', '内容', 'wangeditor', '', [ + 'required' => true, + 'props' => [ + 'class' => 'vh-60' + ] + ]); + + return $builder; + } + + + + public function cacheHtml($id) + { + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c', 'c.article_id=a.id')->field('a.*,c.content')->find(); + $config = new Config('basic', ''); + if (!$PluginArticle->source) { + $PluginArticle->source = $config['web_name']; + } + if ($config['logo_use'] == 'image') { + $PluginArticle->web_logo = $config['web_logo_light']; + } else { + $PluginArticle->web_logo = '/vite.svg'; + } + $PluginArticle->web_title = "{$PluginArticle->title} - {$config['web_title']}"; + $html = view(base_path('plugin/article/public/template.html'), $PluginArticle->toArray(), null, '')->rawBody(); + $path = public_path('article/' . $id . '.html'); + file_put_contents($path, $html); + if ($PluginArticle->alias) { + $path = public_path('article/' . $PluginArticle->alias . '.html'); + file_put_contents($path, $html); + } + } +} diff --git a/fastmovie-admin/plugin/article/app/control/controller/AnnouncementController.php b/fastmovie-admin/plugin/article/app/control/controller/AnnouncementController.php new file mode 100644 index 0000000..a700eac --- /dev/null +++ b/fastmovie-admin/plugin/article/app/control/controller/AnnouncementController.php @@ -0,0 +1,670 @@ +model = new PluginArticle(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '220px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => $this->plugin . 'control/Announcement/update', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('缓存', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'control/Announcement/cache', + 'props' => [ + 'type' => 'warning', + 'title' => '确定要重新缓存《{title}》公告吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'control/Announcement/delete', + 'props' => [ + 'type' => 'danger', + 'title' => '确定要删除《{title}》公告吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('发布公告', [ + 'path' => $this->plugin . 'control/Announcement/create', + 'props' => [ + 'type' => 'success', + 'title' => '发布公告' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('title', '标题', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '标题搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '标题', [ + 'props' => [ + 'minWidth' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'title', + 'label' => '标题', + 'component' => 'text', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ], + [ + 'field' => 'subtitle', + 'label' => '副标题', + 'component' => 'text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ] + ]); + $builder->add('alias', '别名', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('view', '浏览量', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('source', '来源', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => $this->plugin . 'control/Announcement/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('sort', '排序', [ + 'component' => [ + 'name' => 'input-number', + 'api' => $this->plugin . 'control/Announcement/indexUpdateField', + 'props' => [ + 'min' => 0, + 'size' => 'small' + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ], + [ + 'field' => 'release_time', + 'label' => '发布' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['a.title', 'like', "%{$title}%"]; + } + $where[] = ['a.alias', '=', 'announcement']; + $state = $request->get('state'); + if ($state) { + $where[] = ['a.state', '=', $state]; + } + $where[] = ['a.examine', '=', Examine::PASS['value']]; + $list = PluginArticle::alias('a')->where($where) + ->join('plugin_article_classify c', 'c.id = a.classify_id', 'LEFT') + ->field('a.*,c.title as classify_title') + ->order('a.id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $validate = new Article; + try { + $validate->scene('create')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = new PluginArticle; + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + $PluginArticle->channels_uid = $request->channels_uid; + // $PluginArticle->keywords = $D['keywords']; + // $PluginArticle->description = $D['description']; + $PluginArticle->alias = 'announcement'; + // $PluginArticle->view = $D['view']; + // $PluginArticle->sort = $D['sort']; + // $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + $PluginArticle->push_crowd = $D['push_crowd']; + $PluginArticle->push_crowd_uids = $D['push_crowd_uids']; + $PluginArticle->examine = Examine::PASS['value']; + if ($D['release_time']) { + $PluginArticle->release_time = $D['release_time']; + } else { + $PluginArticle->release_time = date('Y-m-d H:i:s'); + } + $PluginArticle->save(); + $PluginArticleContent = new PluginArticleContent; + $PluginArticleContent->article_id = $PluginArticle->id; + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $this->cacheHtml($PluginArticle->id); + // 发送通知消息 + $this->sendNotification($PluginArticle, $D['content'], $request); + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $validate = new Article; + try { + $validate->scene('update')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = PluginArticle::where(['id' => $D['id']])->find(); + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + // $PluginArticle->keywords = $D['keywords']; + // $PluginArticle->description = $D['description']; + // $PluginArticle->view = $D['view']; + // $PluginArticle->sort = $D['sort']; + // $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + // $PluginArticle->release_time = $D['release_time']; + $PluginArticle->push_crowd = $D['push_crowd']; + $PluginArticle->push_crowd_uids = $D['push_crowd_uids']; + $PluginArticle->save(); + $PluginArticleContent = PluginArticleContent::where(['article_id' => $D['id']])->find(); + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + // 同步更新通知消息 + $this->sendNotification($PluginArticle, $D['content'], $request, true); + return $this->success('编辑成功'); + } + $id = $request->get('id'); + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c', 'c.article_id=a.id')->field('a.*,c.content')->find(); + $builder = $this->getFormBuilder(); + $builder->setData($PluginArticle->toArray()); + return $this->resData($builder); + } + public function getFormBuilder() + { + $builder = new FormBuilder(null, null, ['size' => 'large', 'submitEvent' => SubmitEvent::SILENT]); + $Component = new ComponentBuilder; + $builder->add('classify_id', '所属分类', 'cascader', '', [ + 'required' => true, + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $builder->add('push_crowd', '受众', 'select', '', [ + 'options' => CrowdEnum::getOptions(), + 'props' => [ + 'clearable' => true + ] + ]); + $builder->add('push_crowd_uids', '受众用户', 'select', '', [ + 'remote' => [ + 'url' => $this->plugin . 'control/Announcement/getUser', + ], + 'props' => [ + 'clearable' => true, + 'filterable' => true, + 'multiple' => true, + 'remote' => true + ], + 'where' => [ + ['push_crowd', '=', CrowdEnum::SPECIFIED['value']] + ] + ]); + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('subtitle', '副标题', 'input', '', [ + 'props' => [ + 'maxlength' => 200, + 'show-word-limit' => true + ] + ]); + $builder->add('thumb', '封面', 'bundle', [], [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 5 + ] + ]); + $builder->add('sort', '排序', 'input-number', 99, [ + 'col' => [ + 'xs' => 24, + 'md' => 6 + ], + 'props' => [ + 'min' => 0, + 'controls' => false + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('release_time', '发布时间', 'date-picker', null, [ + 'prompt' => [ + $Component->add('text', ['default' => '为空则自动写入当前时间'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'type' => 'datetime', + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'value-format' => 'YYYY-MM-DD HH:mm:ss', + 'placeholder' => '发布时间' + ] + ]); + $builder->add('content', '内容', 'wangeditor', '', [ + 'required' => true, + 'props' => [ + 'class' => 'vh-60' + ] + ]); + return $builder; + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + try { + if ($value === State::NO['value']) { + $this->clearHtml($id); + } else { + $this->cacheHtml($id); + } + } catch (\Throwable $th) { + } + return $this->success(); + } + return $this->fail('操作失败'); + } + public function clearHtml($id) + { + $PluginArticle = PluginArticle::where(['id' => $id])->find(); + $path = public_path('article/' . $id . '.html'); + if (file_exists($path)) { + unlink($path); + } + if (isset($PluginArticle->alias) && $PluginArticle->alias) { + $path = public_path('article/' . $PluginArticle->alias . '.html'); + if (file_exists($path)) { + unlink($path); + } + } + } + public function cacheHtml($id) + { + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c', 'c.article_id=a.id')->field('a.*,c.content')->find(); + $config = new Config('basic', ''); + if (!$PluginArticle->source) { + $PluginArticle->source = $config['web_name']; + } + if ($config['logo_use'] == 'image') { + $PluginArticle->web_logo = $config['web_logo_light']; + } else { + $PluginArticle->web_logo = '/vite.svg'; + } + $PluginArticle->web_title = "{$PluginArticle->title} - {$config['web_title']}"; + $html = view(base_path('plugin/article/public/template.html'), $PluginArticle->toArray(), null, '')->rawBody(); + $path = public_path('article/' . $id . '.html'); + file_put_contents($path, $html); + if ($PluginArticle->alias) { + $path = public_path('article/' . $PluginArticle->alias . '.html'); + file_put_contents($path, $html); + } + } + public function cache(Request $request) + { + $id = $request->post('id'); + $this->cacheHtml($id); + return $this->success('缓存成功'); + } + + /** + * 发送通知消息 + * @param PluginArticle $PluginArticle 公告对象 + * @param string $content 公告内容 + * @param Request $request 请求对象 + * @param bool $isUpdate 是否为更新操作,更新时会同步更新已存在的消息 + */ + private function sendNotification($PluginArticle, $content, $request, $isUpdate = false) + { + $uids = []; + // 获取渠道ID + $channels_uid = $request->channels_uid ?? $PluginArticle->channels_uid ?? null; + // 根据推送受众类型获取用户ID列表 + if ($PluginArticle->push_crowd == CrowdEnum::ALL['value']) { + // 全部用户 + $where = []; + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $users = PluginUser::where($where)->field('id')->select(); + foreach ($users as $user) { + $uids[] = $user->id; + } + } elseif ($PluginArticle->push_crowd == CrowdEnum::SPECIFIED['value']) { + // 指定用户 + if (!empty($PluginArticle->push_crowd_uids) && is_array($PluginArticle->push_crowd_uids)) { + $uids = $PluginArticle->push_crowd_uids; + } + } + var_dump($uids); + if (empty($uids)) { + return; + } + // 获取当前管理员ID + $form_uid = $request->uid ?? null; + + // 如果是更新操作,先获取已存在的消息 + $existingMessages = []; + if ($isUpdate) { + $where = [ + 'form_id' => $PluginArticle->id, + 'scene' => MessageScene::ANNOUNCEMENT['value'] + ]; + if ($channels_uid) { + $where['channels_uid'] = $channels_uid; + } + $messages = PluginNotificationMessage::where($where)->select(); + foreach ($messages as $message) { + $existingMessages[$message->uid] = $message; + } + } + + // 为每个用户发送/更新通知 + foreach ($uids as $uid) { + try { + // 如果是更新操作且消息已存在,则更新消息 + if ($isUpdate && isset($existingMessages[$uid])) { + $message = $existingMessages[$uid]; + // 更新消息基本信息 + $message->title = $PluginArticle->title; + $message->subtitle = $PluginArticle->subtitle ?? null; + if ($form_uid) { + $message->form_uid = $form_uid; + } + $message->save(); + + // 更新消息内容 + $messageContent = PluginNotificationMessageContent::where('message_id', $message->id)->find(); + if ($messageContent) { + $messageContent->content = $content; + $messageContent->save(); + } else { + // 如果内容不存在,创建新内容 + $messageContent = new PluginNotificationMessageContent(); + $messageContent->message_id = $message->id; + $messageContent->content = $content; + $messageContent->save(); + } + } else { + // 创建新消息 + $Message = new Message(); + if ($channels_uid) { + $Message->setChannelsUid($channels_uid); + } + $Message->setUid($uid); + if ($form_uid) { + $Message->setFormUid($form_uid); + } + $Message->setFormId($PluginArticle->id); + $Message->setScene(MessageScene::ANNOUNCEMENT['value']); + $Message->setTitle($PluginArticle->title); + $Message->setContent($content); + if ($PluginArticle->subtitle) { + $Message->setSubtitle($PluginArticle->subtitle); + } + $Message->setEffect('info'); + $Message->save(); + } + } catch (\Throwable $th) { + // 记录错误但不中断流程 + var_dump($th->getMessage()); + continue; + } + } + + // 如果是更新操作,删除不再需要的消息(用户列表变化的情况) + if ($isUpdate && !empty($existingMessages)) { + $currentUids = array_flip($uids); + foreach ($existingMessages as $uid => $message) { + // 如果该用户不在新的用户列表中,删除该消息 + if (!isset($currentUids[$uid])) { + try { + PluginNotificationMessageContent::where('message_id', $message->id)->delete(); + $message->delete(); + } catch (\Throwable $th) { + var_dump($th->getMessage()); + // 记录错误但不中断流程 + continue; + } + } + } + } + } + + /** + * 删除指定公告的所有通知消息 + * @param int $articleId 公告ID + */ + private function deleteNotificationMessages($articleId) + { + try { + // 查找该公告相关的所有通知消息 + $messages = PluginNotificationMessage::where([ + 'form_id' => $articleId, + 'scene' => MessageScene::ANNOUNCEMENT['value'] + ])->select(); + + if ($messages) { + foreach ($messages as $message) { + // 删除消息内容 + PluginNotificationMessageContent::where('message_id', $message->id)->delete(); + // 删除消息 + $message->delete(); + } + } + } catch (\Throwable $th) { + // 记录错误但不中断流程 + } + } + + public function getUser(Request $request) + { + $keyword = $request->get('query'); + $where = []; + $where[] = ['username|nickname|mobile', 'like', "%{$keyword}%"]; + $list = PluginUser::field('id as value,nickname as label')->where($where)->select(); + return $this->resData($list->toArray()); + } + + + public function delete(Request $request) + { + $id = $request->post('id'); + $PluginArticle = PluginArticle::where(['id' => $id])->find(); + if (!$PluginArticle) { + return $this->fail('数据不存在'); + } + $PluginArticle->delete(); + $this->clearHtml($id); + $this->deleteNotificationMessages($id); + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/article/app/control/controller/ClassifyController.php b/fastmovie-admin/plugin/article/app/control/controller/ClassifyController.php new file mode 100644 index 0000000..2695eb7 --- /dev/null +++ b/fastmovie-admin/plugin/article/app/control/controller/ClassifyController.php @@ -0,0 +1,312 @@ +model = new PluginArticleClassify(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder([ + 'rowKey' => 'id', + 'api' => $this->plugin . 'control/Classify/index', + 'lazy' => true, + 'treeProps' => [ + 'children' => 'children', + 'hasChildren' => 'hasChildren' + ] + ]); + $builder->addAction('操作', [ + 'width' => '160px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => $this->plugin . 'control/Classify/update', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'control/Classify/delete', + 'props' => [ + 'type' => 'danger', + 'title' => '确定要删除《{title}》分类吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('新建分类', [ + 'path' => $this->plugin . 'control/Classify/create', + 'props' => [ + 'type' => 'success', + 'title' => '新建分类' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('title', '标题', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '标题搜索', + 'clearable' => true + ] + ]); + + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '标题', [ + 'props' => [ + 'minWidth' => '200px' + ] + ]); + $builder->add('alias', '别名', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => $this->plugin . 'control/Classify/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('sort', '排序', [ + 'component' => [ + 'name' => 'input-number', + 'api' => $this->plugin . 'control/Classify/indexUpdateField', + 'props' => [ + 'min' => 0, + 'size' => 'small' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $id = $request->get('id'); + if ($id) { + $where[] = ['pid', '=', $id]; + } else { + $where[] = ['pid', '=', NULL]; + } + $state = $request->get('state'); + if ($state) { + $where[] = ['state', '=', $state]; + } + $list = PluginArticleClassify::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) { + $item->hasChildren = PluginArticleClassify::where(['pid' => $item->id])->count() > 0; + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + if ($D['alias']) { + $count = PluginArticleClassify::where('alias', $D['alias'])->count(); + if ($count) { + return $this->fail('别名不能重复'); + } + } + Db::startTrans(); + try { + $PluginArticleClassify = new PluginArticleClassify; + if ($D['pid']) { + $PluginArticleClassify->pid = $D['pid']; + } + $D['channels_uid'] = $request->channels_uid; + $PluginArticleClassify->title = $D['title']; + if ($D['alias']) { + $PluginArticleClassify->alias = $D['alias']; + } + $PluginArticleClassify->sort = $D['sort']; + $PluginArticleClassify->state = $D['state']; + $PluginArticleClassify->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + Db::startTrans(); + try { + $PluginArticleClassify = PluginArticleClassify::where(['id' => $D['id']])->find(); + if ($D['pid']) { + $PluginArticleClassify->pid = $D['pid']; + } else { + $PluginArticleClassify->pid = null; + } + $PluginArticleClassify->title = $D['title']; + if ($D['alias']) { + $PluginArticleClassify->alias = $D['alias']; + } + $PluginArticleClassify->sort = $D['sort']; + $PluginArticleClassify->state = $D['state']; + $PluginArticleClassify->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('编辑成功'); + } + $id = $request->get('id'); + $PluginArticleClassify = PluginArticleClassify::where(['id' => $id])->find(); + $builder = $this->getFormBuilder(); + $builder->setData($PluginArticleClassify->toArray()); + return $this->resData($builder); + } + public function getFormBuilder() + { + $builder = new FormBuilder(null, null, ['size' => 'large']); + $builder->add('pid', '分类等级', 'cascader', '', [ + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('alias', '别名', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'md' => 12 + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('sort', '排序', 'input-number', 99, [ + 'required' => true, + 'col' => [ + 'xs' => 24, + 'md' => 12 + ], + 'props' => [ + 'min' => 0, + 'controls' => false + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + return $builder; + } + public function delete(Request $request) + { + $id = $request->post('id'); + $PluginArticleClassify = PluginArticleClassify::where(['id' => $id])->find(); + if (!$PluginArticleClassify) { + return $this->fail('分类不存在'); + } + if (PluginArticleClassify::where(['pid' => $id])->count()) { + return $this->fail('请先删除子分类'); + } + if (PluginArticle::where(['classify_id' => $id])->count()) { + return $this->fail('请先删除该分类下的文章'); + } + if ($PluginArticleClassify->delete()) { + return $this->success('删除成功'); + } + return $this->fail('删除失败'); + } +} diff --git a/fastmovie-admin/plugin/article/app/control/controller/IndexController.php b/fastmovie-admin/plugin/article/app/control/controller/IndexController.php new file mode 100644 index 0000000..1fb3ca6 --- /dev/null +++ b/fastmovie-admin/plugin/article/app/control/controller/IndexController.php @@ -0,0 +1,545 @@ +model = new PluginArticle(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '220px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => $this->plugin . 'control/Index/update', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('缓存', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'control/Index/cache', + 'props' => [ + 'type' => 'warning', + 'title' => '确定要重新缓存《{title}》文章吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => $this->plugin . 'control/Index/delete', + 'props' => [ + 'type' => 'danger', + 'title' => '确定要删除《{title}》文章吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('发布文章', [ + 'path' => $this->plugin . 'control/Index/create', + 'props' => [ + 'type' => 'success', + 'title' => '发布文章' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('title', '标题', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'placeholder' => '标题搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('classify_id', '分类等级', 'cascader', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $formBuilder->add('state', '状态', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 8, + 'lg' => 6, + 'xl' => 4 + ], + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('classify_title', '分类', [ + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('title', '标题', [ + 'props' => [ + 'minWidth' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'title', + 'label' => '标题', + 'component' => 'text', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ], + [ + 'field' => 'subtitle', + 'label' => '副标题', + 'component' => 'text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ] + ]); + $builder->add('alias', '别名', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('view', '浏览量', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('source', '来源', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => $this->plugin . 'control/Index/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('sort', '排序', [ + 'component' => [ + 'name' => 'input-number', + 'api' => $this->plugin . 'control/Index/indexUpdateField', + 'props' => [ + 'min' => 0, + 'size'=>'small' + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ], + [ + 'field' => 'release_time', + 'label' => '发布' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['a.title', 'like', "%{$title}%"]; + } + $classify_id = $request->get('classify_id'); + if ($classify_id) { + $where[] = ['a.classify_id', '=', $classify_id]; + } + $state = $request->get('state'); + if ($state) { + $where[] = ['a.state', '=', $state]; + } + $where[] = ['a.examine', '=', Examine::PASS['value']]; + $list = PluginArticle::alias('a')->where($where) + ->join('plugin_article_classify c', 'c.id = a.classify_id','LEFT') + ->field('a.*,c.title as classify_title') + ->order('a.id desc')->paginate($limit)->each(function ($item) { + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $D['channels_uid'] = $request->channels_uid; + $validate = new Article; + try { + $validate->scene('create')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = new PluginArticle; + $PluginArticle->pid = $D['pid']; + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + $PluginArticle->keywords = $D['keywords']; + $PluginArticle->description = $D['description']; + $PluginArticle->alias = $D['alias']; + $PluginArticle->view = $D['view']; + $PluginArticle->sort = $D['sort']; + $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + $PluginArticle->examine = Examine::PASS['value']; + if($D['release_time']){ + $PluginArticle->release_time = $D['release_time']; + }else{ + $PluginArticle->release_time = date('Y-m-d H:i:s'); + } + $PluginArticle->save(); + $PluginArticleContent = new PluginArticleContent; + $PluginArticleContent->article_id = $PluginArticle->id; + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $this->cacheHtml($PluginArticle->id); + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() == 'POST') { + $D = $request->post(); + $validate = new Article; + try { + $validate->scene('update')->check($D); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $PluginArticle = PluginArticle::where(['id' => $D['id']])->find(); + $PluginArticle->pid = $D['pid']; + $PluginArticle->title = $D['title']; + $PluginArticle->subtitle = $D['subtitle']; + $PluginArticle->thumb = $D['thumb']; + $PluginArticle->keywords = $D['keywords']; + $PluginArticle->description = $D['description']; + $PluginArticle->alias = $D['alias']; + $PluginArticle->view = $D['view']; + $PluginArticle->sort = $D['sort']; + $PluginArticle->source = $D['source']; + $PluginArticle->state = $D['state']; + $PluginArticle->release_time = $D['release_time']; + $PluginArticle->save(); + $PluginArticleContent = PluginArticleContent::where(['article_id' => $D['id']])->find(); + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('编辑成功'); + } + $id = $request->get('id'); + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c','c.article_id=a.id')->field('a.*,c.content')->find(); + $builder = $this->getFormBuilder(); + $builder->setData($PluginArticle->toArray()); + return $this->resData($builder); + } + public function getFormBuilder() + { + $builder = new FormBuilder(null,null,['size'=>'large']); + $Component = new ComponentBuilder; + $builder->add('classify_id', '所属分类', 'cascader', '', [ + 'required' => true, + 'props' => [ + 'options' => PluginArticleClassify::options(), + 'clearable' => true, + 'filterable' => true, + 'props' => [ + 'checkStrictly' => true, + 'emitPath' => false + ] + ] + ]); + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('subtitle', '副标题', 'input', '', [ + 'props' => [ + 'maxlength' => 200, + 'show-word-limit' => true + ] + ]); + $builder->add('thumb', '封面', 'bundle', [], [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 5 + ] + ]); + $builder->add('keywords', '关键词', 'input', '', [ + 'props' => [ + 'type' => 'textarea', + 'maxlength' => 200, + 'show-word-limit' => true, + 'autosize' => [ + 'minRows' => 2, + 'maxRows' => 4 + ] + ] + ]); + $builder->add('description', '描述', 'input', '', [ + 'props' => [ + 'type' => 'textarea', + 'maxlength' => 300, + 'show-word-limit' => true, + 'autosize' => [ + 'minRows' => 3, + 'maxRows' => 6 + ] + ] + ]); + $builder->add('alias', '别名', 'input', '', [ + 'col'=>[ + 'xs'=>24, + 'md'=>12 + ], + 'prompt'=>[ + $Component->add('text',['default'=>'设置别名后可通过别名访问文章'],['type'=>'info','size'=>'small'])->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('view', '浏览量', 'input-number', 0, [ + 'col'=>[ + 'xs'=>24, + 'md'=>6 + ], + 'props' => [ + 'min' => 0, + 'controls'=>false + ] + ]); + $builder->add('sort', '排序', 'input-number', 99, [ + 'col'=>[ + 'xs'=>24, + 'md'=>6 + ], + 'props' => [ + 'min' => 0, + 'controls'=>false + ] + ]); + $builder->add('source', '来源名称', 'input', '', [ + 'prompt'=>[ + $Component->add('text',['default'=>'为空则自动读取网站应用名称'],['type'=>'info','size'=>'small'])->builder() + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps'=>[ + 'border'=>true + ] + ]); + $builder->add('release_time', '发布时间', 'date-picker', null, [ + 'prompt' => [ + $Component->add('text',['default'=>'为空则自动写入当前时间'],['type'=>'info','size'=>'small'])->builder() + ], + 'props' => [ + 'type' => 'datetime', + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'value-format' => 'YYYY-MM-DD HH:mm:ss', + 'placeholder' => '发布时间' + ] + ]); + $builder->add('content', '内容', 'wangeditor', '', [ + 'required' => true, + 'props' => [ + 'class' => 'vh-60' + ] + ]); + return $builder; + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + try { + if($value===State::NO['value']){ + $this->clearHtml($id); + }else{ + $this->cacheHtml($id); + } + } catch (\Throwable $th) { + } + return $this->success(); + } + return $this->fail('操作失败'); + } + public function clearHtml($id) + { + $PluginArticle=PluginArticle::where(['id'=>$id])->find(); + $path = public_path('article/'.$id.'.html'); + if(file_exists($path)){ + unlink($path); + } + if($PluginArticle->alias){ + $path = public_path('article/'.$PluginArticle->alias.'.html'); + if(file_exists($path)){ + unlink($path); + } + } + } + public function cacheHtml($id) + { + $PluginArticle = PluginArticle::alias('a')->where(['a.id' => $id])->join('plugin_article_content c','c.article_id=a.id')->field('a.*,c.content')->find(); + $config=new Config('basic',''); + if(!$PluginArticle->source){ + $PluginArticle->source = $config['web_name']; + } + if($config['logo_use']=='image'){ + $PluginArticle->web_logo=$config['web_logo_light']; + }else{ + $PluginArticle->web_logo='/vite.svg'; + } + $PluginArticle->web_title="{$PluginArticle->title} - {$config['web_title']}"; + $html=view(base_path('plugin/article/public/template.html'),$PluginArticle->toArray(),null,'')->rawBody(); + $path = public_path('article/'.$id.'.html'); + file_put_contents($path, $html); + if($PluginArticle->alias){ + $path = public_path('article/'.$PluginArticle->alias.'.html'); + file_put_contents($path, $html); + } + } + public function cache(Request $request) + { + $id = $request->post('id'); + $this->cacheHtml($id); + return $this->success('缓存成功'); + } +} diff --git a/fastmovie-admin/plugin/article/app/functions.php b/fastmovie-admin/plugin/article/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/article/app/functions.php @@ -0,0 +1,6 @@ + [ + // 设置JSON字段的类型 + 'thumb' => 'json', + 'push_crowd_uids' => 'json' + ] + ]; + } + + public function content() + { + return $this->hasOne(PluginArticleContent::class, 'article_id', 'id'); + } + + // public function getPushCrowdUidsAttr($value) + // { + // if (empty($value)) { + // return ''; + // } + // return json_decode($value, true); + // } + + // public function setPushCrowdUidsAttr($value) + // { + // if (empty($value)) { + // return ''; + // } + // return json_encode($value, JSON_UNESCAPED_UNICODE); + // } +} diff --git a/fastmovie-admin/plugin/article/app/model/PluginArticleClassify.php b/fastmovie-admin/plugin/article/app/model/PluginArticleClassify.php new file mode 100644 index 0000000..74611db --- /dev/null +++ b/fastmovie-admin/plugin/article/app/model/PluginArticleClassify.php @@ -0,0 +1,33 @@ + $pid])->select(); + } else { + $data = self::whereNull('pid')->select(); + } + $options = []; + if ($data->isEmpty()) { + return $options; + } + foreach ($data as $item) { + $temp = [ + 'value' => $item->id, + 'label' => $item->title + ]; + $children = self::options($item->id); + if (!empty($children)) { + $temp['children'] = $children; + } + $options[] = $temp; + } + return $options; + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/app/model/PluginArticleContent.php b/fastmovie-admin/plugin/article/app/model/PluginArticleContent.php new file mode 100644 index 0000000..6dbeaf5 --- /dev/null +++ b/fastmovie-admin/plugin/article/app/model/PluginArticleContent.php @@ -0,0 +1,10 @@ +'require', + 'title'=>'require', + 'subtitle'=>'length:0,200', + 'thumb'=>'array', + 'keywords'=>'length:0,200', + 'description'=>'length:0,300', + 'alias'=>'alphaDash|length:0,30', + 'content'=>'require' + ]; + + protected $message = [ + 'classify_id.require' => '请选择分类', + 'title.require' => '请输入标题', + 'subtitle.length' => '副标题长度不能超过200个字符', + 'thumb.array' => '缩略图格式错误', + 'keywords.length' => '关键词长度不能超过200个字符', + 'description.length' => '描述长度不能超过300个字符', + 'alias.alphaDash' => '别名只能是字母、数字、下划线和破折号', + 'alias.length' => '别名长度不能超过30个字符', + 'content.require' => '请输入内容' + ]; + protected $scene = [ + 'agreement' => ['title','alias','content'], + ]; +} diff --git a/fastmovie-admin/plugin/article/config/app.php b/fastmovie-admin/plugin/article/config/app.php new file mode 100644 index 0000000..bc3fd0f --- /dev/null +++ b/fastmovie-admin/plugin/article/config/app.php @@ -0,0 +1,9 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/article/config/autoload.php b/fastmovie-admin/plugin/article/config/autoload.php new file mode 100644 index 0000000..f812c93 --- /dev/null +++ b/fastmovie-admin/plugin/article/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/article/app/functions.php', + ] +]; diff --git a/fastmovie-admin/plugin/article/config/container.php b/fastmovie-admin/plugin/article/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/article/config/container.php @@ -0,0 +1,2 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/article/config/log.php b/fastmovie-admin/plugin/article/config/log.php new file mode 100644 index 0000000..6cc64b4 --- /dev/null +++ b/fastmovie-admin/plugin/article/config/log.php @@ -0,0 +1,20 @@ + [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/article.log', + 7, + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/fastmovie-admin/plugin/article/config/middleware.php b/fastmovie-admin/plugin/article/config/middleware.php new file mode 100644 index 0000000..9077487 --- /dev/null +++ b/fastmovie-admin/plugin/article/config/middleware.php @@ -0,0 +1,21 @@ + [ + // plugin\article\app\admin\middleware\Auth::class + // ] + '' => [ + app\expose\middleware\Platform::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'control' => [ + plugin\control\expose\middleware\ControlAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ] +]; diff --git a/fastmovie-admin/plugin/article/config/process.php b/fastmovie-admin/plugin/article/config/process.php new file mode 100644 index 0000000..aedd826 --- /dev/null +++ b/fastmovie-admin/plugin/article/config/process.php @@ -0,0 +1,11 @@ + [ + 'eventLoop' => Select::class, + 'handler' => JoinPushMessage::class + ], +]; diff --git a/fastmovie-admin/plugin/article/config/static.php b/fastmovie-admin/plugin/article/config/static.php new file mode 100644 index 0000000..90006e7 --- /dev/null +++ b/fastmovie-admin/plugin/article/config/static.php @@ -0,0 +1,6 @@ + true, + 'middleware' => [ + ], +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/config/translation.php b/fastmovie-admin/plugin/article/config/translation.php new file mode 100644 index 0000000..04a0ae6 --- /dev/null +++ b/fastmovie-admin/plugin/article/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/article/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/control.json b/fastmovie-admin/plugin/article/control.json new file mode 100644 index 0000000..bb62c3b --- /dev/null +++ b/fastmovie-admin/plugin/article/control.json @@ -0,0 +1,154 @@ +{ + "title":"内容", + "icon":"Notebook", + "path":"/app/article/control", + "sort":4999, + "children":[ + { + "title":"公告", + "icon":"BellFilled", + "path":"/app/article/control/Announcement/index", + "component":"table" + }, + { + "title":"隐私政策", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/privacy", + "component":"form" + }, + { + "title":"帮助中心", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/help", + "component":"form" + }, + { + "title":"积分规则", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/points", + "component":"form" + }, + { + "title":"用户协议", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/user", + "component":"form" + }, + { + "title":"使用条款", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/terms", + "component":"form" + }, + { + "title":"关于我们", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/about", + "component":"form" + }, + { + "title":"使用指南", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/guide", + "component":"form", + "show":false + }, + { + "title":"声音克隆协议", + "icon":"BellFilled", + "path":"/app/article/control/Agreement/cloning", + "component":"form", + "show":true + }, + { + "title":"添加公告", + "path":"/app/article/control/Announcement/create", + "component":"form", + "show":false + }, + { + "title":"编辑公告", + "path":"/app/article/control/Announcement/update", + "component":"form", + "show":false + }, + { + "title":"文章列表", + "icon":"Notebook", + "path":"/app/article/control/Index/index", + "component":"table", + "show":false + }, + { + "title":"添加文章", + "path":"/app/article/control/Index/create", + "component":"form", + "show":false + }, + { + "title":"编辑文章", + "path":"/app/article/control/Index/update", + "component":"form", + "show":false + }, + { + "title":"审核文章", + "path":"/app/article/control/Index/examine", + "component":"form", + "show":false + }, + { + "title":"更新文章状态", + "path":"/app/article/control/Index/indexUpdateState", + "show":false + }, + { + "title":"更新文章排序", + "path":"/app/article/control/Index/indexUpdateField", + "show":false + }, + { + "title":"删除文章", + "path":"/app/article/control/Index/delete", + "show":false + }, + { + "title":"缓存文章", + "path":"/app/article/control/Index/cache", + "show":false + }, + { + "title":"文章分类", + "icon":"FolderOpened", + "path":"/app/article/control/Classify/index", + "component":"table" + }, + { + "title":"添加分类", + "path":"/app/article/control/Classify/create", + "component":"form", + "show":false + }, + { + "title":"编辑分类", + "path":"/app/article/control/Classify/update", + "component":"form", + "show":false + }, + { + "title":"更新分类状态", + "path":"/app/article/control/Classify/indexUpdateState", + "show":false + }, + { + "title":"更新分类排序", + "path":"/app/article/control/Classify/indexUpdateField", + "show":false + }, + { + "title":"删除分类", + "path":"/app/article/control/Classify/delete", + "show":false + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/expose/helper/Article.php b/fastmovie-admin/plugin/article/expose/helper/Article.php new file mode 100644 index 0000000..61210df --- /dev/null +++ b/fastmovie-admin/plugin/article/expose/helper/Article.php @@ -0,0 +1,88 @@ +method() == 'POST') { + $D = $request->post(); + $validate = new ValidateArticle; + try { + $validate->scene('agreement')->check($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + Db::startTrans(); + try { + $PluginArticle = PluginArticle::where(['alias' => $alias])->find(); + if (!$PluginArticle) { + $PluginArticle = new PluginArticle; + } + $PluginArticle->title = $D['title']; + $PluginArticle->alias = $D['alias']; + $PluginArticle->state = State::YES['value']; + $PluginArticle->release_time = date('Y-m-d H:i:s'); + $PluginArticle->save(); + $PluginArticleContent = PluginArticleContent::where(['article_id' => $PluginArticle->id])->find(); + if (!$PluginArticleContent) { + $PluginArticleContent = new PluginArticleContent; + } + $PluginArticleContent->article_id = $PluginArticle->id; + $PluginArticleContent->content = $D['content']; + $PluginArticleContent->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $this->cacheHtml($PluginArticle->id); + return $this->success('编辑成功'); + } + $PluginArticle = PluginArticle::alias('a')->where(['a.alias' => $alias])->join('plugin_article_content c', 'c.article_id=a.id')->field('a.*,c.content')->find(); + $builder = new FormBuilder(null, null, ['size' => 'large','submitEvent' => 'SILENT']); + $Component = new ComponentBuilder; + $builder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('alias', '别名', 'input', $alias, [ + 'col' => [ + 'xs' => 24, + 'md' => 12 + ], + 'prompt' => [ + $Component->add('text', ['default' => '设置别名后可通过别名访问文章'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true, + 'disabled' => true + ] + ]); + $builder->add('content', '内容', 'wangeditor', '', [ + 'required' => true, + 'props' => [ + 'class' => 'vh-55' + ] + ]); + if($PluginArticle){ + $builder->setData($PluginArticle->toArray()); + } + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/article/package.json b/fastmovie-admin/plugin/article/package.json new file mode 100644 index 0000000..0ec7564 --- /dev/null +++ b/fastmovie-admin/plugin/article/package.json @@ -0,0 +1,12 @@ +{ + "name": "article", + "title": "文章系统", + "version": "0.0.1", + "description": "文章系统", + "author": { + "name": "余心", + "email": "unknown.renloong@gmail.com" + }, + "plugin":{ + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/process/JoinPushMessage.php b/fastmovie-admin/plugin/article/process/JoinPushMessage.php new file mode 100644 index 0000000..0077770 --- /dev/null +++ b/fastmovie-admin/plugin/article/process/JoinPushMessage.php @@ -0,0 +1,68 @@ +find(); + if (!$PluginArticle) { + return; + } + $content = ''; + $PluginArticleContent = PluginArticleContent::where('article_id', $PluginArticle->id)->find(); + if ($PluginArticleContent) { + $content = $PluginArticleContent->content; + } + Db::startTrans(); + try { + $MessageData = []; + if (empty($PluginArticle->push_crowd_uids)) { + } else { + foreach ($PluginArticle->push_crowd_uids as $key => $value) { + $MessageData[] = [ + 'channels_uid' => $PluginArticle->channels_uid, + 'uid' => $value, + 'form_id' => $PluginArticle->id, + 'scene' => $PluginArticle->alias, + 'state' => State::YES['value'], + 'title' => $PluginArticle->title, + 'content' => $content, + ]; + } + $PluginArticle->push_state = State::YES['value']; + $PluginArticle->last_heartbeat = date('Y-m-d H:i:s'); + $PluginArticle->push_people_num = count($PluginArticle->push_crowd_uids); + } + $PluginArticle->save(); + $PluginNotificationMessage = new PluginNotificationMessage(); + $PluginNotificationMessage->together(['content'=>['content']])->saveAll($MessageData); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error("JoinPushMessage Process Error: " . $th->getMessage(), $th->getTrace()); + } + } catch (\Throwable $th) { + Log::error("JoinPushMessage Process Error: " . $th->getMessage(), $th->getTrace()); + } + }); + } +} diff --git a/fastmovie-admin/plugin/article/public/static/index.css b/fastmovie-admin/plugin/article/public/static/index.css new file mode 100644 index 0000000..5be82d7 --- /dev/null +++ b/fastmovie-admin/plugin/article/public/static/index.css @@ -0,0 +1,35 @@ +html,body +{ + margin: 0; + padding: 0; + font-family: 'Arial', sans-serif; + font-size: 14px; + color: #333; +} +.title{ + margin: 0 auto; + max-width: 1140px; + padding: 20px; +} +.info{ + margin: 0 auto; + max-width: 1140px; + padding: 20px; + display: flex; + gap: 10px; + align-items: center; +} +.content{ + margin: 0 auto; + max-width: 1140px; + padding: 20px; +} +::-webkit-scrollbar { width: 6px; } + +::-webkit-scrollbar:horizontal { height: 6px; } + +::-webkit-scrollbar-track { border-radius: 10px; } + +::-webkit-scrollbar-thumb { background-color: #0003; border-radius: 10px; -webkit-transition: all .2s ease-in-out; transition: all .2s ease-in-out; } + +::-webkit-scrollbar-thumb:hover { cursor: pointer; background-color: #0000004d; } diff --git a/fastmovie-admin/plugin/article/public/template.html b/fastmovie-admin/plugin/article/public/template.html new file mode 100644 index 0000000..5a2397f --- /dev/null +++ b/fastmovie-admin/plugin/article/public/template.html @@ -0,0 +1,24 @@ + + + + + + + + + {$web_title} + + + + + +
+

{$title}

+
+ {$source} + {$release_time} +
+
+
{$content|raw}
+ + \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/resource/translations/en/messages.php b/fastmovie-admin/plugin/article/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/article/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/article/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/article/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/article/utils/enum/CrowdEnum.php b/fastmovie-admin/plugin/article/utils/enum/CrowdEnum.php new file mode 100644 index 0000000..5e16d4a --- /dev/null +++ b/fastmovie-admin/plugin/article/utils/enum/CrowdEnum.php @@ -0,0 +1,25 @@ + '全部', + 'value' => 'all', + ]; + const SPECIFIED = [ + 'label' => '指定', + 'value' => 'specified', + ]; + // const WEEK = [ + // 'label' => '一周内', + // 'value' => 'week', + // ]; + // const MONTH = [ + // 'label' => '一个月', + // 'value' => 'month', + // ]; +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/README.md b/fastmovie-admin/plugin/control/README.md new file mode 100644 index 0000000..ff7e0b9 --- /dev/null +++ b/fastmovie-admin/plugin/control/README.md @@ -0,0 +1,10 @@ +# LoongAdmin Plugin + +#### 使用方法 + + +```binsh +git clone https://github.com/RenLoong/control.git +``` + +克隆仓库至plugin目录,访问:http://你的域名/control/ \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/admin.json b/fastmovie-admin/plugin/control/admin.json new file mode 100644 index 0000000..fa295cc --- /dev/null +++ b/fastmovie-admin/plugin/control/admin.json @@ -0,0 +1,39 @@ +{ + "title": "渠道", + "icon": "ElementPlus", + "path": "/app/control/admin", + "sort": 2, + "children": [ + { + "title": "User List", + "icon": "User", + "path": "/app/control/admin/User/index", + "component": "table", + "children": [ + { + "title": "Create User", + "path": "/app/control/admin/User/create", + "show": false, + "component": "form" + }, + { + "title": "Edit User", + "show": false, + "path": "/app/control/admin/User/update", + "component": "form" + }, + { + "title": "Update Status", + "show": false, + "path": "/app/control/admin/User/indexUpdateState", + "component": "" + } + ] + }, + { + "title": "系统设置", + "path": "/app/control/admin/settings/state", + "component": "form" + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/api/Control.php b/fastmovie-admin/plugin/control/api/Control.php new file mode 100644 index 0000000..89fa54e --- /dev/null +++ b/fastmovie-admin/plugin/control/api/Control.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'control'; + } +} diff --git a/fastmovie-admin/plugin/control/api/Install.php b/fastmovie-admin/plugin/control/api/Install.php new file mode 100644 index 0000000..0fa4502 --- /dev/null +++ b/fastmovie-admin/plugin/control/api/Install.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'control'; + } +} diff --git a/fastmovie-admin/plugin/control/app/admin/controller/SettingsController.php b/fastmovie-admin/plugin/control/app/admin/controller/SettingsController.php new file mode 100644 index 0000000..366cda2 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/admin/controller/SettingsController.php @@ -0,0 +1,20 @@ +builder(); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/app/admin/controller/UserController.php b/fastmovie-admin/plugin/control/app/admin/controller/UserController.php new file mode 100644 index 0000000..2573555 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/admin/controller/UserController.php @@ -0,0 +1,426 @@ +model = new PluginChannelsUser; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '100px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/control/admin/User/update', + 'props' => [ + 'title' => '编辑《ID:{id}》渠道用户' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建渠道用户', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/control/admin/User/create', + 'props' => [ + 'title' => '创建渠道用户' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'placeholder' => '邮箱搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('userinfo', '渠道用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/control/admin/User/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('online_time', '活动', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'activation_time', + 'label' => '激活' + ], + [ + 'field' => 'login_time', + 'label' => '登录' + ], + [ + 'component' => 'tag', + 'field' => 'login_ip', + 'label' => '登录IP', + 'props' => [ + 'size' => 'small', + ] + ] + ] + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function query(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['nickname|username|mobile|email', 'like', "%{$query}%"]; + return $this->resData(PluginChannelsUser::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $username = $request->get('username'); + if ($username) { + $where[] = ['username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['mobile', 'like', "%{$mobile}%"]; + } + $email = $request->get('email'); + if ($email) { + $where[] = ['email', 'like', "%{$email}%"]; + } + $puid = $request->get('puid'); + if ($puid) { + $where[] = ['puid', '=', $puid]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['id', '=', $uid]; + } + $list = PluginChannelsUser::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) { + // 三天以内创建的 + $create_time = strtotime($item->create_time); + $create_time = time() - $create_time; + if ($create_time < 3 * 24 * 60 * 60) { + $item->new_text = '新渠道用户'; + } elseif ($create_time < 7 * 24 * 60 * 60) { + $item->week_text = '一周内'; + } elseif ($create_time < 30 * 24 * 60 * 60) { + $item->month_text = '30天内'; + } + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $data = $request->post(); + try { + $insterData = []; + if (empty($data['username']) && empty($data['mobile'])) { + throw new \Exception('渠道用户名、手机号至少填写一项'); + } + if (!empty($data['username'])) { + $Find = PluginChannelsUser::where(['username' => $data['username']])->find(); + if ($Find) { + throw new \Exception('渠道用户名已存在'); + } + $insterData['username'] = $data['username']; + } + if (!empty($data['mobile'])) { + $Find = PluginChannelsUser::where(['mobile' => $data['mobile']])->find(); + if ($Find) { + throw new \Exception('手机号已存在'); + } + $insterData['mobile'] = $data['mobile']; + } + if (!empty($data['email'])) { + $insterData['email'] = $data['email']; + } + if (!empty($data['password'])) { + $insterData['password'] = $data['password']; + } + if (!empty($data['activation_time'])) { + $insterData['activation_time'] = $data['activation_time']; + } + if (!empty($data['nickname'])) { + $insterData['nickname'] = $data['nickname']; + } + if (!empty($data['headimg'])) { + $insterData['headimg'] = $data['headimg']; + } + $model = new PluginChannelsUser(); + $model->save($insterData); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + $Component = new ComponentBuilder; + $builder->add('activation_time', '激活时间', 'date-picker', null, [ + 'prompt' => [ + $Component->add('text', ['default' => '不选择时间则不激活,由渠道用户前台登录后自动激活'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '选择激活时间', + 'type' => 'datetime', + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'value-format' => 'YYYY-MM-DD HH:mm:ss' + ] + ]); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $data = $request->post(); + try { + $user = PluginChannelsUser::where(['id' => $data['id']])->find(); + if (!$user) { + throw new \Exception('渠道用户不存在'); + } + if (!empty($data['password'])) { + $user->password = $data['password']; + } + if (!empty($data['username'])) { + $Find = PluginChannelsUser::where(['username' => $data['username']])->find(); + if ($Find && $Find->id != $data['id']) { + throw new \Exception('渠道用户名已存在'); + } + $user->username = $data['username']; + } + if (!empty($data['mobile'])) { + $Find = PluginChannelsUser::where(['mobile' => $data['mobile']])->find(); + if ($Find && $Find->id != $data['id']) { + throw new \Exception('手机号已存在'); + } + $user->mobile = $data['mobile']; + } + if (!empty($data['email'])) { + $user->email = $data['email']; + } + if (!empty($data['nickname'])) { + $user->nickname = $data['nickname']; + } + if (!empty($data['headimg'])) { + $user->headimg = $data['headimg']; + } + $user->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('更新成功'); + } + $id = $request->get('id'); + $User = PluginChannelsUser::where(['id' => $id])->withoutField('password')->find(); + $builder = $this->getFormBuilder(); + $builder->setData($User->toArray()); + return $this->resData($builder); + } + private function getFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large', + ]); + $Component = new ComponentBuilder; + $builder->add('nickname', '昵称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true + ]); + $builder->add('username', '账号', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '支持字母、数字、下划线,长度不超过30个字符'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '可用于账号登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('mobile', '手机号', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '中国大陆11位手机号'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '可用于账号,短信验证码登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 11, + 'show-word-limit' => true + ] + ]); + $builder->add('email', '邮箱', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '可用于账号,邮箱验证码登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('password', '密码', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '不修改密码请留空'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '不修改密码请留空', + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + return $builder; + } +} diff --git a/fastmovie-admin/plugin/control/app/api/controller/PublicController.php b/fastmovie-admin/plugin/control/app/api/controller/PublicController.php new file mode 100644 index 0000000..8388b52 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/api/controller/PublicController.php @@ -0,0 +1,113 @@ +channels_uid); + $config->enum = [ + 'actor_species_type' => ActorSpeciesType::getOptions(), + 'actor_gender' => ActorGender::getOptions(), + 'actor_age' => ActorAge::getOptions(), + 'actor_status' => ActorStatus::getOptions(), + 'style_classify' => StyleClassify::getOptions(), + 'voice_emotion' => VoiceEmotion::getOptions(), + 'voice_language' => VoiceLanguage::getOptions(), + ]; + $pushConfig = new Config('push', 'notification', 0); + if ($pushConfig->state) { + $config->push = [ + 'url' => $pushConfig->wss_url, + 'app_key' => config('plugin.webman.push.app.app_key'), + 'auth' => config('plugin.webman.push.app.auth'), + ]; + } + $config->register = $register->toArray(); + return $this->resData($config); + } + public function getSmsVcode(Request $request) + { + $scene = $request->post('scene'); + if (empty($scene)) { + return $this->fail('参数错误'); + } + $token = $request->post('token'); + if ($token) { + $request->sessionId($token); + } + try { + $captcha_state = 1; + if ($captcha_state) { + $captcha = $request->post('captcha'); + if (empty($captcha)) { + return $this->fail('请输入验证码'); + } + Captcha::check($captcha, $token); + } + $username = $request->post('username'); + switch ($scene) { + case VcodeScene::LOGIN['value']: + $UserModel = PluginUser::where(['mobile' => $username])->find(); + if (!$UserModel) { + throw new \Exception('手机号未注册'); + } + Vcode::send($username, VcodeScene::LOGIN['value'], $token); + break; + case VcodeScene::SIGNUP['value']: + Vcode::send($username, VcodeScene::SIGNUP['value'], $token); + break; + case VcodeScene::BIND_MOBILE['value']: + if (!$request->uid) { + throw new \Exception('请先登录'); + } + // $UserModel = PluginUser::where(['mobile' => $username])->find(); + // if ($UserModel && $UserModel->id != $request->uid) { + // throw new \Exception('手机号已存在'); + // } + Vcode::send($username, VcodeScene::BIND_MOBILE['value'], $token); + break; + case VcodeScene::BIND_EMAIL['value']: + if (!$request->uid) { + throw new \Exception('请先登录'); + } + $UserModel = PluginUser::where(['email' => $username])->find(); + if ($UserModel && $UserModel->id != $request->uid) { + throw new \Exception('邮箱已存在'); + } + Vcode::send($username, VcodeScene::BIND_EMAIL['value'], $token); + break; + case VcodeScene::SET_PASSWORD['value']: + if (!$request->uid) { + throw new \Exception('请先登录'); + } + $mobile = PluginUser::where(['id' => $request->uid])->value('mobile'); + Vcode::send($mobile, VcodeScene::SET_PASSWORD['value'], $token); + break; + default: + throw new \Exception('场景错误'); + } + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + return $this->success(); + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/CaptchaController.php b/fastmovie-admin/plugin/control/app/control/controller/CaptchaController.php new file mode 100644 index 0000000..a41bddb --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/CaptchaController.php @@ -0,0 +1,39 @@ +resData( Captcha::captchaCode()); + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/DomainController.php b/fastmovie-admin/plugin/control/app/control/controller/DomainController.php new file mode 100644 index 0000000..c33c240 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/DomainController.php @@ -0,0 +1,289 @@ +model = new PluginChannelsDomain(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '100px', + 'fixed' => 'right' + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/control/control/Domain/delete', + 'props' => [ + 'message' => '确定要删除《{domain}》域名吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('绑定域名', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/control/control/Domain/create', + 'props' => [ + 'title' => '绑定域名' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('domain', '域名', 'input', '', [ + 'props' => [ + 'placeholder' => '域名搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('domain', '域名', [ + 'props' => [ + 'minWidth' => '200px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/control/control/Domain/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('remarks', '备注', [ + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $domain = $request->get('domain'); + if ($domain) { + $where[] = ['domain', 'like', "%{$domain}%"]; + } + $where[] = ['channels_uid', '=', $request->channels_uid]; + $list = PluginChannelsDomain::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + /** + * 更新状态字段 + * @method POST + */ + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + if ($field == 'state' && $value == State::YES['value']) { + Redis::hSet('domain_map', $model->domain, $model->channels_uid); + } else if ($field == 'state' && $value == State::NO['value']) { + Redis::hDel('domain_map', $model->domain); + } + return $this->success(); + } + return $this->fail('操作失败'); + } + public function create(Request $request) + { + $file_name = md5('domain-verify-' . $request->channels_uid) . '.txt'; + $file = runtime_path('temp/') . $file_name; + if ($request->method() === 'POST') { + $data = $request->post(); + try { + $content = file_get_contents($file); + if ($data['verify_type'] === 'file') { + $remote_content = file_get_contents('http://' . $data['domain'] . '/' . $file_name); + if ($content !== $remote_content) { + throw new \Exception('验证失败'); + } + } else { + // 获取域名解析记录 + $this->verifyDomainRecord($file_name . '.' . $data['domain'], $content); + } + } catch (\Throwable $th) { + return $this->fail('验证失败'); + } + try { + $insterData = []; + if (empty($data['domain'])) { + throw new \Exception('请填写域名'); + } + if (!empty($data['domain'])) { + $Find = PluginChannelsDomain::where(['domain' => $data['domain']])->find(); + if ($Find) { + throw new \Exception('域名已存在'); + } + $insterData['domain'] = $data['domain']; + } + if (!empty($data['remarks'])) { + $insterData['remarks'] = $data['remarks']; + } + $insterData['channels_uid'] = $request->channels_uid; + $insterData['state'] = State::YES['value']; + $model = new PluginChannelsDomain(); + $model->save($insterData); + Redis::hSet('domain_map', $model->domain, $model->channels_uid); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large', + ]); + $Component = new ComponentBuilder; + $builder->add('verify_type', '验证方式', 'radio', 'file', [ + 'required' => true, + 'options' => [ + [ + 'label' => '文件', + 'value' => 'file' + ], + [ + 'label' => 'DNS', + 'value' => 'dns' + ] + ], + 'subProps' => [ + 'border' => true + ] + ]); + if (!is_dir(runtime_path('temp'))) { + mkdir(runtime_path('temp'), 0755, true); + } + if (!file_exists($file)) { + $content = Str::random(32); + file_put_contents($file, $content); + } else { + $content = file_get_contents($file); + } + $builder->add('domain', '域名(HOST)', 'input', '', [ + 'required' => true, + 'prompt' => [ + $Component->add('link', ['default' => '点击下载验证文件'], ['href' => '/app/control/control/Domain/downloadFile?file_name=' . $file_name, 'type' => 'primary', 'size' => 'small', 'target' => '_blank', 'underline' => 'never']) + ->add('text', ['default' => "文件验证:将文件 {$file_name} 放置域名根目录下,确保域名+文件名能访问到文件内容:{$content}"], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => "DNS验证:添加一条域名解析记录,记录类型为TXT,记录值为:{$content}"], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入不带协议的域名(HOST),如:www.example.com', + 'clearable' => true + ] + ]); + $builder->add('remarks', '备注', 'input', '', [ + 'props' => [ + 'clearable' => true + ] + ]); + return $this->resData($builder); + } + public function downloadFile(Request $request) + { + $file_name = $request->get('file_name'); + $file = runtime_path('temp/') . $file_name; + if (file_exists($file)) { + return response()->download($file, $file_name); + } + return $this->fail('文件不存在'); + } + public function verifyDomainRecord($domain, $content) + { + $res = dns_get_record($domain, DNS_ALL); + foreach ($res as $key => $value) { + if ($value['type'] === 'TXT') { + if ($value['txt'] === $content) { + return true; + } + } + } + throw new \Exception('验证失败'); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $model = $this->model->where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + if (!$model->delete()) { + return $this->fail('删除失败'); + } + Redis::hDel('domain_map', $model->domain); + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/IndexController.php b/fastmovie-admin/plugin/control/app/control/controller/IndexController.php new file mode 100644 index 0000000..0d6f97f --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/IndexController.php @@ -0,0 +1,216 @@ +path(); + # 如果不是以/结尾的,就重定向到以/结尾的URL + if (substr($path, -1) != '/') { + return redirect($path . '/'); + } + return view(public_path('index.html')); + } + public function control(Request $request) + { + $builder = new DataboardBuilder([ + 'gutter' => 6 + ]); + $today = PluginUser::where(['channels_uid' => $request->channels_uid])->whereDay('create_time')->count(); + $yesterday = PluginUser::where(['channels_uid' => $request->channels_uid])->whereDay('create_time', 'yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('今日新注册用户') + ->setUnit('人') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time')->count(); + $yesterday = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', 'yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('今日支付订单数') + ->setUnit('笔') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + $today = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time')->sum('money'); + $yesterday = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', 'yesterday')->sum('money'); + $statistic = new Statistic; + $statistic->setLabel('今日支付金额') + ->setUnit('元') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today = PluginShortplayDrama::where(['channels_uid' => $request->channels_uid])->whereDay('create_time')->count(); + $yesterday = PluginShortplayDrama::where(['channels_uid' => $request->channels_uid])->whereDay('create_time', 'yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('今日短剧数') + ->setUnit('部') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $this->echarts($builder); + return $this->resData($builder); + } + private function echarts(DataboardBuilder $builder) + { + $endTime = strtotime('23:59:59', time()) - time(); + $request = request(); + $echarts = new Echarts; + $echarts->setClass('bg-white p-4 rounded-4 shadow-lighter vh-65'); + $EchartsData = Cache::get('control_echarts_data_' . $request->channels_uid); + if (!$EchartsData) { + $EchartsData = $this->getEchartsData($request); + Cache::set('control_echarts_data_' . $request->channels_uid, $EchartsData, $endTime); + } + $echarts->setData($EchartsData); + $builder->add($echarts); + } + private function getEchartsData(Request $request) + { + $data = [ + 'color' => ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'], + 'title' => [ + 'text' => '数据可视化' + ], + 'tooltip' => [ + 'trigger' => 'axis', + 'axisPointer' => [ + 'type' => 'cross', + 'label' => [ + 'backgroundColor' => '#6a7985' + ] + ] + ], + 'legend' => [ + 'data' => ["用户", "订单数", "支付金额"] + ], + 'toolbox' => [ + 'show' => false + ], + 'grid' => [ + 'left' => '3%', + 'right' => '4%', + 'bottom' => '3%', + 'containLabel' => true + ], + 'xAxis' => [ + [ + 'type' => 'category', + 'boundaryGap' => false, + 'data' => [] + ] + ], + 'yAxis' => [ + [ + 'type' => 'value' + ] + ], + 'series' => [ + [ + 'name' => "用户", + 'type' => 'line', + 'smooth' => true, + 'data' => [] + ], + [ + 'name' => "订单数", + 'type' => 'line', + 'smooth' => true, + 'data' => [] + ], + [ + 'name' => "支付金额", + 'type' => 'line', + 'smooth' => true, + 'data' => [] + ] + ] + ]; + for ($i = 7; $i > 0; $i--) { + $date = date('Y-m-d', strtotime("-$i day")); + $data['xAxis'][0]['data'][] = $date; + $Statistic = PluginUser::where(['channels_uid' => $request->channels_uid])->whereDay('create_time', $date)->count(); + if ($Statistic) { + $data['series'][0]['data'][] = $Statistic; + } else { + $data['series'][0]['data'][] = 0; + } + $Order = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', $date)->count(); + if ($Order) { + $data['series'][1]['data'][] = $Order; + } else { + $data['series'][1]['data'][] = 0; + } + $Payment = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', $date)->sum('money'); + if ($Payment) { + $data['series'][2]['data'][] = $Payment; + } else { + $data['series'][2]['data'][] = 0; + } + } + return $data; + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/LoginController.php b/fastmovie-admin/plugin/control/app/control/controller/LoginController.php new file mode 100644 index 0000000..6df710c --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/LoginController.php @@ -0,0 +1,157 @@ +post(); + $captcha_state = Config::get('captcha', 'state'); + if ($captcha_state) { + if (!Captcha::check($D['captcha'], $D['token'])) { + throw new Exception("验证码不正确", ResponseCode::CAPTCHA); + } + } + $where = []; + $where[] = ['mobile|username', '=', $D['username']]; + $User = PluginChannelsUser::where($where)->find(); + if (!$User) { + throw new Exception('用户不存在'); + } + if (!$User->password) { + throw new Exception('当前用户不支持密码登录,请使用验证码登录'); + } + + if (!password_verify($D['password'], $User->password)) { + throw new Exception('密码错误'); + } + if (!$User->state) { + throw new Exception('用户已被禁用'); + } + if (!$User->activation_time) { + $User->activation_time = date('Y-m-d H:i:s'); + } + $User->login_ip = $request->getRealIp(true); + $User->login_time = date('Y-m-d H:i:s'); + if ($User->save()) { + Event::emit(EventName::USER_LOGIN['value'], $User); + return $this->success('登录成功', PluginChannelsUser::getTokenInfo($User)); + } else { + throw new Exception("登录失败"); + } + } catch (\Throwable $th) { + return $this->exception($th); + } + } + public function vcode(Request $request) + { + $vcode = $request->post('vcode'); + $username = $request->post('username'); + $token = $request->post('token'); + if (!Vcode::check($username, $vcode, 'login', $token)) { + return $this->fail('验证码不正确'); + } + $User = PluginChannelsUser::where(['mobile' => $username])->find(); + if (empty($User)) { + try { + HelperUser::register([ + 'mobile' => $username + ]); + $User = PluginChannelsUser::where(['mobile' => $username])->find(); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + if ($User->state != State::YES['value']) { + return $this->fail('用户已被禁用'); + } + if (!$User->activation_time) { + $User->activation_time = date('Y-m-d H:i:s'); + } + $User->login_ip = $request->getRealIp(true); + $User->login_time = date('Y-m-d H:i:s'); + if ($User->save()) { + Event::emit(EventName::USER_LOGIN['value'], $User); + return $this->success('登录成功', PluginChannelsUser::getTokenInfo($User)); + } else { + throw new Exception("登录失败"); + } + } + public function register(Request $request) + { + $vcode = $request->post('vcode'); + $username = $request->post('username'); + $token = $request->post('token'); + if (!Vcode::check($username, $vcode, 'register', $token)) { + return $this->fail('验证码不正确'); + } + $password = $request->post('password'); + try { + HelperUser::register([ + 'mobile' => $username, + 'password' => $password + ]); + return $this->success('注册成功'); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + public function qrcode(Request $request) + { + try { + $expire = 5 * 60; + $params = []; + if ($request->icode) { + $params['puid'] = HelperUser::getUidByIcode($request->icode); + } + + $res = Wechat::createQrCode(Wechat::QR_STR_SCENE, $expire, [\plugin\control\event\WechatOfficialAccount::class, 'login', $params]); + } catch (\Throwable $th) { + return $this->exception($th); + } + $data = [ + 'id' => $res['id'], + 'qrcode' => $res['url'], + 'expire' => $res['expire_seconds'] + ]; + return $this->resData($data); + } + public function checkQrcode(Request $request) + { + $id = $request->post('id'); + $uid = Redis::get($id . '_callback'); + if ($uid) { + $User = PluginChannelsUser::where('id', $uid)->find(); + if ($User) { + return $this->success('登录成功', PluginChannelsUser::getTokenInfo($User)); + } else { + return $this->fail('登录失败'); + } + } + return $this->code(ResponseCode::WAIT); + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/PaymentController.php b/fastmovie-admin/plugin/control/app/control/controller/PaymentController.php new file mode 100644 index 0000000..6da198e --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/PaymentController.php @@ -0,0 +1,190 @@ +model = new PaymentConfig; + } + public function index(Request $request) + { + $tabs = []; + $PaymentConfig = PaymentConfig::where(['channels_uid' => $request->channels_uid])->select(); + $Platform = Platform::getOptions(function($value){ + return $value['value'] === Platform::PC['value']; + }); + foreach ($Platform as $key => $item) { + $channels = []; + switch ($item['value']) { + case Platform::PC['value']: + $channels = [ + PaymentChannels::WXPAY, + // PaymentChannels::ALIPAY, + // PaymentChannels::BALANCE, + // PaymentChannels::INTEGRAL + ]; + break; + case Platform::H5['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::ALIPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + case Platform::WECHAT_MINIAPP['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + case Platform::WECHAT_OFFICIAL_ACCOUNT['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + case Platform::APP['value']: + $channels = [ + PaymentChannels::WXPAY, + PaymentChannels::ALIPAY, + PaymentChannels::BALANCE, + PaymentChannels::INTEGRAL + ]; + break; + } + $tabs[] = $this->getFormBuilder($PaymentConfig, $item, $channels, $request); + } + return $this->resData($tabs); + } + private function getFormBuilder($PaymentConfig, $Platform, $channels, $request) + { + $builder = new TableBuilder([ + 'border' => false, + 'stripe' => false + ]); + $builder->add('channels', '支付方式', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels_text', + 'avatar' => [ + 'field' => 'channels_icon', + 'size' => 40 + ] + ] + ], + 'props' => [] + ]); + $builder->add('template_id', '支付模板', [ + 'component' => [ + 'name' => 'select', + 'api' => '/app/control/control/Payment/indexUpdateField', + 'options' => PaymentTemplate::options(['channels_uid' => $request->channels_uid]), + 'props' => [ + 'clearable' => true + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('state', '是否启用', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/control/control/Payment/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('is_default', '是否为默认支付', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/control/control/Payment/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $tableData = []; + $exist = []; + foreach ($PaymentConfig as $item) { + if ($item->platform != $Platform['value']) continue; + $temp = $item->toArray(); + $PaymentChannels = PaymentChannels::get($item->channels); + if ($PaymentChannels) { + $temp['channels_text'] = $PaymentChannels['label']; + $temp['channels_icon'] = $PaymentChannels['icon']; + } + $exist[] = $item->channels; + $tableData[] = $temp; + } + foreach ($channels as $key => $channel) { + if (!in_array($channel['value'], $exist)) { + $model = new PaymentConfig; + $model->channels_uid = $request->channels_uid; + $model->platform = $Platform['value']; + $model->channels = $channel['value']; + $model->state = State::NO['value']; + $model->save(); + $tableData[] = [ + 'id' => $model->id, + 'channels' => $channel['value'], + 'channels_text' => $channel['label'], + 'channels_icon' => $channel['icon'], + 'template_id' => null, + 'state' => State::NO['value'], + 'is_default' => State::NO['value'] + ]; + } + } + $data = [ + 'title' => $Platform['label'], + 'name' => $Platform['value'], + 'tips' => '在' . $Platform['label'] . '端付款时使用', + 'builder' => $builder, + 'data' => $tableData + ]; + return $data; + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $PaymentConfig = PaymentConfig::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$PaymentConfig) { + return $this->fail('数据不存在'); + } + if ($field == 'is_default' && $value == State::YES['value']) { + $PaymentConfig->where(['platform' => $PaymentConfig->platform])->update(['is_default' => State::NO['value']]); + } + $PaymentConfig->{$field} = $value; + if ($PaymentConfig->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/PaymentTemplateController.php b/fastmovie-admin/plugin/control/app/control/controller/PaymentTemplateController.php new file mode 100644 index 0000000..dfd4612 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/PaymentTemplateController.php @@ -0,0 +1,645 @@ +addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => '/app/control/control/PaymentTemplate/update', + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{title}》支付模板' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/control/control/PaymentTemplate/delete', + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{title}》支付模板吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('新增模板', [ + 'path' => '/app/control/control/PaymentTemplate/create', + 'props' => [ + 'type' => 'success', + 'title' => '新增支付模板' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success', + 'icon' => 'Plus' + ] + ] + ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '模板名称'); + $builder->add('channels', '支付方式', [ + 'component' => [ + 'name' => 'tag', + 'options' => PaymentChannels::getOptions() + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/control/control/PaymentTemplate/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $list = PaymentTemplate::where($where)->withoutField('value') + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $model = new PaymentTemplate; + $model->channels_uid = $request->channels_uid; + $model->title = $D['title']; + unset($D['title']); + $model->channels = $D['channels']; + unset($D['channels']); + $D['notify_url'] = 'https://' . $request->host(); + $model->state = State::YES['value']; + $model->value = $this->getValue($model->channels, $D); + if ($model->save()) { + return $this->success('新增成功'); + } + return $this->fail('新增失败'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $model = PaymentTemplate::where(['id' => $D['id'], 'channels_uid' => $request->channels_uid])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + unset($D['id']); + $model->title = $D['title']; + unset($D['title']); + $model->channels = $D['channels']; + unset($D['channels']); + $D['notify_url'] = 'https://' . $request->host(); + $model->state = State::YES['value']; + $model->value = $this->getValue($model->channels, $D); + if ($model->save()) { + return $this->success('更新成功'); + } + return $this->fail('更新失败'); + } + $id = $request->get('id'); + $model = PaymentTemplate::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $builder = $this->getFormBuilder(true); + $data = [ + 'id' => $model->id, + 'title' => $model->title, + 'channels' => $model->channels + ]; + foreach ($model->value as $key => $value) { + $data[$key] = $value; + } + $builder->setData($data); + return $this->resData($builder); + } + private function getValue($channels, $D) + { + $value = []; + switch ($channels) { + case PaymentChannels::WXPAY['value']: + $value = [ + 'mch_type' => $D['mch_type'], + 'appid' => $D['appid'], + 'mch_id' => $D['mch_id'], + 'mch_key' => $D['mch_key'], + 'ssl_cert' => $D['ssl_cert'], + 'ssl_key' => $D['ssl_key'], + 'notify_url' => $D['notify_url'], + ]; + break; + // case PaymentChannels::ALIPAY['value']: + // $value = [ + // 'appid' => $D['appid'], + // 'ras_model' => $D['ras_model'], + // 'app_public_cert' => $D['app_public_cert'], + // 'alipay_public_cert' => $D['alipay_public_cert'], + // 'alipay_root_cert' => $D['alipay_root_cert'], + // 'alipay_public_key' => $D['alipay_public_key'], + // 'private_key' => $D['private_key'], + // 'notify_url' => $D['notify_url'], + // ]; + // break; + // case PaymentChannels::INTEGRAL['value']: + // $value = [ + // 'display_name' => $D['display_name'], + // 'proportion' => $D['proportion'], + // 'is_integer' => $D['is_integer'], + // 'integer' => $D['integer'] + // ]; + // break; + } + return $value; + } + private function getFormBuilder($update = false) + { + $builder = new FormBuilder(null, '', [ + 'labelWidth' => '300px', + 'labelPosition' => 'right' + ]); + $Component = new ComponentBuilder; + $builder->add('title', '模板名称', 'input', null, [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '仅用于后台管理使用,对前台用户不可见;例如:H5-支付宝支付;微信小程序-微信支付'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入模板名称' + ] + ]); + $builder->add('channels', '支付方式', 'radio', PaymentChannels::WXPAY['value'], [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '保存以后支付方式将不可修改,请谨慎操作,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信商户平台'], ['type' => 'success', 'href' => 'https://pay.weixin.qq.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small']) + ->add('text', ['default' => ','], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '支付宝开放平台'], ['type' => 'primary', 'href' => 'https://alipay.com', 'target' => '_blank', 'underline' => 'never', 'size' => 'small']) + ->builder() + ], + 'options' => PaymentChannels::getOptions(function ($item) { + return $item['value'] === PaymentChannels::WXPAY['value']; + }), + 'props' => [ + 'disabled' => $update + ], + 'subProps' => [ + 'border' => true + ] + ]); + + # 微信支付开始 + /* $builder->add('api_version', '微信支付接口版本', 'radio', WxpayVersion::V3['value'], [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']] + ], + 'options' => WxpayVersion::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); */ + $builder->add('mch_type', '微信商户号类型', 'radio', WxpayMchType::NORMAL['value'], [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']] + ], + 'options' => WxpayMchType::getOptions(function ($item) { + return $item['value'] === WxpayMchType::NORMAL['value']; + }), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('appid', '应用ID (AppID)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入应用ID (AppID)' + ] + ]); + $builder->add('mch_id', '微信商户号 (MchId)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入微信商户号 (MchId)' + ] + ]); + $builder->add('mch_key', '支付密钥 (APIKEY)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置APIv3密钥'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入支付密钥 (APIKEY)' + ] + ]); + $builder->add('ssl_cert', '证书文件 (CERT)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + $builder->add('ssl_key', '证书文件 (KEY)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::NORMAL['value']] + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + # 微信支付结束 + + # 微信支付服务商模式开始 + $builder->add('service_appid', '服务商应用ID (AppID)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '请填写微信支付服务商的AppID'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入服务商应用ID (AppID)' + ] + ]); + $builder->add('service_mch_id', '服务商户号 (MchId)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付服务商的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入服务商户号 (MchId)' + ] + ]); + $builder->add('service_mch_key', '服务商密钥 (APIKEY)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付商户平台 - 账户中心 - API安全 - 设置API密钥'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入服务商密钥 (APIKEY)' + ] + ]); + $builder->add('appid', '子商户应用ID (AppID)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入子商户应用ID (AppID)' + ] + ]); + $builder->add('mch_id', '子商户号 (MchId)', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信支付的商户号,纯数字格式;例如:1600000109'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入子商户号 (MchId)' + ] + ]); + $builder->add('service_ssl_cert', '服务商证书文件 (CERT)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'props' => [ + 'view' => 'file', + 'multiple' => 1 + ] + ]); + $builder->add('service_ssl_key', '服务商证书文件 (KEY)', 'bundle', '', [ + 'required' => true, + 'where' => [ + ['channels', '=', PaymentChannels::WXPAY['value']], + ['mch_type', '=', WxpayMchType::SERVICE['value']] + ], + 'props' => [ + 'accept' => 'application/octet-stream', + 'view' => 'file', + 'multiple' => 1 + ] + ]); + # 微信支付服务商模式结束 + + # 支付宝支付开始 + // $builder->add('appid', '支付宝应用 (AppID)', 'input', null, [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::ALIPAY['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '支付宝分配给开发者的应用ID;例如:2021072300007148'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'type' => 'text', + // 'placeholder' => '请输入支付宝应用 (AppID)' + // ] + // ]); + // $builder->add('ras_model', '加签模式', 'radio', AlipayRsaModel::PUBLIC_CERT['value'], [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::ALIPAY['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '如需使用资金支出类的接口,则必须使用公钥证书模式'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'options' => AlipayRsaModel::getOptions(), + // 'subProps' => [ + // 'border' => true + // ] + // ]); + // $builder->add('app_public_cert', '应用公钥证书', 'bundle', '', [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::ALIPAY['value']], + // ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '请上传 "appCertPublicKey_xxxxxxxx.crt" 文件'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'view' => 'file', + // 'multiple' => 1 + // ] + // ]); + // $builder->add('alipay_public_cert', '支付宝公钥证书', 'bundle', '', [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::ALIPAY['value']], + // ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '请上传 "alipayCertPublicKey_RSA2.crt" 文件'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'view' => 'file', + // 'multiple' => 1 + // ] + // ]); + // $builder->add('alipay_root_cert', '支付宝根证书', 'bundle', '', [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::ALIPAY['value']], + // ['ras_model', '=', AlipayRsaModel::PUBLIC_CERT['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '请上传 "alipayRootCert.crt" 文件'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'view' => 'file', + // 'multiple' => 1 + // ] + // ]); + // $builder->add('alipay_public_key', '支付宝公钥 (alipayPublicKey)', 'input', '', [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::ALIPAY['value']], + // ['ras_model', '=', AlipayRsaModel::PUBLIC_KEY['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '可在 "支付宝开放平台" - "应用信息" - "接口加签方式" - "支付宝公钥" 中复制'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'type' => 'textarea', + // 'autosize' => [ + // 'minRows' => 4, + // 'maxRows' => 6 + // ], + // 'placeholder' => '请输入支付宝公钥 (alipayPublicKey)' + // ] + // ]); + // $builder->add('private_key', '应用私钥 (privateKey)', 'input', '', [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::ALIPAY['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '查看 "应用私钥RSA2048-敏感数据,请妥善保管.txt" 文件,将全部内容复制到此处'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'type' => 'textarea', + // 'autosize' => [ + // 'minRows' => 4, + // 'maxRows' => 6 + // ], + // 'placeholder' => '请输入应用私钥 (privateKey)' + // ] + // ]); + // $builder->add('display_name', '显示名称', 'input', null, [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::INTEGRAL['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '在客户端显示的名称'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'type' => 'text', + // 'placeholder' => '请输入显示名称' + // ] + // ]); + // $builder->add('proportion', '充值比例', 'input-number', null, [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::INTEGRAL['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '1元=多少积分'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'controls' => false, + // 'min' => 1, + // 'max' => 1000000 + // ] + // ]); + // $builder->add('is_integer', '整数使用', 'switch', State::NO['value'], [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::INTEGRAL['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '开启后只能使用整数的倍数积分'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'active-value' => State::YES['value'], + // 'inactive-value' => State::NO['value'] + // ] + // ]); + // $builder->add('integer', '整数', 'input-number', null, [ + // 'required' => true, + // 'where' => [ + // ['channels', '=', PaymentChannels::INTEGRAL['value']], + // ['is_integer', '=', State::YES['value']] + // ], + // 'prompt' => [ + // $Component->add('text', ['default' => '整数的倍数使用'], ['type' => 'info', 'size' => 'small']) + // ->builder() + // ], + // 'props' => [ + // 'controls' => false, + // 'min' => 1, + // 'max' => 1000000 + // ] + // ]); + /* $builder->add('notify_url', '支付通知', 'input', null, [ + 'required' => true, + 'where' => [ + ['channels', 'in', [PaymentChannels::WXPAY['value'], PaymentChannels::ALIPAY['value']]] + ], + 'prompt' => [ + $Component->add('text', ['default' => '只需要填写域名,不需要填写路径,如:https://www.baidu.com'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '支付通知接口域名' + ] + ]); */ + return $builder; + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/PlatformController.php b/fastmovie-admin/plugin/control/app/control/controller/PlatformController.php new file mode 100644 index 0000000..0536cc5 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/PlatformController.php @@ -0,0 +1,22 @@ +channels_uid = $request->channels_uid; + return $this->builder(); + } + public function wechat_official_account(Request $request) + { + $this->channels_uid = $request->channels_uid; + return $this->builder(); + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/PublicController.php b/fastmovie-admin/plugin/control/app/control/controller/PublicController.php new file mode 100644 index 0000000..9ba9f07 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/PublicController.php @@ -0,0 +1,195 @@ +lang; + $domain = 'control'; + $config = new Config('basic', ''); + $config = new Web($config->toArray()); + $captcha_config = new Config('captcha', ''); + $config->useLogin([ + 'url' => 'Login/login', + 'title' => trans('Login Title', [], $domain, $lang), + 'captcha' => $captcha_config->state, + 'link_text' => trans('Login Link Text', [], $domain, $lang), + 'user_agreement_config' => [ + 'title' => trans('Login User Agreement Title', [], $domain, $lang), + 'label' => trans('Login User Agreement Label', [], $domain, $lang) + ] + ]); + $config->useVcode([ + 'url' => 'Login/vcode', + 'title' => trans('Vcode Login', [], $domain, $lang) + ]); + $config->useRegister([ + 'url' => 'Login/register', + 'title' => trans('Register Title', ['%web_name%' => $config['web_name']], $domain, $lang), + 'link_text' => trans('Register Link Text', [], $domain, $lang), + 'user_agreement_config' => [ + 'title' => trans('Register User Agreement Title', [], $domain, $lang), + 'label' => trans('Register User Agreement Label', [], $domain, $lang) + ] + ]); + $wechat_official_account_config = new Config('wechat_official_account', ''); + if ($wechat_official_account_config->state && $wechat_official_account_config->message_state) { + $config->useQrcodeLogin([ + 'url' => 'Login/qrcode', + 'check' => 'Login/checkQrcode', + 'title' => trans('Qrcode Login', [], $domain, $lang) + ]); + } + $config->useApis([ + 'userinfo' => '/app/control/control/User/getInfo', + 'lock' => '/app/control/control/User/lock', + 'unlock' => '/app/control/control/Public/unlock', + 'menus' => '/app/control/control/Public/menus', + 'vcode' => '/app/control/control/Public/vcode', + 'outLogin' => '/app/control/control/Public/outLogin', + ]); + $toolbar = new Action(); + $toolbar->add(EnumAction::LOCK['value'], [ + 'icon' => 'Lock', + 'tips' => trans('toolbar Lock', [], $domain, $lang) + ]); + $toolbar->add(EnumAction::SEARCH['value'], [ + 'icon' => 'Search', + 'tips' => trans('toolbar Search', [], $domain, $lang) + ]); + $toolbar->add(EnumAction::NOTIFICATION['value'], [ + 'icon' => 'Notification', + 'tips' => trans('toolbar Notification', [], $domain, $lang) + ]); + $toolbar->add(EnumAction::FULL_SCREEN['value'], [ + 'icon' => 'FullScreen', + 'tips' => trans('toolbar FullScreen', [], $domain, $lang) + ]); + $config->useToolbar($toolbar->toArray()); + $userDropdownMenu = new Action(); + $userDropdownMenu->add(EnumAction::DIALOG['value'], [ + 'path' => '/app/control/control/User/update', + 'label' => trans('userDropdownMenu updateSelf', [], $domain, $lang), + 'icon' => 'User', + 'props' => [ + 'title' => trans('userDropdownMenu updateSelf', [], $domain, $lang) + ] + ]); + $config->useUserDropdownMenu($userDropdownMenu->toArray()); + $config->storage = Filesystem::getOptions(function ($item) { + return !in_array($item['value'], [Filesystem::PUBLIC['value'], Filesystem::LOCAL['value']]); + }); + $pluginConfig = glob(base_path("plugin/*/api/{$request->app}/PublicController.php")); + foreach ($pluginConfig as $path) { + $plugin_name = basename(dirname(dirname(dirname($path)))); + if ($plugin_name == 'control') { + continue; + } + $class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\PublicController"; + if (!class_exists($class)) { + continue; + } + $plugin = new $class; + if (method_exists($plugin, 'config')) { + $plugin->config($config); + } + } + $updateVersionContent = file_get_contents(base_path('update/VERSION')); + $updateVersionArr = explode("\n", $updateVersionContent); + $config->version_name=$updateVersionArr[1]; + $config->version=$updateVersionArr[0]; + return $this->resData($config); + } + public function menus(Request $request) + { + $Control = new \plugin\control\api\Control; + $menus = new Control($Control); + return $this->resData($menus); + } + + + // private function filterMenu(array $menus, array $allowPaths): array + // { + // $result = []; + // foreach ($menus as $menu) { + // // 递归处理 children + // if (!empty($menu['children'])) { + // $menu['children'] = $this->filterMenu($menu['children'], $allowPaths); + // } + // // 是否保留当前节点 + // $keep = in_array($menu['path'], $allowPaths, true) + // || !empty($menu['children']); + + // if ($keep) { + // $result[] = $menu; + // } + // } + // return $result; + // } + + + + public function outLogin(Request $request) + { + $token = $request->header('Authorization'); + if ($token) { + try { + Auth::setPrefix('CONTROL')->delete($token); + } catch (\Throwable $th) { + } + } + return $this->success(trans('Logout Success', [], $request->app, $request->lang)); + } + public function vcode(Request $request) + { + $username = $request->post('username'); + $token = $request->post('token'); + $captcha = $request->post('captcha'); + if (!Captcha::check($captcha, $token)) { + return $this->fail(trans('Captcha Incorrect', [], $request->app, $request->lang)); + } + $scene = $request->post('scene'); + if (!$scene) { + return $this->fail(trans('Scene Cannot Be Empty', [], $request->app, $request->lang)); + } + try { + Vcode::send($username, $scene, $token); + return $this->success(trans('Vcode Send Success', [], $request->app, $request->lang)); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + public function unlock(Request $request) + { + $password = $request->post('password'); + try { + $token = $request->header('Authorization'); + Auth::setPrefix('CONTROL')->unlock($token, $password); + return $this->success(trans('Unlock Success', [], $request->app, $request->lang)); + } catch (\Throwable $th) { + return $this->exception($th); + } + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/RoleController.php b/fastmovie-admin/plugin/control/app/control/controller/RoleController.php new file mode 100644 index 0000000..6f85feb --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/RoleController.php @@ -0,0 +1,260 @@ + 'id', + 'api' => '/app/control/Role/index', + 'lazy' => true, + 'treeProps' => [ + 'children' => 'children', + 'hasChildren' => 'hasChildren' + ] + ]); + $builder->addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => '/app/control/control/Role/update', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{name}》角色' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => 'AdminRole/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{name}》角色吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建角色', [ + 'path' => '/app/control/control/Role/create', + 'props' => [ + 'type' => 'success', + 'title' => '创建角色' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('name', '角色名称'); + $builder->add('admin_num', '人数', [ + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('rule_num', '拥有权限数', [ + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[]=['channels_uid','=',$request->channels_uid]; + $list = PluginChannelsRole::where($where) + ->order('id asc')->paginate($limit)->each(function ($item) { + $item->admin_num = Admin::where(['role_id' => $item->id])->count(); + $item->rule_num = 0; + if ($item->is_system) { + $item->rule_num = '所有'; + } elseif ($item->rule) { + $item->rule_num = count($item->rule); + } + $item->hasChildren = PluginChannelsRole::where(['pid' => $item->id])->count() > 0; + }); + return $this->resData($list); + } + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $AdminRole = PluginChannelsRole::where(['id' => $id])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + if ($AdminRole->is_system) { + return $this->fail('系统角色不允许操作'); + } + $AdminRole->{$field} = $value; + if ($AdminRole->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $AdminRole = PluginChannelsRole::where(['id' => $id])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + if ($AdminRole->is_system) { + return $this->fail('系统角色不允许操作'); + } + if (PluginChannelsRole::where(['pid' => $id])->count() > 0) { + return $this->fail('请先删除子角色'); + } + if (Admin::where(['role_id' => $id])->count() > 0) { + return $this->fail('请先将拥有该角色的用户移除'); + } + if ($AdminRole->delete()) { + return $this->success(); + } + return $this->fail('操作失败'); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $AdminRole = new PluginChannelsRole; + $AdminRole->name = $D['name']; + $AdminRole->rule = $D['rule']; + $AdminRole->channels_uid=$request->channels_uid; + if ($AdminRole->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $AdminRole = PluginChannelsRole::where(['id' => $D['id']])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + $AdminRole->name = $D['name']; + $AdminRole->rule = $D['rule']; + if ($AdminRole->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $id = $request->get('id'); + $AdminRole = PluginChannelsRole::where(['id' => $id])->find(); + if (!$AdminRole) { + return $this->fail('角色不存在'); + } + $builder = $this->getFormBuilder(true); + if (!$AdminRole->rule) { + $AdminRole->rule = []; + } + $builder->setData($AdminRole->toArray()); + return $this->resData($builder); + } + private function getFormBuilder($update = false) + { + $builder = new FormBuilder; + $Component = new ComponentBuilder; + $builder->add('name', '角色名称', 'input', '', [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '建议使用公司组织架构为角色名称'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + + $Control = new \plugin\control\api\Control; + $menus = new Control($Control); + $builder->add('rule', '规则', 'admin-rule', [], [ + 'required' => true, + 'options' => $menus, + 'props' => [ + 'rightDefaultChecked' => [ + 'Index', + 'Index/index', + 'Index/control', + 'Admin/updateSelf', + 'Admin/getSelfInfo', + 'Public', + 'Public/outLogin' + ] + ] + ]); + return $builder; + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/app/control/controller/SystemController.php b/fastmovie-admin/plugin/control/app/control/controller/SystemController.php new file mode 100644 index 0000000..d2ea1a1 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/SystemController.php @@ -0,0 +1,326 @@ +channels_uid = $request->channels_uid; + return $this->builder(); + } + public function yidevs(Request $request) + { + $this->channels_uid = $request->channels_uid; + return $this->builder(); + } + public function payment(Request $request) + { + $this->channels_uid = $request->channels_uid; + return $this->builder(); + } + public function sms(Request $request) + { + $this->channels_uid = $request->channels_uid; + return $this->tabsBuilder(); + } + public function upload(Request $request) + { + $request = request(); + if ($request->method() === 'POST') { + $D = $request->post(); + $config = []; + $config['default'] = $D['default']; + $config['max_size'] = (int)$D['max_size']; + foreach ($D['ftp'] as $key => $value) { + $config['storage']['ftp'][$key] = $value; + } + foreach ($D['s3'] as $key => $value) { + if (in_array($key, ['key', 'secret'])) { + $config['storage']['s3']['credentials'][$key] = $value; + } else { + $config['storage']['s3'][$key] = $value; + } + } + foreach ($D['minio'] as $key => $value) { + if (in_array($key, ['key', 'secret'])) { + $config['storage']['minio']['credentials'][$key] = $value; + } else { + $config['storage']['minio'][$key] = $value; + } + } + foreach ($D['oss'] as $key => $value) { + $config['storage']['oss'][$key] = $value; + } + foreach ($D['qiniu'] as $key => $value) { + $config['storage']['qiniu'][$key] = $value; + } + foreach ($D['cos'] as $key => $value) { + $config['storage']['cos'][$key] = $value; + } + $ModelConfig = ModelConfig::where(['group' => 'filesystem', 'channels_uid' => $request->channels_uid])->find(); + if (!$ModelConfig) { + $ModelConfig = new ModelConfig; + $ModelConfig->group = 'filesystem'; + $ModelConfig->channels_uid = $request->channels_uid; + } + $ModelConfig->value = $config; + $ModelConfig->save(); + return $this->success('保存成功'); + } + $config = [ + 'enable' => true, + 'default' => Filesystem::OSS['value'], + 'max_size' => 1024 * 1024 * 10, //单个文件大小10M + 'ext_yes' => [], //允许上传文件类型 为空则为允许所有 + 'ext_no' => [], // 不允许上传文件类型 为空则不限制 + 'storage' => [ + 'ftp' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class, + 'host' => 'ftp.example.com', + 'username' => 'username', + 'password' => 'password', + 'url' => '', // 静态文件访问域名 + 'port' => 21, + 'root' => '/path/to/root', + 'passive' => true, + 'ssl' => true, + 'timeout' => 30, + 'ignorePassiveAddress' => false, + 'timestampsOnUnixListingsEnabled' => true, + ], + 'memory' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\MemoryAdapterFactory::class, + ], + 's3' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => false, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'minio' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'oss' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class, + 'accessId' => 'OSS_ACCESS_ID', + 'accessSecret' => 'OSS_ACCESS_SECRET', + 'bucket' => 'OSS_BUCKET', + 'endpoint' => 'OSS_ENDPOINT', + 'url' => '', // 静态文件访问域名 + 'timeout' => 3600, + 'connectTimeout' => 10, + 'isCName' => false, + 'token' => null, + 'proxy' => null, + ], + 'qiniu' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class, + 'accessKey' => 'QINIU_ACCESS_KEY', + 'secretKey' => 'QINIU_SECRET_KEY', + 'bucket' => 'QINIU_BUCKET', + 'domain' => 'QINBIU_DOMAIN', + 'url' => '' // 静态文件访问域名 + ], + 'cos' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class, + 'region' => 'COS_REGION', + 'app_id' => 'COS_APPID', + 'secret_id' => 'COS_SECRET_ID', + 'secret_key' => 'COS_SECRET_KEY', + // 可选,如果 bucket 为私有访问请打开此项 + 'signed_url' => false, + 'bucket' => 'COS_BUCKET', + 'read_from_cdn' => false, + 'url' => '' // 静态文件访问域名 + // 'timeout' => 60, + // 'connect_timeout' => 60, + // 'cdn' => '', + // 'scheme' => 'https', + ], + ], + ]; + $ModelConfig = ModelConfig::where(['group' => 'filesystem', 'channels_uid' => $request->channels_uid])->find(); + if ($ModelConfig) { + $user = $ModelConfig->value; + foreach ($user as $key => $value) { + if ($key !== 'storage') { + $config[$key] = $value; + } + } + foreach ($config['storage'] as $key => $value) { + if (isset($user['storage'][$key])) { + $config['storage'][$key] = array_merge($config['storage'][$key], $user['storage'][$key]); + } + } + } + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'submitEvent' => SubmitEvent::SILENT + ]); + $defaultOptions = Filesystem::getOptions(function ($item) { + return !in_array($item['value'], [Filesystem::PUBLIC['value'], Filesystem::LOCAL['value']]); + }); + $builder->add('default', '默认存储渠道商', 'select', $config['default'], [ + 'options' => $defaultOptions + ]); + $builder->add('max_size', '单个文件大小(字节)', 'input-number', $config['max_size'], [ + 'props' => [ + 'min' => 0, + 'controls' => false, + 'style' => [ + 'width' => '200px' + ] + ] + ]); + $Component = new ComponentBuilder; + + $subBuilder = new FormBuilder('oss', '阿里云存储'); + $subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class); + $subBuilder->add('accessId', 'AccessId', 'input', $config['storage']['oss']['accessId']); + $subBuilder->add('accessSecret', 'AccessSecret', 'input', $config['storage']['oss']['accessSecret']); + $subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['oss']['bucket']); + $subBuilder->add('endpoint', 'Bucket域名', 'input', $config['storage']['oss']['endpoint']); + $subBuilder->add('isCName', '私有空间', 'switch', $config['storage']['oss']['isCName']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['oss']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('cos', '腾讯云存储'); + $subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class); + $subBuilder->add('app_id', 'Appid', 'input', $config['storage']['cos']['app_id']); + $subBuilder->add('secret_id', 'SecretId', 'input', $config['storage']['cos']['secret_id']); + $subBuilder->add('secret_key', 'SecretKey', 'input', $config['storage']['cos']['secret_key']); + $subBuilder->add('region', 'Region', 'input', $config['storage']['cos']['region']); + $subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['cos']['bucket']); + $subBuilder->add('read_from_cdn', '从CDN读取', 'switch', $config['storage']['cos']['read_from_cdn']); + $subBuilder->add('signed_url', '私有空间', 'switch', $config['storage']['cos']['signed_url']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['cos']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('qiniu', '七牛存储'); + $subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class); + $subBuilder->add('accessKey', 'AccessKey', 'input', $config['storage']['qiniu']['accessKey']); + $subBuilder->add('secretKey', 'SecretKey', 'input', $config['storage']['qiniu']['secretKey']); + $subBuilder->add('bucket', 'Bucket', 'input', $config['storage']['qiniu']['bucket']); + $subBuilder->add('domain', 'Domain', 'input', $config['storage']['qiniu']['domain']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['qiniu']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('ftp', 'FTP存储'); + $subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class); + $subBuilder->add('host', 'HOST', 'input', $config['storage']['ftp']['host']); + $subBuilder->add('username', 'UserName', 'input', $config['storage']['ftp']['username']); + $subBuilder->add('password', 'Password', 'input', $config['storage']['ftp']['password']); + $subBuilder->add('port', 'Port', 'input', $config['storage']['ftp']['port']); + $subBuilder->add('root', '目录', 'input', $config['storage']['ftp']['root']); + $subBuilder->add('passive', '被动模式', 'switch', $config['storage']['ftp']['passive']); + $subBuilder->add('ssl', 'SSL', 'switch', $config['storage']['ftp']['ssl']); + $subBuilder->add('timeout', '超时', 'input-number', $config['storage']['ftp']['timeout']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['ftp']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('s3', 'S3存储'); + $subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class); + $subBuilder->add('key', 'KEY', 'input', $config['storage']['s3']['credentials']['key']); + $subBuilder->add('secret', 'Secret', 'input', $config['storage']['s3']['credentials']['secret']); + $subBuilder->add('region', 'Region', 'input', $config['storage']['s3']['region']); + $subBuilder->add('version', 'Version', 'input', $config['storage']['s3']['version']); + $subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['s3']['bucket_endpoint']); + $subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['s3']['use_path_style_endpoint']); + $subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['s3']['endpoint']); + $subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['s3']['bucket_name']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['s3']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + + $subBuilder = new FormBuilder('minio', 'Minio存储'); + $subBuilder->addValue('driver', \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class); + $subBuilder->add('key', 'KEY', 'input', $config['storage']['minio']['credentials']['key']); + $subBuilder->add('secret', 'Secret', 'input', $config['storage']['minio']['credentials']['secret']); + $subBuilder->add('region', 'Region', 'input', $config['storage']['minio']['region']); + $subBuilder->add('version', 'Version', 'input', $config['storage']['minio']['version']); + $subBuilder->add('bucket_endpoint', 'Bucket Endpoint', 'switch', $config['storage']['minio']['bucket_endpoint']); + $subBuilder->add('use_path_style_endpoint', 'Use path style endpoint', 'switch', $config['storage']['minio']['use_path_style_endpoint']); + $subBuilder->add('endpoint', 'Endpoint', 'input', $config['storage']['minio']['endpoint']); + $subBuilder->add('bucket_name', 'Bucket Name', 'input', $config['storage']['minio']['bucket_name']); + $subBuilder->add('url', '静态文件访问域名', 'input', $config['storage']['minio']['url'], [ + 'prompt' => [ + $Component->add('text', ['default' => '对外访问域名,不以斜杠结尾'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'http://static.example.com' + ] + ]); + $builder->addGroupForm($subBuilder); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/UploadsController.php b/fastmovie-admin/plugin/control/app/control/controller/UploadsController.php new file mode 100644 index 0000000..ffc33a3 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/UploadsController.php @@ -0,0 +1,22 @@ +channels_uid = $request->channels_uid; + $this->admin_uid = $request->uid; + $Config = new Config('filesystem', '', $this->channels_uid); + if (empty($Config->toArray())) { + throw new \Exception('请先完善上传配置'); + } + } +} diff --git a/fastmovie-admin/plugin/control/app/control/controller/UserController.php b/fastmovie-admin/plugin/control/app/control/controller/UserController.php new file mode 100644 index 0000000..a89b6a3 --- /dev/null +++ b/fastmovie-admin/plugin/control/app/control/controller/UserController.php @@ -0,0 +1,385 @@ +addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/control/control/User/updateUser', + 'props' => [ + 'title' => '编辑《{id}》账号' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建账号', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/control/control/User/create', + 'props' => [ + 'title' => '创建账号' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + $builder->add('login_ip', '登录IP', [ + 'props' => [ + 'width' => '130px' + ] + ]); + $builder->add('role.name', '角色', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('role.state', '状态', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('sort', '排序(小到大)', [ + 'props' => [ + 'width' => '130px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $list = PluginChannelsUser::with('role')->where($where)->paginate($limit); + return $this->resData($list); + } + + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $D['channels_uid'] = $request->channels_uid; + if (empty($D['password'])) { + return $this->fail('密码不能为空'); + } + try { + PluginChannelsUser::create($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getForm($request->channels_uid); + return $this->resData($builder); + } + public function updateUser(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $User = PluginChannelsUser::where(['id' => $D['id']])->find(); + if (!$User) { + return $this->fail('用户不存在'); + } + if (empty($D['password'])) { + unset($D['password']); + } else { + $password = $D['password']; + } + $User->role_id = $D['role_id']; + $User->username = $D['username']; + $User->nickname = $D['nickname']; + $User->headimg = $D['headimg']; + $User->mobile = $D['mobile']; + $User->email = $D['email']; + $User->password = $password; + if ($User->save()) { + return $this->success('更新成功'); + } + return $this->fail('更新失败'); + } + $id = $request->get('id'); + $User = PluginChannelsUser::where(['id' => $id])->find(); + if (!$User) { + return $this->fail('用户不存在'); + } + $builder = $this->getForm($request->channels_uid); + unset($User->password); + $builder->setData($User->toArray()); + return $this->resData($builder); + } + + private function getForm($uid) + { + $list = PluginChannelsRole::field('id as value,name as label')->where('channels_uid', $uid)->where('state', State::YES['value'])->select(); + $builder = new FormBuilder; + $builder->add('role_id', '所属角色', 'select', null, [ + 'options' => $list + ]); + $builder->add('username', '账号', 'input', '', [ + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('password', '密码', 'input', '', [ + 'props' => [ + 'placeholder' => '不修改密码请留空', + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('nickname', '昵称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true + ]); + $builder->add('headimg', '头像', 'bundle', '', [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ]); + $builder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'maxlength' => 11, + 'show-word-limit' => true + ] + ]); + $builder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'maxlength' => 50, + 'show-word-limit' => true + ] + ]); + return $builder; + } + + + + + + + + + + public function update(Request $request) + { + $id = $request->channels_uid; + if ($request->method() === 'POST') { + $D = $request->post(); + $D['id'] = $id; + try { + $validate = new ValidateUser; + $validate->scene('self')->check($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + $User = PluginChannelsUser::where(['id' => $D['id']])->find(); + if (!$User) { + return $this->fail('用户不存在'); + } + if (!$User->username) { + $User->username = $D['username']; + } + $User->nickname = $D['nickname']; + $User->headimg = $D['headimg']; + $User->mobile = $D['mobile']; + $User->email = $D['email']; + if ($D['password']) { + $User->password = $D['password']; + } + if ($User->save()) { + return $this->event(ResponseEvent::UPDATE_USERINFO, '保存成功'); + } + return $this->fail('保存失败'); + } + $User = PluginChannelsUser::where(['id' => $id])->withoutField('password')->find(); + if (!$User) { + return $this->fail('用户不存在'); + } + $builder = new FormBuilder(); + $builder->add('username', '账号', 'input', '', [ + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true, + 'disabled' => $User->username ? true : false + ] + ]); + $builder->add('password', '密码', 'input', '', [ + 'props' => [ + 'placeholder' => '不修改密码请留空', + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('nickname', '昵称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true + ]); + $builder->add('headimg', '头像', 'bundle', '', [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ]); + $builder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'maxlength' => 11, + 'show-word-limit' => true + ] + ]); + $builder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'maxlength' => 50, + 'show-word-limit' => true + ] + ]); + $builder->setData($User->toArray()); + return $this->resData($builder); + } + + + + + + public function getInfo(Request $request) + { + $User = PluginChannelsUser::where(['id' => $request->uid])->withoutField('password')->find(); + return $this->resData(PluginChannelsUser::getTokenInfo($User)); + } + public function refresh() + { + return $this->event(ResponseEvent::UPDATE_USERINFO, '刷新成功'); + } + public function lock(Request $request) + { + try { + $password = $request->post('password'); + if (!$password) { + return $this->fail('PIN码不能为空'); + } + if (mb_strlen($password) != 6) { + return $this->fail('请输入6位PIN码'); + } + $token = $request->header('Authorization'); + Auth::setPrefix('CONTROL')->lock($token, $password); + return $this->event(ResponseEvent::UPDATE_USERINFO, '锁定成功'); + } catch (\Throwable $th) { + return $this->exception($th); + } + } +} diff --git a/fastmovie-admin/plugin/control/app/controller/WechatOfficialAccountController.php b/fastmovie-admin/plugin/control/app/controller/WechatOfficialAccountController.php new file mode 100644 index 0000000..d1ec21f --- /dev/null +++ b/fastmovie-admin/plugin/control/app/controller/WechatOfficialAccountController.php @@ -0,0 +1,32 @@ +state) { + return 'wechat official account is not enabled'; + } + try { + $OfficialAccount->checkSignature($request, $config); + if ($request->method() === 'GET') { + return $request->get('echostr'); + } + } catch (\Throwable $th) { + if ($request->method() === 'GET') { + return $th->getMessage(); + } else { + } + } + return $OfficialAccount->handle($request, $config); + } +} diff --git a/fastmovie-admin/plugin/control/app/middleware/Auth.php b/fastmovie-admin/plugin/control/app/middleware/Auth.php new file mode 100644 index 0000000..c67b92c --- /dev/null +++ b/fastmovie-admin/plugin/control/app/middleware/Auth.php @@ -0,0 +1,9 @@ +field('id,nickname,mobile')->select(); + $data = []; + foreach ($models as $item) { + $data[] = [ + 'label' => $item->nickname, + 'value' => $item->id, + 'tips' => "UID:{$item->id},M:{$item->mobile}" + ]; + } + return $data; + } + public static function genUser($id) + { + $id = (int)$id; + return str_replace(['+', '/'], ['-', '_'], Rsa::encryptNumber($id)); + } + public static function getUidByUser($user) + { + return Rsa::decryptNumber(str_replace(['-', '_'], ['+', '/'], $user)); + } + public static function getTokenInfo($model, $twofa = false) + { + $request = request(); + /* 重组用户信息 */ + $User = new \stdClass; + $User->user = self::genUser($model->id); + $User->nickname = $model->nickname; + $User->headimg = $model->headimg; + $User->username = $model->username; + $User->username_time = $model->username_time ? 30 - ceil((time() - strtotime($model->username_time)) / 86400) : 0; + $User->mobile = Str::mask($model->mobile); + $User->email = Str::mask($model->email); + $User->password = $model->password ? 1 : 0; + $User->create_time = $model->create_time; + $User->last_login_time = $model->login_time; + $User->last_login_ip = $model->login_ip; + $User->twofa_state = $model->twofa_state; + $PluginChannelsWechat = PluginChannelsWechat::where(['uid' => $model->id])->field('openid,unionid,mp_openid,nickname,headimg,subscribe')->find(); + if ($PluginChannelsWechat) { + $User->wechat = $PluginChannelsWechat; + } + if ($model->channels_uid === null) { + $User->is_system = 1; + $User->permissions = null; + $User->channels_uid = $model->id; + } else { + $role = PluginChannelsRole::where(['id' => $model->role_id, 'channels_uid' => $model->channels_uid])->find(); + if (!$role) { + throw new Exception("无权限访问"); + } + # 是否为主账号 + $User->is_system = 0; + # 不为主账号则所拥有的权限列表 + $User->permissions = $role->rule; + } + $pluginConfig = glob(base_path("plugin/*/api/{$request->app}/PublicController.php")); + foreach ($pluginConfig as $path) { + $plugin_name = basename(dirname(dirname(dirname($path)))); + if ($plugin_name == 'user') { + continue; + } + $class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\PublicController"; + if (!class_exists($class)) { + continue; + } + $plugin = new $class; + if (method_exists($plugin, 'appendUserInfo')) { + $plugin->appendUserInfo($User, $model); + } + } + /* 生成token */ + $data = new \stdClass; + $data->uid = $model->id; + $data->username = $model->username; + $data->mobile = $model->mobile; + $data->email = $model->email; + $data->channels_uid = $User->channels_uid; + $data->is_system = $User->is_system; + $data->permissions = $User->permissions; + $data->twofa_state = $model->twofa_state; + if ($twofa) { + if ($twofa === true) { + $data->twofa = [ + 'expire' => time() + (config('oauth.expire') / 2), + 'time' => time(), + ]; + } else { + $data->twofa = $twofa; + } + } + $User->token = Auth::setPrefix('CONTROL')->encrypt($data); + if ($request->token) { + Auth::setPrefix('CONTROL')->refresh($request->token, 60); + } + return $User; + } + public static function onAfterRead($model) + { + if (empty($model->nickname)) { + if (!empty($model->mobile)) { + $model->nickname = 'FM-' . substr($model->mobile, -4); + } else { + $model->nickname = '未命名的用户'; + } + } + } + public static function onBeforeWrite($model) + { + if (empty($model->nickname)) { + if (!empty($model->mobile)) { + $model->nickname = 'FM-' . substr($model->mobile, -4); + } else { + $model->nickname = 'FM-' . Str::random(); + } + } else { + $model->nickname = iconv('UTF-8', 'UTF-8//IGNORE', $model->nickname); + } + } + + + public function role(){ + return $this->hasOne(PluginChannelsRole::class, 'id', 'role_id'); + } +} diff --git a/fastmovie-admin/plugin/control/app/model/PluginChannelsWechat.php b/fastmovie-admin/plugin/control/app/model/PluginChannelsWechat.php new file mode 100644 index 0000000..8b3181f --- /dev/null +++ b/fastmovie-admin/plugin/control/app/model/PluginChannelsWechat.php @@ -0,0 +1,15 @@ +nickname)){ + $model->nickname= iconv('UTF-8', 'UTF-8//IGNORE', $model->nickname); + } + } +} diff --git a/fastmovie-admin/plugin/control/config/app.php b/fastmovie-admin/plugin/control/config/app.php new file mode 100644 index 0000000..bc3fd0f --- /dev/null +++ b/fastmovie-admin/plugin/control/config/app.php @@ -0,0 +1,9 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/control/config/autoload.php b/fastmovie-admin/plugin/control/config/autoload.php new file mode 100644 index 0000000..d0b9c34 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/autoload.php @@ -0,0 +1,3 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/control/config/log.php b/fastmovie-admin/plugin/control/config/log.php new file mode 100644 index 0000000..8a4744c --- /dev/null +++ b/fastmovie-admin/plugin/control/config/log.php @@ -0,0 +1,20 @@ + [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/article.log', + 7, + \Monolog\Level::Debug, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/fastmovie-admin/plugin/control/config/middleware.php b/fastmovie-admin/plugin/control/config/middleware.php new file mode 100644 index 0000000..c9cc79c --- /dev/null +++ b/fastmovie-admin/plugin/control/config/middleware.php @@ -0,0 +1,19 @@ + [ + app\middleware\Template::class, + app\middleware\Platform::class + ], + 'control' => [ + plugin\control\expose\middleware\ControlAuth::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ] +]; diff --git a/fastmovie-admin/plugin/control/config/process.php b/fastmovie-admin/plugin/control/config/process.php new file mode 100644 index 0000000..a72a250 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/process.php @@ -0,0 +1,5 @@ +isAbstract() || $reflection->isInterface()) { + continue; + } + + $actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); + $actionsLen = count($actions); + for ($n = 0; $n < $actionsLen; $n++) { + $name = $actions[$n]->name; + if (!str_starts_with($name, '__')) { + $routes["/{$controllerName}/{$name}"] = [$classStr, $name]; + } + } +} +$controllersVersionClass = glob(base_path('plugin/control') . '/app/control/controller/**/*Controller.php'); +if (!empty($controllersVersionClass)) { + $len = count($controllersVersionClass); + for ($i = 0; $i < $len; $i++) { + $value = $controllersVersionClass[$i]; + $version = basename(dirname($value)); + $controllerName = str_replace('Controller', '', basename($value, '.php')); + $classStr = str_replace('.php', '', substr($value, strlen(base_path()) + 1)); + $classStr = str_replace('/', '\\', $classStr); + $reflection = new \ReflectionClass('\\' . $classStr); + + // 忽略抽象类、接口 + if ($reflection->isAbstract() || $reflection->isInterface()) { + continue; + } + + $actions = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); + $actionsLen = count($actions); + for ($n = 0; $n < $actionsLen; $n++) { + $name = $actions[$n]->name; + + if (!str_starts_with($name, '__')) { + $routes["/{$version}/{$controllerName}/{$name}"] = [$classStr, $name]; + } + } + } +} +Route::group('/control', function () use ($routes) { + foreach ($routes as $key => $value) { + if (in_array($value[1], ['indexUpdateField', 'indexUpdateState'])) { + Route::post($key, $value); + } else if (strpos($value[1], 'GetTable')) { + Route::get($key, $value); + } else { + Route::any($key, $value); + } + } +}); +Route::any('/app/control/WechatOfficialAccount/message/{channels_uid}', [plugin\control\app\controller\WechatOfficialAccountController::class, 'message']); diff --git a/fastmovie-admin/plugin/control/config/settings-tabs/sms.php b/fastmovie-admin/plugin/control/config/settings-tabs/sms.php new file mode 100644 index 0000000..3b2e4d0 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/settings-tabs/sms.php @@ -0,0 +1,272 @@ + $value) { + $prompt=$Component->add("text", ["default" => "短信模板ID,变量:"], ["type" => "info", "size" => "small"]); + foreach ($value['variable'] as $k => $v) { + $prompt->add("tag", ["default" => $v.':'.$k], ["type" => "info", "size" => "small"]); + } + $vcode_templates[]=[ + 'title' => $value['label'].'短信模板ID', + 'field' => 'vcode_template_'.$value['value'], + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $prompt->builder() + ] + ] + ]; +} +return [ + [ + 'title' => '服务商', + 'field' => 'channels', + 'value' => [SmsChannels::ALIYUN['value'], SmsChannels::TENCENT['value'], SmsChannels::SMSBAO['value']], + 'component' => 'drag-sort', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '短信服务商优先级,当发送失败时,会自动切换到下一个服务商'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'options' => SmsChannels::getOptions(), + ] + ], + 'group' => [ + [ + + 'title' => SmsChannels::ALIYUN['label'], + 'name' => SmsChannels::ALIYUN['value'], + 'children' => [ + [ + 'title' => '开关', + 'field' => 'enable', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '是否启用阿里云短信服务'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'AccessKey ID', + 'field' => 'access_key_id', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '阿里云-AccessKey管理-AccessKey ID,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '阿里云AccessKey'], ['target' => '_blank', 'href' => 'https://ram.console.aliyun.com/manage/ak', 'type' => 'info', 'underline' => 'never', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'AccessKey Secret', + 'field' => 'access_secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '阿里云-AccessKey管理-AccessKey Secret'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => '短信签名', + 'field' => 'sign_name', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '阿里云-短信服务-国内消息-签名管理,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '阿里云短信签名'], ['target' => '_blank', 'href' => 'https://dysms.console.aliyun.com/domestic/text', 'type' => 'info', 'underline' => 'never', 'size' => 'small']) + ->builder() + ] + ] + ], + ...$vcode_templates + ] + ], + [ + "title" => SmsChannels::TENCENT['label'], + "name" => SmsChannels::TENCENT['value'], + "children" => [ + [ + 'title' => '开关', + 'field' => 'enable', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '是否启用腾讯云短信服务'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + "title" => "SecretId", + "field" => "secret_id", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-访问管理-访问密钥-AccessKey ID,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云SecretId"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/cam/capi", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + [ + "title" => "SecretKey", + "field" => "secret_key", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-访问管理-访问密钥-AccessKey Secret,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云SecretKey"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/cam/capi", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + [ + "title" => "应用 ID", + "field" => "appid", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-短信-应用管理-应用 ID,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云应用 ID"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/smsv2/app-manage", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + [ + "title" => "短信签名", + "field" => "sign_name", + "value" => "", + "component" => "input", + "extra" => [ + "required" => true, + 'where' => [ + ['enable', '=', true] + ], + "prompt" => [ + $Component->add("text", ["default" => "腾讯云-短信-国内短信-签名管理,"], ["type" => "info", "size" => "small"]) + ->add("link", ["default" => "腾讯云短信签名"], ["target" => "_blank", "href" => "https://console.cloud.tencent.com/smsv2/csms-sign", "type" => "info", 'underline' => 'never', "size" => "small"]) + ->builder() + ] + ] + ], + ...$vcode_templates + ] + ], + [ + "title" => SmsChannels::SMSBAO['label'], + "name" => SmsChannels::SMSBAO['value'], + "children" => [ + [ + 'title' => '开关', + 'field' => 'enable', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '是否启用短信宝短信服务'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'UserName', + 'field' => 'key', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '短信宝-用户名'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => 'API Key或密码', + 'field' => 'secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '短信宝-API Key或密码'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + [ + 'title' => '短信签名', + 'field' => 'sign_name', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['enable', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '短信宝-短信签名'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ], + ...$vcode_templates + ] + ] + ], +]; diff --git a/fastmovie-admin/plugin/control/config/settings/basic.php b/fastmovie-admin/plugin/control/config/settings/basic.php new file mode 100644 index 0000000..5100ac4 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/settings/basic.php @@ -0,0 +1,219 @@ + 'Settings_basic_web_name', + 'field' => 'web_name', + 'value' => 'FastMovie', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '应用名称并非网页标题'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入应用名称' + ] + ] + ], + [ + 'title' => '网站标题', + 'field' => 'web_title', + 'value' => 'FastMovie', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '网页标题,显示于浏览器tab标题'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入网站标题' + ] + ] + ], + [ + 'title' => 'LOGO展示方式', + 'field' => 'logo_use', + 'value' => 'svg', + 'component' => 'radio', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '应用名称将会显示在页面标题中'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'options' => [ + ['label' => '默认', 'value' => 'svg'], + ['label' => '图片', 'value' => 'image'] + ] + ] + ], + [ + 'title' => 'LOGO(浅色)', + 'field' => 'web_logo_light', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'where' => [ + ['logo_use', '=', 'image'] + ], + 'prompt' => [ + $Component->add('text', ['default' => '浅色LOGO,用于深色背景'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ], + [ + 'title' => 'LOGO(深色)', + 'field' => 'web_logo_dark', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'where' => [ + ['logo_use', '=', 'image'] + ], + 'prompt' => [ + $Component->add('text', ['default' => '深色LOGO,用于浅色背景'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ], + [ + 'title' => 'ICP备案号', + 'field' => 'web_icp', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入ICP备案号' + ] + ] + ], + [ + 'title' => '公安备案号', + 'field' => 'web_mps', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '用于跳转至公安备案系统携带的备案号'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入公安备案号' + ], + 'children' => SoltBuilder::solt('prepend')->add('el-image', ['style' => 'display:flex;', 'src' => '/static/beian.gov.png', 'width' => '15px', 'height' => '15px']) + ] + ], + [ + 'title' => '公安备案号文本', + 'field' => 'web_mps_text', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '展示在网站底部公安备案号'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入公安备案号文本' + ], + 'children' => SoltBuilder::solt('prepend')->add('el-image', ['style' => 'display:flex;', 'src' => '/static/beian.gov.png', 'width' => '15px', 'height' => '15px']) + ] + ], + [ + 'title' => '客服链接', + 'field' => 'wechat_url', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入客服链接链接' + ] + ] + ], + [ + 'title' => '使用教程链接', + 'field' => 'guide_url', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入使用教程链接' + ] + ] + ], + [ + 'title' => '客服二维码', + 'field' => 'wechat_qrcode_url', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ], + [ + 'title' => '微信群二维码', + 'field' => 'wechat_group_qrcode_url', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ], + [ + 'title' => '登录背景图', + 'field' => 'login_background_image_url', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'props' => [ + 'accept' => 'image/*', + 'multiple' => 1 + ] + ] + ], + [ + 'title' => '项目背景视频', + 'field' => 'project_background_video_url', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => '项目背景视频,用于展示在网站首页,建议使用webm格式'], ['type' => 'success', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'accept' => 'video/*', + 'multiple' => 1 + ] + ] + ] +]; diff --git a/fastmovie-admin/plugin/control/config/settings/state.php b/fastmovie-admin/plugin/control/config/settings/state.php new file mode 100644 index 0000000..9b1bbc0 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/settings/state.php @@ -0,0 +1,24 @@ + '状态', + 'field' => 'state', + 'value' => 0, + 'component' => 'radio', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '是否开启内置Control'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'options' => [ + ['label' => '关闭', 'value' => 0], + ['label' => '开启', 'value' => 1] + ] + ] + ], +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/config/settings/wechat_miniproject.php b/fastmovie-admin/plugin/control/config/settings/wechat_miniproject.php new file mode 100644 index 0000000..e5616ac --- /dev/null +++ b/fastmovie-admin/plugin/control/config/settings/wechat_miniproject.php @@ -0,0 +1,138 @@ + '开关', + 'field' => 'state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '是否开启微信小程序'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [] + ] + ], + [ + 'title' => 'AppID', + 'field' => 'app_id', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序AppID(小程序ID),'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppID' + ] + ] + ], + [ + 'title' => 'AppSecret', + 'field' => 'app_secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序AppSecret(小程序密钥) ,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppSecret' + ] + ] + ], + [ + 'title' => '小程序代码上传密钥', + 'field' => 'code_upload_key', + 'value' => '', + 'component' => 'bundle', + 'extra' => [ + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序代码上传密钥 ,'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'view' => 'file' + ] + ] + ], + [ + 'title' => 'request合法域名', + 'field' => 'request_domain', + 'value' => '{DOMAIN}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序request合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-request合法域名'], ['type' => 'warning', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入request合法域名' + ] + ] + ], + [ + 'title' => 'uploadFile合法域名', + 'field' => 'upload_file_domain', + 'value' => '{DOMAIN}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序uploadFile合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-uploadFile合法域名'], ['type' => 'warning', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入uploadFile合法域名' + ] + ] + ], + [ + 'title' => 'downloadFile合法域名', + 'field' => 'download_file_domain', + 'value' => '{DOMAIN}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信小程序downloadFile合法域名,请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信小程序-开发-开发管理-服务器域名-配置服务器域名-downloadFile合法域名,'], ['type' => 'warning', 'size' => 'small']) + ->add('text', ['default' => '注:如若该域名与本站系统-上传配置-中储存域名不一致时,以上传配置中使用的域名为准'], ['type' => 'danger', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入downloadFile合法域名' + ] + ] + ], +]; diff --git a/fastmovie-admin/plugin/control/config/settings/wechat_official_account.php b/fastmovie-admin/plugin/control/config/settings/wechat_official_account.php new file mode 100644 index 0000000..41c7fe7 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/settings/wechat_official_account.php @@ -0,0 +1,142 @@ + '开关', + 'field' => 'state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '是否开启微信公众号'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + ] + ] + ], + [ + 'title' => 'AppID', + 'field' => 'app_id', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号开发者ID(AppID),'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppID' + ] + ] + ], + [ + 'title' => 'AppSecret', + 'field' => 'app_secret', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号开发者密码(AppSecret),'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '微信公众平台'], ['href' => 'https://mp.weixin.qq.com/', 'underline' => 'never', 'target' => '_blank', 'type' => 'primary', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入AppSecret' + ] + ] + ], + [ + 'title' => '接收公众号消息通知状态', + 'field' => 'message_state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '是否开启接收公众号消息通知'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + ] + ] + ], + [ + 'title' => '服务器地址', + 'field' => 'url', + 'value' => '{DOMAIN}/app/control/WechatOfficialAccount/message/{CHANNELS_UID}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true], + ['message_state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号服务器地址(URL),请将此URL填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-服务器地址(URL)'], ['type' => 'warning', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '请输入服务器地址' + ] + ] + ], + [ + 'title' => '令牌', + 'field' => 'token', + 'value' => Str::random(32), + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true], + ['message_state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号令牌(Token),请将此Token填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-令牌(Token)'], ['type' => 'warning', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入令牌' + ] + ] + ], + [ + 'title' => '消息加解密密钥', + 'field' => 'aes_key', + 'value' => Str::random(43), + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true], + ['message_state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '微信公众号消息加解密密钥(AESKey),请将此AESKey填入:'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => '微信公众号-设置与开发-基本配置-服务器配置-消息加解密方式(AESKey)'], ['type' => 'warning', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '请输入消息加解密密钥' + ] + ] + ] +]; diff --git a/fastmovie-admin/plugin/control/config/settings/yidevs.php b/fastmovie-admin/plugin/control/config/settings/yidevs.php new file mode 100644 index 0000000..f188bab --- /dev/null +++ b/fastmovie-admin/plugin/control/config/settings/yidevs.php @@ -0,0 +1,25 @@ + '壹定开放平台密钥', + 'field' => 'token', + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '壹定开放平台密钥,用于获取壹定开放平台数据。获取方式:在壹定开放平台->API服务->'], ['type' => 'info', 'size' => 'small']) + ->add('link', ['default' => '密钥'], ['href'=>'https://api.yidevs.com/?icode=V7hN2xiudv5qeISzYiklLFFTMElweG44dmwzTEp4K3Mwa3ZGelE9PQ%3D%3D','type' => 'primary', 'size' => 'small', 'target' => '_blank', 'underline' => 'never']) + ->builder() + ], + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入壹定开放平台密钥' + ] + ] + ], +]; diff --git a/fastmovie-admin/plugin/control/config/translation.php b/fastmovie-admin/plugin/control/config/translation.php new file mode 100644 index 0000000..bc77260 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/control/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/config/view.php b/fastmovie-admin/plugin/control/config/view.php new file mode 100644 index 0000000..fe78682 --- /dev/null +++ b/fastmovie-admin/plugin/control/config/view.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +// use support\view\ThinkPHP; +use support\ThinkPHP; + +return [ + 'handler' => ThinkPHP::class, +]; diff --git a/fastmovie-admin/plugin/control/control.json b/fastmovie-admin/plugin/control/control.json new file mode 100644 index 0000000..18c5655 --- /dev/null +++ b/fastmovie-admin/plugin/control/control.json @@ -0,0 +1,235 @@ +[ + { + "title": "首页", + "icon": "House", + "path": "/app/control/control", + "sort": 0, + "children": [ + { + "title": "控制台", + "icon": "ElementPlus", + "path": "/app/control/control/Index/control", + "component": "databoard" + } + ] + }, + { + "title": "Systems", + "icon": "Setting", + "path": "/app/control/control/System", + "children": [ + { + "title": "Basic Settings", + "icon": "Setting", + "path": "/app/control/control/System/basic", + "component": "form" + }, + { + "title": "壹定开放平台", + "icon": "Setting", + "path": "/app/control/control/System/yidevs", + "component": "form" + }, + { + "title": "Upload Settings", + "icon": "UploadFilled", + "path": "/app/control/control/System/upload", + "component": "form" + }, + { + "title": "SMS Settings", + "icon": "Message", + "path": "/app/control/control/System/sms", + "component": "form" + }, + { + "title": "域名管理", + "icon": "Link", + "path": "/app/control/control/Domain/index", + "component": "table", + "children": [ + { + "title": "绑定域名", + "path": "/app/control/control/Domain/create", + "component": "form", + "show": false + }, + { + "title": "编辑域名", + "path": "/app/control/control/Domain/update", + "component": "form", + "show": false + } + ] + }, + { + "title": "Payment Management", + "icon": "Wallet", + "path": "/app/control/control/Payment/group", + "children": [ + { + "title": "Payment Settings", + "path": "/app/control/control/Payment/index", + "component": "payment-form" + }, + { + "title": "Payment Template", + "path": "/app/control/control/PaymentTemplate/index", + "component": "table", + "children": [ + { + "title": "Create Payment Template", + "path": "/app/control/control/PaymentTemplate/create", + "show": false, + "component": "form" + }, + { + "title": "Edit Payment Template", + "path": "/app/control/control/PaymentTemplate/update", + "show": false, + "component": "form" + }, + { + "title": "Delete Payment Template", + "path": "/app/control/control/PaymentTemplate/delete", + "show": false, + "component": "" + } + ] + } + ] + }, + { + "title": "Platform Management", + "icon": "Monitor", + "path": "/app/control/control/Platform/index", + "children": [ + { + "title": "微信", + "path": "/app/control/control/Platform/wechat", + "children": [ + { + "title": "WeChat Official Account", + "path": "/app/control/control/Platform/wechat_official_account", + "component": "form" + } + ] + } + ] + }, + { + "title": "权限管理", + "icon": "Setting", + "path": "/app/control/control/Permission", + "component": "table", + "children": [ + { + "title": "用户列表", + "path": "/app/control/control/User/index", + "component": "table", + "children": [ + { + "title": "创建用户", + "path": "/app/control/control/User/create", + "component": "form", + "show": false + }, + { + "title": "编辑用户", + "path": "/app/control/control/User/updateUser", + "component": "form", + "show": false + }, + { + "title": "删除用户", + "path": "/app/control/control/User/delete", + "component": "form", + "show": false + } + ] + }, + { + "title": "角色管理", + "path": "/app/control/control/Role/index", + "component": "table", + "children": [ + { + "title": "创建角色", + "path": "/app/control/control/Role/create", + "component": "form", + "show": false + }, + { + "title": "编辑角色", + "path": "/app/control/control/Role/update", + "component": "form", + "show": false + }, + { + "title": "删除角色", + "path": "/app/control/control/Role/delete", + "component": "form", + "show": false + } + ] + } + ] + } + ] + }, + { + "title": "公共权限", + "path": "/app/control/control/Public", + "show": false, + "children": [ + { + "title": "更新用户信息", + "path": "/app/control/control/User/update", + "show": false, + "component": "form" + }, + { + "title": "获取自己的信息", + "show": false, + "path": "/app/control/control/User/getInfo", + "component": "" + }, + { + "title": "退出登录", + "show": false, + "path": "/app/control/control/Public/outLogin", + "component": "" + }, + { + "title": "获取上传分类", + "path": "/app/control/control/Uploads/getUploadClassify", + "show": false, + "component": "" + }, + { + "title": "上传文件", + "path": "/app/control/control/Uploads/upload", + "show": false, + "component": "" + }, + { + "title": "删除上传文件", + "path": "/app/control/control/Uploads/deleteUploads", + "show": false, + "component": "" + }, + { + "title": "编辑上传文件", + "path": "/app/control/control/Uploads/updateUploads", + "show": false, + "component": "" + }, + { + "title": "获取上传文件列表", + "path": "/app/control/control/Uploads/getList", + "show": false, + "component": "" + } + ] + } +] \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/event/WechatOfficialAccount.php b/fastmovie-admin/plugin/control/event/WechatOfficialAccount.php new file mode 100644 index 0000000..923c003 --- /dev/null +++ b/fastmovie-admin/plugin/control/event/WechatOfficialAccount.php @@ -0,0 +1,69 @@ + $data['FromUserName']])->find(); + if ($UserWechat) { + if ($UserWechat->uid) { + $User = PluginChannelsUser::where('id', $UserWechat->uid)->find(); + Redis::set($data['EventKey'] . '_callback', $User->id, 'EX', 60); + Db::commit(); + Event::emit(EventName::USER_LOGIN['value'], $User); + return $this->replyText($data, '欢迎登录:' . $User->nickname); + } + $User = new PluginChannelsUser(); + if ($UserWechat->nickname) { + $User->nickname = $UserWechat->nickname; + } + if ($UserWechat->headimg) { + try { + $User->headimg = Uploads::download($UserWechat->headimg); + } catch (\Throwable $th) { + } + } + if (isset($data['params']) && isset($data['params']['puid'])) { + $User->puid = $data['params']['puid']; + } + $User->activation_time = date('Y-m-d H:i:s'); + $User->save(); + } else { + $User = new PluginChannelsUser(); + if (isset($data['params']) && isset($data['params']['puid'])) { + $User->puid = $data['params']['puid']; + } + $User->activation_time = date('Y-m-d H:i:s'); + $User->save(); + $UserWechat = new PluginChannelsWechat(); + $UserWechat->openid = $data['FromUserName']; + } + $UserWechat->subscribe = 1; + $UserWechat->uid = $User->id; + $UserWechat->save(); + Redis::set($data['EventKey'] . '_callback', $User->id, 'EX', 60); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('微信扫码登录失败:' . $th->getMessage(), $th->getTrace()); + return $this->replyText($data, '登录失败'); + } + // Event::emit(EventName::USER_REGISTER['value'], $User); + return $this->replyText($data, '注册成功'); + } +} diff --git a/fastmovie-admin/plugin/control/expose/api/Control.php b/fastmovie-admin/plugin/control/expose/api/Control.php new file mode 100644 index 0000000..a71be4c --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/api/Control.php @@ -0,0 +1,56 @@ +path . '/../control.json')) { + return json_decode(file_get_contents($menu_file), true); + } + return false; + } + public function getPermissions(){ + $menus = $this->getMenus(); + $permissions = []; + if($menus){ + $this->getPath($menus,$permissions); + } + return $permissions; + } + public function getPath($arr,&$data) + { + if(isset($arr['path'])){ + $data[] = $arr['path']; + if(isset($arr['children'])){ + $this->getPath($arr['children'],$data); + } + }else{ + foreach ($arr as $key => $value) { + if (isset($value['path'])) { + $data[] = $value['path']; + } + if (isset($value['children'])) { + $this->getPath($value['children'],$data); + } + } + } + } + public function getPlugin() + { + return $this->plugin; + } +} diff --git a/fastmovie-admin/plugin/control/expose/helper/Control.php b/fastmovie-admin/plugin/control/expose/helper/Control.php new file mode 100644 index 0000000..b69e7a5 --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/helper/Control.php @@ -0,0 +1,46 @@ +lang) { + $lang = $request->lang; + } + $data = $Install->getMenus(); + if ($lang) { + $this->translateChildren($data, $request->app, $lang); + } + foreach (glob(base_path('plugin/*')) as $path) { + $plugin_name = basename($path); + if ($plugin_name == 'control') { + continue; + } + $class = 'plugin\\' . $plugin_name . '\\api\\Control'; + if (!class_exists($class)) { + continue; + } + $plugin = new $class; + $menus = $plugin->getMenus(); + if ($menus) { + if ($lang && $plugin->getPlugin()) { + $request->plugin = $plugin_name; + $this->translateChildren($menus, $request->app, $lang); + } + $data[] = $menus; + } + } + $this->builder($data); + } +} diff --git a/fastmovie-admin/plugin/control/expose/helper/Uploads.php b/fastmovie-admin/plugin/control/expose/helper/Uploads.php new file mode 100644 index 0000000..05e621d --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/helper/Uploads.php @@ -0,0 +1,382 @@ + $path, 'channels_uid' => $channels_uid])->find(); + if (!$model) { + return $path; + } + $config = new Config('filesystem', '', $channels_uid); + $Storage = new Storage($config->toArray()); + return $Storage->adapter($model->channels)->url($model->path); + } + /** + * 获取文件在本地存储的完整路径 + * @param int $channels_uid 渠道用户ID + * @param string|array|null $path 文件路径 + * @return string|array + */ + public static function local(int $channels_uid, string|array|null $path) + { + if (empty($path)) { + return $path; + } + if (is_array($path)) { + $data = []; + foreach ($path as $value) { + $data[] = self::local($channels_uid, $value); + } + return $data; + } + if (strpos($path, '[') === 0) { + $key = substr($path, 1, strpos($path, ']') - 1); + $model = new \stdClass; + $model->channels = $key; + $model->path = substr($path, strpos($path, ']') + 1); + } else { + $model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find(); + if (!$model) { + return $path; + } + } + $config = new Config('filesystem', '', $channels_uid); + $filesystem = FilesystemFactory::get($model->channels, $config->toArray()); + $has = $filesystem->has($model->path); + if ($has) { + return self::downloadTemp(self::url($channels_uid, $model->path)); + } + return $path; + } + /** + * 获取文件路径 + * @param string|array|null $url URL地址 + * @return string|array + */ + public static function path(int $channels_uid, string|array|null $url) + { + if (empty($url)) { + return ''; + } + if (is_array($url)) { + $data = []; + if (count($url) === 1) { + return self::path($channels_uid, current($url)); + } + foreach ($url as $value) { + $data[] = self::path($channels_uid, $value); + } + return $data; + } else { + if (filter_var($url, FILTER_SANITIZE_URL) === false) { + throw new Exception('URL地址不合法'); + } + $parseUrl = parse_url($url); + return ltrim($parseUrl['path'], '/'); + } + } + public static function getClassify(int $channels_uid, string $dir_name, string $title, $channels = null) + { + if (!$channels) { + $config = new Config('filesystem', '', $channels_uid); + $channels = $config->default; + } + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels, 'channels_uid' => $channels_uid])->find(); + if (!$UploadsClassify) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = $title; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = $channels; + $UploadsClassify->channels_uid = $channels_uid; + $UploadsClassify->sort = 0; + $UploadsClassify->is_system = 1; + $UploadsClassify->save(); + } + return $UploadsClassify; + } + /** + * 保存文件 + * @param int $channels_uid 渠道用户ID + * @param string $path 文件路径 + * @param string $channels 文件存储通道 + * @param string $dir_name 文件存储目录 + * @param string $title 文件分类标题 + * @return array + */ + public static function save(int $channels_uid, string $path, $channels = null, $dir_name = 'uploads/save', $title = '本地保存') + { + $config = new Config('filesystem', '', $channels_uid); + if (!$channels) { + $channels = $config->default; + } + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels' => $channels, 'channels_uid' => $channels_uid])->find(); + if (!$UploadsClassify) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = $title; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = $channels; + $UploadsClassify->channels_uid = $channels_uid; + $UploadsClassify->sort = 0; + $UploadsClassify->is_system = 1; + $UploadsClassify->save(); + } + $date_path = date('Ymd'); + $originName = basename($path); + //单文件上传 + $file = new UploadFile($path, $originName, mime_content_type($path), filesize($path)); + $Storage = new Storage($config->toArray()); + $result = $Storage->adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + $Uploads = new ModelUploads; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->channels_uid = $channels_uid; + $Uploads->save(); + return [ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]; + } + /** + * 远程下载文件 + * @param string $url 文件URL + * @param string $channels 文件存储通道 + * @return mixed + */ + public static function download(int $channels_uid, string $url, UploadsClassify $UploadsClassify) + { + $dir_name = $UploadsClassify->dir_name; + $config = new Config('filesystem', '', $channels_uid); + $channels = $UploadsClassify->channels; + $date_path = date('Ymd'); + $client = new Client([ + 'timeout' => 600, + 'verify' => false, + 'proxy' => false + ]); + $response = $client->get($url); + $body = $response->getBody(); + $file = $body->getContents(); + $urlPath = parse_url($url, PHP_URL_PATH); + $ext = pathinfo($urlPath, PATHINFO_EXTENSION); + $fileName = uniqid() . '.' . $ext; + $temp = tempnam(sys_get_temp_dir(), '') . $fileName; + file_put_contents($temp, $file); + $file = new UploadFile($temp, $temp, $response->getHeaderLine('Content-Type'), 0); + $Storage = new Storage($config->toArray()); + $result = $Storage->adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + \unlink($temp); + $Uploads = new ModelUploads; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->channels_uid = $channels_uid; + $Uploads->save(); + return $result; + } + /** + * 下载文件到临时文件 + * @param string $url 文件URL + * @return string 临时文件路径 + */ + public static function downloadTemp(string $url, ?string $fileName = null) + { + if (!$fileName) { + $fileName = uniqid(); + } + $urlPath = parse_url($url, PHP_URL_PATH); + $ext = pathinfo($urlPath, PATHINFO_EXTENSION); + $temp = runtime_path('temp/' . $fileName . '.' . $ext); + if (file_exists($temp)) { + return $temp; + } + $client = new Client([ + 'proxy' => null, + 'verify' => false, + 'timeout' => 600 + ]); + $response = $client->get($url); + $body = $response->getBody(); + $file = $body->getContents(); + file_put_contents($temp, $file); + return $temp; + } + /** + * 上传文件 + * @param string $path 文件路径 + * @param string $channels 文件存储通道 + * @param string $dir_name 文件存储目录 + * @return array + */ + public static function upload(int $channels_uid, string|UploadFile $path, $channels = null, $dir_name = 'uploads/save') + { + $date_path = date('Ymd'); + //单文件上传 + if (is_string($path)) { + $originName = basename($path); + $file = new UploadFile($path, $originName, mime_content_type($path), filesize($path)); + } else { + $originName = $path->getUploadName(); + $file = $path; + } + $config = new Config('filesystem', '', $channels_uid); + if (!$channels) { + $channels = $config->default; + } + $Storage = new Storage($config->toArray()); + $result = $Storage->adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + $Uploads = new ModelUploads; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->channels_uid = $channels_uid; + $Uploads->save(); + return [ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]; + } + /** + * 删除文件 + * @param int $channels_uid 渠道用户ID + * @param string $path 文件路径 + * @return bool + */ + public static function delete(int $channels_uid, string $path) + { + $model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find(); + if (!$model) { + return true; + } + $config = new Config('filesystem', '', $channels_uid); + $filesystem = FilesystemFactory::get($model->channels, $config->toArray()); + $filesystem->delete($model->path); + $model->delete(); + return true; + } + /** + * 重命名文件 + * @param int $channels_uid 渠道用户ID + * @param string $path 文件路径 + * @param string $newName 新文件名 + * @return bool + */ + public static function rename(int $channels_uid, string $path, string $newName) + { + $model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find(); + if (!$model) { + return false; + } + $config = new Config('filesystem', '', $channels_uid); + $filesystem = FilesystemFactory::get($model->channels, $config->toArray()); + $filesystem->move($model->path, $newName); + $model->path = $newName; + $model->save(); + return true; + } + /** + * 判断文件是否存在 + * @param int $channels_uid 渠道用户ID + * @param string $path 文件路径 + * @return bool + */ + public static function has(int $channels_uid, string $path) + { + $model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find(); + if (!$model) { + return false; + } + $config = new Config('filesystem', '', $channels_uid); + $filesystem = FilesystemFactory::get($model->channels, $config->toArray()); + return $filesystem->has($model->path); + } + /** + * 复制文件 + * @param int $channels_uid 渠道用户ID + * @param string $path 文件路径 + * @param string $newName 新文件名 + * @return bool + */ + public static function copy(int $channels_uid, string $path, string $newName) + { + $model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find(); + if (!$model) { + return false; + } + $config = new Config('filesystem', '', $channels_uid); + $filesystem = FilesystemFactory::get($model->channels, $config->toArray()); + $filesystem->copy($model->path, $newName); + $model = new ModelUploads; + $model->classify_id = $model->classify_id; + $model->filename = $model->filename; + $model->path = $newName; + $model->ext = $model->ext; + $model->mime = $model->mime; + $model->size = $model->size; + $model->path = $newName; + $model->save(); + return true; + } + /** + * 获取文件列表 + * @param int $channels_uid 渠道用户ID + * @param string $path 文件路径 + * @param bool $recursive 是否递归 + * @return array + */ + public static function listContents(int $channels_uid, string $path, $recursive = false) + { + $model = ModelUploads::where(['path' => $path, 'channels_uid' => $channels_uid])->find(); + if (!$model) { + return false; + } + $config = new Config('filesystem', '', $channels_uid); + $filesystem = FilesystemFactory::get($model->channels, $config->toArray()); + return $filesystem->listContents($path, $recursive); + } +} diff --git a/fastmovie-admin/plugin/control/expose/helper/Vcode.php b/fastmovie-admin/plugin/control/expose/helper/Vcode.php new file mode 100644 index 0000000..e453f7d --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/helper/Vcode.php @@ -0,0 +1,80 @@ +sessionId($token); + } + $vcodeData = $request->session()->get('vcode:' . $scene . ':' . $username); + if (empty($vcodeData)) { + throw new \Exception('验证码不存在'); + } + if ($vcodeData['vcode'] != $vcode) { + throw new \Exception('验证码错误'); + } + if ($vcodeData['expire'] < time()) { + throw new \Exception('验证码已过期'); + } + return true; + } + public static function send($username, $scene, $token = null) + { + $request = request(); + if (!empty($token)) { + $request->sessionId($token); + } + $vcode = Str::random(4, 1); + if (filter_var($username, FILTER_VALIDATE_EMAIL)) { + $Email = new Email; + $Email->toemail = $username; + $Email->setTemplate(EmailVcode::class); + $Email->setData(['vcode' => $vcode]); + $Email->send(); + } else { + $request->session()->set('vcode:' . $scene . ':' . $username, [ + 'vcode' => $vcode, + 'expire' => time() + 60 * 5 + ]); + $config = new ConfigGroup('sms', 'control'); + foreach ($config['channels'] as $channel) { + $item = $config[$channel]; + if ($item['enable'] == State::NO['value']) { + continue; + } + try { + if (empty($item['vcode_template_' . $scene])) { + continue; + } + $SmsVcode = new SmsVcode(); + $SmsVcode->channel = $channel; + $SmsVcode->config = $item; + $SmsVcode->template_code = $item['vcode_template_' . $scene]; + $Sms = new Sms; + $Sms->mobile = $username; + $Sms->setTemplate($SmsVcode); + $Sms->setData(['code' => $vcode]); + $Sms->send(); + return true; + } catch (\Throwable $th) { + Log::error("发送短信失败:" . $th->getMessage(), $th->getTrace()); + continue; + } + } + throw new \Exception('发送失败'); + } + } +} diff --git a/fastmovie-admin/plugin/control/expose/helper/Wechat.php b/fastmovie-admin/plugin/control/expose/helper/Wechat.php new file mode 100644 index 0000000..1f2831a --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/helper/Wechat.php @@ -0,0 +1,129 @@ +channels_uid); + if ($access_token) { + return $access_token; + } + if (!$config['state']) { + throw new \Exception('请先开启公众号'); + } + $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$config['app_id']}&secret={$config['app_secret']}"; + try { + $client = new Client(); + $response = $client->get($url); + $data = json_decode($response->getBody()->getContents(), true); + if (isset($data['errcode'])) { + throw new \Exception($data['errmsg']); + } + Redis::set('wechat_official_account_access_token_' . $request->channels_uid, $data['access_token'], 'EX', $data['expires_in']); + return $data['access_token']; + } catch (\Throwable $th) { + throw $th; + } + } + /** + * 创建公众号二维码 + * + * @param string $action_name QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值 + * @param int $expire 二维码有效时间,以秒为单位。最大不超过2592000(即30天) + * @param array $eventData 回调事件数据 + * @param Class $eventData[0] 回调事件类 + * @param string $eventData[1] 回调事件方法 + * @param array $eventData[2] 回调事件参数,会在回调事件方法中$data.params原样传入 + * @return array $data + * @return string $data['url'] 二维码图片地址 + * @return string $data['id'] 二维码id,在回调事件中$data['EventKey'] + * @return int $data['expire_seconds'] 二维码有效时间 + * @throws \Exception + */ + public static function createQrCode($action_name, $expire, $eventData) + { + $request = request(); + $id = $request->sessionId(); + if (count($eventData) < 2) { + throw new \Exception('回调事件数据不完整'); + } + $access_token = self::getAccessToken($request); + $url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $access_token; + $client = new Client(); + $data = [ + 'expire_seconds' => $expire, + 'action_name' => $action_name, + 'action_info' => [ + 'scene' => [] + ] + ]; + if (in_array($action_name, [self::QR_SCENE, self::QR_LIMIT_SCENE])) { + $data['action_info']['scene'] = ['scene_id' => $id]; + } else { + $data['action_info']['scene'] = ['scene_str' => $id]; + } + Redis::set($id, json_encode($eventData, JSON_UNESCAPED_UNICODE), 'EX', $expire + 60); + $response = $client->post($url, [ + 'json' => $data + ]); + $res = json_decode($response->getBody()->getContents(), true); + if (isset($res['errcode'])) { + throw new \Exception($res['errmsg']); + } + $res['id'] = $id; + return $res; + } + /** + * 发送模板消息 + * + * @param mixed $params 模板消息参数 + * @return string + */ + public static function sendTemplate($params) + { + $request = request(); + $access_token = self::getAccessToken($request); + $url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $access_token; + $client = new Client([ + 'content_type' => 'application/json', + ]); + $response = $client->post($url, ['body' => json_encode($params, JSON_UNESCAPED_UNICODE)]); + $data = json_decode($response->getBody()->getContents(), true); + if (empty($data['msgid'])) { + throw new \Exception("[{$data['errcode']}]" . $data['errmsg']); + } + return $data['msgid']; + } +} diff --git a/fastmovie-admin/plugin/control/expose/middleware/ControlAuth.php b/fastmovie-admin/plugin/control/expose/middleware/ControlAuth.php new file mode 100644 index 0000000..ce01f11 --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/middleware/ControlAuth.php @@ -0,0 +1,127 @@ +method() == 'OPTIONS') { + return response('', 204); + } + // 鉴权检测 + try { + $this->Icode($request); + $this->Authorization($request); + $response = $next($request); + } catch (\Throwable $th) { + if ($request->expectsJson()) { + $response = $this->exception($th); + } else { + if (config('app.debug')) { + Log::error($th->getTraceAsString()); + throw $th; + } else { + $response = response($th->getMessage(), 500); + } + } + } + return $response; + } + public function Icode(Request $request) + { + $icode = $request->header('x-icode'); + if (!$icode) { + return true; + } + $request->icode = $icode; + } + + /** + * 业务逻辑 + * @author 贵州猿创科技有限公司 + * @Email 416716328@qq.com + * @DateTime 2023-03-11 + */ + public function Authorization(Request $request) + { + # 无控制器地址 + if (!$request->controller) { + return true; + } + # 获取控制器鉴权信息 + $controller = new \ReflectionClass($request->controller); + $properties = $controller->getDefaultProperties(); + # 无需登录方法 + $notNeedLogin = $properties['notNeedLogin'] ?? []; + # 是否强制登录 + $isForceLogin = true; + if (in_array($request->action, $notNeedLogin)) { + $isForceLogin = false; + } + try { + # 令牌验证 + $token = $request->header('Authorization'); + if (!$token) { + throw new Exception('请先登录', ResponseCode::NEED_LOGIN); + } + $user = Auth::setPrefix('CONTROL')->decrypt($token); + if (!$user) { + throw new TokenExpireException(); + } + $request->uid = $user['uid']; + $request->channels_uid = $user['channels_uid']; + $request->token = $token; + } catch (TokenExpireException $e) { + if ($isForceLogin) { + throw new Exception('登录已过期,请重新登录', ResponseCode::NEED_LOGIN); + } + } catch (LockException $e) { + if ($isForceLogin) { + throw new Exception($e->getMessage(), ResponseCode::LOCK); + } + } catch (\Throwable $th) { + if ($isForceLogin) { + throw new \Exception($th->getMessage(), ResponseCode::NEED_LOGIN); + } + } + if (!$isForceLogin) { + return true; + } + if ($user['is_system']) { + return true; + } + if (empty($user['permissions'])) { + throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION); + } + $plugin = $request->plugin; + // plugin\api\app\admin\controller\IndexController,只取Index + $controllers = explode('\\', $request->controller); + $controller = str_replace('Controller', '', end($controllers)); + $action = str_replace(['GetTable', 'GetGrid'], ['', ''], $request->action); + $rule = $controller . '/' . $action; + if ($plugin) { + $app = $request->app; + $rule = "/app/{$plugin}/{$app}/{$rule}"; + } + if (!in_array($rule, $user['permissions'])) { + throw new Exception('您没有权限访问', ResponseCode::NO_PERMISSION); + } + } +} diff --git a/fastmovie-admin/plugin/control/expose/middleware/DomainMapping.php b/fastmovie-admin/plugin/control/expose/middleware/DomainMapping.php new file mode 100644 index 0000000..0b24f63 --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/middleware/DomainMapping.php @@ -0,0 +1,80 @@ +header('origin')) { + $domain = parse_url($origin, PHP_URL_HOST); + } + // 2. 再尝试 Referer + elseif ($referer = $request->header('referer')) { + $domain = parse_url($referer, PHP_URL_HOST); + } elseif ($request->host()) { + $domain = $request->host(); + } else { + return response('Domain not found', 401); + } + $corsHeaders = [ + 'Access-Control-Allow-Origin' => $origin, + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS, PATCH', + 'Access-Control-Allow-Headers' => strtolower(implode(',', $headersKeys)), + ]; + // 1. LRU 进程缓存 + $userId = self::$cache->get($domain); + if ($userId) { + $request->channels_uid = $userId; + $response = $next($request); + # 允许跨域 + $response->withHeaders($corsHeaders); + return $response; + } + + // 2. Redis Hash + $userId = Redis::hGet('domain_map', $domain); + if ($userId) { + self::$cache->set($domain, $userId); + $request->channels_uid = $userId; + $response = $next($request); + # 允许跨域 + $response->withHeaders($corsHeaders); + return $response; + } + + // 3. MySQL 兜底 + $PluginChannelsDomain = PluginChannelsDomain::where('domain', $domain)->where('state', State::YES['value'])->find(); + + if ($PluginChannelsDomain) { + Redis::hSet('domain_map', $domain, $PluginChannelsDomain->channels_uid); + self::$cache->set($domain, $PluginChannelsDomain->channels_uid); + $request->channels_uid = $PluginChannelsDomain->channels_uid; + $response = $next($request); + # 允许跨域 + $response->withHeaders($corsHeaders); + return $response; + } + return response('Domain not found', 401); + } +} diff --git a/fastmovie-admin/plugin/control/expose/trait/Uploads.php b/fastmovie-admin/plugin/control/expose/trait/Uploads.php new file mode 100644 index 0000000..3a7515e --- /dev/null +++ b/fastmovie-admin/plugin/control/expose/trait/Uploads.php @@ -0,0 +1,293 @@ +get('limit', 10); + $dir_name = $request->get('dir_name', 'all'); + $where = []; + $where[] = ['u.channels_uid', '=', $this->channels_uid]; + if ($dir_name != 'all') { + $where[] = ['c.dir_name', '=', $dir_name]; + } + $accept = $request->get('accept'); + if ($accept) { + # 判断是否包含*号 + if (strpos($accept, '*') !== false) { + $accept = str_replace('*', '', $accept); + $where[] = ['u.mime', 'like', "%{$accept}%"]; + } elseif (strpos($accept, '.') !== false) { + $accept = str_replace('.', '', $accept); + if (strpos($accept, ',') !== false) { + $accept = explode(',', $accept); + $where[] = ['u.ext', 'in', $accept]; + } else { + $where[] = ['u.ext', '=', $accept]; + } + } else { + $where[] = ['u.mime', 'in', explode(',', $accept)]; + } + } + if ($this->uid) { + $where[] = ['u.uid', '=', $this->uid]; + } + if ($this->admin_uid) { + $where[] = ['u.admin_uid', '=', $this->admin_uid]; + } + $ModelUploads = ModelUploads::alias('u')->where($where) + ->join('uploads_classify c', 'u.classify_id=c.id') + ->field('u.*,c.title as classify_title') + ->order('u.id desc') + ->paginate($limit)->each(function ($item) { + $item->url = HelperUploads::url($item->channels_uid, $item->path); + return $item; + }); + return $this->resData($ModelUploads); + } + public function upload(Request $request) + { + $dir_name = $request->post('dir_name'); + $dir_title = $request->post('dir_title'); + $config = new Config('filesystem', '', $this->channels_uid); + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find(); + if (!$UploadsClassify) { + $UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1, 'channels_uid' => $this->channels_uid])->find(); + if (!$UploadsClassify) { + if (empty($dir_title)) { + return $this->fail('分类不存在'); + } + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = $dir_title; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = $config->default; + $UploadsClassify->channels_uid = $this->channels_uid; + $UploadsClassify->save(); + } + } + $channels = $request->post('channels'); + if (!$channels) { + $channels = $UploadsClassify->channels; + } + $date_path = date('Ymd'); + //单文件上传 + $file = $request->file('file'); + try { + $Storage = new Storage($config->toArray()); + $result = $Storage->adapter($channels)->path($dir_name . '/' . $date_path)->upload($file); + } catch (\Throwable $th) { + return $this->exception($th); + } + try { + $Uploads = new ModelUploads; + $Uploads->uid = $this->uid; + $Uploads->admin_uid = $this->admin_uid; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->channels_uid = $this->channels_uid; + $Uploads->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->resData([ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]); + } + public function uploads(Request $request) + { + $dir_name = $request->post('dir_name'); + $dir_title = $request->post('dir_title'); + $config = new Config('filesystem', '', $this->channels_uid); + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find(); + if (!$UploadsClassify) { + $UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1, 'channels_uid' => $this->channels_uid])->find(); + if (!$UploadsClassify) { + if (empty($dir_title)) { + return $this->fail('分类不存在'); + } + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = $dir_title; + $UploadsClassify->dir_name = $dir_name; + $UploadsClassify->channels = $config->default; + $UploadsClassify->channels_uid = $this->channels_uid; + $UploadsClassify->save(); + } + } + $channels = $request->post('channels'); + if (!$channels) { + $channels = $UploadsClassify->channels; + } + $date_path = date('Ymd'); + $files = $request->file(); + try { + $Storage = new Storage($config->toArray()); + $results = $Storage->adapter($channels)->path($dir_name . '/' . $date_path)->uploads($files); + } catch (\Throwable $th) { + return $this->exception($th); + } + $resData = []; + try { + foreach ($results as $result) { + $Uploads = new ModelUploads; + $Uploads->uid = $this->uid; + $Uploads->admin_uid = $this->admin_uid; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $channels; + $Uploads->channels_uid = $this->channels_uid; + $Uploads->save(); + $resData[] = [ + 'id' => $Uploads->id, + 'url' => $result->file_url, + 'path' => $result->file_name, + 'mime' => $result->mime_type, + 'dir_name' => $dir_name + ]; + } + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->resData($resData); + } + public function deleteUploads(Request $request) + { + $ids = $request->post('ids'); + $data = ModelUploads::whereIn('id', $ids)->where('channels_uid', $this->channels_uid)->select(); + $config = new Config('filesystem', '', $this->channels_uid); + foreach ($data as $item) { + if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) { + continue; + } + try { + $filesystem = FilesystemFactory::get($item->channels, $config->toArray()); + $filesystem->delete($item->path); + } catch (\Throwable $th) { + Log::error('删除文件"' . $item->path . '"失败:' . $th->getMessage(), $th->getTrace()); + } + $item->delete(); + } + return $this->success('删除成功'); + } + public function updateUploads(Request $request) + { + $ids = $request->post('ids'); + $dir_name = $request->post('dir_name'); + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find(); + if (!$UploadsClassify) { + return $this->fail('分类不存在'); + } + $data = ModelUploads::whereIn('id', $ids)->where('channels_uid', $this->channels_uid)->select(); + foreach ($data as $item) { + if ($item->uid != $this->uid && $item->admin_uid != $this->admin_uid) { + continue; + } + $item->classify_id = $UploadsClassify->id; + $item->save(); + } + return $this->success('修改成功'); + } + public function getUploadClassify(Request $request) + { + $config = new Config('filesystem', '', $this->channels_uid); + if (!UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1, 'channels_uid' => $this->channels_uid])->count()) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->title = '默认分类'; + $UploadsClassify->dir_name = 'uploads/default'; + $UploadsClassify->channels = $config->default; + $UploadsClassify->channels_uid = $this->channels_uid; + $UploadsClassify->sort = 0; + $UploadsClassify->is_system = 1; + $UploadsClassify->save(); + } + $UploadsClassify = UploadsClassify::where(['channels_uid' => $this->channels_uid])->order('sort asc,id asc')->select()->toArray(); + $all = [ + 'title' => '全部', + 'dir_name' => 'all', + 'is_system' => 1, + ]; + array_unshift($UploadsClassify, $all); + return $this->resData($UploadsClassify); + } + public function saveClassify(Request $request) + { + $data = $request->post(); + if (empty($data['title'])) { + return $this->fail('标题不能为空'); + } + if (empty($data['channels'])) { + return $this->fail('请选择储存渠道'); + } + if (empty($data['dir_name'])) { + return $this->fail('目录名不能为空'); + } + $UploadsClassify = UploadsClassify::where(['dir_name' => $data['dir_name'], 'channels_uid' => $this->channels_uid])->find(); + if (!$UploadsClassify) { + $UploadsClassify = new UploadsClassify; + $UploadsClassify->dir_name = 'uploads/' . $data['dir_name']; + $UploadsClassify->channels_uid = $this->channels_uid; + } + $UploadsClassify->title = $data['title']; + $UploadsClassify->channels = $data['channels']; + if (!empty($data['sort'])) { + $UploadsClassify->sort = $data['sort']; + } + if ($UploadsClassify->save()) { + return $this->success('创建成功'); + } else { + return $this->fail('创建失败'); + } + } + public function deleteClassify(Request $request) + { + $dir_name = $request->post('dir_name'); + if (empty($dir_name)) { + return $this->fail('目录名不能为空'); + } + if ($dir_name == 'all') { + return $this->fail('全部目录不能删除'); + } + $UploadsClassify = UploadsClassify::where(['dir_name' => $dir_name, 'channels_uid' => $this->channels_uid])->find(); + if (!$UploadsClassify) { + return $this->fail('目录不存在'); + } + if ($UploadsClassify->is_system) { + return $this->fail('系统分类,无法删除'); + } + if (ModelUploads::where(['classify_id' => $UploadsClassify->id])->count() > 0) { + return $this->fail('目录分类下有文件不能删除'); + } + if ($UploadsClassify->delete()) { + return $this->success('删除成功'); + } else { + return $this->fail('删除失败'); + } + } +} diff --git a/fastmovie-admin/plugin/control/resource/translations/en/admin.php b/fastmovie-admin/plugin/control/resource/translations/en/admin.php new file mode 100644 index 0000000..56f91e9 --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/en/admin.php @@ -0,0 +1,9 @@ + 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'form_basic_title'=>'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/resource/translations/en/api.php b/fastmovie-admin/plugin/control/resource/translations/en/api.php new file mode 100644 index 0000000..45a7854 --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/en/api.php @@ -0,0 +1,6 @@ + 'Success', + 'fail' => 'Fail', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/resource/translations/en/control.php b/fastmovie-admin/plugin/control/resource/translations/en/control.php new file mode 100644 index 0000000..92d5a0f --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/en/control.php @@ -0,0 +1,89 @@ + 'Account Password Login', + 'Vcode Login' => 'SMS verification code Login', + 'Register Title'=>'Register %web_name% Account', + 'Qrcode Login'=>'Please use WeChat "Scan Code" to log in', + 'toolbar Lock'=>'Lock', + 'toolbar Search'=>'Search', + 'toolbar Notification'=>'Notification', + 'toolbar FullScreen'=>'FullScreen', + 'Logout Success'=>'Logout Success', + 'Captcha Incorrect'=>'Captcha Incorrect', + 'Scene Cannot Be Empty'=>'Scene Cannot Be Empty', + 'Vcode Send Success'=>'Vcode Send Success', + 'Unlock Success'=>'Unlock Success', + 'userDropdownMenu updateSelf'=>'User Center', + 'Login Link Text'=>'Already have an account? Login now', + 'Register Link Text'=>'No account yet? Click to register for free', + 'Login User Agreement Title'=>'《Login Agreement》', + 'Login User Agreement Label'=>'Click "Login" to represent that you have read and agree to', + 'Register User Agreement Title'=>'《Register Agreement》', + 'Register User Agreement Label'=>'Click "Register" to represent that you have read and agree to', + 'Control Not Open'=>'Control Not Open', + 'Dashboard' => 'Dashboard', + 'Console' => 'Console', + 'User' => 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'Update Status' => 'Update Status', + 'Functional' => 'Functional', + 'Administrator' => 'Administrator', + 'Staff List' => 'Staff List', + 'Create Administrator' => 'Create Administrator', + 'Edit Administrator' => 'Edit Administrator', + 'Delete Administrator' => 'Delete Administrator', + 'Move Role' => 'Move Role', + 'Role Management' => 'Role Management', + 'Role List' => 'Role List', + 'Create Role' => 'Create Role', + 'Edit Role' => 'Edit Role', + 'Delete Role' => 'Delete Role', + 'Systems' => 'Systems', + 'Basic Settings' => 'Basic Settings', + 'Control Settings' => 'Control Settings', + 'Upload Settings' => 'Upload Settings', + 'SMS Settings' => 'SMS Settings', + 'Payment Management' => 'Payment Management', + 'Payment Settings' => 'Payment Settings', + 'Payment Template' => 'Payment Template', + 'Create Payment Template' => 'Create Payment Template', + 'Edit Payment Template' => 'Edit Payment Template', + 'Delete Payment Template' => 'Delete Payment Template', + 'Platform Management' => 'Platform Management', + 'WeChat Mini Program' => 'WeChat Mini Program', + 'WeChat Official Account' => 'WeChat Official Account', + 'Alipay Mini Program' => 'Alipay Mini Program', + 'WeChat Open Platform' => 'WeChat Open Platform', + 'App Platform' => 'App Platform', + 'H5 Platform' => 'H5 Platform', + 'PC Platform' => 'PC Platform', + 'Public Permissions' => 'Public Permissions', + 'Edit Profile' => 'Edit Profile', + 'Get Profile' => 'Get Profile', + 'Logout' => 'Logout', + 'Get Upload Categories' => 'Get Upload Categories', + 'Edit Upload Category' => 'Edit Upload Category', + 'Delete Upload Category' => 'Delete Upload Category', + 'Upload File' => 'Upload File', + 'Delete Upload File' => 'Delete Upload File', + 'Edit Upload File' => 'Edit Upload File', + 'Get Upload File List' => 'Get Upload File List', + 'Login Title'=>'Admin Login', + 'toolbar Lock'=>'Lock', + 'toolbar Search'=>'Search', + 'toolbar Notification'=>'Notification', + 'toolbar FullScreen'=>'FullScreen', + 'toolbar House'=>'Home', + 'toolbar Control'=>'Control', + 'userDropdownMenu updateSelf'=>'Admin Info', + 'Logout Success'=>'Logout Success', + 'PIN Code Cannot Be Empty'=>'PIN Code Cannot Be Empty', + 'Please Enter 6 Digits PIN Code'=>'Please Enter 6 Digits PIN Code', + 'Lock Success'=>'Lock Success', + 'Unlock Success'=>'Unlock Success', + 'form_basic_title'=>'Basic', + 'Settings_basic_web_name'=>'Apps Name' +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/resource/translations/en/messages.php b/fastmovie-admin/plugin/control/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/control/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..15ae448 --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/zh_CN/admin.php @@ -0,0 +1,9 @@ + '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'form_basic_title'=>'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/resource/translations/zh_CN/api.php b/fastmovie-admin/plugin/control/resource/translations/zh_CN/api.php new file mode 100644 index 0000000..40bd25b --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/zh_CN/api.php @@ -0,0 +1,6 @@ + '成功', + 'fail' => '失败', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/resource/translations/zh_CN/control.php b/fastmovie-admin/plugin/control/resource/translations/zh_CN/control.php new file mode 100644 index 0000000..d781a99 --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/zh_CN/control.php @@ -0,0 +1,89 @@ + '账号密码登录', + 'Vcode Login' => '验证码登录', + 'Register Title' => '注册%web_name%', + 'Qrcode Login'=>'请使用微信“扫一扫”扫码登录', + 'toolbar Lock'=>'锁屏', + 'toolbar Search'=>'搜索菜单', + 'toolbar Notification'=>'查看通知', + 'toolbar FullScreen'=>'全屏/退出全屏', + 'Logout Success'=>'退出成功', + 'Captcha Incorrect'=>'验证码不正确', + 'Scene Cannot Be Empty'=>'场景不能为空', + 'Vcode Send Success'=>'验证码发送成功', + 'Unlock Success'=>'解锁成功', + 'userDropdownMenu updateSelf'=>'个人中心', + 'Login Link Text'=>'已有账号?立即登录', + 'Register Link Text'=>'还没有账号?点击免费注册', + 'Login User Agreement Title'=>'《登录协议》', + 'Login User Agreement Label'=>'点击"登录"即代表你已阅读并同意', + 'Register User Agreement Title'=>'《注册协议》', + 'Register User Agreement Label'=>'点击"注册"即代表你已阅读并同意', + 'Control Not Open'=>'控制台未开启', + 'Dashboard' => '首页', + 'Console' => '控制台', + 'User' => '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'Update Status' => '更新状态', + 'Functional' => '职能', + 'Administrator' => '管理员', + 'Staff List' => '人员列表', + 'Create Administrator' => '创建管理员', + 'Edit Administrator' => '编辑管理员', + 'Delete Administrator' => '删除管理员', + 'Move Role' => '移动角色', + 'Role Management' => '角色', + 'Role List' => '角色管理', + 'Create Role' => '创建角色', + 'Edit Role' => '编辑角色', + 'Delete Role' => '删除角色', + 'Systems' => '系统', + 'Basic Settings' => '基本配置', + 'Control Settings' => '控制台配置', + 'Upload Settings' => '上传配置', + 'SMS Settings' => '短信配置', + 'Payment Management' => '支付管理', + 'Payment Settings' => '支付配置', + 'Payment Template' => '支付模板', + 'Create Payment Template' => '新增支付模板', + 'Edit Payment Template' => '编辑支付模板', + 'Delete Payment Template' => '删除支付模板', + 'Platform Management' => '平台管理', + 'WeChat Mini Program' => '微信小程序', + 'WeChat Official Account' => '微信公众号', + 'Alipay Mini Program' => '支付宝小程序', + 'WeChat Open Platform' => '微信开放平台', + 'App Platform' => 'APP', + 'H5 Platform' => 'H5', + 'PC Platform' => 'PC', + 'Public Permissions' => '公共权限', + 'Edit Profile' => '修改自己的信息', + 'Get Profile' => '获取自己的信息', + 'Logout' => '退出登录', + 'Get Upload Categories' => '获取上传分类', + 'Edit Upload Category' => '编辑上传分类', + 'Delete Upload Category' => '删除上传分类', + 'Upload File' => '上传文件', + 'Delete Upload File' => '删除上传文件', + 'Edit Upload File' => '编辑上传文件', + 'Get Upload File List' => '获取上传文件列表', + 'Login Title'=>'管理员登录', + 'toolbar Lock'=>'锁屏', + 'toolbar Search'=>'搜索菜单', + 'toolbar Notification'=>'查看通知', + 'toolbar FullScreen'=>'全屏/退出全屏', + 'toolbar House'=>'网站首页', + 'toolbar Control'=>'网站前台控制台', + 'userDropdownMenu updateSelf'=>'管理员信息', + 'Logout Success'=>'退出成功', + 'PIN Code Cannot Be Empty'=>'PIN码不能为空', + 'Please Enter 6 Digits PIN Code'=>'请输入6位PIN码', + 'Lock Success'=>'锁定成功', + 'Unlock Success'=>'解锁成功', + 'form_basic_title'=>'基础信息', + 'Settings_basic_web_name'=>'应用名称' +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/control/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/control/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/utils/LRUCache.php b/fastmovie-admin/plugin/control/utils/LRUCache.php new file mode 100644 index 0000000..dc51f44 --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/LRUCache.php @@ -0,0 +1,43 @@ +capacity = $capacity; + } + + public function get($key) + { + if (!isset($this->cache[$key])) { + return null; + } + unset($this->order[$key]); + $this->order[$key] = true; + return $this->cache[$key]; + } + + public function set($key, $value) + { + if (isset($this->cache[$key])) { + $this->cache[$key] = $value; + unset($this->order[$key]); + $this->order[$key] = true; + return; + } + + if (count($this->cache) >= $this->capacity) { + $oldestKey = array_key_first($this->order); + unset($this->cache[$oldestKey], $this->order[$oldestKey]); + } + + $this->cache[$key] = $value; + $this->order[$key] = true; + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/Client.php b/fastmovie-admin/plugin/control/utils/yidevs/Client.php new file mode 100644 index 0000000..73828c4 --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/Client.php @@ -0,0 +1,97 @@ +domain = $domain; + } + } + public function setChannelsUid(int $channels_uid) + { + $this->channels_uid = $channels_uid; + $config = new Config('yidevs', 'control', $channels_uid); + if (!$config->token) { + throw new InvalidTokenException('YIDEVS_TOKEN is not set for channels_uid: ' . $channels_uid); + } + $this->token = $config->token; + } + public function client() + { + $this->HttpClient = new GuzzleHttpClient([ + 'base_uri' => $this->domain, + 'timeout' => 600, + 'verify' => false, + 'proxy' => false + ]); + } + public function request(string $method, string $url, array $options = []) + { + try { + $this->client(); + $response = $this->HttpClient->request($method, $url, array_merge([ + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $this->token, + ] + ], $options)); + $res = $response->getBody()->getContents(); + } catch (ClientException | RequestException $e) { + Log::error($e->getMessage() . PHP_EOL . $e->getTraceAsString()); + throw new \Exception($e->getMessage()); + } catch (\Throwable $th) { + Log::error($th->getMessage() . PHP_EOL . $th->getTraceAsString()); + throw new \Exception($th->getMessage()); + } + return $res; + } + public function stream(string $url, array $data = [], ?callable $stream = null) + { + return new StreamClient($this->token, $this->domain . '/' . $url, $data, $stream); + } + public function get(string $url, array $query = []) + { + $res = $this->request('GET', $url, ['query' => $query]); + $result = json_decode($res, true); + if (!isset($result['code'])) { + throw new InvalidResultException($res); + } + if ($result['code'] != 200) { + throw new \Exception($result['msg']); + } + return $result['data']; + } + public function post(string $url, array $data = [], ?callable $stream = null) + { + if ($stream) { + return new StreamClient($this->token, $this->domain . '/' . $url, $data, $stream); + } + $res = $this->request('POST', $url, ['json' => $data]); + $result = json_decode($res, true); + if (!isset($result['code'])) { + throw new InvalidResultException($res); + } + if ($result['code'] != 200) { + throw new \Exception($result['msg']); + } + return $result['data']; + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/Exception/HttpCurlException.php b/fastmovie-admin/plugin/control/utils/yidevs/Exception/HttpCurlException.php new file mode 100644 index 0000000..dfb60d5 --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/Exception/HttpCurlException.php @@ -0,0 +1,7 @@ +streamFunction = $streamFunction; + $data['stream'] = true; + $curl_info = [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 600, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_POSTFIELDS => json_encode($data), + CURLOPT_HTTPHEADER => [ + 'Content-Type:application/json', + 'Accept:application/json', + 'Authorization:Bearer ' . $token, + ], + CURLOPT_PROXY => false + ]; + + $curl_info[CURLOPT_WRITEFUNCTION] = [$this, 'streamWriteFunction']; + + $curl = curl_init(); + + curl_setopt_array($curl, $curl_info); + $response = curl_exec($curl); + // $info = curl_getinfo($curl); + $error = curl_error($curl); + curl_close($curl); + + if (! $response) { + throw new HttpCurlException($error); + } + } catch (\Throwable $th) { + throw $th; + } + } + public function streamWriteFunction($ch, $data) + { + // data 必须是字符串 + if (!is_string($data)) { + return strlen($data); + } + + try { + // 用户处理 + $this->parseResponse($data, function ($line) use ($ch) { + if (is_callable($this->streamFunction)) { + $line = ($this->streamFunction)($ch, $line); + } + + if (is_string($line)) { + // 检查 DONE + if ($line === 'DONE') { + throw new \Exception('DONE'); + } + } else { + if (isset($line['error'])) { + throw new \Exception('DONE'); + } + } + }); + } catch (\Throwable $th) { + if ($th->getMessage() !== 'DONE') { + Log::error($th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + } + flush(); + + // 必须返回 exact byte count + return strlen($data); + } + public function parseResponse($data, callable $callback) + { + // 按换行分割,兼容 \n 和 \r\n + $lines = preg_split("/\r?\n/", $data); + + foreach ($lines as $line) { + $line = trim($line); + if ($line === '') { + continue; // 跳过空行 + } + + // 必须以 data: 开头 + if (strpos($line, 'data: ') === 0) { + // 截掉 data: 前缀 + $json = trim(substr($line, 6)); + } else { + $json = $line; + } + // SSE 结束标记:[DONE] + if ($json === '[DONE]') { + $callback('DONE'); + continue; + } + try { + // 尝试解析 JSON + $payload = json_decode($json, true); + if (isset($payload['error'])) { + throw new \Exception("[{$payload['error']['code']}]" . $payload['error']['message']); + } + $line = $payload; + } catch (\Throwable $th) { + } + $callback($line); + } + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/Audio.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/Audio.php new file mode 100644 index 0000000..fd3acaf --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/Audio.php @@ -0,0 +1,45 @@ +uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function AudioModels(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/Audio/models', $data); + } + /** + * 调用壹定开放平台大模型-音频-生成 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function AudioTTS(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/Audio/tts', $data); + } + public static function AudioVoiceClone(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/Audio/cloneVoice', $data); + } + public static function AudioVoiceList(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/Audio/voiceList', $data); + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/AudioAssistant.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/AudioAssistant.php new file mode 100644 index 0000000..96d66a5 --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/AudioAssistant.php @@ -0,0 +1,33 @@ +uid,客户端使用$request->channels_uid + * @param array $query + * @return array + */ + public static function AudioAssistantlist(int $channels_uid, array $query = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/AudioAssistant/list', $query); + } + /** + * 调用壹定开放平台大模型-音频助手 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function AudioAssistantTTS(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/AudioAssistant/tts', $data); + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/Chat.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/Chat.php new file mode 100644 index 0000000..906c51b --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/Chat.php @@ -0,0 +1,37 @@ +uid,客户端使用$request->channels_uid + * @param array $query + * @return array + */ + public static function ChatModels(int $channels_uid, array $query = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/Chat/models', $query); + } + /** + * 调用壹定开放平台大模型-聊天-对话 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @param callable|null $stream + * @return mixed + */ + public static function ChatCompletions(int $channels_uid, array $data = [], ?callable $stream = null) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + if ($stream) { + return $Client->stream('app/yimind/api/Chat/completions', $data, $stream); + } + return $Client->request('POST', 'app/yimind/api/Chat/completions', ['json' => $data]); + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/ChatAssistant.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/ChatAssistant.php new file mode 100644 index 0000000..ccf4cac --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/ChatAssistant.php @@ -0,0 +1,33 @@ +uid,客户端使用$request->channels_uid + * @param array $query + * @return array + */ + public static function ChatAssistantlist(int $channels_uid, array $query = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/ChatAssistant/list', $query); + } + /** + * 调用壹定开放平台大模型-助手 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function ChatAssistantCompletions(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/ChatAssistant/completions', $data); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/Draw.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/Draw.php new file mode 100644 index 0000000..a9eccbb --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/Draw.php @@ -0,0 +1,33 @@ +uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function DrawModels(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/Draw/models', $data); + } + /** + * 调用壹定开放平台大模型-绘图-生成 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function DrawTIGI(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/Draw/tigi', $data); + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/DrawAssistant.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/DrawAssistant.php new file mode 100644 index 0000000..efd8df9 --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/DrawAssistant.php @@ -0,0 +1,33 @@ +uid,客户端使用$request->channels_uid + * @param array $query + * @return array + */ + public static function DrawAssistantlist(int $channels_uid, array $query = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/DrawAssistant/list', $query); + } + /** + * 调用壹定开放平台大模型-绘图助手 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function DrawAssistantTIGI(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/DrawAssistant/tigi', $data); + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/Video.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/Video.php new file mode 100644 index 0000000..fde9b1d --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/Video.php @@ -0,0 +1,33 @@ +uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function VideoModels(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/Video/models', $data); + } + /** + * 调用壹定开放平台大模型-视频-生成 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function VideoTITV(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/Video/titv', $data); + } +} diff --git a/fastmovie-admin/plugin/control/utils/yidevs/trait/VideoAssistant.php b/fastmovie-admin/plugin/control/utils/yidevs/trait/VideoAssistant.php new file mode 100644 index 0000000..f0c891d --- /dev/null +++ b/fastmovie-admin/plugin/control/utils/yidevs/trait/VideoAssistant.php @@ -0,0 +1,33 @@ +uid,客户端使用$request->channels_uid + * @param array $query + * @return array + */ + public static function VideoAssistantlist(int $channels_uid, array $query = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->get('app/yimind/api/VideoAssistant/list', $query); + } + /** + * 调用壹定开放平台大模型-绘图助手 + * @param int $channels_uid 分站使用$request->uid,客户端使用$request->channels_uid + * @param array $data + * @return array + */ + public static function VideoAssistantTITV(int $channels_uid, array $data = []) + { + $Client = new Client(); + $Client->setChannelsUid($channels_uid); + return $Client->post('app/yimind/api/VideoAssistant/titv', $data); + } +} diff --git a/fastmovie-admin/plugin/finance/admin.json b/fastmovie-admin/plugin/finance/admin.json new file mode 100644 index 0000000..e6dbf4e --- /dev/null +++ b/fastmovie-admin/plugin/finance/admin.json @@ -0,0 +1,40 @@ +{ + "title": "财务", + "icon": "Wallet", + "path": "/app/finance/admin", + "sort":4999, + "children":[ + { + "title":"控制台", + "icon":"ElementPlus", + "path": "/app/finance/admin/Index/index", + "component": "databoard" + }, + { + "title":"订单列表", + "icon":"ShoppingCart", + "path": "/app/finance/admin/Orders/index", + "component": "table", + "children":[ + { + "title":"实收", + "path": "/app/finance/admin/Orders/real_money", + "component": "form", + "show":false + }, + { + "title":"收付", + "path": "/app/finance/admin/Orders/receipt_money", + "component": "form", + "show":false + } + ] + }, + { + "title":"交易设置", + "icon":"Setting", + "path": "/app/finance/admin/Settings/basic", + "component": "form" + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/api/Control.php b/fastmovie-admin/plugin/finance/api/Control.php new file mode 100644 index 0000000..79fe53d --- /dev/null +++ b/fastmovie-admin/plugin/finance/api/Control.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'finance'; + } +} diff --git a/fastmovie-admin/plugin/finance/api/Install.php b/fastmovie-admin/plugin/finance/api/Install.php new file mode 100644 index 0000000..9102495 --- /dev/null +++ b/fastmovie-admin/plugin/finance/api/Install.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'finance'; + } +} diff --git a/fastmovie-admin/plugin/finance/api/notify/Wechat.php b/fastmovie-admin/plugin/finance/api/notify/Wechat.php new file mode 100644 index 0000000..f378981 --- /dev/null +++ b/fastmovie-admin/plugin/finance/api/notify/Wechat.php @@ -0,0 +1,48 @@ + $data['out_trade_no']])->find(); + if (!$order) { + return false; + } + $order->state = OrdersState::PAID['value']; + $order->pay_time = date('Y-m-d H:i:s'); + $order->finish_time = date('Y-m-d H:i:s'); + $order->transaction_id = $data['transaction_id']; + $order->save(); + PluginFinanceOrdersLog::info($order, PaymentChannels::WXPAY['label'] . '支付成功'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + try { + PluginFinanceOrders::finish(['id' => $order->id]); + } catch (\Throwable $th) { + Log::error("订单完成失败:{$order->trade_no},{$th->getMessage()},{$th->getFile()}:{$th->getLine()}", $th->getTrace()); + } + return true; + } +} diff --git a/fastmovie-admin/plugin/finance/app/admin/controller/IndexController.php b/fastmovie-admin/plugin/finance/app/admin/controller/IndexController.php new file mode 100644 index 0000000..38ed2f4 --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/admin/controller/IndexController.php @@ -0,0 +1,248 @@ +addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => 'Admin/update', + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{nickname}》管理员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => 'Admin/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{nickname}》管理员吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建管理员', [ + 'path' => 'Admin/create', + 'props' => [ + 'type' => 'success', + 'title' => '创建管理员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('userinfo', '管理员', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ], + [ + 'field' => 'new_text1', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + 'tags' => [ + [ + 'field' => 'role_name', + 'props' => [ + 'type' => 'success' + ] + ] + ], + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ], + [ + 'field' => 'wx_openid', + 'label' => 'OpenID' + ] + ] + ] + ] + ]); + $builder->add('allow_week', '工作日', [ + 'component' => [ + 'name' => 'tag', + 'options' => Week::getOptions() + ], + 'props' => [ + 'width' => '240px' + ] + ]); + $builder->add('allow_work', '工作时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'danger' + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('online_time', '活动', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'online_time', + 'label' => '在线' + ], + [ + 'field' => 'login_time', + 'label' => '登录' + ], + [ + 'component' => 'tag', + 'field' => 'login_ip', + 'label' => '登录IP', + 'props' => [ + 'size' => 'small', + ] + ] + ] + ] + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => 'Admin/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + return $this->success(); + } +} diff --git a/fastmovie-admin/plugin/finance/app/admin/controller/OrdersController.php b/fastmovie-admin/plugin/finance/app/admin/controller/OrdersController.php new file mode 100644 index 0000000..192ea63 --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/admin/controller/OrdersController.php @@ -0,0 +1,520 @@ +model = new PluginFinanceOrders(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '160px', + 'fixed' => 'right' + ]); + $builder->addTableAction('完成', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/finance/admin/Orders/finish', + 'props' => [ + 'title' => '完成《{title}》订单' + ], + 'where' => [ + ['state', '=', OrdersState::PAID['value']] + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('实收', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/finance/admin/Orders/real_money', + 'props' => [ + 'title' => '实收《{title}》订单' + ], + 'where' => [ + ['state', '=', OrdersState::WAIT_PAY['value']] + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('收付', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/finance/admin/Orders/receipt_money', + 'props' => [ + 'title' => '确认已收到《{title}》订单对公转账' + ], + 'where' => [ + ['state', '=', OrdersState::WAIT_VERIFIED['value']] + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('title', '订单标题', 'input', '', [ + 'props' => [ + 'placeholder' => '订单标题搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('trade_no', '订单号', 'input', '', [ + 'props' => [ + 'placeholder' => '订单号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', null, [ + 'options' => OrdersState::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('type', '订单类型', 'select', null, [ + 'options' => OrdersType::getOptions(), + 'props' => [ + 'placeholder' => '订单类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', '用户', 'select', null, [ + 'options' => [], + 'remote' => [ + 'url' => '/app/finance/admin/Orders/queryUser', + ], + 'props' => [ + 'placeholder' => '用户搜索', + 'clearable' => true, + 'filterable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('order', '订单', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'title', + 'info' => 'trade_no', + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => OrdersState::getOptions() + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('type', '订单类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => OrdersType::getOptions() + ], + 'props' => [ + 'width' => '140px' + ] + ]); + $builder->add('statistic', '数据', [ + 'component' => [ + 'name' => 'statistic', + 'options' => [ + [ + 'label' => '原价', + 'value' => 'origin_money' + ], + [ + 'label' => '实付', + 'value' => 'money' + ], + [ + 'label' => '数量', + 'value' => 'num' + ] + ] + ], + 'props' => [ + 'minWidth' => '380px' + ] + ]); + $builder->add('coupon', '优惠券', [ + 'component' => [ + 'name' => 'tag', + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ], + [ + 'field' => 'pay_time', + 'label' => '支付' + ], + [ + 'field' => 'expire_time', + 'label' => '过期' + ], + [ + 'field' => 'cancel_time', + 'label' => '取消' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function queryUser(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['nickname|mobile', 'like', "%{$query}%"]; + return $this->resData(PluginUser::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $trade_no = $request->get('trade_no'); + if ($trade_no) { + $where[] = ['trade_no', '=', $trade_no]; + } + $state = $request->get('state'); + if ($state !== null) { + $where[] = ['state', '=', $state]; + } + $type = $request->get('type'); + if ($type !== null) { + $where[] = ['type', '=', $type]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $list = PluginFinanceOrders::where($where)->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function finish(Request $request) + { + $id = $request->post('id'); + try { + PluginFinanceOrders::finish(['id' => $id]); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('完成订单成功'); + } + public function real_money(Request $request) + { + if ($request->method() == 'POST') { + $id = $request->post('id'); + $order = PluginFinanceOrders::where(['id' => $id])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state != OrdersState::WAIT_PAY['value']) { + return $this->fail('订单状态不是待支付'); + } + Db::startTrans(); + try { + $order->state = OrdersState::PAID['value']; + $order->pay_type = PaymentChannels::ADMIN['value']; + $order->pay_time = date('Y-m-d H:i:s'); + $order->system_money = $request->post('system_money'); + $order->money = $order->origin_money - $order->system_money - $order->channels_money - $order->developer_money; + $order->save(); + PluginFinanceOrdersLog::info($order, '管理员操作实收成功'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + return $this->exception($th); + } + Event::emit(EventName::ORDERS_PAY['value'], [ + 'data' => $order, + 'payment_channel' => PaymentChannels::ADMIN['value'], + 'plugin' => 'finance' + ]); + try { + PluginFinanceOrders::finish(['id' => $order->id]); + } catch (\Throwable $th) { + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + Log::error("订单完成失败:{$order->trade_no},{$th->getMessage()},{$th->getFile()}:{$th->getLine()}", $th->getTrace()); + return $this->exception($th); + } + return $this->success('实收订单成功'); + } + $id = $request->get('id'); + $order = PluginFinanceOrders::where(['id' => $id])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state != OrdersState::WAIT_PAY['value']) { + return $this->fail('订单状态不是待支付'); + } + $Component = new ComponentBuilder; + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $builder->add('title', '订单', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + ]); + $builder->add('trade_no', '订单号', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('create_time', '下单时间', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('expire_time', '过期时间', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('settle_accounts_time', '结算日期', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('num', '数量', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('origin_money', '原价', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('unit_money', '单价', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('money', '应付', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + 'props' => [ + 'type' => 'danger' + ] + ]); + $maxSystemMoney = $order->money; + $builder->add('system_money', '平台优惠金额', 'input-number', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + 'required' => true, + 'props' => [ + 'min' => 0, + 'max' => $maxSystemMoney, + 'step' => 0.01, + 'controls' => false + ], + 'prompt' => [ + $Component->add('text', ['default' => '最大可优惠金额:' . $maxSystemMoney . '元'], ['type' => 'danger', 'size' => 'small'])->builder(), + ], + 'children' => [ + 'suffix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '元' + ] + ], + 'prefix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '¥' + ] + ] + ] + ]); + $formula = 'money - system_money'; + $builder->add('real_money', '实收金额', 'compute', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + 'prompt' => [ + $Component->add('text', ['default' => '请确认财务已收到款项'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '实收后订单状态将变为:'], ['type' => 'info', 'size' => 'small'])->add('tag', ['default' => OrdersState::FINISH['label']], ['type' => OrdersState::FINISH['props']['type'], 'size' => 'small'])->builder(), + ], + 'props' => [ + 'formula' => $formula, + 'type' => 'danger', + 'size' => 'large', + 'tag' => 'b' + ] + ]); + $builder->setData($order->toArray()); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/finance/app/admin/controller/SettingsController.php b/fastmovie-admin/plugin/finance/app/admin/controller/SettingsController.php new file mode 100644 index 0000000..42e740d --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/admin/controller/SettingsController.php @@ -0,0 +1,15 @@ +builder(); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/app/api/controller/IndexController.php b/fastmovie-admin/plugin/finance/app/api/controller/IndexController.php new file mode 100644 index 0000000..eadd719 --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/api/controller/IndexController.php @@ -0,0 +1,115 @@ +get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['title|trade_no', 'like', "%{$title}%"]; + } + $state = $request->get('state'); + if ($state !== null) { + $where[] = ['state', '=', $state]; + } + $type = $request->get('type'); + if ($type) { + $where[] = ['type', '=', $type]; + } + $where[] = ['uid', '=', $request->uid]; + $config = config('plugin.shopwwi.filesystem.app.storage.public'); + $list = PluginFinanceOrders::where($where)->with(['site' => function ($query) { + $query->field('id,title,logo,domain,ip')->hidden(['id']); + }]) + ->order('id desc')->paginate($limit)->scene('external')->each(function ($item) use ($config) { + $item->state_text = OrdersState::get($item->state); + $pay_type = PaymentChannels::get($item->pay_type); + if ($pay_type) { + $pay_type['icon'] = $config['url'] . $pay_type['icon']; + $item->pay_type_text = $pay_type; + } + if (!empty($item->coupon)) { + $coupon_ids = PluginMarketingCouponCode::whereIn('id', $item->coupon)->column('coupon_id'); + $item->coupon = PluginMarketingCoupon::whereIn('id', array_unique($coupon_ids))->column('title'); + } + }); + return $this->resData($list); + } + public function details(Request $request) + { + $trade_no = $request->get('trade_no'); + $order = PluginFinanceOrders::where('trade_no', $trade_no)->find()->scene('external'); + $order->state_text = OrdersState::get($order->state); + return $this->resData($order); + } + public function OrdersStateEnum(Request $request) + { + $list = OrdersState::getOptions(); + return $this->resData($list); + } + public function cancel(Request $request) + { + $trade_no = $request->post('trade_no'); + $where = [ + ['trade_no', '=', $trade_no], + ['uid', '=', $request->uid], + ['state', '=', OrdersState::WAIT_PAY['value']] + ]; + $order = PluginFinanceOrders::where($where)->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + Db::startTrans(); + try { + PluginFinanceOrders::cancel($order); + PluginFinanceOrdersLog::info($order, '用户订单取消'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('User Orders Cancel Error:' . $th->getMessage(), $th->getTrace()); + return $this->fail('取消失败'); + } + return $this->success('取消成功'); + } + public function delete(Request $request) + { + $trade_no = $request->post('trade_no'); + $where = [ + ['trade_no', '=', $trade_no], + ['uid', '=', $request->uid], + ]; + $order = PluginFinanceOrders::where($where)->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state == OrdersState::WAIT_PAY['value']) { + Db::startTrans(); + try { + PluginFinanceOrders::cancel($order); + PluginFinanceOrdersLog::info($order, '用户删除订单取消'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('User Orders Delete Error:' . $th->getMessage(), $th->getTrace()); + return $this->fail('删除失败'); + } + } + $order->delete(); + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/finance/app/api/controller/OrdersController.php b/fastmovie-admin/plugin/finance/app/api/controller/OrdersController.php new file mode 100644 index 0000000..44372cf --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/api/controller/OrdersController.php @@ -0,0 +1,301 @@ +post(); + if (empty($data['trade_no'])) { + return $this->fail('参数错误'); + } + $order = PluginFinanceOrders::where(['trade_no' => $data['trade_no'], 'uid' => $request->uid])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state !== OrdersState::WAIT_PAY['value']) { + return $this->fail('订单状态错误'); + } + try { + return $this->builderPay($request, $order); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + public function create(Request $request) + { + $data = $request->post(); + if (empty($data['type'])) { + return $this->fail('参数错误'); + } + try { + switch ($data['type']) { + case OrdersType::POINTS['value']: + $order = $this->createPoints($request); + break; + case OrdersType::VIP['value']: + $order = $this->createVip($request); + break; + default: + throw new \Exception('订单类型错误'); + } + } catch (\Throwable $th) { + return $this->exception($th); + } + Event::emit(EventName::ORDERS_CREATE['value'], $order); + try { + return $this->builderPay($request, $order); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + private function builderPay(Request $request, PluginFinanceOrders $order) + { + if ($order->state === OrdersState::PAID['value']) { + try { + PluginFinanceOrders::finish(['trade_no' => $order->trade_no]); + } catch (\Throwable $th) { + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + Log::error("订单完成失败:{$order->trade_no},{$th->getMessage()},{$th->getFile()}:{$th->getLine()}", $th->getTrace()); + throw $th; + } + return $this->code(ResponseCode::PAY_SUCCESS, '支付成功'); + } else { + $data = $request->post(); + if (empty($data['payment_id'])) { + throw new \Exception('支付方式不存在'); + } + $PaymentConfig = PaymentConfig::where(['id' => $data['payment_id']])->find(); + if (!$PaymentConfig) { + throw new \Exception('支付方式不存在'); + } + $order->plugin = 'finance'; + $order->trade = $order->trade_no; + $order->price = $order->money; + switch ($PaymentConfig->channels) { + case PaymentChannels::WXPAY['value']: + $result = Payment::wxPay($order, $PaymentConfig); + $orderInfo = [ + 'platform' => $PaymentConfig->platform, + 'title' => $order->title, + 'pay_info' => $result, + 'trade_no' => $order->trade_no, + 'origin_money' => $order->origin_money, + 'expire_time' => $order->expire_time, + 'money' => $order->money + ]; + return $this->resData($orderInfo); + case PaymentChannels::ALIPAY['value']: + break; + case PaymentChannels::INTEGRAL['value']: + break; + case PaymentChannels::BALANCE['value']: + Db::startTrans(); + try { + $order = PluginFinanceOrders::where(['id' => $order->id])->find(); + if (!$order) { + throw new \Exception('订单不存在'); + } + $order->state = OrdersState::PAID['value']; + $order->pay_type = PaymentChannels::BALANCE['value']; + $order->pay_time = date('Y-m-d H:i:s'); + $order->save(); + PluginFinanceWallet::consume($order); + PluginFinanceOrdersLog::info($order, '余额支付成功'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + throw $th; + } + Event::emit(EventName::ORDERS_PAY['value'], [ + 'data' => $order, + 'payment_channel' => PaymentChannels::BALANCE['value'], + 'plugin' => 'finance', + 'template_id' => $PaymentConfig->template_id + ]); + return $this->builderPay($request, $order); + default: + throw new \Exception('未知支付方式'); + } + } + } + + + private function createVip(Request $request){ + Db::startTrans(); + try { + $data = $request->post(); + $vipInfoPrice = PluginMarketingPlanPrice::where(['id' => $data['id']])->find(); + if (!$vipInfoPrice) { + throw new \Exception('会员套餐不存在'); + } + $vipInfo = PluginMarketingPlan::where(['id' => $vipInfoPrice->plan_id])->find(); + if (!$vipInfo) { + throw new \Exception('会员套餐不存在'); + } + $order = new PluginFinanceOrders; + $order->uid = $request->uid; + $order->alias_id = $vipInfoPrice->id; + $order->title = '购买会员:' . $vipInfo->name; + $order->origin_money = $vipInfoPrice->price; + $order->unit_money = $vipInfoPrice->price; + $order->money = $vipInfoPrice->price; + $order->num = 1; + $order->pay_type = $data['pay_type']; + $order->state = OrdersState::WAIT_PAY['value']; + $order->type = OrdersType::VIP['value']; + $order->channels_uid = $request->channels_uid; + $order->plugin = 'finance'; + $order->expire_time = date('Y-m-d H:i:s', strtotime('+15 minutes')); + $order->save(); + PluginFinanceOrdersLog::info($order, '创建订单'); + Db::commit(); + return $order; + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + private function createPoints(Request $request) + { + Db::startTrans(); + try { + $data = $request->post(); + $pointsInfo = PluginMarketingPoints::where(['id' => $data['id'], 'channels_uid' => $request->channels_uid])->find(); + if (!$pointsInfo) { + throw new \Exception('积分套餐不存在'); + } + $order = new PluginFinanceOrders; + $order->uid = $request->uid; + $order->alias_id = $pointsInfo->id; + $order->title = '购买套餐:' . $pointsInfo->name; + $order->origin_money = $pointsInfo->price; + $order->unit_money = $pointsInfo->price; + $order->money = $pointsInfo->price; + $order->num = 1; + $order->pay_type = $data['pay_type']; + $order->state = OrdersState::WAIT_PAY['value']; + $order->type = OrdersType::POINTS['value']; + $order->channels_uid = $request->channels_uid; + $order->plugin = 'finance'; + $order->expire_time = date('Y-m-d H:i:s', strtotime('+15 minutes')); + $order->save(); + PluginFinanceOrdersLog::info($order, '创建订单'); + Db::commit(); + return $order; + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } + + private function createMoney(Request $request) + { + $data = $request->post(); + Db::startTrans(); + try { + //组装订单数据 + $order = new PluginFinanceOrders; + $order->uid = $request->uid; + $order->money = (int)$data['num']; //原价 未实现优惠券逻辑 + if ($order->money < 1) { + throw new \Exception('充值金额不能小于1元'); + } + $order->origin_money = $order->money; + $order->title = '充值金额:' . $order->money . '元'; + $order->state = OrdersState::WAIT_PAY['value']; + $order['type'] = $data['type']; + $order->save(); + PluginFinanceOrdersLog::info($order->id, '创建订单'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + return $order; + } + + /** + * 查看订单状态 是否支付 + * @Author: 贵州猿创科技有限公司 + * @Date: 2023-05-20 + */ + public function state(Request $request) + { + $trade_no = $request->get('trade_no'); + if (!$trade_no) { + return $this->fail('参数错误'); + } + $info = PluginFinanceOrders::where(['trade_no' => $trade_no, 'uid' => $request->uid])->find(); + if (!$info) { + return $this->fail('订单不存在'); + } + switch ($info->state) { + case OrdersState::WAIT_PAY['value']: + return $this->code(ResponseCode::WAIT, '支付中'); + case OrdersState::PAID['value']: + case OrdersState::FINISH['value']: + return $this->code(ResponseCode::PAY_SUCCESS, '支付成功'); + case OrdersState::REFUND_ING['value']: + case OrdersState::REFUND_SUCCESS['value']: + case OrdersState::REFUND_FAIL['value']: + return $this->fail('订单已退款或退款中'); + case OrdersState::INVALID['value']: + return $this->fail('订单已作废'); + case OrdersState::NOTIFY_FAIL['value']: + return $this->fail('订单通知失败'); + case OrdersState::CANCEL['value']: + return $this->fail('订单已取消'); + default: + return $this->fail('订单状态错误'); + } + } + public function cancel(Request $request) + { + $trade_no = $request->get('trade_no'); + if (!$trade_no) { + return $this->fail('参数错误'); + } + try { + PluginFinanceOrders::cancel(['trade_no' => $trade_no, 'uid' => $request->uid]); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + return $this->success('取消成功'); + } + + public function getOrderStatus(Request $request) + { + $trade_no = $request->get('trade'); + if (!$trade_no) { + return $this->fail('参数错误'); + } + $order = PluginFinanceOrders::where(['trade_no' => $trade_no, 'uid' => $request->uid])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + return $this->resData(['status' => in_array($order->state, [OrdersState::PAID['value'], OrdersState::FINISH['value']])]); + } +} diff --git a/fastmovie-admin/plugin/finance/app/api/controller/UserController.php b/fastmovie-admin/plugin/finance/app/api/controller/UserController.php new file mode 100644 index 0000000..ebc2f83 --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/api/controller/UserController.php @@ -0,0 +1,22 @@ +uid; + $PluginFinanceWallet = PluginFinanceWallet::where('uid', $uid)->find(); + if (!$PluginFinanceWallet) { + $PluginFinanceWallet = new PluginFinanceWallet(); + $PluginFinanceWallet->uid = $uid; + $PluginFinanceWallet->save(); + } + return $this->resData($PluginFinanceWallet->hidden(['uid'])); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/app/control/controller/IndexController.php b/fastmovie-admin/plugin/finance/app/control/controller/IndexController.php new file mode 100644 index 0000000..1242095 --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/control/controller/IndexController.php @@ -0,0 +1,439 @@ +addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => 'Control/update', + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{nickname}》管理员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => 'Control/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{nickname}》管理员吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建管理员', [ + 'path' => 'Control/create', + 'props' => [ + 'type' => 'success', + 'title' => '创建管理员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('userinfo', '管理员', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ], + [ + 'field' => 'new_text1', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + 'tags' => [ + [ + 'field' => 'role_name', + 'props' => [ + 'type' => 'success' + ] + ] + ], + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ], + [ + 'field' => 'wx_openid', + 'label' => 'OpenID' + ] + ] + ] + ] + ]); + $builder->add('allow_week', '工作日', [ + 'component' => [ + 'name' => 'tag', + 'options' => Week::getOptions() + ], + 'props' => [ + 'width' => '240px' + ] + ]); + $builder->add('allow_work', '工作时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'danger' + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('online_time', '活动', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'online_time', + 'label' => '在线' + ], + [ + 'field' => 'login_time', + 'label' => '登录' + ], + [ + 'component' => 'tag', + 'field' => 'login_ip', + 'label' => '登录IP', + 'props' => [ + 'size' => 'small', + ] + ] + ] + ] + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => 'Control/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $builder = new DataboardBuilder([ + 'gutter' => 6 + ]); + + // 今日支付订单数 + $today = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time')->count(); + $yesterday = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', 'yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('今日支付订单数') + ->setUnit('笔') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + // 今日支付金额 + $today = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time')->sum('money'); + $yesterday = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', 'yesterday')->sum('money'); + $statistic = new Statistic; + $statistic->setLabel('今日支付金额') + ->setUnit('元') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + // 总订单数 + $total = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->count(); + $lastMonthTotal = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereMonth('create_time', '-1 month')->count(); + $statistic = new Statistic; + $statistic->setLabel('总订单数') + ->setUnit('笔') + ->setData([ + 'today' => $total, + 'yesterday' => $lastMonthTotal, + 'growth_rate' => $lastMonthTotal ? round(($total - $lastMonthTotal) / $lastMonthTotal * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + // 当月订单数 + $month = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereMonth('create_time')->count(); + $lastMonth = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereMonth('create_time', '-1 month')->count(); + $statistic = new Statistic; + $statistic->setLabel('当月订单数') + ->setUnit('笔') + ->setData([ + 'today' => $month, + 'yesterday' => $lastMonth, + 'growth_rate' => $lastMonth ? round(($month - $lastMonth) / $lastMonth * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + // 添加图表 + $this->echarts($builder, $request); + + return $this->resData($builder); + } + + /** + * 添加图表组件 + * @param DataboardBuilder $builder + * @param Request $request + */ + private function echarts(DataboardBuilder $builder, Request $request) + { + $endTime = strtotime('23:59:59', time()) - time(); + $echarts = new Echarts; + $echarts->setClass('bg-white p-4 rounded-4 shadow-lighter vh-65'); + $EchartsData = Cache::get('finance_echarts_data_' . $request->channels_uid); + if (!$EchartsData) { + $EchartsData = $this->getEchartsData($request); + Cache::set('finance_echarts_data_' . $request->channels_uid, $EchartsData, $endTime); + } + $echarts->setData($EchartsData); + $builder->add($echarts); + } + + /** + * 获取图表数据 + * @param Request $request + * @return array + */ + private function getEchartsData(Request $request) + { + $data = [ + 'color' => ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'], + 'title' => [ + 'text' => '订单数据可视化' + ], + 'tooltip' => [ + 'trigger' => 'axis', + 'axisPointer' => [ + 'type' => 'cross', + 'label' => [ + 'backgroundColor' => '#6a7985' + ] + ] + ], + 'legend' => [ + 'data' => ["订单数", "支付金额"] + ], + 'toolbox' => [ + 'show' => false + ], + 'grid' => [ + 'left' => '3%', + 'right' => '4%', + 'bottom' => '3%', + 'containLabel' => true + ], + 'xAxis' => [ + [ + 'type' => 'category', + 'boundaryGap' => false, + 'data' => [] + ] + ], + 'yAxis' => [ + [ + 'type' => 'value' + ] + ], + 'series' => [ + [ + 'name' => "订单数", + 'type' => 'line', + 'smooth' => true, + 'data' => [] + ], + [ + 'name' => "支付金额", + 'type' => 'line', + 'smooth' => true, + 'data' => [] + ] + ] + ]; + for ($i = 7; $i > 0; $i--) { + $date = date('Y-m-d', strtotime("-$i day")); + $data['xAxis'][0]['data'][] = $date; + $Order = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', $date)->count(); + if ($Order) { + $data['series'][0]['data'][] = $Order; + } else { + $data['series'][0]['data'][] = 0; + } + $Payment = PluginFinanceOrders::where(['channels_uid' => $request->channels_uid])->whereIn('state', [OrdersState::PAID['value'], OrdersState::FINISH['value']])->whereDay('create_time', $date)->sum('money'); + if ($Payment) { + $data['series'][1]['data'][] = $Payment; + } else { + $data['series'][1]['data'][] = 0; + } + } + return $data; + } +} diff --git a/fastmovie-admin/plugin/finance/app/control/controller/OrdersController.php b/fastmovie-admin/plugin/finance/app/control/controller/OrdersController.php new file mode 100644 index 0000000..6e0b53d --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/control/controller/OrdersController.php @@ -0,0 +1,686 @@ +model = new PluginFinanceOrders(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '160px', + 'fixed' => 'right' + ]); + $builder->addTableAction('完成', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/finance/control/Orders/finish', + 'props' => [ + 'title' => '完成《{title}》订单' + ], + 'where' => [ + ['state', '=', OrdersState::PAID['value']] + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('实收', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/finance/control/Orders/real_money', + 'props' => [ + 'title' => '实收《{title}》订单' + ], + 'where' => [ + ['state', '=', OrdersState::WAIT_PAY['value']] + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('收付', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/finance/control/Orders/receipt_money', + 'props' => [ + 'title' => '确认已收到《{title}》订单对公转账' + ], + 'where' => [ + ['state', '=', OrdersState::WAIT_VERIFIED['value']] + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('title', '订单标题', 'input', '', [ + 'props' => [ + 'placeholder' => '订单标题搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('trade_no', '订单号', 'input', '', [ + 'props' => [ + 'placeholder' => '订单号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', null, [ + 'options' => OrdersState::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('type', '订单类型', 'select', null, [ + 'options' => OrdersType::getOptions(), + 'props' => [ + 'placeholder' => '订单类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', '用户', 'select', null, [ + 'options' => [], + 'remote' => [ + 'url' => '/app/finance/control/Orders/queryUser', + ], + 'props' => [ + 'placeholder' => '用户搜索', + 'clearable' => true, + 'filterable' => true + ] + ]); + $formBuilder->add('user_id', '用户ID', 'input', null, [ + 'options' => [], + 'props' => [ + 'placeholder' => '用户搜索', + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('order', '订单', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'title', + 'info' => 'trade_no', + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => OrdersState::getOptions() + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('type', '订单类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => OrdersType::getOptions() + ], + 'props' => [ + 'width' => '140px' + ] + ]); + $builder->add('statistic', '数据', [ + 'component' => [ + 'name' => 'statistic', + 'options' => [ + [ + 'label' => '原价', + 'value' => 'origin_money' + ], + [ + 'label' => '实付', + 'value' => 'money' + ], + [ + 'label' => '数量', + 'value' => 'num' + ] + ] + ], + 'props' => [ + 'minWidth' => '380px' + ] + ]); + $builder->add('coupon', '优惠券', [ + 'component' => [ + 'name' => 'tag', + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '220px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ], + [ + 'field' => 'pay_time', + 'label' => '支付' + ], + [ + 'field' => 'expire_time', + 'label' => '过期' + ], + [ + 'field' => 'finish_time', + 'label' => '完成' + ], + [ + 'field' => 'comment_time', + 'label' => '评价' + ], + [ + 'field' => 'cancel_time', + 'label' => '取消' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function queryUser(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['nickname|mobile', 'like', "%{$query}%"]; + $where[] = ['channels_uid', '=', $request->channels_uid]; + return $this->resData(PluginUser::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $trade_no = $request->get('trade_no'); + if ($trade_no) { + $where[] = ['trade_no', '=', $trade_no]; + } + $state = $request->get('state'); + if ($state !== null) { + $where[] = ['state', '=', $state]; + } + $type = $request->get('type'); + if ($type !== null) { + $where[] = ['type', '=', $type]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $user_id = $request->get('user_id'); + if ($user_id) { + $where[] = ['uid', '=', $user_id]; + } + $list = PluginFinanceOrders::where($where) + ->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function finish(Request $request) + { + $id = $request->post('id'); + try { + PluginFinanceOrders::finish(['id' => $id, 'channels_uid' => $request->channels_uid]); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('完成订单成功'); + } + public function real_money(Request $request) + { + if ($request->method() == 'POST') { + $id = $request->post('id'); + $order = PluginFinanceOrders::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state != OrdersState::WAIT_PAY['value']) { + return $this->fail('订单状态不是待支付'); + } + Db::startTrans(); + try { + $order->state = OrdersState::PAID['value']; + $order->pay_type = PaymentChannels::ADMIN['value']; + $order->pay_time = date('Y-m-d H:i:s'); + $order->system_money = $request->post('system_money'); + $order->money = $order->origin_money - $order->system_money - $order->channels_money - $order->developer_money; + $order->save(); + PluginFinanceOrdersLog::info($order, '管理员操作实收成功'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + return $this->exception($th); + } + Event::emit(EventName::ORDERS_PAY['value'], [ + 'data' => $order, + 'payment_channel' => PaymentChannels::ADMIN['value'], + 'plugin' => 'finance' + ]); + try { + PluginFinanceOrders::finish(['id' => $order->id, 'channels_uid' => $request->channels_uid]); + } catch (\Throwable $th) { + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + Log::error("订单完成失败:{$order->trade_no},{$th->getMessage()},{$th->getFile()}:{$th->getLine()}", $th->getTrace()); + return $this->exception($th); + } + return $this->success('实收订单成功'); + } + $id = $request->get('id'); + $order = PluginFinanceOrders::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state != OrdersState::WAIT_PAY['value']) { + return $this->fail('订单状态不是待支付'); + } + $Component = new ComponentBuilder; + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $builder->add('title', '订单', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + ]); + $builder->add('trade_no', '订单号', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('create_time', '下单时间', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('expire_time', '过期时间', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('settle_accounts_time', '结算日期', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('num', '数量', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('origin_money', '原价', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('unit_money', '单价', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('money', '应付', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + 'props' => [ + 'type' => 'danger' + ] + ]); + $maxSystemMoney = $order->money; + $builder->add('system_money', '平台优惠金额', 'input-number', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + 'required' => true, + 'props' => [ + 'min' => 0, + 'max' => $maxSystemMoney, + 'step' => 0.01, + 'controls' => false + ], + 'prompt' => [ + $Component->add('text', ['default' => '最大可优惠金额:' . $maxSystemMoney . '元'], ['type' => 'danger', 'size' => 'small'])->builder(), + ], + 'children' => [ + 'suffix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '元' + ] + ], + 'prefix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '¥' + ] + ] + ] + ]); + $formula = 'money - system_money'; + $builder->add('real_money', '实收金额', 'compute', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + 'prompt' => [ + $Component->add('text', ['default' => '请确认财务已收到款项'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '实收后订单状态将变为:'], ['type' => 'info', 'size' => 'small'])->add('tag', ['default' => OrdersState::FINISH['label']], ['type' => OrdersState::FINISH['props']['type'], 'size' => 'small'])->builder(), + ], + 'props' => [ + 'formula' => $formula, + 'type' => 'danger', + 'size' => 'large', + 'tag' => 'b' + ] + ]); + $builder->setData($order->toArray()); + return $this->resData($builder); + } + public function receipt_money(Request $request) + { + if ($request->method() == 'POST') { + $id = $request->post('id'); + $order = PluginFinanceOrders::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state != OrdersState::WAIT_VERIFIED['value']) { + return $this->fail('订单状态不是待验证'); + } + Db::startTrans(); + try { + $order->state = OrdersState::PAID['value']; + $order->pay_type = PaymentChannels::ADMIN['value']; + $order->pay_time = date('Y-m-d H:i:s'); + $order->save(); + PluginFinanceOrdersLog::info($order, '管理员确认对公转账成功'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + return $this->exception($th); + } + Event::emit(EventName::ORDERS_PAY['value'], [ + 'data' => $order, + 'payment_channel' => PaymentChannels::ADMIN['value'], + 'plugin' => 'finance' + ]); + try { + PluginFinanceOrders::finish(['id' => $order->id, 'channels_uid' => $request->channels_uid]); + } catch (\Throwable $th) { + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + Log::error("订单完成失败:{$order->trade_no},{$th->getMessage()},{$th->getFile()}:{$th->getLine()}", $th->getTrace()); + return $this->exception($th); + } + return $this->success('确认对公转账成功'); + } + $id = $request->get('id'); + $order = PluginFinanceOrders::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$order) { + return $this->fail('订单不存在'); + } + if ($order->state != OrdersState::WAIT_VERIFIED['value']) { + return $this->fail('订单状态不是待验证'); + } + $Component = new ComponentBuilder; + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $builder->add('title', '订单', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + ]); + $builder->add('trade_no', '订单号', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('create_time', '下单时间', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('expire_time', '过期时间', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 12, + 'xl' => 12 + ], + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ]); + $builder->add('num', '数量', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('origin_money', '原价', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('unit_money', '单价', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + ]); + $builder->add('money', '应付', 'text', null, [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 6 + ], + 'prompt' => [ + $Component->add('text', ['default' => '请确认已收到该订单的对公转账款项'], ['type' => 'warning', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '确认后订单状态将变为:'], ['type' => 'info', 'size' => 'small'])->add('tag', ['default' => OrdersState::FINISH['label']], ['type' => OrdersState::FINISH['props']['type'], 'size' => 'small'])->builder(), + ], + 'props' => [ + 'type' => 'danger' + ] + ]); + $builder->setData($order->toArray()); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/finance/app/control/controller/WalletController.php b/fastmovie-admin/plugin/finance/app/control/controller/WalletController.php new file mode 100644 index 0000000..337b0fe --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/control/controller/WalletController.php @@ -0,0 +1,357 @@ +model = new PluginFinanceWallet; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '180px', + 'fixed' => 'right' + ]); + $builder->addTableAction('余额', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/finance/control/Wallet/recharge', + 'props' => [ + 'title' => '余额操作《{nickname}》' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('积分', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/finance/control/Wallet/rechargePoints', + 'props' => [ + 'title' => '积分操作《{nickname}》' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('临时积分', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/finance/control/Wallet/rechargeTmpPoints', + 'props' => [ + 'title' => '临时积分操作《{nickname}》' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '240px' + ] + ]); + $builder->add('points', '可用积分', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('points_sum', '累计积分', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('points_used', '累计支出积分', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('tmp_points', '临时积分', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('tmp_points_sum', '累计临时积分', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('tmp_points_used', '累计支出临时积分', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('balance', '可用余额', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('balance_sum', '累计余额', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder->add('balance_used', '累计支出余额', [ + 'props' => [ + 'minWidth' => '100px' + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['w.channels_uid', '=', $request->channels_uid]; + $nickname = $request->get('nickname'); + if ($nickname) { + $where[] = ['u.nickname', 'like', "%{$nickname}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['u.mobile', '=', $mobile]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['w.uid', '=', $uid]; + } + $list = PluginFinanceWallet::alias('w') + ->join('plugin_user u', ' u.id=w.uid ') + ->field('w.*,u.id,u.nickname,u.headimg,u.mobile') + ->where($where) + ->order('w.uid desc') + ->paginate($limit) + ->each(function ($item) { + $item->headimg = Uploads::url($item->headimg); + }); + return $this->resData($list); + } + + + public function recharge(Request $request) + { + if ($request->method() == 'POST') { + $data = $request->post(); + Account::updateAccount($data['id'], $request->channels_uid, $data['number'], $data['action'], BillScene::ADMIN['value'], 0, $data['remarks'], $data['is_accumulate'] == 1); + return $this->success('操作成功'); + } + $builder = new FormBuilder(); + + $builder->add('action', '操作', 'radio', MoneyAction::INCREASE['value'], [ + 'options' => MoneyAction::getOptions() + ]); + $Component = new ComponentBuilder; + $builder->add('is_accumulate', '是否计入累计', 'switch', State::NO['value'], [ + 'prompt' => [ + $Component->add('text', ['default' => '是否计入累计,如退款时,不计入累计'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $builder->add('number', '操作数量', 'input-number', 0, [ + 'required' => true, + 'props' => [ + 'controls' => false, + 'min' => 1, + 'step' => 1, + 'style' => [ + 'width' => '200px' + ] + ] + ]); + $builder->add('remarks', '备注', 'input', '', [ + 'required' => true, + ]); + return $this->resData($builder); + } + + public function rechargePoints(Request $request) + { + if ($request->method() == 'POST') { + $data = $request->post(); + Account::updatePermanentPoints($data['id'], $request->channels_uid, $data['number'], PointsBillScene::ADMIN['value'], 0, $data['action'], $data['remarks'], $data['is_accumulate']); + return $this->success('操作成功'); + } + $builder = new FormBuilder(); + $builder->add('action', '操作', 'radio', MoneyAction::INCREASE['value'], [ + 'options' => MoneyAction::getOptions() + ]); + + $Component = new ComponentBuilder; + $builder->add('is_accumulate', '是否计入累计', 'switch', State::NO['value'], [ + 'prompt' => [ + $Component->add('text', ['default' => '是否计入累计,如退款时,不计入累计消费'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + + ] + ]); + $builder->add('number', '操作数量', 'input-number', 0, [ + 'required' => true, + 'props' => [ + 'controls' => false, + 'min' => 1, + 'step' => 1, + 'style' => [ + 'width' => '200px' + ] + ] + ]); + $builder->add('remarks', '备注', 'input', '', [ + 'required' => true, + ]); + return $this->resData($builder); + } + public function rechargeTmpPoints(Request $request) + { + if ($request->method() == 'POST') { + $data = $request->post(); + + if ($data['action'] == MoneyAction::INCREASE['value']) { + Account::incPoints($data['id'], $request->channels_uid, $data['number'], PointsBillScene::ADMIN['value'], 0, $data['remarks'], $data['is_accumulate'], $data['valid_time']); + } else { + Account::decTemporaryPoints($data['id'], $request->channels_uid, $data['number'], PointsBillScene::ADMIN['value'], 0, $data['action'], $data['remarks'], $data['is_accumulate'], $data['point_id']); + } + return $this->success('操作成功'); + } + $list = PluginUserPoints::field('id as value,points as label, valid_time as tips') + ->where('channels_uid', $request->channels_uid) + ->where('uid', $request->get('id')) + ->where('points', '>', 0) + ->where('valid_time', '>=', date('Y-m-d H:i:s')) + ->where('extended_time', '>=', date('Y-m-d H:i:s')) + ->order('extended_time ASC, valid_time ASC') + ->select(); + $list = $list->map(function ($item) { + return [ + 'value' => $item['value'], + 'label' => '剩余积分' . $item['label'], + 'tips' => '有效期至:' . $item['tips'] + ]; + }); + $builder = new FormBuilder(); + $builder->add('action', '操作', 'radio', MoneyAction::INCREASE['value'], [ + 'options' => MoneyAction::getOptions() + ]); + + $builder->add('point_id', '选择扣除对象', 'select', null, [ + 'options' => $list, + 'required' => true, + 'where' => [ + ['action', '=', MoneyAction::DECREASE['value']] + ] + ]); + $Component = new ComponentBuilder; + $builder->add('is_accumulate', '是否计入累计', 'switch', State::NO['value'], [ + 'prompt' => [ + $Component->add('text', ['default' => '是否计入累计,如退款时,不计入累计消费'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $builder->add('number', '操作数量', 'input-number', 0, [ + 'required' => true, + 'props' => [ + 'controls' => false, + 'min' => 1, + 'step' => 1, + 'style' => [ + 'width' => '200px' + ] + ] + ]); + $builder->add('valid_time', '有效期', 'date-picker', null, [ + 'required' => true, + 'where' => [ + ['action', '=', MoneyAction::INCREASE['value']] + ] + ]); + $builder->add('remarks', '备注', 'input', '', [ + 'required' => true, + ]); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/finance/app/functions.php b/fastmovie-admin/plugin/finance/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/functions.php @@ -0,0 +1,6 @@ + [ + // 设置JSON字段的类型 + 'origin_money' => 'float', + 'unit_money' => 'float', + 'money' => 'float', + 'system_money' => 'float', + 'channels_money' => 'float', + 'developer_money' => 'float', + 'coupon' => 'json' + ] + ]; + } + public function getTransferVoucherImageAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setTransferVoucherImageAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function user() + { + return $this->hasOne(PluginUser::class, 'id', 'uid'); + } + protected function sceneExternal() + { + return $this->visible(['title', 'create_time', 'update_time', 'trade_no', 'origin_money', 'money', 'num', 'pay_type', 'state', 'state_text', 'pay_time', 'type', 'cancel_time', 'expire_time', 'comment_time', 'coupon', 'apps', 'pay_type_text', 'user', 'site']); + } + public static function onBeforeInsert($model) + { + $model->trade_no = self::generateTradeNo(); + } + public static function generateTradeNo() + { + $get_random = Str::random(8, 1); + $date = date('YmdHis'); + $trade_no = "{$date}{$get_random}"; + while (self::where('trade_no', '=', $trade_no)->find()) { + $trade_no = self::generateTradeNo(); + } + return $trade_no; + } + public static function finish($where) + { + Db::startTrans(); + try { + $Orders = self::where($where)->find(); + if (!$Orders) { + throw new \Exception('订单不存在'); + } + $notify = null; + switch ($Orders->payment_channels) { + case PaymentChannels::WXPAY['value']: + $notify = PluginFinancePaymentNotify::where(['transaction_id' => $Orders->transaction_id])->find(); + break; + } + switch ($Orders->type) { + case OrdersType::POINTS['value']: + $pointsInfo = PluginMarketingPoints::where(['id' => $Orders->alias_id])->find(); + if (!$pointsInfo) { + throw new \Exception('积分套餐不存在'); + } + $points = (int)$pointsInfo->points + (int)$pointsInfo->give; + Account::incPoints($Orders->uid, $Orders->channels_uid, $points, PointsBillScene::RECHARGE['value'], $Orders->id, '购买套餐:' . $pointsInfo->name, true); + break; + case OrdersType::VIP['value']: + $vipInfoPrice = PluginMarketingPlanPrice::where(['id' => $Orders->alias_id])->find(); + if (!$vipInfoPrice) { + throw new \Exception('会员套餐不存在'); + } + $vipInfo = PluginMarketingPlan::where(['id' => $vipInfoPrice->plan_id])->find(); + if (!$vipInfo) { + throw new \Exception('会员套餐不存在'); + } + // 会员完成逻辑 + self::vip($Orders->alias_id, $Orders->uid, $Orders->channels_uid, $Orders->id); + break; + default: + throw new \Exception('订单类型错误'); + } + $Orders->state = OrdersState::FINISH['value']; + $Orders->finish_time = date('Y-m-d H:i:s'); + $Orders->save(); + PluginFinanceOrdersLog::info($Orders, '订单处理完成'); + if ($notify) { + $notify->state = OrdersState::FINISH['value']; + $notify->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + Event::emit(EventName::ORDERS_FINISH['value'], $Orders); + } + public static function cancel($model) + { + $model->state = OrdersState::CANCEL['value']; + $model->cancel_time = date('Y-m-d H:i:s'); + $model->save(); + if (!empty($model->coupon)) { + $PluginMarketingCouponCodes = PluginMarketingCouponCode::whereIn('id', $model->coupon)->select(); + if (!$PluginMarketingCouponCodes->isEmpty()) { + foreach ($PluginMarketingCouponCodes as $coupon) { + $coupon->state = UseState::ON['value']; + $coupon->use_time = null; + $coupon->save(); + $appCoupon = PluginMarketingCoupon::where('id', $coupon->coupon_id)->find(); + $appCoupon->use_num = Db::raw('use_num-1'); + $appCoupon->save(); + } + } + } + } + + + private static function vip($alias_id, $uid, $channels_uid, $order_id) + { + $vipInfoPrice = PluginMarketingPlanPrice::where(['id' => $alias_id])->find(); + if (!$vipInfoPrice) { + return false; + } + $vipInfo = PluginMarketingPlan::where(['id' => $vipInfoPrice->plan_id])->find(); + if (!$vipInfo) { + return false; + } + $vip = new PluginUserVip(); + $vip->uid = $uid; + $vip->channels_uid = $channels_uid; + $vip->plan_id = $vipInfo->id; + $vip->plan_price_id = $vipInfoPrice->id; + $vip->num = $vipInfo->key == 'basic' ? 2 : 11; + $vip->key = $vipInfo->key; + $vip->save(); + $time = date('Y-m-d H:i:s', strtotime('+ ' . $vipInfo->num + 1 . ' month')); + Account::incPoints($uid, $channels_uid, $vipInfoPrice->points + $vipInfoPrice->give, PointsBillScene::RECHARGE['value'], $order_id, '购买会员:' . $vipInfo->name, true, $time); + } +} diff --git a/fastmovie-admin/plugin/finance/app/model/PluginFinanceOrdersLog.php b/fastmovie-admin/plugin/finance/app/model/PluginFinanceOrdersLog.php new file mode 100644 index 0000000..dceb36b --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/model/PluginFinanceOrdersLog.php @@ -0,0 +1,51 @@ +orders_id = $order->id; + $log->remarks = $remarks; + $log->level = OrdersLogLevel::INFO['value']; + $log->admin_id = $admin_id; + $log->save(); + } + public static function error($order, $remarks,$admin_id=null){ + $log = new self(); + $log->orders_id = $order->id; + $log->remarks = $remarks; + $log->level = OrdersLogLevel::ERROR['value']; + $log->admin_id = $admin_id; + $log->save(); + } + public static function warning($order, $remarks,$admin_id=null){ + $log = new self(); + $log->orders_id = $order->id; + $log->remarks = $remarks; + $log->level = OrdersLogLevel::WARNING['value']; + $log->admin_id = $admin_id; + $log->save(); + } + public static function success($order, $remarks,$admin_id=null){ + $log = new self(); + $log->orders_id = $order->id; + $log->remarks = $remarks; + $log->level = OrdersLogLevel::SUCCESS['value']; + $log->admin_id = $admin_id; + $log->save(); + } + public static function fail($order, $remarks,$admin_id=null){ + $log = new self(); + $log->orders_id = $order->id; + $log->remarks = $remarks; + $log->level = OrdersLogLevel::FAIL['value']; + $log->admin_id = $admin_id; + $log->save(); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/app/model/PluginFinancePaymentNotify.php b/fastmovie-admin/plugin/finance/app/model/PluginFinancePaymentNotify.php new file mode 100644 index 0000000..bf6ccd1 --- /dev/null +++ b/fastmovie-admin/plugin/finance/app/model/PluginFinancePaymentNotify.php @@ -0,0 +1,9 @@ + [ + 'balance' => 'float', + 'balance_sum' => 'float', + 'balance_used' => 'float', + ] + ]; + } + public static function onAfterRead($model) + { + $model->available_points = $model->points + $model->tmp_points; + } + public static function onAfterUpdate($model) + { + Push::send([ + 'uid' => $model->uid, + 'channels_uid' => $model->channels_uid, + 'event' => 'wallet', + ], []); + } +} diff --git a/fastmovie-admin/plugin/finance/config/app.php b/fastmovie-admin/plugin/finance/config/app.php new file mode 100644 index 0000000..bc3fd0f --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/app.php @@ -0,0 +1,9 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/finance/config/autoload.php b/fastmovie-admin/plugin/finance/config/autoload.php new file mode 100644 index 0000000..7fdda8b --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/finance/app/functions.php', + ] +]; diff --git a/fastmovie-admin/plugin/finance/config/container.php b/fastmovie-admin/plugin/finance/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/container.php @@ -0,0 +1,2 @@ +[ + [\plugin\finance\event\Orders::class,'pay'] + ], + EventName::ORDERS_PAY_SUCCESS['value']=>[ + [\plugin\finance\event\Orders::class,'paySuccess'] + ], +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/config/exception.php b/fastmovie-admin/plugin/finance/config/exception.php new file mode 100644 index 0000000..cad6520 --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/exception.php @@ -0,0 +1,5 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/finance/config/middleware.php b/fastmovie-admin/plugin/finance/config/middleware.php new file mode 100644 index 0000000..3e04c72 --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/middleware.php @@ -0,0 +1,18 @@ + [ + app\expose\middleware\Platform::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'control' => [ + plugin\control\expose\middleware\ControlAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ] +]; diff --git a/fastmovie-admin/plugin/finance/config/process.php b/fastmovie-admin/plugin/finance/config/process.php new file mode 100644 index 0000000..5bef326 --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/process.php @@ -0,0 +1,10 @@ + [ + 'eventLoop' => Select::class, + 'handler' => Expire::class + ], +]; diff --git a/fastmovie-admin/plugin/finance/config/settings/basic.php b/fastmovie-admin/plugin/finance/config/settings/basic.php new file mode 100644 index 0000000..e876cbc --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/settings/basic.php @@ -0,0 +1,27 @@ + '订单过期时间', + 'field' => 'expire_time', + 'value' => 15, + 'component' => 'input-number', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '订单过期时间,单位:分钟'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'min' => 1, + 'max' => 1440, + 'step' => 1, + 'precision' => 0, + 'controls' => false + ] + ] + ], +]; diff --git a/fastmovie-admin/plugin/finance/config/translation.php b/fastmovie-admin/plugin/finance/config/translation.php new file mode 100644 index 0000000..923929a --- /dev/null +++ b/fastmovie-admin/plugin/finance/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/finance/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/control.json b/fastmovie-admin/plugin/finance/control.json new file mode 100644 index 0000000..d547a3f --- /dev/null +++ b/fastmovie-admin/plugin/finance/control.json @@ -0,0 +1,34 @@ +{ + "title": "财务", + "icon": "Wallet", + "path": "/app/finance/control", + "sort": 6, + "children": [ + { + "title": "控制台", + "icon": "ElementPlus", + "path": "/app/finance/control/Index/index", + "component": "databoard" + }, + { + "title": "订单列表", + "icon": "ShoppingCart", + "path": "/app/finance/control/Orders/index", + "component": "table", + "children": [ + { + "title": "实收", + "path": "/app/finance/control/Orders/real_money", + "component": "form", + "show": false + }, + { + "title": "收付", + "path": "/app/finance/control/Orders/receipt_money", + "component": "form", + "show": false + } + ] + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/event/Orders.php b/fastmovie-admin/plugin/finance/event/Orders.php new file mode 100644 index 0000000..b253c44 --- /dev/null +++ b/fastmovie-admin/plugin/finance/event/Orders.php @@ -0,0 +1,73 @@ +$data['transaction_id']])->count()){ + return true; + } + $PluginFinancePaymentNotify=new PluginFinancePaymentNotify; + $PluginFinancePaymentNotify->mchid=$data['mchid']; + $PluginFinancePaymentNotify->appid=$data['appid']; + $PluginFinancePaymentNotify->out_trade_no=$data['out_trade_no']; + $PluginFinancePaymentNotify->transaction_id=$data['transaction_id']; + $PluginFinancePaymentNotify->trade_type=$data['trade_type']; + $PluginFinancePaymentNotify->trade_state=$data['trade_state']; + $PluginFinancePaymentNotify->trade_state_desc=$data['trade_state_desc']; + $PluginFinancePaymentNotify->bank_type=$data['bank_type']; + $PluginFinancePaymentNotify->attach=$data['attach']; + $PluginFinancePaymentNotify->payer_openid=$data['payer']['openid']; + $PluginFinancePaymentNotify->amount_total=$data['amount']['total']; + $PluginFinancePaymentNotify->amount_payer_total=$data['amount']['payer_total']; + $PluginFinancePaymentNotify->amount_currency=$data['amount']['currency']; + $PluginFinancePaymentNotify->amount_payer_currency=$data['amount']['payer_currency']; + $PluginFinanceOrders=PluginFinanceOrders::where(['trade'=>$data['out_trade_no']])->find(); + if($PluginFinanceOrders){ + $PluginFinancePaymentNotify->state=OrdersState::PAID['value']; + $PluginFinancePaymentNotify->save(); + $PluginFinanceOrders->state=OrdersState::PAID['value']; + $PluginFinanceOrders->pay_type=PaymentChannels::WXPAY['value']; + $PluginFinanceOrders->pay_time=date('Y-m-d H:i:s'); + $PluginFinanceOrders->transaction_id=$data['transaction_id']; + $PluginFinanceOrders->save(); + PluginFinanceOrdersLog::info($PluginFinanceOrders, PaymentChannels::WXPAY['label'].'支付成功'); + Event::emit(EventName::ORDERS_PAY_SUCCESS['value'],$PluginFinanceOrders); + }else{ + $PluginFinancePaymentNotify->state=OrdersState::NOTIFY_FAIL['value']; + $PluginFinancePaymentNotify->save(); + } + } + public static function paySuccess($order) + { + try { + PluginFinanceOrders::finish(['id'=>$order->id]); + } catch (\Throwable $th) { + PluginFinanceOrdersLog::fail($order, $th->getMessage()); + Log::error("订单完成失败:{$order->trade_no},{$th->getMessage()},{$th->getFile()}:{$th->getLine()}", $th->getTrace()); + throw $th; + } + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/expose/helper/Account.php b/fastmovie-admin/plugin/finance/expose/helper/Account.php new file mode 100644 index 0000000..ca9da74 --- /dev/null +++ b/fastmovie-admin/plugin/finance/expose/helper/Account.php @@ -0,0 +1,513 @@ +where('channels_uid', $channels_uid) + ->find(); + if (!$wallet) { + $wallet = new PluginFinanceWallet(); + $wallet->uid = $uid; + $wallet->channels_uid = $channels_uid; + $wallet->save(); + $wallet = PluginFinanceWallet::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->find(); + } + $validity_period = empty($valid_time) ? ValidityPeriod::PERMANENT['value'] : ValidityPeriod::TEMPORARY['value']; + + $log = new PluginUserPointsBill(); + $log->channels_uid = $channels_uid; + $log->uid = $uid; + $log->form_id = $form_id; + $log->num = $amount; + $log->remarks = $remarks; + $log->action = MoneyAction::INCREASE['value']; + $log->scene = $scene; + $log->type = $validity_period; + $log->is_sum = $is_accumulate ? State::YES['value'] : State::NO['value']; + //永久积只累加 + if ($validity_period === ValidityPeriod::PERMANENT['value']) { + $log->before = $wallet->points; + $log->after = $wallet->points + $amount; + $wallet->points = Db::raw('points+' . $amount); + if ($is_accumulate) { + $wallet->points_sum = Db::raw('points_sum + ' . $amount); + } + } else { + $log->before = $wallet->tmp_points; + $log->after = $wallet->tmp_points + $amount; + $wallet->tmp_points = Db::raw('tmp_points + ' . $amount); + if ($is_accumulate) { + $wallet->tmp_points_sum = Db::raw('tmp_points_sum + ' . $amount); + } + + $userPoints = new PluginUserPoints(); + $userPoints->channels_uid = $channels_uid; + $userPoints->uid = $uid; + $userPoints->total_points = $amount; + $userPoints->points = $amount; + $userPoints->used_points = 0; + $userPoints->valid_time = $valid_time; + $userPoints->extended_time = $valid_time; + $userPoints->scene = $scene; + $userPoints->source_id = $form_id; + $userPoints->save(); + $log->source_id = $userPoints->id; + } + $wallet->save(); + $log->save(); + return $log->id; + } + public static function hasPoints($uid, $channels_uid, $amount) + { + if ($amount <= 0) { + return true; + } + $wallet = PluginFinanceWallet::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->lock(true) + ->find(); + if (!$wallet) { + throw new \Exception('钱包不存在'); + } + if ($wallet->points + $wallet->tmp_points < $amount) { + throw new \Exception('积分不足'); + } + } + + /** + * 减少积分 + * @param int $uid 用户ID + * @param int $channels_uid 渠道ID + * @param float $amount 操作金额 + * @param string $scene 场景 + * @param int $form_id 表单ID + * @param string $remarks 备注 + * @param bool $is_accumulate 是否计入累计 + * @param bool $is_ignore_balance 是否忽略余额 + * @throws \Exception + * @author:1950781041@qq.com + * @Date:2025-12-30 + */ + public static function decPoints($uid, $channels_uid, $amount, $scene = '', $form_id = 0, $remarks = '', $is_accumulate = true,$is_ignore_balance=false) + { + if ($amount <= 0) return []; + $wallet = PluginFinanceWallet::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->lock(true) + ->find(); + if (!$wallet) { + throw new \Exception('钱包不存在'); + } + if (!$is_ignore_balance && $wallet->points + $wallet->tmp_points < $amount) { + throw new \Exception('积分不足'); + } + $ids = []; + $tempPointsList = PluginUserPoints::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->where('points', '>', 0) + ->where('valid_time', '>=', date('Y-m-d H:i:s')) + ->where('extended_time', '>=', date('Y-m-d H:i:s')) + ->order('extended_time ASC, valid_time ASC') + ->lock(true) + ->select(); + $tmp_points = 0; + $tmp_points_used = 0; + foreach ($tempPointsList as $point) { + if ($amount <= 0) break; + $deduct = min($point['points'] - $point['used_points'], $amount); + if ($deduct <= 0) continue; + $PluginUserPoints = PluginUserPoints::where('id', $point['id'])->find(); + if (!$PluginUserPoints) { + continue; + } + $PluginUserPoints->used_points = Db::raw('used_points + ' . $deduct); + $PluginUserPoints->points = Db::raw('points - ' . $deduct); + $PluginUserPoints->save(); + $log = new PluginUserPointsBill(); + $log->channels_uid = $channels_uid; + $log->uid = $uid; + $log->form_id = $form_id; + $log->source_id = $point['id']; + $log->num = $deduct; + $log->before = $wallet->tmp_points; + $log->after = $wallet->tmp_points - $deduct; + $log->remarks = $remarks; + $log->action = MoneyAction::DECREASE['value']; + $log->scene = $scene; + $log->type = ValidityPeriod::TEMPORARY['value']; + $log->is_sum = $is_accumulate ? State::YES['value'] : State::NO['value']; + $log->save(); + $ids[] = $log->id; + $amount -= $deduct; + $tmp_points += $deduct; + if ($is_accumulate) { + $tmp_points_used += $deduct; + } + } + $points = 0; + $points_used = 0; + if ($amount > 0) { + $deduct = min($wallet->points, $amount); + if ($deduct > 0) { + $log = new PluginUserPointsBill(); + $log->channels_uid = $channels_uid; + $log->uid = $uid; + $log->form_id = $form_id; + $log->source_id = 0; + $log->num = $deduct; + $log->before = $wallet->points; + $log->after = $wallet->points - $deduct; + $log->remarks = $remarks; + $log->action = MoneyAction::DECREASE['value']; + $log->scene = $scene; + $log->type = ValidityPeriod::PERMANENT['value']; + $log->is_sum = $is_accumulate ? State::YES['value'] : State::NO['value']; + $log->save(); + $amount -= $deduct; + $points += $deduct; + if ($is_accumulate) { + $points_used += $deduct; + } + $ids[] = $log->id; + } + } + if ($tmp_points_used > 0) { + $wallet->tmp_points_used = Db::raw('tmp_points_used + ' . $tmp_points_used); + } + if ($tmp_points > 0) { + $wallet->tmp_points = Db::raw('tmp_points - ' . $tmp_points); + } + if ($points > 0) { + $wallet->points = Db::raw('points - ' . $points); + } + if ($points_used > 0) { + $wallet->points_used = Db::raw('points_used + ' . $points_used); + } + $wallet->save(); + return $ids; + } + public static function rollbackPoints($uid, $channels_uid, $ids) + { + $bills = PluginUserPointsBill::whereIn('id', $ids)->select(); + foreach ($bills as $bill) { + $bill->action = MoneyAction::INCREASE['value']; + $bill->save(); + } + } + /** + * 操作永久积分 + * @param int $uid 用户ID + * @param int $channels_uid 渠道ID + * @param float $amount 操作金额 + * @param string $scene 场景 + * @param int $form_id 表单ID + * @param string $remarks 备注 + * @param bool $is_accumulate 是否计入累计 + * @throws \Exception + * @author:1950781041@qq.com + * @Date:2025-12-30 + */ + public static function updatePermanentPoints($uid, $channels_uid, $amount, $scene = '', $form_id = 0, $action = '', $remarks = '', $is_accumulate = 1) + { + if ($amount <= 0) { + throw new \Exception('操作金额必须大于0'); + } + if (!in_array($action, [MoneyAction::DECREASE['value'], MoneyAction::INCREASE['value']])) { + throw new \Exception('操作类型错误'); + } + $wallet = PluginFinanceWallet::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->lock(true) + ->find(); + + if (!$wallet) { + throw new \Exception('钱包不存在'); + } + if ($action === MoneyAction::DECREASE['value']) { + if ($wallet->points < $amount) { + throw new \Exception('金额不足'); + } + } + $log = new PluginUserPointsBill(); + $log->channels_uid = $channels_uid; + $log->uid = $uid; + $log->form_id = $form_id; + $log->num = $amount; + $log->remarks = $remarks; + $log->action = $action; + $log->scene = $scene; + $log->before = $wallet->points; + $log->type = ValidityPeriod::PERMANENT['value']; + $log->is_accumulate = $is_accumulate ? State::YES['value'] : State::NO['value']; + if ($action === MoneyAction::DECREASE['value']) { + $log->after = $wallet->points - $amount; + $wallet->points -= $amount; + if ($is_accumulate) { + $wallet->points_used += $amount; + } + } else { + $log->after = $wallet->points + $amount; + $wallet->points += $amount; + if ($is_accumulate) { + $wallet->points_sum += $amount; + } + } + $wallet->save(); + $log->save(); + return $log->id; + } + /** + * 操作临时积分 + * @param int $uid 用户ID + * @param int $channels_uid 渠道ID + * @param float $amount 操作金额 + * @param string $scene 场景 + * @param int $form_id 表单ID + * @param string $remarks 备注 + * @param bool $is_accumulate 是否计入累计 + * @throws \Exception + * @author:1950781041@qq.com + * @Date:2025-12-30 + */ + public static function decTemporaryPoints($uid, $channels_uid, $amount, $scene = '', $form_id = 0, $action = '', $remarks = '', $is_accumulate = 1, $id = 0) + { + $userPoints = PluginUserPoints::where('id', $id)->find(); + $wallet = PluginFinanceWallet::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->lock(true) + ->find(); + if (!$userPoints || !$wallet) { + throw new \Exception('积分或钱包不存在'); + } + if ($userPoints->points < $amount) { + throw new \Exception('积分不足'); + } + if ($wallet->tmp_points < $amount) { + throw new \Exception('临时积分不足'); + } + $log = new PluginUserPointsBill(); + $log->channels_uid = $channels_uid; + $log->uid = $uid; + $log->form_id = $form_id; + $log->source_id = $id; + $log->num = $amount; + $log->remarks = $remarks; + $log->action = $action; + $log->scene = $scene; + $log->type = ValidityPeriod::TEMPORARY['value']; + $log->is_accumulate = $is_accumulate ? State::YES['value'] : State::NO['value']; + if ($action === MoneyAction::DECREASE['value']) { + $log->after = $wallet->tmp_points - $amount; + $wallet->tmp_points -= $amount; + if ($is_accumulate) { + $wallet->tmp_points_used += $amount; + $userPoints->used_points += $amount; + } + } + $userPoints->points -= $amount; + $userPoints->save(); + $wallet->save(); + $log->save(); + return $log->id; + } + + + /** + * 操作用户资产(余额或积分) + * @param int $uid 用户ID + * @param int $channels_uid 渠道ID + * @param float $amount 操作金额 + * @param string $action 操作类型 MoneyAction::DECREASE['value'] / MoneyAction::INCREASE['value'] + * @param string $type 操作类型 balance/points + * @param string $scene 触发场景 + * @param int $form_id 表单ID + * @param string|null $remarks 备注 + * @param bool $is_accumulate 是否计入累计 如退款时,不计入累计 + * @return int 日志ID + * @throws \Exception + */ + public static function updateAccount($uid, $channels_uid, $amount, $action, $scene = '', $form_id = 0, $remarks = null, $is_accumulate = true, $validity_period = ValidityPeriod::PERMANENT['value']) + { + if ($amount <= 0) { + throw new \Exception('操作金额必须大于0'); + } + if (!in_array($action, [MoneyAction::DECREASE['value'], MoneyAction::INCREASE['value']])) { + throw new \Exception('操作类型错误'); + } + $wallet = PluginFinanceWallet::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->lock(true) + ->find(); + + if (!$wallet) { + throw new \Exception('钱包不存在'); + } + $fieldBalance = 'balance'; + $fieldUsed = 'balance_used'; + $fieldSum = 'balance_sum'; + //判断金额是否足够 + if ($action === MoneyAction::DECREASE['value']) { + if ($wallet->$fieldBalance < $amount) { + throw new \Exception('金额不足'); + } + } + + $log = new PluginUserBill(); + $log->channels_uid = $channels_uid; + $log->uid = $uid; + $log->form_id = $form_id; + $log->num = $amount; + $log->remarks = $remarks; + $log->action = $action; + $log->scene = $scene; + $log->before = $wallet->$fieldBalance; + $log->type = $validity_period; + $log->is_accumulate = $is_accumulate ? State::YES['value'] : State::NO['value']; + if ($action === MoneyAction::DECREASE['value']) { + $log->after = $wallet->$fieldBalance - $amount; + $wallet->$fieldBalance -= $amount; + if ($is_accumulate) { + $wallet->$fieldUsed += $amount; + } + } else { + $log->after = $wallet->$fieldBalance + $amount; + $wallet->$fieldBalance += $amount; + if ($is_accumulate) { + $wallet->$fieldSum += $amount; + } + } + $wallet->save(); + $log->save(); + return $log->id; + } + + + /** + * 退费 + * @param int $log_id 账单ID + * @param float $number 退费金额 + * @param string $type 操作类型 balance/points + * @author:1950781041@qq.com + * @Date:2025-12-20 + */ + public static function refund($uid, $channels_uid, $billIds, $number = null, $remarks = '积分退还', $scene = 'refunded') + { + if ($number !== null && $number <= 0) { + throw new \Exception('退费金额必须大于0'); + } + $wallet = PluginFinanceWallet::where('uid', $uid) + ->where('channels_uid', $channels_uid) + ->lock(true) + ->find(); + if (!$wallet) { + throw new \Exception('钱包不存在'); + } + $bills = PluginUserPointsBill::whereIn('id', $billIds)->whereRaw('refunded < num') + ->where('action', 'decrease') + ->order('id', 'desc') + ->lock(true) + ->select(); + $tmp_points = 0; + $tmp_points_used = 0; + $points = 0; + $points_used = 0; + foreach ($bills as $bill) { + if ($number === null) { + $refund = $bill->num; + } else { + if ($number <= 0) { + break; + } + $canRefund = $bill->num - $bill->refunded; + if ($canRefund <= 0) { + continue; + } + $refund = min($canRefund, $number); + } + $log = new PluginUserPointsBill(); + $log->channels_uid = $channels_uid; + $log->uid = $uid; + $log->form_id = $bill->form_id; + $log->source_id = $bill->source_id; + $log->num = $refund; + $log->action = MoneyAction::INCREASE['value']; + $log->scene = $scene; + $log->remarks = "退款,原账单ID:{$bill->id}," . $remarks; + $log->type = $bill->type; + $log->is_sum = 0; + if ($bill->type === 'temporary') { + $points = PluginUserPoints::where('id', $bill->source_id) + ->lock(true) + ->find(); + if (!$points) { + throw new \Exception('临时积分记录不存在'); + } + $points->points = Db::raw('points + ' . $refund); + $points->used_points = Db::raw('used_points - ' . $refund); + $points->state = 0; + $points->save(); + $log->source_id = $points->id; + $log->before = $wallet->tmp_points; + $tmp_points += $refund; + $tmp_points_used += $refund; + $log->after = $wallet->tmp_points; + } else { + $log->before = $wallet->points; + $points += $refund; + $points_used += $refund; + $log->after = $wallet->points; + } + $bill->refunded = Db::raw('refunded + ' . $refund); + $bill->save(); + + $log->save(); + + if ($number !== null) { + $number -= $refund; + } + } + if ($tmp_points > 0) { + $wallet->tmp_points = Db::raw('tmp_points + ' . $tmp_points); + } + if ($tmp_points_used > 0) { + $wallet->tmp_points_used = Db::raw('tmp_points_used - ' . $tmp_points_used); + } + if ($points > 0) { + $wallet->points = Db::raw('points + ' . $points); + } + if ($points_used > 0) { + $wallet->points_used = Db::raw('points_used - ' . $points_used); + } + $wallet->save(); + return true; + } +} diff --git a/fastmovie-admin/plugin/finance/package.json b/fastmovie-admin/plugin/finance/package.json new file mode 100644 index 0000000..c496739 --- /dev/null +++ b/fastmovie-admin/plugin/finance/package.json @@ -0,0 +1,10 @@ +{ + "name": "interface-example", + "title": "应用示例项目", + "version": "0.0.1", + "description": "应用市场", + "author": { + "name": "余心", + "email": "unknown.renloong@gmail.com" + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/process/Expire.php b/fastmovie-admin/plugin/finance/process/Expire.php new file mode 100644 index 0000000..7166754 --- /dev/null +++ b/fastmovie-admin/plugin/finance/process/Expire.php @@ -0,0 +1,37 @@ + OrdersState::WAIT_PAY['value']])->whereNotNull('expire_time')->whereTime('expire_time', '<=', date('Y-m-d H:i:s'))->limit(20)->select(); + foreach ($data as $item) { + Db::startTrans(); + try { + PluginFinanceOrders::cancel($item); + PluginFinanceOrdersLog::warning($item,'订单超时取消'); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('Process Orders Expire Error:'.$th->getMessage(),$th->getTrace()); + } + } + } catch (\Throwable $th) { + Log::error('Process Orders Expire Error:'.$th->getMessage(),$th->getTrace()); + } + }); + } +} diff --git a/fastmovie-admin/plugin/finance/resource/translations/en/admin.php b/fastmovie-admin/plugin/finance/resource/translations/en/admin.php new file mode 100644 index 0000000..5e9f768 --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/en/admin.php @@ -0,0 +1,5 @@ +'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/resource/translations/en/api.php b/fastmovie-admin/plugin/finance/resource/translations/en/api.php new file mode 100644 index 0000000..45a7854 --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/en/api.php @@ -0,0 +1,6 @@ + 'Success', + 'fail' => 'Fail', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/resource/translations/en/control.php b/fastmovie-admin/plugin/finance/resource/translations/en/control.php new file mode 100644 index 0000000..a2a633c --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/en/control.php @@ -0,0 +1,68 @@ + 'Dashboard', + 'Console' => 'Console', + 'User' => 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'Update Status' => 'Update Status', + 'Functional' => 'Functional', + 'Administrator' => 'Administrator', + 'Staff List' => 'Staff List', + 'Create Administrator' => 'Create Administrator', + 'Edit Administrator' => 'Edit Administrator', + 'Delete Administrator' => 'Delete Administrator', + 'Move Role' => 'Move Role', + 'Role Management' => 'Role Management', + 'Role List' => 'Role List', + 'Create Role' => 'Create Role', + 'Edit Role' => 'Edit Role', + 'Delete Role' => 'Delete Role', + 'Systems' => 'Systems', + 'Basic Settings' => 'Basic Settings', + 'Control Settings' => 'Control Settings', + 'Upload Settings' => 'Upload Settings', + 'SMS Settings' => 'SMS Settings', + 'Payment Management' => 'Payment Management', + 'Payment Settings' => 'Payment Settings', + 'Payment Template' => 'Payment Template', + 'Create Payment Template' => 'Create Payment Template', + 'Edit Payment Template' => 'Edit Payment Template', + 'Delete Payment Template' => 'Delete Payment Template', + 'Platform Management' => 'Platform Management', + 'WeChat Mini Program' => 'WeChat Mini Program', + 'WeChat Official Account' => 'WeChat Official Account', + 'Alipay Mini Program' => 'Alipay Mini Program', + 'WeChat Open Platform' => 'WeChat Open Platform', + 'App Platform' => 'App Platform', + 'H5 Platform' => 'H5 Platform', + 'PC Platform' => 'PC Platform', + 'Public Permissions' => 'Public Permissions', + 'Edit Profile' => 'Edit Profile', + 'Get Profile' => 'Get Profile', + 'Logout' => 'Logout', + 'Get Upload Categories' => 'Get Upload Categories', + 'Edit Upload Category' => 'Edit Upload Category', + 'Delete Upload Category' => 'Delete Upload Category', + 'Upload File' => 'Upload File', + 'Delete Upload File' => 'Delete Upload File', + 'Edit Upload File' => 'Edit Upload File', + 'Get Upload File List' => 'Get Upload File List', + 'Login Title'=>'Admin Login', + 'toolbar Lock'=>'Lock', + 'toolbar Search'=>'Search', + 'toolbar Notification'=>'Notification', + 'toolbar FullScreen'=>'FullScreen', + 'toolbar House'=>'Home', + 'toolbar Control'=>'Control', + 'userDropdownMenu updateSelf'=>'Admin Info', + 'Logout Success'=>'Logout Success', + 'PIN Code Cannot Be Empty'=>'PIN Code Cannot Be Empty', + 'Please Enter 6 Digits PIN Code'=>'Please Enter 6 Digits PIN Code', + 'Lock Success'=>'Lock Success', + 'Unlock Success'=>'Unlock Success', + 'form_basic_title'=>'Basic', + 'Settings_basic_web_name'=>'Apps Name' +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/resource/translations/en/messages.php b/fastmovie-admin/plugin/finance/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..c9d49f9 --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/admin.php @@ -0,0 +1,5 @@ +'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/resource/translations/zh_CN/api.php b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/api.php new file mode 100644 index 0000000..40bd25b --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/api.php @@ -0,0 +1,6 @@ + '成功', + 'fail' => '失败', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/resource/translations/zh_CN/control.php b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/control.php new file mode 100644 index 0000000..fd67c8e --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/control.php @@ -0,0 +1,68 @@ + '首页', + 'Console' => '控制台', + 'User' => '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'Update Status' => '更新状态', + 'Functional' => '职能', + 'Administrator' => '管理员', + 'Staff List' => '人员列表', + 'Create Administrator' => '创建管理员', + 'Edit Administrator' => '编辑管理员', + 'Delete Administrator' => '删除管理员', + 'Move Role' => '移动角色', + 'Role Management' => '角色', + 'Role List' => '角色管理', + 'Create Role' => '创建角色', + 'Edit Role' => '编辑角色', + 'Delete Role' => '删除角色', + 'Systems' => '系统', + 'Basic Settings' => '基本配置', + 'Control Settings' => '控制台配置', + 'Upload Settings' => '上传配置', + 'SMS Settings' => '短信配置', + 'Payment Management' => '支付管理', + 'Payment Settings' => '支付配置', + 'Payment Template' => '支付模板', + 'Create Payment Template' => '新增支付模板', + 'Edit Payment Template' => '编辑支付模板', + 'Delete Payment Template' => '删除支付模板', + 'Platform Management' => '平台管理', + 'WeChat Mini Program' => '微信小程序', + 'WeChat Official Account' => '微信公众号', + 'Alipay Mini Program' => '支付宝小程序', + 'WeChat Open Platform' => '微信开放平台', + 'App Platform' => 'APP', + 'H5 Platform' => 'H5', + 'PC Platform' => 'PC', + 'Public Permissions' => '公共权限', + 'Edit Profile' => '修改自己的信息', + 'Get Profile' => '获取自己的信息', + 'Logout' => '退出登录', + 'Get Upload Categories' => '获取上传分类', + 'Edit Upload Category' => '编辑上传分类', + 'Delete Upload Category' => '删除上传分类', + 'Upload File' => '上传文件', + 'Delete Upload File' => '删除上传文件', + 'Edit Upload File' => '编辑上传文件', + 'Get Upload File List' => '获取上传文件列表', + 'Login Title'=>'管理员登录', + 'toolbar Lock'=>'锁屏', + 'toolbar Search'=>'搜索菜单', + 'toolbar Notification'=>'查看通知', + 'toolbar FullScreen'=>'全屏/退出全屏', + 'toolbar House'=>'网站首页', + 'toolbar Control'=>'网站前台控制台', + 'userDropdownMenu updateSelf'=>'管理员信息', + 'Logout Success'=>'退出成功', + 'PIN Code Cannot Be Empty'=>'PIN码不能为空', + 'Please Enter 6 Digits PIN Code'=>'请输入6位PIN码', + 'Lock Success'=>'锁定成功', + 'Unlock Success'=>'解锁成功', + 'form_basic_title'=>'基础信息', + 'Settings_basic_web_name'=>'应用名称' +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/finance/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/finance/utils/enum/BillScene.php b/fastmovie-admin/plugin/finance/utils/enum/BillScene.php new file mode 100644 index 0000000..4d5f911 --- /dev/null +++ b/fastmovie-admin/plugin/finance/utils/enum/BillScene.php @@ -0,0 +1,16 @@ + '管理员操作', + 'value' => 'admin', + 'props' => [ + 'type' => 'info' + ] + ]; +} diff --git a/fastmovie-admin/plugin/finance/utils/enum/OrdersLogLevel.php b/fastmovie-admin/plugin/finance/utils/enum/OrdersLogLevel.php new file mode 100644 index 0000000..513dfb5 --- /dev/null +++ b/fastmovie-admin/plugin/finance/utils/enum/OrdersLogLevel.php @@ -0,0 +1,44 @@ + '普通', + 'value' => 'info', + 'props' => [ + 'type' => 'info' + ] + ]; + const ERROR = [ + 'label' => '错误', + 'value' => 'error', + 'props' => [ + 'type' => 'danger' + ] + ]; + const WARNING = [ + 'label' => '警告', + 'value' => 'warning', + 'props' => [ + 'type' => 'warning' + ] + ]; + const SUCCESS = [ + 'label' => '成功', + 'value' => 'success', + 'props' => [ + 'type' => 'success' + ] + ]; + const FAIL = [ + 'label' => '失败', + 'value' => 'fail', + 'props' => [ + 'type' => 'danger' + ] + ]; +} diff --git a/fastmovie-admin/plugin/finance/utils/enum/OrdersState.php b/fastmovie-admin/plugin/finance/utils/enum/OrdersState.php new file mode 100644 index 0000000..76e756a --- /dev/null +++ b/fastmovie-admin/plugin/finance/utils/enum/OrdersState.php @@ -0,0 +1,79 @@ + '待支付', + 'value' => 0, + 'props' => [ + 'type' => 'info' + ] + ]; + const PAID = [ + 'label' => '已支付', + 'value' => 10, + 'props' => [ + 'type' => 'warning' + ] + ]; + const WAIT_VERIFIED = [ + 'label' => '待验证', + 'value' => 11, + 'props' => [ + 'type' => 'warning' + ] + ]; + const FINISH = [ + 'label' => '已完成', + 'value' => 20, + 'props' => [ + 'type' => 'success' + ] + ]; + const REFUND_ING = [ + 'label' => '退款中', + 'value' => 30, + 'props' => [ + 'type' => 'warning' + ] + ]; + const REFUND_SUCCESS = [ + 'label' => '退款成功', + 'value' => 40, + 'props' => [ + 'type' => 'success' + ] + ]; + const REFUND_FAIL = [ + 'label' => '退款失败', + 'value' => 50, + 'props' => [ + 'type' => 'danger' + ] + ]; + const INVALID = [ + 'label' => '作废', + 'value' => 60, + 'props' => [ + 'type' => 'danger' + ] + ]; + const NOTIFY_FAIL = [ + 'label' => '通知失败', + 'value' => 70, + 'props' => [ + 'type' => 'danger' + ] + ]; + const CANCEL = [ + 'label' => '已取消', + 'value' => 99, + 'props' => [ + 'type' => 'info' + ] + ]; +} diff --git a/fastmovie-admin/plugin/finance/utils/enum/OrdersType.php b/fastmovie-admin/plugin/finance/utils/enum/OrdersType.php new file mode 100644 index 0000000..88098ea --- /dev/null +++ b/fastmovie-admin/plugin/finance/utils/enum/OrdersType.php @@ -0,0 +1,25 @@ + '充值', + 'value' => 'points', + 'props' => [ + 'type' => 'danger', + 'allow_payment' => ['wxpay', 'alipay'] + ] + ]; + const VIP = [ + 'label' => '会员', + 'value' => 'vip', + 'props' => [ + 'type' => 'success', + 'allow_payment' => ['wxpay', 'alipay'] + ] + ]; +} diff --git a/fastmovie-admin/plugin/finance/utils/enum/PointsBillScene.php b/fastmovie-admin/plugin/finance/utils/enum/PointsBillScene.php new file mode 100644 index 0000000..be1bf6b --- /dev/null +++ b/fastmovie-admin/plugin/finance/utils/enum/PointsBillScene.php @@ -0,0 +1,58 @@ + '管理员操作', + 'value' => 'admin', + 'props' => [ + 'type' => 'warning' + ] + ]; + const RECHARGE = [ + 'label' => '充值', + 'value' => 'recharge', + 'props' => [ + 'type' => 'success' + ] + ]; + const VIP_UPGRADE = [ + 'label' => 'VIP升级', + 'value' => 'vip_upgrade', + 'props' => [ + 'type' => 'success' + ] + ]; + const REGISTER = [ + 'label' => '注册', + 'value' => 'register', + 'props' => [ + 'type' => 'warning' + ] + ]; + const INVITE = [ + 'label' => '邀请', + 'value' => 'invite', + 'props' => [ + 'type' => 'warning' + ] + ]; + const CONSUME = [ + 'label' => '消费', + 'value' => 'consume', + 'props' => [ + 'type' => 'primary' + ] + ]; + const REFUNDED = [ + 'label' => '退款', + 'value' => 'refunded', + 'props' => [ + 'type' => 'danger' + ] + ]; +} diff --git a/fastmovie-admin/plugin/finance/utils/enum/ValidityPeriod.php b/fastmovie-admin/plugin/finance/utils/enum/ValidityPeriod.php new file mode 100644 index 0000000..e9c8013 --- /dev/null +++ b/fastmovie-admin/plugin/finance/utils/enum/ValidityPeriod.php @@ -0,0 +1,23 @@ + '临时', + 'value' => 'temporary', + 'props' => [ + 'type' => 'info' + ] + ]; + const PERMANENT = [ + 'label' => '永久', + 'value' => 'permanent', + 'props' => [ + 'type' => 'success' + ] + ]; +} diff --git a/fastmovie-admin/plugin/marketing/admin.json b/fastmovie-admin/plugin/marketing/admin.json new file mode 100644 index 0000000..72fbf7d --- /dev/null +++ b/fastmovie-admin/plugin/marketing/admin.json @@ -0,0 +1,59 @@ +{ + "title": "营销", + "icon": "Discount", + "path": "/app/marketing/admin", + "sort":4999, + "children": [ + { + "title": "控制台", + "icon": "ElementPlus", + "path": "/app/marketing/admin/Index/index", + "component": "databoard" + }, + { + "title": "优惠券", + "icon": "Coupon", + "path": "/app/marketing/admin/Coupon/index", + "component": "table", + "show":false, + "children": [ + { + "title": "创建优惠券", + "path": "/app/marketing/admin/Coupon/create", + "component": "form", + "show": false + }, + { + "title": "发行优惠券码", + "path": "/app/marketing/admin/Coupon/release", + "component": "form", + "show": false + }, + { + "title": "设置优惠券状态", + "path": "/app/marketing/admin/Coupon/indexUpdateState", + "show": false + } + ] + }, + { + "title": "优惠券码", + "icon": "BarCode", + "path": "/app/marketing/admin/CouponCode/index", + "component": "table", + "show":false, + "children": [ + { + "title": "删除券码", + "path": "/app/marketing/admin/CouponCode/delete", + "show": false + }, + { + "title": "作废券码", + "path": "/app/marketing/admin/CouponCode/zuofe", + "show": false + } + ] + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/api/Control.php b/fastmovie-admin/plugin/marketing/api/Control.php new file mode 100644 index 0000000..07a9f63 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/api/Control.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'marketing'; + } +} diff --git a/fastmovie-admin/plugin/marketing/api/Install.php b/fastmovie-admin/plugin/marketing/api/Install.php new file mode 100644 index 0000000..034ef59 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/api/Install.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'marketing'; + } +} diff --git a/fastmovie-admin/plugin/marketing/app/admin/controller/CouponCodeController.php b/fastmovie-admin/plugin/marketing/app/admin/controller/CouponCodeController.php new file mode 100644 index 0000000..5cd6628 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/admin/controller/CouponCodeController.php @@ -0,0 +1,312 @@ +model = new PluginMarketingCoupon(); + } + public function indexGetTable(Request $request) + { + $coupon_id = $request->get('coupon_id'); + if ($coupon_id) { + $coupon_id = (int)$coupon_id; + } + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '100px', + 'fixed' => 'right' + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/marketing/admin/CouponCode/delete', + 'props' => [ + 'message' => '确定删除该优惠券码吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('优惠券', [ + 'model' => Action::REDIRECT['value'], + 'path' => '/app/marketing/admin/Coupon/index', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('coupon_code', '券码', 'input', '', [ + 'props' => [ + 'placeholder' => '券码搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', null, [ + 'options' => UseState::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('receive_state', '领取状态', 'select', null, [ + 'options' => [ + [ + 'label' => '已领取', + 'value' => State::YES['value'] + ], + [ + 'label' => '未领取', + 'value' => State::NO['value'] + ] + ], + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('coupon_id', '优惠券', 'select', $coupon_id, [ + 'options' => PluginMarketingCoupon::options(), + 'props' => [ + 'placeholder' => '优惠券搜索', + 'clearable' => true, + 'filterable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '优惠券名', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'coupon.title', + 'copy' => 'coupon_code', + ] + ], + 'props' => [ + 'minWidth' => '340px' + ] + ]); + $builder->add('user', '领取人', [ + 'where' => [ + ['uid', 'not null', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.id' + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => UseState::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('receive_state', '领取状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'label' => '已领取', + 'value' => State::YES['value'], + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'label' => '未领取', + 'value' => State::NO['value'], + 'props' => [ + 'type' => 'info' + ] + ] + ], + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('receive_timer', '领取/过期时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'primary' + ] + ], + + [ + 'index' => 1, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 2, + 'props' => [ + 'type' => 'danger' + ] + ], + [ + 'index' => 3, + 'props' => [ + 'type' => 'warning' + ] + ] + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('receive_use', '领取\使用规则', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'primary' + ] + ] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('timer', '使用时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'danger' + ] + ] + ] + ], + 'props' => [ + 'minWidth' => '180px' + ] + ]); + $builder->add('coupon.discount_text', '优惠', [ + 'component' => [ + 'name' => 'tag' + ], + 'props' => [ + 'minWidth' => '180px' + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $coupon_code = $request->get('coupon_code'); + if ($coupon_code) { + $where[] = ['coupon_code', 'like', "%{$coupon_code}%"]; + } + $state = $request->get('state'); + if ($state !== null) { + $where[] = ['state', '=', $state]; + } + $receive_state = $request->get('receive_state'); + if ($receive_state !== null) { + if ($receive_state) { + $where[] = ['receive_time', 'NOT NULL', NULL]; + } else { + $where[] = ['receive_time', 'NULL', NULL]; + } + } + $coupon_id = $request->get('coupon_id'); + if ($coupon_id !== null) { + $where[] = ['coupon_id', '=', $coupon_id]; + } + $list = PluginMarketingCouponCode::where($where)->with(['user' => function ($query) { + $query->field('id,nickname,headimg,channels_uid'); + }, 'coupon' => function ($query) { + $query->field('id,title,receive_type,use_type,discount,full_price,money,coupon_rule'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) { + $item->receive_use = [ReceiveType::getText($item->receive_type), UseType::getText($item->use_type)]; + $item->timer = [$item->start_time, $item->end_time]; + $receive_state = 0; + if ($item->receive_time) { + $receive_state = 1; + } + $item->receive_state = $receive_state; + $item->receive_timer = ["有效期:{$item->day}天", $item->receive_time, $item->expire_time,$item->use_time]; + }); + return $this->resData($list); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $PluginMarketingCouponCode = PluginMarketingCouponCode::where(['id' => $id])->find(); + if (!$PluginMarketingCouponCode) { + return $this->fail('券码不存在'); + } + if ($PluginMarketingCouponCode->state === 1) { + return $this->fail('已使用的券码不能删除'); + } + if (!$PluginMarketingCouponCode->delete()) { + return $this->fail('删除失败'); + } + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/admin/controller/CouponController.php b/fastmovie-admin/plugin/marketing/app/admin/controller/CouponController.php new file mode 100644 index 0000000..28b0d66 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/admin/controller/CouponController.php @@ -0,0 +1,651 @@ +model = new PluginMarketingCoupon(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '160px', + 'fixed' => 'right' + ]); + $builder->addTableAction('发行', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/admin/Coupon/release', + 'props' => [ + 'title' => '发行《{title}》优惠券' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('券码', [ + 'model' => Action::REDIRECT['value'], + 'path' => '/app/marketing/admin/CouponCode/index', + 'field' => [ + 'id' => 'coupon_id' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建优惠券', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/admin/Coupon/create', + 'props' => [ + 'title' => '创建优惠券' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('title', '券名', 'input', '', [ + 'props' => [ + 'placeholder' => '券名搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', null, [ + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('show_list', '列表显示', 'select', null, [ + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '列表显示搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $expandBuilder = new TableBuilder([ + 'size' => 'default', + ]); + $expandBuilder->add('password', '口令', [ + 'component' => [ + 'name' => 'copy' + ] + ]); + $builder->add('password', '', [ + 'components' => [ + [ + 'name' => 'table', + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + ], + 'builder' => $expandBuilder->builder() + ] + ], + 'props' => [ + 'type' => 'expand' + ] + ]); + $builder->add('title', '优惠券名', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'title', + 'info' => 'code', + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/admin/Coupon/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('show_list', '列表显示', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/admin/Coupon/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('receive_use', '领取\使用规则', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'primary' + ] + ] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('discount_text', '优惠', [ + 'component' => [ + 'name' => 'tag' + ], + 'props' => [ + 'minWidth' => '180px' + ] + ]); + $builder->add('statistic', '统计', [ + 'component' => [ + 'name' => 'statistic', + 'options' => [ + [ + 'label' => '总数', + 'value' => 'sum' + ], + [ + 'label' => '已使用', + 'value' => 'use_num' + ], + [ + 'label' => '未领取', + 'value' => 'num' + ], + [ + 'label' => '已领取', + 'value' => 'receive_num' + ], + [ + 'label' => '已过期', + 'value' => 'expire_num' + ] + ] + ], + 'props' => [ + 'minWidth' => '380px' + ] + ]); + $builder->add('stackable', '是否叠加', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/admin/Coupon/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('server', '服务范围', [ + 'component' => [ + 'name' => 'tag', + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $state = $request->get('state'); + if ($state !== null) { + $where[] = ['state', '=', $state]; + } + $show_list = $request->get('show_list'); + if ($show_list !== null) { + $where[] = ['show_list', '=', $show_list]; + } + $list = PluginMarketingCoupon::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) { + $item->receive_use = [ReceiveType::getText($item->receive_type), UseType::getText($item->use_type)]; + $item->password = $item->password()->where(['coupon_id' => $item->id])->select(); + $serverTemp = $item->server()->column('server'); + $item->server = array_map(function ($value) { + return $value['label']; + }, OrdersType::getOptions(function ($value) use ($serverTemp) { + return in_array($value['value'], $serverTemp); + })); + }); + return $this->resData($list); + } + + /** + * 创建优惠券品类 + */ + public function create(Request $request) + { + if ($request->method() === 'POST') { + $post = $request->post(); + $validate = new ValidatePluginMarketingCoupon; + try { + $validate->check($post); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $model = new PluginMarketingCoupon; + $model->title = $post['title']; + $model->coupon_rule = $post['coupon_rule']; + switch ($post['coupon_rule']) { + case 'discount': + $model->discount = $post['discount']; + break; + case 'full_price': + $model->full_price = $post['full_price']; + $model->money = $post['money']; + break; + } + $model->state = $post['state']; + $model->show_list = $post['show_list']; + $model->receive_type = $post['receive_type']; + $model->use_type = $post['use_type']; + $model->stackable = $post['stackable'] ? 1 : 0; + $model->save(); + foreach ($post['server'] as $item) { + $PluginMarketingCouponServer = new PluginMarketingCouponServer(); + $PluginMarketingCouponServer->coupon_id = $model->id; + $PluginMarketingCouponServer->server = $item; + $PluginMarketingCouponServer->save(); + } + $passwords = $post['passwords']; + if (!empty($passwords)) { + $passwords = explode("\n", $passwords); + foreach ($passwords as $item) { + $item = trim($item); + if (empty($item)) { + continue; + } + if (PluginMarketingCouponPassword::where(['password' => $item])->count()) { + throw new \Exception("{$item}:优惠券口令重复"); + } + $PluginMarketingCouponPassword = new PluginMarketingCouponPassword; + $PluginMarketingCouponPassword->coupon_id = $model->id; + $PluginMarketingCouponPassword->password = $item; + $PluginMarketingCouponPassword->save(); + } + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->success('创建成功'); + } + $Component = new ComponentBuilder; + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large' + ]); + $builder->add('title', '优惠券名', 'input', '', [ + 'required' => true, + 'props' => [ + 'placeholder' => '优惠券名' + ] + ]); + $builder->add('passwords', '口令', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '每行一个口令,每个口令长度在50个字符以内。每个口令需保持全局唯一'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'props' => [ + 'type' => "textarea", + 'placeholder' => '每行一个口令', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ] + ] + ]); + $builder->add('coupon_rule', '优惠策略', 'radio', '', [ + 'required' => true, + 'options' => CouponRule::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('discount', '折扣', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['coupon_rule', '=', CouponRule::DISCOUNT['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '折扣说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 满小于等于0则为无限制'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'props' => [ + 'step' => 0.01, + 'precision' => 2, + 'min' => 0, + 'max' => 1 + ] + ]); + $builder->add('full_price', '满减', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['coupon_rule', '=', CouponRule::FULL_PRICE['value']] + ], + 'props' => [ + 'precision' => 2, + 'min' => 0, + 'controls' => false + ], + 'children' => [ + 'prefix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '满' + ] + ] + ] + ]); + $builder->add('money', '满减', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['coupon_rule', '=', CouponRule::FULL_PRICE['value']] + ], + 'props' => [ + 'precision' => 2, + 'min' => 0, + 'controls' => false + ], + 'prompt' => [ + $Component->add('text', ['default' => '折扣说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 0和1为不打折'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'children' => [ + 'prefix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '减' + ] + ] + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('show_list', '列表中显示', 'radio', State::NO['value'], [ + 'required' => true, + 'options' => [ + [ + 'label' => '显示', + 'value' => State::YES['value'] + ], + [ + 'label' => '隐藏', + 'value' => State::NO['value'] + ] + ], + 'prompt' => [ + $Component->add('text', ['default' => '是否在优惠广场列表中显示:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '不可用:在优惠广场中,优惠券列表将不显示该优惠券'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '正常:在优惠广场中,优惠券列表显示可被所有用户领取'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('receive_type', '领取规则', 'radio', ReceiveType::REPEAT_DAY['value'], [ + 'required' => true, + 'options' => ReceiveType::getOptions(), + 'prompt' => [ + $Component->add('text', ['default' => '领取规则:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '账户唯一:该应用用户仅可领取一张,全平台生效'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '每日可领,每周可领,每月可领:用户可在规定时间内未领取则可领取一张'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('use_type', '使用规则', 'radio', UseType::UNLIMITED['value'], [ + 'required' => true, + 'options' => UseType::getOptions(), + 'prompt' => [ + $Component->add('text', ['default' => '使用规则:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '首单可用:新用户首单可用'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '应用首单可用:用户未购买过应用可用'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('server', '适用服务', 'checkbox', [], [ + 'options' => OrdersType::getOptions(), + 'required' => true, + 'subProps' => [ + 'border' => true + ], + 'props' => [ + 'min' => 1, + ], + ]); + return $this->resData($builder); + } + public function release(Request $request) + { + if ($request->method() === 'POST') { + $post = $request->post(); + if (empty($post['coupon_id'])) { + return $this->fail('请选择优惠券'); + } + $num = 1; + if (!empty($post['num'])) { + $num = (int)$post['num']; + if ($num > 1000) { + $num = 1000; + } + } + $start_time = null; + $end_time = null; + if (!empty($post['times'])) { + $start_time = $post['times'][0]; + $end_time = $post['times'][1]; + if ($start_time > $end_time || $end_time < date('Y-m-d H:i:s')) { + return $this->fail('开始时间不能大于结束时间或结束时间不能小于当前时间'); + } + } + Db::startTrans(); + try { + $prefix = 'A'; + $PluginMarketingCoupon = PluginMarketingCoupon::where(['id' => $post['coupon_id']])->find(); + $couponData = []; + $createCode = []; + for ($i = 0; $i < $num; $i++) { + $code = PluginMarketingCoupon::getCouponCode($prefix, $post['coupon_id']); + if (in_array($code, $createCode)) { + $i--; + continue; + } + $createCode[] = $code; + $couponData[] = [ + 'coupon_id' => $post['coupon_id'], + 'coupon_rule' => $PluginMarketingCoupon->coupon_rule, + 'coupon_code' => $code, + 'plugin_id' => $PluginMarketingCoupon->plugin_id, + 'discount' => $PluginMarketingCoupon->discount, + 'full_price' => $PluginMarketingCoupon->full_price, + 'money' => $PluginMarketingCoupon->money, + 'receive_type' => $PluginMarketingCoupon->receive_type, + 'start_time' => $start_time, + 'end_time' => $end_time + ]; + } + if (empty($couponData)) { + return $this->fail('创建失败'); + } + $PluginMarketingCoupon->sum = Db::raw('sum+' . $num); + $PluginMarketingCoupon->num = Db::raw('num+' . $num); + $PluginMarketingCoupon->save(); + $PluginMarketingCouponCode = new PluginMarketingCouponCode(); + $PluginMarketingCouponCode->saveAll($couponData); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail('发行失败:' . $th->getMessage()); + } + return $this->success('发行成功'); + } + $id = $request->get('id'); + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large', + 'submitButtonText' => '发行' + ]); + $Component = new ComponentBuilder; + $builder->add('coupon_id', '优惠券', 'select', (int)$id, [ + 'required' => true, + 'options' => PluginMarketingCoupon::options(), + 'props' => [ + 'placeholder' => '请选择优惠券', + 'disabled' => true + ] + ]); + $builder->add('times', '使用日期', 'date-picker', [], [ + 'prompt' => [ + $Component->add('text', ['default' => '开始和结束日期说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 优惠券需在开始和结束日期之间才可使用'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '2. 未限制开始日期或未限制结束日期则优惠券随时可用'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '3. 如已过结束日期,系统将会清空未被领取的优惠券码'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'value-format' => "YYYY-MM-DD HH:mm:ss", + 'type' => "datetimerange", + 'start-placeholder' => "选择可使用开始日期", + 'end-placeholder' => "选择可使用结束日期" + ] + ]); + $builder->add('day', '有效期', 'input-number', 30, [ + 'required' => true, + 'props' => [ + 'step' => 1, + 'precision' => 0, + 'min' => 1, + 'max' => 365 + ] + ]); + $builder->add('num', '数量', 'input-number', 1, [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '数量说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 批量创建优惠券券码'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '2. 最低为1,最高单次为1000'], ['type' => 'info', 'size' => 'small'])->builder() + + ], + 'props' => [ + 'step' => 1, + 'precision' => 0, + 'min' => 1, + 'max' => 1000 + ] + ]); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/admin/controller/IndexController.php b/fastmovie-admin/plugin/marketing/app/admin/controller/IndexController.php new file mode 100644 index 0000000..048581a --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/admin/controller/IndexController.php @@ -0,0 +1,96 @@ + 6 + ]); + $today=PluginMarketingCoupon::whereDay('create_time')->count(); + $yesterday=PluginMarketingCoupon::whereDay('create_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('新增优惠券') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today=PluginMarketingCouponCode::whereDay('create_time')->count(); + $yesterday=PluginMarketingCouponCode::whereDay('create_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('发行券码') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today=PluginMarketingCouponCode::whereDay('receive_time')->count(); + $yesterday=PluginMarketingCouponCode::whereDay('receive_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('领取优惠券') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today=PluginMarketingCouponCode::whereDay('use_time')->count(); + $yesterday=PluginMarketingCouponCode::whereDay('use_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('使用优惠券') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/api/controller/CouponController.php b/fastmovie-admin/plugin/marketing/app/api/controller/CouponController.php new file mode 100644 index 0000000..1d8b307 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/api/controller/CouponController.php @@ -0,0 +1,216 @@ +get('server'); + if ($server) { + $ids = PluginMarketingCoupon::alias('a') + ->join('plugin_marketing_coupon_server', 'a.id = plugin_marketing_coupon_server.coupon_id', 'left') + ->where(function ($query) use ($server) { + $query->where('plugin_marketing_coupon_server.server', '=', $server); + }) + ->distinct(true) + ->field('a.id') + ->column('a.id'); + $model->whereIn('id', $ids); + } + + $uid = $request->uid; + if ($uid) { + $hasPaidOrder = PluginFinanceOrders::where(['uid' => $uid]) + ->whereIn('state', PluginMarketingCouponCode::$validOrderStates) + ->count(); + if ($hasPaidOrder > 0) { + $model->where('use_type', 'in', [UseType::FIRST_PLUGIN['value'], UseType::UNLIMITED['value']]); + } + } + $data = $model->order('id desc')->select()->scene('external')->each(function ($item) use ($uid) { + $item->percentage = 0; + if ($item->sum > 0 && $item->num > 0) { + $item->percentage = round($item->num / $item->sum * 100, 2); + } + $item->receive_type_text = ReceiveType::get($item->receive_type); + $item->use_type_text = UseType::get($item->use_type); + $item->receive = 0; + if ($uid) { + switch ($item->receive_type) { + case ReceiveType::USER_ONLY['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'receive_type' => ReceiveType::USER_ONLY['value'], 'coupon_id' => $item->id])->count()) { + $item->receive = 1; + } + break; + case ReceiveType::REPEAT_DAY['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'coupon_id' => $item->id])->whereDay('receive_time')->count()) { + $item->receive = 1; + } + break; + case ReceiveType::REPEAT_WEEK['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'coupon_id' => $item->id])->whereWeek('receive_time')->count()) { + $item->receive = 1; + } + break; + case ReceiveType::REPEAT_MONTH['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'coupon_id' => $item->id])->whereMonth('receive_time')->count()) { + $item->receive = 1; + } + break; + } + switch ($item->use_type) { + case UseType::FIRST_ORDER['value']: + if (PluginFinanceOrders::where(['uid' => $uid, 'state' => OrdersState::FINISH['value']])->count()) { + $item->receive = 1; + } + break; + } + } + if ($item->stackable) { + $item->exclusive = PluginMarketingCoupon::whereIn('id', $item->exclusiveWith()->column('exclusive_with_id'))->select()->scene('external'); + } else { + $item->exclusive = []; + } + $server = $item->server()->column('server'); + $item->server = OrdersType::getOptions(function ($value) use ($server) { + return in_array($value['value'], $server); + }); + }); + return $this->resData($data); + } + public function receive(Request $request) + { + $uid = $request->uid; + $code = $request->post('code'); + # 根据code获取coupon_id + $coupon_id = PluginMarketingCoupon::codeToId($code); + Db::startTrans(); + try { + $coupon = PluginMarketingCoupon::where(['id' => $coupon_id, 'state' => State::YES['value']])->find(); + if (!$coupon) { + throw new \Exception('优惠券不存在'); + } + if ($coupon->num <= 0) { + throw new \Exception('优惠券已领完'); + } + switch ($coupon->receive_type) { + case ReceiveType::USER_ONLY['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'receive_type' => ReceiveType::USER_ONLY['value']])->count()) { + throw new \Exception('已领取过相同类型的优惠券'); + } + break; + case ReceiveType::REPEAT_DAY['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'coupon_id' => $coupon->id])->whereDay('receive_time')->count()) { + throw new \Exception('今日已领取过该优惠券'); + } + break; + case ReceiveType::REPEAT_WEEK['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'coupon_id' => $coupon->id])->whereWeek('receive_time')->count()) { + throw new \Exception('本周已领取过该优惠券'); + } + break; + case ReceiveType::REPEAT_MONTH['value']: + if (PluginMarketingCouponCode::where(['uid' => $uid, 'coupon_id' => $coupon->id])->whereMonth('receive_time')->count()) { + throw new \Exception('本月已领取过该优惠券'); + } + break; + } + $couponCode = PluginMarketingCouponCode::where(['coupon_id' => $coupon_id])->whereNull('uid')->find(); + if (!$couponCode) { + throw new \Exception('优惠券已领完'); + } + $coupon->num = Db::raw('num-1'); + $coupon->receive_num = Db::raw('receive_num+1'); + $coupon->save(); + $couponCode->uid = $uid; + $couponCode->expire_time = date('Y-m-d H:i:s', strtotime("+{$couponCode->day} day")); + if ($couponCode->end_time && $couponCode->end_time < $couponCode->expire_time) { + $couponCode->expire_time = $couponCode->end_time; + } + $couponCode->receive_time = date('Y-m-d H:i:s'); + $couponCode->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->success('领取成功'); + } + public function getAvailable(Request $request) + { + $uid = $request->uid; + $now = date('Y-m-d H:i:s'); + $where = []; + $where[] = ['uid', '=', $uid]; + $where[] = ['plugin_marketing_coupon_code.state', '=', State::NO['value']]; + $model = PluginMarketingCouponCode::where($where); + $server = $request->get('server'); + if ($server) { + $codeIds = PluginMarketingCouponCode::alias('plugin_marketing_coupon_code')->where($where)->column('coupon_id'); + $codeIds = array_unique($codeIds); + $ids = PluginMarketingCoupon::alias('a')->whereIn('a.id', $codeIds) + ->join('plugin_marketing_coupon_server', 'a.id = plugin_marketing_coupon_server.coupon_id', 'left') + ->where(function ($query) use ($server) { + $query->where('plugin_marketing_coupon_server.server', '=', $server); + }) + ->distinct(true) + ->field('a.id') + ->column('a.id'); + $model->whereIn('coupon_id', $ids); + } + $hasPaidOrder = PluginFinanceOrders::where(['uid' => $request->uid]) + ->whereIn('state', PluginMarketingCouponCode::$validOrderStates) + ->count(); + if ($hasPaidOrder > 0) { + $model->where('use_type', 'in', [UseType::FIRST_PLUGIN['value'], UseType::UNLIMITED['value']]); + } + $coupon = $model->where(function ($query) use ($now) { + $startTime = [ + ['start_time', '=', NULL], + ['start_time', '<=', $now] + ]; + $query->whereOr($startTime); + }) + ->where(function ($query) use ($now) { + $endTime = [ + ['end_time', '=', NULL], + ['end_time', '>=', $now] + ]; + $query->whereOr($endTime); + }) + ->whereTime('expire_time', '>=', $now) + ->with(['coupon' => function ($query) { + $query->field('id,title,coupon_rule,discount,full_price,money,stackable')->hidden(['id']); + }]) + ->hasWhere('coupon', ['plugin_marketing_coupon.state' => State::YES['value']]) + ->order('receive_time desc') + ->select()->scene('external')->each(function ($item) { + $item->exclusive = PluginMarketingCoupon::whereIn('id', $item->coupon->exclusiveWith()->column('exclusive_with_id'))->field('id,title')->select()->scene('external'); + $server = $item->coupon->server()->column('server'); + $item->server = OrdersType::getOptions(function ($value) use ($server) { + return in_array($value['value'], $server); + }); + }); + return $this->resData($coupon); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/api/controller/PaymentController.php b/fastmovie-admin/plugin/marketing/app/api/controller/PaymentController.php new file mode 100644 index 0000000..baf8ec9 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/api/controller/PaymentController.php @@ -0,0 +1,16 @@ +channels_uid); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/api/controller/PointsController.php b/fastmovie-admin/plugin/marketing/app/api/controller/PointsController.php new file mode 100644 index 0000000..18ddfc6 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/api/controller/PointsController.php @@ -0,0 +1,21 @@ +channels_uid; + $where = []; + $where[] = ['channels_uid', '=', $channels_uid]; + $where[] = ['state', '=', State::YES['value']]; + $list = PluginMarketingPoints::where($where)->order('id desc')->select(); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/api/controller/UserCouponController.php b/fastmovie-admin/plugin/marketing/app/api/controller/UserCouponController.php new file mode 100644 index 0000000..23b3832 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/api/controller/UserCouponController.php @@ -0,0 +1,79 @@ +uid; + $now = date('Y-m-d H:i:s'); + $where = []; + $where[] = ['uid', '=', $uid]; + $action = $request->get('action'); + if ($action) { + switch ($action) { + case 'unused': + $where[] = ['plugin_marketing_coupon_code.state', '=', UseState::ON['value']]; + break; + case 'used': + $where[] = ['plugin_marketing_coupon_code.state', 'in', [UseState::USED['value'], UseState::EXPIRE['value']]]; + break; + } + } + $model = PluginMarketingCouponCode::where($where); + $server = $request->get('server'); + if ($server) { + $codeIds = PluginMarketingCouponCode::alias('plugin_marketing_coupon_code')->where($where)->column('coupon_id'); + $codeIds = array_unique($codeIds); + $ids = PluginMarketingCoupon::alias('a')->whereIn('a.id', $codeIds) + ->join('plugin_marketing_coupon_server', 'a.id = plugin_marketing_coupon_server.coupon_id', 'left') + ->where(function ($query) use ($server) { + $query->where('plugin_marketing_coupon_server.server', '=', $server); + }) + ->distinct(true) + ->field('a.id') + ->column('a.id'); + $model->whereIn('coupon_id', $ids); + } + + $coupon = $model->where(function ($query) use ($now) { + $startTime = [ + ['start_time', '=', NULL], + ['start_time', '<=', $now] + ]; + $query->whereOr($startTime); + }) + ->where(function ($query) use ($now) { + $endTime = [ + ['end_time', '=', NULL], + ['end_time', '>=', $now] + ]; + $query->whereOr($endTime); + }) + ->with(['coupon' => function ($query) { + $query->field('id,title,coupon_rule,discount,full_price,money,stackable,use_type')->hidden(['id']); + }]) + ->hasWhere('coupon', ['plugin_marketing_coupon.state' => State::YES['value']]) + ->order('receive_time desc') + ->select()->scene('external')->each(function ($item) { + $item->exclusive = PluginMarketingCoupon::whereIn('id', $item->coupon->exclusiveWith()->column('exclusive_with_id'))->field('id,title')->select()->scene('external'); + $server = $item->coupon->server()->column('server'); + $item->server = OrdersType::getOptions(function ($value) use ($server) { + return in_array($value['value'], $server); + }); + $item->use_type_text = UseType::get($item->coupon->use_type); + $item->state_text = UseState::get($item->state); + }); + return $this->resData($coupon); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/api/controller/VipController.php b/fastmovie-admin/plugin/marketing/app/api/controller/VipController.php new file mode 100644 index 0000000..1fd027f --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/api/controller/VipController.php @@ -0,0 +1,21 @@ +channels_uid; + $where = []; + $where[] = ['channels_uid', '=', $channels_uid]; + $where[] = ['state', '=', State::YES['value']]; + $list = PluginMarketingPlan::with('price')->where($where)->order('id desc')->select(); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/control/controller/CouponCodeController.php b/fastmovie-admin/plugin/marketing/app/control/controller/CouponCodeController.php new file mode 100644 index 0000000..9d4bb52 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/control/controller/CouponCodeController.php @@ -0,0 +1,313 @@ +model = new PluginMarketingCoupon(); + } + public function indexGetTable(Request $request) + { + $coupon_id = $request->get('coupon_id'); + if ($coupon_id) { + $coupon_id = (int)$coupon_id; + } + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '100px', + 'fixed' => 'right' + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/marketing/control/CouponCode/delete', + 'props' => [ + 'message' => '确定删除该优惠券码吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('优惠券', [ + 'model' => Action::REDIRECT['value'], + 'path' => '/app/marketing/control/Coupon/index', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('coupon_code', '券码', 'input', '', [ + 'props' => [ + 'placeholder' => '券码搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', null, [ + 'options' => UseState::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('receive_state', '领取状态', 'select', null, [ + 'options' => [ + [ + 'label' => '已领取', + 'value' => State::YES['value'] + ], + [ + 'label' => '未领取', + 'value' => State::NO['value'] + ] + ], + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('coupon_id', '优惠券', 'select', $coupon_id, [ + 'options' => PluginMarketingCoupon::options(['channels_uid' => $request->channels_uid]), + 'props' => [ + 'placeholder' => '优惠券搜索', + 'clearable' => true, + 'filterable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('title', '优惠券名', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'coupon.title', + 'copy' => 'coupon_code', + ] + ], + 'props' => [ + 'minWidth' => '340px' + ] + ]); + $builder->add('user', '领取人', [ + 'where' => [ + ['uid', 'not null', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.id' + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => UseState::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('receive_state', '领取状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'label' => '已领取', + 'value' => State::YES['value'], + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'label' => '未领取', + 'value' => State::NO['value'], + 'props' => [ + 'type' => 'info' + ] + ] + ], + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('receive_timer', '领取/过期时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'primary' + ] + ], + + [ + 'index' => 1, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 2, + 'props' => [ + 'type' => 'danger' + ] + ], + [ + 'index' => 3, + 'props' => [ + 'type' => 'warning' + ] + ] + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('receive_use', '领取\使用规则', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'primary' + ] + ] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('timer', '使用时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'danger' + ] + ] + ] + ], + 'props' => [ + 'minWidth' => '180px' + ] + ]); + $builder->add('coupon.discount_text', '优惠', [ + 'component' => [ + 'name' => 'tag' + ], + 'props' => [ + 'minWidth' => '180px' + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $coupon_code = $request->get('coupon_code'); + if ($coupon_code) { + $where[] = ['coupon_code', 'like', "%{$coupon_code}%"]; + } + $state = $request->get('state'); + if ($state !== null) { + $where[] = ['state', '=', $state]; + } + $receive_state = $request->get('receive_state'); + if ($receive_state !== null) { + if ($receive_state) { + $where[] = ['receive_time', 'NOT NULL', NULL]; + } else { + $where[] = ['receive_time', 'NULL', NULL]; + } + } + $coupon_id = $request->get('coupon_id'); + if ($coupon_id !== null) { + $where[] = ['coupon_id', '=', $coupon_id]; + } + $list = PluginMarketingCouponCode::where($where)->with(['user' => function ($query) { + $query->field('id,nickname,headimg,channels_uid'); + }, 'coupon' => function ($query) { + $query->field('id,title,receive_type,use_type,discount,full_price,money,coupon_rule'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) { + $item->receive_use = [ReceiveType::getText($item->receive_type), UseType::getText($item->use_type)]; + $item->timer = [$item->start_time, $item->end_time]; + $receive_state = 0; + if ($item->receive_time) { + $receive_state = 1; + } + $item->receive_state = $receive_state; + $item->receive_timer = ["有效期:{$item->day}天", $item->receive_time, $item->expire_time, $item->use_time]; + }); + return $this->resData($list); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $PluginMarketingCouponCode = PluginMarketingCouponCode::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$PluginMarketingCouponCode) { + return $this->fail('券码不存在'); + } + if ($PluginMarketingCouponCode->state === 1) { + return $this->fail('已使用的券码不能删除'); + } + if (!$PluginMarketingCouponCode->delete()) { + return $this->fail('删除失败'); + } + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/control/controller/CouponController.php b/fastmovie-admin/plugin/marketing/app/control/controller/CouponController.php new file mode 100644 index 0000000..e6c7f4c --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/control/controller/CouponController.php @@ -0,0 +1,657 @@ +model = new PluginMarketingCoupon(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '160px', + 'fixed' => 'right' + ]); + $builder->addTableAction('发行', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Coupon/release', + 'props' => [ + 'title' => '发行《{title}》优惠券' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('券码', [ + 'model' => Action::REDIRECT['value'], + 'path' => '/app/marketing/control/CouponCode/index', + 'field' => [ + 'id' => 'coupon_id' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建优惠券', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Coupon/create', + 'props' => [ + 'title' => '创建优惠券' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('title', '券名', 'input', '', [ + 'props' => [ + 'placeholder' => '券名搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', null, [ + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('show_list', '列表显示', 'select', null, [ + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '列表显示搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $expandBuilder = new TableBuilder([ + 'size' => 'default', + ]); + $expandBuilder->add('password', '口令', [ + 'component' => [ + 'name' => 'copy' + ] + ]); + $builder->add('password', '', [ + 'components' => [ + [ + 'name' => 'table', + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + ], + 'builder' => $expandBuilder->builder() + ] + ], + 'props' => [ + 'type' => 'expand' + ] + ]); + $builder->add('title', '优惠券名', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'title', + 'info' => 'code', + ] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/control/Coupon/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('show_list', '列表显示', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/control/Coupon/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('receive_use', '领取\使用规则', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'primary' + ] + ] + ] + ], + 'props' => [ + 'width' => '160px' + ] + ]); + $builder->add('discount_text', '优惠', [ + 'component' => [ + 'name' => 'tag' + ], + 'props' => [ + 'minWidth' => '180px' + ] + ]); + $builder->add('statistic', '统计', [ + 'component' => [ + 'name' => 'statistic', + 'options' => [ + [ + 'label' => '总数', + 'value' => 'sum' + ], + [ + 'label' => '已使用', + 'value' => 'use_num' + ], + [ + 'label' => '未领取', + 'value' => 'num' + ], + [ + 'label' => '已领取', + 'value' => 'receive_num' + ], + [ + 'label' => '已过期', + 'value' => 'expire_num' + ] + ] + ], + 'props' => [ + 'minWidth' => '380px' + ] + ]); + $builder->add('stackable', '是否叠加', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/control/Coupon/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('server', '服务范围', [ + 'component' => [ + 'name' => 'tag', + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $state = $request->get('state'); + if ($state !== null) { + $where[] = ['state', '=', $state]; + } + $show_list = $request->get('show_list'); + if ($show_list !== null) { + $where[] = ['show_list', '=', $show_list]; + } + $list = PluginMarketingCoupon::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) { + $item->receive_use = [ReceiveType::getText($item->receive_type), UseType::getText($item->use_type)]; + $item->password = $item->password()->where(['coupon_id' => $item->id])->select(); + $serverTemp = $item->server()->column('server'); + $item->server = array_map(function ($value) { + return $value['label']; + }, OrdersType::getOptions(function ($value) use ($serverTemp) { + return in_array($value['value'], $serverTemp); + })); + }); + return $this->resData($list); + } + + /** + * 创建优惠券品类 + */ + public function create(Request $request) + { + if ($request->method() === 'POST') { + $post = $request->post(); + $validate = new ValidatePluginMarketingCoupon; + try { + $validate->check($post); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + Db::startTrans(); + try { + $model = new PluginMarketingCoupon; + $model->channels_uid = $request->channels_uid; + $model->title = $post['title']; + $model->coupon_rule = $post['coupon_rule']; + switch ($post['coupon_rule']) { + case 'discount': + $model->discount = $post['discount']; + break; + case 'full_price': + $model->full_price = $post['full_price']; + $model->money = $post['money']; + break; + } + $model->state = $post['state']; + $model->show_list = $post['show_list']; + $model->receive_type = $post['receive_type']; + $model->use_type = $post['use_type']; + $model->stackable = $post['stackable'] ? 1 : 0; + $model->save(); + foreach ($post['server'] as $item) { + $PluginMarketingCouponServer = new PluginMarketingCouponServer(); + $PluginMarketingCouponServer->coupon_id = $model->id; + $PluginMarketingCouponServer->server = $item; + $PluginMarketingCouponServer->save(); + } + $passwords = $post['passwords']; + if (!empty($passwords)) { + $passwords = explode("\n", $passwords); + foreach ($passwords as $item) { + $item = trim($item); + if (empty($item)) { + continue; + } + if (PluginMarketingCouponPassword::where(['password' => $item])->count()) { + throw new \Exception("{$item}:优惠券口令重复"); + } + $PluginMarketingCouponPassword = new PluginMarketingCouponPassword; + $PluginMarketingCouponPassword->coupon_id = $model->id; + $PluginMarketingCouponPassword->password = $item; + $PluginMarketingCouponPassword->save(); + } + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->success('创建成功'); + } + $Component = new ComponentBuilder; + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large' + ]); + $builder->add('title', '优惠券名', 'input', '', [ + 'required' => true, + 'props' => [ + 'placeholder' => '优惠券名' + ] + ]); + $builder->add('passwords', '口令', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '每行一个口令,每个口令长度在50个字符以内。每个口令需保持全局唯一'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'props' => [ + 'type' => "textarea", + 'placeholder' => '每行一个口令', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ] + ] + ]); + $builder->add('coupon_rule', '优惠策略', 'radio', '', [ + 'required' => true, + 'options' => CouponRule::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('discount', '折扣', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['coupon_rule', '=', CouponRule::DISCOUNT['value']] + ], + 'prompt' => [ + $Component->add('text', ['default' => '折扣说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 满小于等于0则为无限制'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'props' => [ + 'step' => 0.01, + 'precision' => 2, + 'min' => 0, + 'max' => 1 + ] + ]); + $builder->add('full_price', '满减', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['coupon_rule', '=', CouponRule::FULL_PRICE['value']] + ], + 'props' => [ + 'precision' => 2, + 'min' => 0, + 'controls' => false + ], + 'children' => [ + 'prefix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '满' + ] + ] + ] + ]); + $builder->add('money', '满减', 'input-number', null, [ + 'required' => true, + 'where' => [ + ['coupon_rule', '=', CouponRule::FULL_PRICE['value']] + ], + 'props' => [ + 'precision' => 2, + 'min' => 0, + 'controls' => false + ], + 'prompt' => [ + $Component->add('text', ['default' => '折扣说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 0和1为不打折'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'children' => [ + 'prefix' => [ + 'component' => 'el-text', + 'props' => [ + 'size' => 'small' + ], + 'children' => [ + 'default' => '减' + ] + ] + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'required' => true, + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('show_list', '列表中显示', 'radio', State::NO['value'], [ + 'required' => true, + 'options' => [ + [ + 'label' => '显示', + 'value' => State::YES['value'] + ], + [ + 'label' => '隐藏', + 'value' => State::NO['value'] + ] + ], + 'prompt' => [ + $Component->add('text', ['default' => '是否在优惠广场列表中显示:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '不可用:在优惠广场中,优惠券列表将不显示该优惠券'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '正常:在优惠广场中,优惠券列表显示可被所有用户领取'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('receive_type', '领取规则', 'radio', ReceiveType::REPEAT_DAY['value'], [ + 'required' => true, + 'options' => ReceiveType::getOptions(), + 'prompt' => [ + $Component->add('text', ['default' => '领取规则:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '账户唯一:该应用用户仅可领取一张,全平台生效'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '每日可领,每周可领,每月可领:用户可在规定时间内未领取则可领取一张'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('use_type', '使用规则', 'radio', UseType::UNLIMITED['value'], [ + 'required' => true, + 'options' => UseType::getOptions(), + 'prompt' => [ + $Component->add('text', ['default' => '使用规则:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '首单可用:新用户首单可用'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '应用首单可用:用户未购买过应用可用'], ['type' => 'info', 'size' => 'small'])->builder(), + ], + 'subProps' => [ + 'border' => true + ] + ]); + $builder->add('server', '适用服务', 'checkbox', [], [ + 'options' => OrdersType::getOptions(), + 'required' => true, + 'subProps' => [ + 'border' => true + ], + 'props' => [ + 'min' => 1, + ], + ]); + return $this->resData($builder); + } + public function release(Request $request) + { + if ($request->method() === 'POST') { + $post = $request->post(); + if (empty($post['coupon_id'])) { + return $this->fail('请选择优惠券'); + } + $num = 1; + if (!empty($post['num'])) { + $num = (int)$post['num']; + if ($num > 1000) { + $num = 1000; + } + } + $start_time = null; + $end_time = null; + if (!empty($post['times'])) { + $start_time = $post['times'][0]; + $end_time = $post['times'][1]; + if ($start_time > $end_time || $end_time < date('Y-m-d H:i:s')) { + return $this->fail('开始时间不能大于结束时间或结束时间不能小于当前时间'); + } + } + Db::startTrans(); + try { + $prefix = 'A'; + $PluginMarketingCoupon = PluginMarketingCoupon::where(['id' => $post['coupon_id'], 'channels_uid' => $request->channels_uid])->find(); + if (!$PluginMarketingCoupon) { + throw new \Exception('优惠券不存在'); + } + $couponData = []; + $createCode = []; + for ($i = 0; $i < $num; $i++) { + $code = PluginMarketingCoupon::getCouponCode($prefix, $post['coupon_id']); + if (in_array($code, $createCode)) { + $i--; + continue; + } + $createCode[] = $code; + $couponData[] = [ + 'channels_uid' => $request->channels_uid, + 'coupon_id' => $post['coupon_id'], + 'coupon_rule' => $PluginMarketingCoupon->coupon_rule, + 'coupon_code' => $code, + 'plugin_id' => $PluginMarketingCoupon->plugin_id, + 'discount' => $PluginMarketingCoupon->discount, + 'full_price' => $PluginMarketingCoupon->full_price, + 'money' => $PluginMarketingCoupon->money, + 'receive_type' => $PluginMarketingCoupon->receive_type, + 'start_time' => $start_time, + 'end_time' => $end_time + ]; + } + if (empty($couponData)) { + return $this->fail('创建失败'); + } + $PluginMarketingCoupon->sum = Db::raw('sum+' . $num); + $PluginMarketingCoupon->num = Db::raw('num+' . $num); + $PluginMarketingCoupon->save(); + $PluginMarketingCouponCode = new PluginMarketingCouponCode(); + $PluginMarketingCouponCode->saveAll($couponData); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail('发行失败:' . $th->getMessage()); + } + return $this->success('发行成功'); + } + $id = $request->get('id'); + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large', + 'submitButtonText' => '发行' + ]); + $Component = new ComponentBuilder; + $builder->add('coupon_id', '优惠券', 'select', (int)$id, [ + 'required' => true, + 'options' => PluginMarketingCoupon::options(), + 'props' => [ + 'placeholder' => '请选择优惠券', + 'disabled' => true + ] + ]); + $builder->add('times', '使用日期', 'date-picker', [], [ + 'prompt' => [ + $Component->add('text', ['default' => '开始和结束日期说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 优惠券需在开始和结束日期之间才可使用'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '2. 未限制开始日期或未限制结束日期则优惠券随时可用'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '3. 如已过结束日期,系统将会清空未被领取的优惠券码'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'value-format' => "YYYY-MM-DD HH:mm:ss", + 'type' => "datetimerange", + 'start-placeholder' => "选择可使用开始日期", + 'end-placeholder' => "选择可使用结束日期" + ] + ]); + $builder->add('day', '有效期', 'input-number', 30, [ + 'required' => true, + 'props' => [ + 'step' => 1, + 'precision' => 0, + 'min' => 1, + 'max' => 365 + ] + ]); + $builder->add('num', '数量', 'input-number', 1, [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '数量说明:'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '1. 批量创建优惠券券码'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '2. 最低为1,最高单次为1000'], ['type' => 'info', 'size' => 'small'])->builder() + + ], + 'props' => [ + 'step' => 1, + 'precision' => 0, + 'min' => 1, + 'max' => 1000 + ] + ]); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/control/controller/IndexController.php b/fastmovie-admin/plugin/marketing/app/control/controller/IndexController.php new file mode 100644 index 0000000..3bdb0dc --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/control/controller/IndexController.php @@ -0,0 +1,96 @@ + 6 + ]); + $today=PluginMarketingCoupon::where('channels_uid', $request->channels_uid)->whereDay('create_time')->count(); + $yesterday=PluginMarketingCoupon::where('channels_uid', $request->channels_uid)->whereDay('create_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('新增优惠券') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today=PluginMarketingCouponCode::where('channels_uid', $request->channels_uid)->whereDay('create_time')->count(); + $yesterday=PluginMarketingCouponCode::where('channels_uid', $request->channels_uid)->whereDay('create_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('发行券码') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today=PluginMarketingCouponCode::where('channels_uid', $request->channels_uid)->whereDay('receive_time')->count(); + $yesterday=PluginMarketingCouponCode::where('channels_uid', $request->channels_uid)->whereDay('receive_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('领取优惠券') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + + $today=PluginMarketingCouponCode::where('channels_uid', $request->channels_uid)->whereDay('use_time')->count(); + $yesterday=PluginMarketingCouponCode::where('channels_uid', $request->channels_uid)->whereDay('use_time','yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('使用优惠券') + ->setUnit('') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/control/controller/PlanController.php b/fastmovie-admin/plugin/marketing/app/control/controller/PlanController.php new file mode 100644 index 0000000..78761ac --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/control/controller/PlanController.php @@ -0,0 +1,475 @@ +model = new PluginMarketingPlan(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addHeader(); + $builder->addHeaderAction('创建会员套餐', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Plan/create', + 'props' => [ + 'title' => '创建会员套餐' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('name', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->addAction('操作', [ + 'width' => '220px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Plan/update', + 'props' => [ + 'title' => '编辑《ID:{id}》会员套餐' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('价格', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Plan/priceList', + 'props' => [ + 'title' => '会员套餐价格列表' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/marketing/control/Plan/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('name', '名称', [ + 'props' => [ + 'width' => '150px' + ] + ]); + $builder->add('description', '名称', [ + 'props' => [ + 'min-width' => '250px' + ] + ]); + $builder->add('key', '套餐类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => PlanKey::getOptions() + ] + ]); + $builder->add('sort', '排序'); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/control/Plan/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $list = PluginMarketingPlan::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $D['channels_uid'] = $request->channels_uid; + $PluginMarketingPlan = new PluginMarketingPlan; + $PluginMarketingPlan->save($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $PluginMarketingPlan = PluginMarketingPlan::where(['id' => $D['id'], 'channels_uid' => $request->channels_uid])->find(); + $PluginMarketingPlan->save($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('更新成功'); + } + $id = $request->get('id'); + $Style = PluginMarketingPlan::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + $builder = $this->getFormBuilder(); + $builder->setData($Style->toArray()); + return $this->resData($builder); + } + private function getFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $builder->add('name', '名称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true, + ]); + $builder->add('key', '套餐类型', 'radio', PlanKey::BASIC['value'], [ + 'options' => PlanKey::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $component = new ComponentBuilder; + $builder->add('description', '描述', 'input', '', [ + 'required' => true, + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ], + 'placeholder' => '描述' + ], + 'prompt' => [ + $component->add('text', ['default' => '描述使用 | 分割标签'], ['type' => 'info', 'size' => 'small'])->builder(), + ] + ]); + $builder->add('sort', '排序', 'input-number', 0, [ + 'required' => true, + 'min' => 0, + 'max' => 100, + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ], + 'required' => true, + ]); + return $builder; + } + + public function delete(Request $request) + { + $id = $request->get('id'); + $PluginMarketingPlan = PluginMarketingPlan::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + PluginMarketingPlanPrice::where(['plan_id' => $id])->delete(); + if (!$PluginMarketingPlan->delete()) { + return $this->fail('删除失败'); + } + return $this->success('删除成功'); + } + + + public function priceListGetTable(Request $request) + { + $id = $request->get('id'); + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '180px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Plan/updatePrice', + 'props' => [ + 'title' => '编辑《ID:{id}》会员套餐价格' + ], + 'params' => [ + 'id' => $id + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建会员套餐价格', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Plan/createPrice', + 'props' => [ + 'title' => '创建会员套餐价格' + ], + 'params' => [ + 'plan_id' => $id + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/marketing/control/Plan/priceDelete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('billing_cycle', '计费周期', 'select', '', [ + 'options' => BillingCycle::getOptions(), + 'props' => [ + 'placeholder' => '计费周期搜索', + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('price', '价格', [ + 'props' => [ + 'width' => '150px' + ] + ]); + $builder->add('original_price', '划线价', [ + 'props' => [ + 'width' => '150px' + ] + ]); + $builder->add('points', '积分', [ + 'props' => [ + 'width' => '150px' + ] + ]); + $builder->add('billing_cycle', '计费周期', [ + 'component' => [ 'name' => 'tag', + 'options' => BillingCycle::getOptions() + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function priceList(Request $request) + { + $id = $request->get('id'); + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['plan_id', '=', $id]; + $list = PluginMarketingPlanPrice::where($where)->order('id desc')->paginate($limit); + return $this->resData($list); + } + + public function createPrice(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $PluginMarketingPlanPrice = new PluginMarketingPlanPrice; + $PluginMarketingPlanPrice->save($D); + } + $builder = $this->getPriceFormBuilder(); + return $this->resData($builder); + } + public function updatePrice(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $PluginMarketingPlanPrice = PluginMarketingPlanPrice::where(['id' => $D['id']])->find(); + $PluginMarketingPlanPrice->save($D); + return $this->success('更新成功'); + } + $id = $request->get('id'); + $PluginMarketingPlanPrice = PluginMarketingPlanPrice::where(['id' => $id])->find(); + if (!$PluginMarketingPlanPrice) { + return $this->fail('会员套餐价格不存在'); + } + $builder = $this->getPriceFormBuilder(); + $builder->setData($PluginMarketingPlanPrice->toArray()); + return $this->resData($builder); + } + private function getPriceFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $builder->add('price', '价格', 'input-number', 0, [ + 'required' => true, + 'min' => 0, + 'max' => 100, + 'props' => [ + 'controls' => false + ] + ]); + $builder->add('original_price', '划线价', 'input-number', 0, [ + 'required' => true, + 'min' => 0, + 'max' => 100, + 'props' => [ + 'controls' => false + ] + ]); + $builder->add('points', '积分', 'input-number', 0, [ + 'required' => true, + 'min' => 0, + 'max' => 100, + 'props' => [ + 'controls' => false + ] + ]); + $builder->add('billing_cycle', '计费周期', 'radio', BillingCycle::MONTH['value'], [ + 'options' => BillingCycle::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + return $builder; + } + + public function priceDelete(Request $request) + { + $id = $request->post('id'); + $PluginMarketingPlanPrice = PluginMarketingPlanPrice::where(['id' => $id])->find(); + if (!$PluginMarketingPlanPrice) { + return $this->fail('会员套餐价格不存在'); + } + if ($PluginMarketingPlanPrice->delete()) { + return $this->success('删除成功'); + } + return $this->fail('失败'); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/control/controller/PointsController.php b/fastmovie-admin/plugin/marketing/app/control/controller/PointsController.php new file mode 100644 index 0000000..dd01d6c --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/control/controller/PointsController.php @@ -0,0 +1,243 @@ +model = new PluginMarketingPoints(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '180px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Points/update', + 'props' => [ + 'title' => '编辑《ID:{id}》积分套餐' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建积分套餐', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/marketing/control/Points/create', + 'props' => [ + 'title' => '创建积分套餐' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('name', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('name', '名称', [ + 'props' => [ + 'width' => '150px' + ] + ]); + $builder->add('price', '价格', []); + $builder->add('original_price', '原价', []); + $builder->add('points', '积分', []); + $builder->add('give', '操作次数', []); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/marketing/control/Points/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $classify = $request->get('classify'); + if ($classify) { + $where[] = ['classify', '=', $classify]; + } + $list = PluginMarketingPoints::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $D['channels_uid'] = $request->channels_uid; + $PluginMarketingPoints = new PluginMarketingPoints; + $PluginMarketingPoints->save($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $PluginMarketingPoints = PluginMarketingPoints::where(['id' => $D['id'], 'channels_uid' => $request->channels_uid])->find(); + $PluginMarketingPoints->save($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('更新成功'); + } + $id = $request->get('id'); + $Style = PluginMarketingPoints::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + $Style->discount =(int)$Style->discount > 0 ? 1 : 0; + $builder = $this->getFormBuilder(); + $builder->setData($Style->toArray()); + return $this->resData($builder); + } + private function getFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $builder->add('name', '名称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true, + ]); + $builder->add('desc', '描述', 'input', '', [ + 'required' => true, + ]); + $builder->add('price', '价格', 'input-number', '', [ + 'required' => true, + 'props' => [ + 'controls' => false + ] + ]); + $builder->add('original_price', '原价', 'input-number', '', [ + 'required' => true, + 'props' => [ + 'controls' => false + ] + ]); + $builder->add('points', '积分', 'input-number', '', [ + 'required' => true, + 'props' => [ + 'controls' => false + ] + ]); + $builder->add('give', '赠送积分', 'input-number', '', [ + 'required' => true, + 'props' => [ + 'controls' => false + ] + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ], + 'required' => true, + ]); + $builder->add('discount', '是否优惠', 'radio', 1, [ + 'options' => [ + [ + 'label' => '优惠', + 'value' => '1' + ], + [ + 'label' => '无', + 'value' => '0' + ] + ], + 'subProps' => [ + 'border' => true + ], + 'required' => true, + ]); + return $builder; + } + + public function delete(Request $request) + { + $id = $request->get('id'); + $PluginMarketingPoints = PluginMarketingPoints::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$PluginMarketingPoints) { + return $this->fail('积分套餐不存在'); + } + if ($PluginMarketingPoints->delete()) { + return $this->success('删除成功'); + } + return $this->fail('删除失败'); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/functions.php b/fastmovie-admin/plugin/marketing/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/functions.php @@ -0,0 +1,6 @@ + [ + // 设置JSON字段的类型 + 'discount' => 'float', + 'full_price' => 'float', + 'money' => 'float', + ] + ]; + } + public function password() + { + return $this->hasMany(PluginMarketingCouponPassword::class, 'id', 'coupon_id'); + } + public function server() + { + return $this->hasMany(PluginMarketingCouponServer::class, 'coupon_id', 'id'); + } + /* public function user() + { + return $this->hasOne(); + } */ + public static function options($where = []) + { + $models = self::where($where)->order('id desc')->select(); + $data = []; + foreach ($models as $item) { + $data[] = [ + 'label' => $item->title, + 'value' => $item->id, + 'tips' => "折扣:{$item->discount_text}" + ]; + } + return $data; + } + protected function sceneExternal() + { + return $this->visible(['title', 'coupon_rule', 'discount', 'full_price', 'money', 'receive_type', 'use_type', 'stackable', 'num', 'receive_num', 'sum', 'discount_text', 'code', 'percentage', 'receive', 'exclusive', 'scope', 'server', 'receive_type_text', 'use_type_text']); + } + public static function onAfterRead($model) + { + if ($model->coupon_rule) { + if ($model->coupon_rule === CouponRule::DISCOUNT['value']) { + $model->discount_text = ($model->discount * 10) . '折'; + } else { + if ($model->full_price > 0) { + $model->discount_text = "满{$model->full_price}元减{$model->money}元"; + } else { + $model->discount_text = "直减{$model->money}元"; + } + } + } + // coupon_id转16进制 + $coupon_id = dechex((int)$model->id); + // coupon_id长度为6位,不足6位前面补0 + $model->code = 'A' . str_pad($coupon_id, 6, '0', STR_PAD_LEFT); + } + public static function codeToId($code) + { + return hexdec(substr($code, 1)); + } + + /** + * 获取优惠券码 + */ + public static function getCouponCode($prefix, $coupon_id) + { + // coupon_id转16进制 + $coupon_id = dechex((int)$coupon_id); + // coupon_id长度为6位,不足6位前面补0 + $coupon_id = str_pad($coupon_id, 6, '0', STR_PAD_LEFT); + $createDate = date('yWz'); + $round = Str::random(6); + // 生成优惠券码 + $code = "{$prefix}{$coupon_id}-{$createDate}-{$round}"; + $couponCode = PluginMarketingCouponCode::where(['coupon_code' => $code])->find(); + if ($couponCode) { + self::getCouponCode($prefix, $coupon_id); + } + return $code; + } +} diff --git a/fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponCode.php b/fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponCode.php new file mode 100644 index 0000000..3d0f85b --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponCode.php @@ -0,0 +1,111 @@ + [ + // 设置JSON字段的类型 + 'discount' => 'float', + 'full_price' => 'float', + 'money' => 'float', + ] + ]; + } + public function user() + { + return $this->hasOne(PluginUser::class, 'id', 'uid'); + } + public function coupon() + { + return $this->hasOne(PluginMarketingCoupon::class, 'id', 'coupon_id'); + } + protected function sceneExternal() + { + return $this->visible(['coupon_code', 'coupon_rule', 'state', 'state_text', 'discount', 'full_price', 'money', 'start_time', 'end_time', 'expire_time', 'receive_time', 'coupon', 'exclusive', 'scope', 'update_scope', 'support_scope', 'role', 'receive_type_text', 'use_type_text', 'server']); + } + /** + * 使用优惠券 + * @param PluginFinanceOrders 订单 + * @param PluginMarketingCouponCode 所选优惠券列表 + * @param null $extra 额外参数 + * @return array 返回可使用优惠券码id + * @throws \Exception + */ + public static function useCoupon($Order, $Coupons, $extra = null) + { + $Order->system_money = 0; + $result = []; + + foreach ($Coupons as $item) { + $coupon = $item->coupon; + $server = $coupon->server()->column('server'); + if (!empty($server) && !in_array($Order->type, $server)) { + throw new \Exception("优惠券「{$coupon->title}」不适用于当前服务"); + } + + // 3. 校验使用类型 + switch ($coupon->use_type) { + case UseType::FIRST_ORDER['value']: + $hasPaidOrder = PluginFinanceOrders::where(['uid' => $Order->uid]) + ->whereIn('state', self::$validOrderStates) + ->count(); + if ($hasPaidOrder > 0) { + throw new \Exception("优惠券「{$coupon->title}」仅限首单使用"); + } + break; + } + + // 5. 优惠金额计算(先复制当前金额) + $newMoney = $Order->money; + + switch ($coupon->coupon_rule) { + case CouponRule::DISCOUNT['value']: + if ($coupon->discount > 0 && $coupon->discount < 1) { + $newMoney = $newMoney * $coupon->discount; + } + break; + + case CouponRule::FULL_PRICE['value']: + if ($coupon->full_price > 0 && $Order->origin_money < $coupon->full_price) { + throw new \Exception("优惠券「{$coupon->title}」不满足满减条件"); + } + $newMoney -= $coupon->money; + break; + } + + $newMoney = max(0, $newMoney); + $discountAmount = $Order->money - $newMoney; + + // 6. 更新订单金额及归属 + $Order->money = $newMoney; + $Order->system_money += $discountAmount; + + // 7. 记录已使用的券码ID + $result[] = $item->id; + } + + return $result; + } +} diff --git a/fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponPassword.php b/fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponPassword.php new file mode 100644 index 0000000..ad1d3fc --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/model/PluginMarketingCouponPassword.php @@ -0,0 +1,10 @@ +hasMany(PluginMarketingPlanPrice::class, 'plan_id', 'id'); + } +} diff --git a/fastmovie-admin/plugin/marketing/app/model/PluginMarketingPlanPrice.php b/fastmovie-admin/plugin/marketing/app/model/PluginMarketingPlanPrice.php new file mode 100644 index 0000000..a73748b --- /dev/null +++ b/fastmovie-admin/plugin/marketing/app/model/PluginMarketingPlanPrice.php @@ -0,0 +1,10 @@ + 'require', + 'coupon_rule' => 'require', + 'discount' => 'requireIf:coupon_rule,discount', + 'full_price' => 'requireIf:coupon_rule,full_price', + 'money' => 'requireIf:coupon_rule,full_price', + 'receive_type' => 'require', + 'use_type' => 'require', + ]; + + protected $message = [ + 'title.require' => '请输入优惠券名', + 'coupon_rule.require'=>'请选择优惠策略', + 'discount.requireIf'=>'请输入折扣', + 'full_price.requireIf'=>'请输入满减策略', + 'money.requireIf'=>'请输入满减策略', + 'receive_type.require'=>'请选择领取规则', + 'use_type.require'=>'请选择使用规则', + ]; +} diff --git a/fastmovie-admin/plugin/marketing/config/app.php b/fastmovie-admin/plugin/marketing/config/app.php new file mode 100644 index 0000000..bc3fd0f --- /dev/null +++ b/fastmovie-admin/plugin/marketing/config/app.php @@ -0,0 +1,9 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/marketing/config/autoload.php b/fastmovie-admin/plugin/marketing/config/autoload.php new file mode 100644 index 0000000..67b9d3f --- /dev/null +++ b/fastmovie-admin/plugin/marketing/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/marketing/app/functions.php', + ] +]; diff --git a/fastmovie-admin/plugin/marketing/config/container.php b/fastmovie-admin/plugin/marketing/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/marketing/config/container.php @@ -0,0 +1,2 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/marketing/config/log.php b/fastmovie-admin/plugin/marketing/config/log.php new file mode 100644 index 0000000..dcdf3e6 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/config/log.php @@ -0,0 +1,20 @@ + [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/api.log', + 7, + \Monolog\Level::Debug, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/fastmovie-admin/plugin/marketing/config/middleware.php b/fastmovie-admin/plugin/marketing/config/middleware.php new file mode 100644 index 0000000..3e04c72 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/config/middleware.php @@ -0,0 +1,18 @@ + [ + app\expose\middleware\Platform::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'control' => [ + plugin\control\expose\middleware\ControlAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ] +]; diff --git a/fastmovie-admin/plugin/marketing/config/process.php b/fastmovie-admin/plugin/marketing/config/process.php new file mode 100644 index 0000000..6be9666 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/config/process.php @@ -0,0 +1,10 @@ + [ + 'eventLoop' => Select::class, + 'handler' => Expire::class + ], +]; diff --git a/fastmovie-admin/plugin/marketing/config/translation.php b/fastmovie-admin/plugin/marketing/config/translation.php new file mode 100644 index 0000000..6787b20 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/marketing/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/control.json b/fastmovie-admin/plugin/marketing/control.json new file mode 100644 index 0000000..5b18d83 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/control.json @@ -0,0 +1,138 @@ +{ + "title": "营销", + "icon": "Discount", + "path": "/app/marketing/control", + "sort": 5, + "children": [ + { + "title": "优惠券", + "icon": "Coupon", + "path": "/app/marketing/control/Coupon/index", + "component": "table", + "show":false, + "children": [ + { + "title": "创建优惠券", + "path": "/app/marketing/control/Coupon/create", + "component": "form", + "show": false + }, + { + "title": "发行优惠券码", + "path": "/app/marketing/control/Coupon/release", + "component": "form", + "show": false + }, + { + "title": "设置优惠券状态", + "path": "/app/marketing/control/Coupon/indexUpdateState", + "show": false + } + ] + }, + { + "title": "优惠券码", + "icon": "BarCode", + "path": "/app/marketing/control/CouponCode/index", + "component": "table", + "show":false, + "children": [ + { + "title": "删除券码", + "path": "/app/marketing/control/CouponCode/delete", + "show": false + }, + { + "title": "作废券码", + "path": "/app/marketing/control/CouponCode/zuofe", + "show": false + } + ] + }, + { + "title": "积分套餐", + "icon": "CreditCard", + "path": "/app/marketing/control/Points/index", + "component": "table", + "children": [ + { + "title": "创建积分套餐", + "path": "/app/marketing/control/Points/create", + "component": "form", + "show": false + }, + { + "title": "编辑积分套餐", + "path": "/app/marketing/control/Points/update", + "component": "form", + "show": false + }, + { + "title": "删除积分套餐", + "path": "/app/marketing/control/Points/delete", + "show": false + }, + { + "title": "设置积分套餐状态", + "path": "/app/marketing/control/Points/indexUpdateState", + "show": false + } + ] + }, + { + "title": "会员套餐", + "icon": "CreditCard", + "path": "/app/marketing/control/Plan/index", + "component": "table", + "children": [ + { + "title": "创建会员套餐", + "path": "/app/marketing/control/Plan/create", + "component": "form", + "show": false + }, + { + "title": "编辑会员套餐", + "path": "/app/marketing/control/Plan/update", + "component": "form", + "show": false + }, + { + "title": "会员套餐价格列表", + "path": "/app/marketing/control/Plan/priceList", + "component": "table", + "show": false, + "children": [ + { + "title": "创建会员套餐价格", + "path": "/app/marketing/control/Plan/createPrice", + "component": "form", + "show": false + }, + { + "title": "编辑会员套餐价格", + "path": "/app/marketing/control/Plan/updatePrice", + "component": "form", + "show": false + }, + { + "title": "删除会员套餐价格", + "path": "/app/marketing/control/Plan/deletePrice", + "show": false + } + ] + }, + { + "title": "删除会员套餐", + "path": "/app/marketing/control/Plan/delete", + "show": false + }, + { + "title": "设置会员套餐状态", + "path": "/app/marketing/control/Plan/indexUpdateState", + "show": false + } + ] + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/package.json b/fastmovie-admin/plugin/marketing/package.json new file mode 100644 index 0000000..a43749a --- /dev/null +++ b/fastmovie-admin/plugin/marketing/package.json @@ -0,0 +1,10 @@ +{ + "name": "marketing", + "title": "营销", + "version": "0.0.1", + "description": "各种营销工具", + "author": { + "name": "余心", + "email": "unknown.renloong@gmail.com" + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/process/Expire.php b/fastmovie-admin/plugin/marketing/process/Expire.php new file mode 100644 index 0000000..fc7b0ad --- /dev/null +++ b/fastmovie-admin/plugin/marketing/process/Expire.php @@ -0,0 +1,53 @@ + UseState::ON['value']])->whereNull('uid')->whereTime('end_time', '<=', date('Y-m-d H:i:s'))->limit(20)->select(); + foreach ($data as $item) { + Db::startTrans(); + try { + $PluginMarketingCoupon = PluginMarketingCoupon::where(['id' => $item->coupon_id])->find(); + $PluginMarketingCoupon->num = Db::raw('num-1'); + $PluginMarketingCoupon->expire_num = Db::raw('expire_num+1'); + $PluginMarketingCoupon->save(); + $item->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('Process Coupon Expire Error:'.$th->getMessage(),$th); + } + } + $data = PluginMarketingCouponCode::where(['state' => UseState::ON['value']])->whereNotNull('uid')->whereTime('expire_time', '<=', date('Y-m-d H:i:s'))->limit(20)->select(); + foreach ($data as $item) { + Db::startTrans(); + try { + $PluginMarketingCoupon = PluginMarketingCoupon::where(['id' => $item->coupon_id])->find(); + $PluginMarketingCoupon->expire_num = Db::raw('expire_num+1'); + $PluginMarketingCoupon->save(); + $item->state=UseState::EXPIRE['value']; + $item->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('Process UserCoupon Expire Error:'.$th->getMessage(),$th); + } + } + } catch (\Throwable $th) { + } + }); + } +} diff --git a/fastmovie-admin/plugin/marketing/resource/translations/en/admin.php b/fastmovie-admin/plugin/marketing/resource/translations/en/admin.php new file mode 100644 index 0000000..5e9f768 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/en/admin.php @@ -0,0 +1,5 @@ +'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/resource/translations/en/api.php b/fastmovie-admin/plugin/marketing/resource/translations/en/api.php new file mode 100644 index 0000000..45a7854 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/en/api.php @@ -0,0 +1,6 @@ + 'Success', + 'fail' => 'Fail', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/resource/translations/en/control.php b/fastmovie-admin/plugin/marketing/resource/translations/en/control.php new file mode 100644 index 0000000..a2a633c --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/en/control.php @@ -0,0 +1,68 @@ + 'Dashboard', + 'Console' => 'Console', + 'User' => 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'Update Status' => 'Update Status', + 'Functional' => 'Functional', + 'Administrator' => 'Administrator', + 'Staff List' => 'Staff List', + 'Create Administrator' => 'Create Administrator', + 'Edit Administrator' => 'Edit Administrator', + 'Delete Administrator' => 'Delete Administrator', + 'Move Role' => 'Move Role', + 'Role Management' => 'Role Management', + 'Role List' => 'Role List', + 'Create Role' => 'Create Role', + 'Edit Role' => 'Edit Role', + 'Delete Role' => 'Delete Role', + 'Systems' => 'Systems', + 'Basic Settings' => 'Basic Settings', + 'Control Settings' => 'Control Settings', + 'Upload Settings' => 'Upload Settings', + 'SMS Settings' => 'SMS Settings', + 'Payment Management' => 'Payment Management', + 'Payment Settings' => 'Payment Settings', + 'Payment Template' => 'Payment Template', + 'Create Payment Template' => 'Create Payment Template', + 'Edit Payment Template' => 'Edit Payment Template', + 'Delete Payment Template' => 'Delete Payment Template', + 'Platform Management' => 'Platform Management', + 'WeChat Mini Program' => 'WeChat Mini Program', + 'WeChat Official Account' => 'WeChat Official Account', + 'Alipay Mini Program' => 'Alipay Mini Program', + 'WeChat Open Platform' => 'WeChat Open Platform', + 'App Platform' => 'App Platform', + 'H5 Platform' => 'H5 Platform', + 'PC Platform' => 'PC Platform', + 'Public Permissions' => 'Public Permissions', + 'Edit Profile' => 'Edit Profile', + 'Get Profile' => 'Get Profile', + 'Logout' => 'Logout', + 'Get Upload Categories' => 'Get Upload Categories', + 'Edit Upload Category' => 'Edit Upload Category', + 'Delete Upload Category' => 'Delete Upload Category', + 'Upload File' => 'Upload File', + 'Delete Upload File' => 'Delete Upload File', + 'Edit Upload File' => 'Edit Upload File', + 'Get Upload File List' => 'Get Upload File List', + 'Login Title'=>'Admin Login', + 'toolbar Lock'=>'Lock', + 'toolbar Search'=>'Search', + 'toolbar Notification'=>'Notification', + 'toolbar FullScreen'=>'FullScreen', + 'toolbar House'=>'Home', + 'toolbar Control'=>'Control', + 'userDropdownMenu updateSelf'=>'Admin Info', + 'Logout Success'=>'Logout Success', + 'PIN Code Cannot Be Empty'=>'PIN Code Cannot Be Empty', + 'Please Enter 6 Digits PIN Code'=>'Please Enter 6 Digits PIN Code', + 'Lock Success'=>'Lock Success', + 'Unlock Success'=>'Unlock Success', + 'form_basic_title'=>'Basic', + 'Settings_basic_web_name'=>'Apps Name' +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/resource/translations/en/messages.php b/fastmovie-admin/plugin/marketing/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..c9d49f9 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/admin.php @@ -0,0 +1,5 @@ +'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/api.php b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/api.php new file mode 100644 index 0000000..40bd25b --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/api.php @@ -0,0 +1,6 @@ + '成功', + 'fail' => '失败', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/control.php b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/control.php new file mode 100644 index 0000000..fd67c8e --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/control.php @@ -0,0 +1,68 @@ + '首页', + 'Console' => '控制台', + 'User' => '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'Update Status' => '更新状态', + 'Functional' => '职能', + 'Administrator' => '管理员', + 'Staff List' => '人员列表', + 'Create Administrator' => '创建管理员', + 'Edit Administrator' => '编辑管理员', + 'Delete Administrator' => '删除管理员', + 'Move Role' => '移动角色', + 'Role Management' => '角色', + 'Role List' => '角色管理', + 'Create Role' => '创建角色', + 'Edit Role' => '编辑角色', + 'Delete Role' => '删除角色', + 'Systems' => '系统', + 'Basic Settings' => '基本配置', + 'Control Settings' => '控制台配置', + 'Upload Settings' => '上传配置', + 'SMS Settings' => '短信配置', + 'Payment Management' => '支付管理', + 'Payment Settings' => '支付配置', + 'Payment Template' => '支付模板', + 'Create Payment Template' => '新增支付模板', + 'Edit Payment Template' => '编辑支付模板', + 'Delete Payment Template' => '删除支付模板', + 'Platform Management' => '平台管理', + 'WeChat Mini Program' => '微信小程序', + 'WeChat Official Account' => '微信公众号', + 'Alipay Mini Program' => '支付宝小程序', + 'WeChat Open Platform' => '微信开放平台', + 'App Platform' => 'APP', + 'H5 Platform' => 'H5', + 'PC Platform' => 'PC', + 'Public Permissions' => '公共权限', + 'Edit Profile' => '修改自己的信息', + 'Get Profile' => '获取自己的信息', + 'Logout' => '退出登录', + 'Get Upload Categories' => '获取上传分类', + 'Edit Upload Category' => '编辑上传分类', + 'Delete Upload Category' => '删除上传分类', + 'Upload File' => '上传文件', + 'Delete Upload File' => '删除上传文件', + 'Edit Upload File' => '编辑上传文件', + 'Get Upload File List' => '获取上传文件列表', + 'Login Title'=>'管理员登录', + 'toolbar Lock'=>'锁屏', + 'toolbar Search'=>'搜索菜单', + 'toolbar Notification'=>'查看通知', + 'toolbar FullScreen'=>'全屏/退出全屏', + 'toolbar House'=>'网站首页', + 'toolbar Control'=>'网站前台控制台', + 'userDropdownMenu updateSelf'=>'管理员信息', + 'Logout Success'=>'退出成功', + 'PIN Code Cannot Be Empty'=>'PIN码不能为空', + 'Please Enter 6 Digits PIN Code'=>'请输入6位PIN码', + 'Lock Success'=>'锁定成功', + 'Unlock Success'=>'解锁成功', + 'form_basic_title'=>'基础信息', + 'Settings_basic_web_name'=>'应用名称' +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/marketing/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/marketing/utils/enum/BillingCycle.php b/fastmovie-admin/plugin/marketing/utils/enum/BillingCycle.php new file mode 100644 index 0000000..7125daf --- /dev/null +++ b/fastmovie-admin/plugin/marketing/utils/enum/BillingCycle.php @@ -0,0 +1,17 @@ + '按月', + 'value' => 'month' + ]; + const YEAR = [ + 'label' => '按年', + 'value' => 'year' + ]; +} diff --git a/fastmovie-admin/plugin/marketing/utils/enum/CouponRule.php b/fastmovie-admin/plugin/marketing/utils/enum/CouponRule.php new file mode 100644 index 0000000..ad39aa2 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/utils/enum/CouponRule.php @@ -0,0 +1,17 @@ + '满减券', + 'value' => 'full_price' + ]; + const DISCOUNT = [ + 'label' => '打折券', + 'value' => 'discount' + ]; +} diff --git a/fastmovie-admin/plugin/marketing/utils/enum/PlanKey.php b/fastmovie-admin/plugin/marketing/utils/enum/PlanKey.php new file mode 100644 index 0000000..5805ceb --- /dev/null +++ b/fastmovie-admin/plugin/marketing/utils/enum/PlanKey.php @@ -0,0 +1,17 @@ + '基础版', + 'value' => 'basic' + ]; + const PRO = [ + 'label' => '专业版', + 'value' => 'pro' + ]; +} diff --git a/fastmovie-admin/plugin/marketing/utils/enum/ReceiveType.php b/fastmovie-admin/plugin/marketing/utils/enum/ReceiveType.php new file mode 100644 index 0000000..cb3bd8f --- /dev/null +++ b/fastmovie-admin/plugin/marketing/utils/enum/ReceiveType.php @@ -0,0 +1,37 @@ + '每日可领', + 'value' => 'repeat_day', + 'props' => [ + 'type' => 'primary' + ] + ]; + const REPEAT_WEEK= [ + 'label' => '每周可领', + 'value' => 'repeat_week', + 'props' => [ + 'type' => 'primary' + ] + ]; + const REPEAT_MONTH = [ + 'label' => '每月可领', + 'value' => 'repeat_month', + 'props' => [ + 'type' => 'primary' + ] + ]; + const USER_ONLY = [ + 'label' => '账户唯一', + 'value' => 'user_only', + 'props' => [ + 'type' => 'success' + ] + ]; +} diff --git a/fastmovie-admin/plugin/marketing/utils/enum/UseState.php b/fastmovie-admin/plugin/marketing/utils/enum/UseState.php new file mode 100644 index 0000000..0ecc9bf --- /dev/null +++ b/fastmovie-admin/plugin/marketing/utils/enum/UseState.php @@ -0,0 +1,30 @@ + '可用', + 'value' => 0, + 'props' => [ + 'type' => 'success' + ] + ]; + const USED = [ + 'label' => '已使用', + 'value' => 10, + 'props' => [ + 'type' => 'info' + ] + ]; + const EXPIRE= [ + 'label' => '过期', + 'value' => 99, + 'props' => [ + 'type' => 'danger' + ] + ]; +} diff --git a/fastmovie-admin/plugin/marketing/utils/enum/UseType.php b/fastmovie-admin/plugin/marketing/utils/enum/UseType.php new file mode 100644 index 0000000..70d0634 --- /dev/null +++ b/fastmovie-admin/plugin/marketing/utils/enum/UseType.php @@ -0,0 +1,23 @@ + '无限制', + 'value' => 'unlimited', + 'props' => [ + 'type' => 'danger' + ] + ]; + const FIRST_ORDER = [ + 'label' => '首单可用', + 'value' => 'first_order', + 'props' => [ + 'type' => 'success' + ] + ]; +} diff --git a/fastmovie-admin/plugin/model/admin.json b/fastmovie-admin/plugin/model/admin.json new file mode 100644 index 0000000..e13b2f5 --- /dev/null +++ b/fastmovie-admin/plugin/model/admin.json @@ -0,0 +1,18 @@ +{ + "title": "模型", + "icon": "Grid", + "path": "/app/model/admin", + "sort": 999, + "children": [ + { + "title": "模型管理", + "path": "/app/model/admin/Model/index", + "component": "table" + }, + { + "title": "任务列表", + "path": "/app/model/admin/Task/index", + "component": "table" + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/api/Control.php b/fastmovie-admin/plugin/model/api/Control.php new file mode 100644 index 0000000..d66bdc6 --- /dev/null +++ b/fastmovie-admin/plugin/model/api/Control.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'model'; + } +} diff --git a/fastmovie-admin/plugin/model/api/Install.php b/fastmovie-admin/plugin/model/api/Install.php new file mode 100644 index 0000000..e49a70e --- /dev/null +++ b/fastmovie-admin/plugin/model/api/Install.php @@ -0,0 +1,15 @@ +plugin='model'; + $this->path = __DIR__; + } +} diff --git a/fastmovie-admin/plugin/model/app/admin/controller/ModelController.php b/fastmovie-admin/plugin/model/app/admin/controller/ModelController.php new file mode 100644 index 0000000..928dd16 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/admin/controller/ModelController.php @@ -0,0 +1,213 @@ +model = new PluginModel(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('name', '模型名称', 'input', '', [ + 'props' => [ + 'placeholder' => '模型名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('model_type', '模型类型', 'select', '', [ + 'options' => ModelType::getOptions(), + 'props' => [ + 'placeholder' => '模型类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('model_name', '模型名称(壹定)', 'input', '', [ + 'props' => [ + 'placeholder' => '模型名称(壹定)搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('assistant_name', '助手名称', 'input', '', [ + 'props' => [ + 'placeholder' => '助手名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'options' => ModelScene::getOptions(), + 'props' => [ + 'placeholder' => '场景搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('icon', '模型图标', [ + 'props' => [ + 'width' => '100px' + ], + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width:40px;height:40px;' + ] + ] + ]); + $builder->add('name', '模型名称(本地)', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_type', '模型类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelType::getOptions() + ], + ]); + $builder->add('model_name', '模型名称(壹定)', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('assistant_name', '助手名称', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('sort', '排序(小到大)', [ + 'props' => [ + 'width' => '130px' + ] + ]); + $builder->add('point', '积分', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('scene', '场景', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelScene::getOptions() + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => State::getOptions() + ], + ]); + $builder->add('description', '描述', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $model_name = $request->get('model_name'); + if ($model_name) { + $where[] = ['model_name', 'like', "%{$model_name}%"]; + } + $assistant_name = $request->get('assistant_name'); + if ($assistant_name) { + $where[] = ['assistant_name', 'like', "%{$assistant_name}%"]; + } + $scene = $request->get('scene'); + if ($scene) { + $where[] = ['scene', '=', $scene]; + } + $model_type = $request->get('model_type'); + if ($model_type) { + $where[] = ['model_type', '=', $model_type]; + } + $list = PluginModel::where($where)->with(['channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }])->order('id desc')->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/model/app/admin/controller/TaskController.php b/fastmovie-admin/plugin/model/app/admin/controller/TaskController.php new file mode 100644 index 0000000..eeadc25 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/admin/controller/TaskController.php @@ -0,0 +1,271 @@ +model = new PluginModelTask(); + } + + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('task_id', '任务ID', 'input', '', [ + 'props' => [ + 'placeholder' => '任务ID搜索', + ] + ]); + $formBuilder->add('user_id', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + ] + ]); + $formBuilder->add('model_type', '模型类型', 'select', '', [ + 'options' => ModelType::getOptions(), + 'props' => [ + 'placeholder' => '模型类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('status', '状态', 'select', '', [ + 'options' => ModelTaskStatus::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + + $formBuilder->add('model_name', '模型名称(壹定)', 'input', '', [ + 'props' => [ + 'placeholder' => '模型名称(壹定)搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'options' => ModelScene::getOptions(), + 'props' => [ + 'placeholder' => '场景搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + // $builder->addAction('操作', [ + // 'width' => '200px', + // 'fixed' => 'right' + // ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels.headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('task_id', '任务ID', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_name', '模型名称', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_type', '模型类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelType::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('status', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelTaskStatus::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('scene', '场景', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelScene::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('result.image_path', '结果图片', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width:40px;height:40px;' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('result.video_path', '结果视频', [ + 'component' => [ + 'name' => 'video', + 'props' => [ + 'style' => 'width:40px;height:40px;' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('result.message', '错误消息', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'text', + 'props' => [ + 'type' => 'danger' + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['pmt.channels_uid', '=', $channels_uid]; + } + $model_name = $request->get('model_name'); + if ($model_name) { + $where[] = ['pm.name', 'like', "%{$model_name}%"]; + } + $model_type = $request->get('model_type'); + if ($model_type) { + $where[] = ['pm.model_type', '=', $model_type]; + } + $scene = $request->get('scene'); + if ($scene) { + $where[] = ['pm.scene', '=', $scene]; + } + $status = $request->get('status'); + if ($status) { + $where[] = ['pmt.status', '=', $status]; + } + $task_id = $request->get('task_id'); + if ($task_id) { + $where[] = ['pmt.task_id', '=', $task_id]; + } + $user_id = $request->get('user_id'); + if ($user_id) { + $where[] = ['pmt.uid', '=', $user_id]; + } + $list = PluginModelTask::alias('pmt') + ->join('plugin_model pm', 'pmt.model_id = pm.id') + ->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }, 'result' => function ($query) { + $query->field('task_id,result,image,image_path,video_path,message,channels_uid'); + }, 'channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->field('pmt.*,pm.name as model_name') + ->where($where) + ->order('pmt.id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/model/app/admin/controller/VoiceController.php b/fastmovie-admin/plugin/model/app/admin/controller/VoiceController.php new file mode 100644 index 0000000..4019f3b --- /dev/null +++ b/fastmovie-admin/plugin/model/app/admin/controller/VoiceController.php @@ -0,0 +1,271 @@ +model = new PluginModelVoice(); + } + + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('task_id', '任务ID', 'input', '', [ + 'props' => [ + 'placeholder' => '任务ID搜索', + ] + ]); + $formBuilder->add('user_id', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + ] + ]); + $formBuilder->add('model_type', '模型类型', 'select', '', [ + 'options' => ModelType::getOptions(), + 'props' => [ + 'placeholder' => '模型类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('status', '状态', 'select', '', [ + 'options' => ModelVoiceStatus::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + + $formBuilder->add('model_name', '模型名称(壹定)', 'input', '', [ + 'props' => [ + 'placeholder' => '模型名称(壹定)搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'options' => ModelScene::getOptions(), + 'props' => [ + 'placeholder' => '场景搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + // $builder->addAction('操作', [ + // 'width' => '200px', + // 'fixed' => 'right' + // ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels.headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('task_id', '任务ID', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_name', '模型名称', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_type', '模型类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelType::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('status', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelVoiceStatus::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('scene', '场景', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelScene::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('result.image_path', '结果图片', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width:40px;height:40px;' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('result.video_path', '结果视频', [ + 'component' => [ + 'name' => 'video', + 'props' => [ + 'style' => 'width:40px;height:40px;' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('result.message', '错误消息', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'text', + 'props' => [ + 'type' => 'danger' + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['pmt.channels_uid', '=', $channels_uid]; + } + $model_name = $request->get('model_name'); + if ($model_name) { + $where[] = ['pm.name', 'like', "%{$model_name}%"]; + } + $model_type = $request->get('model_type'); + if ($model_type) { + $where[] = ['pm.model_type', '=', $model_type]; + } + $scene = $request->get('scene'); + if ($scene) { + $where[] = ['pm.scene', '=', $scene]; + } + $status = $request->get('status'); + if ($status) { + $where[] = ['pmt.status', '=', $status]; + } + $task_id = $request->get('task_id'); + if ($task_id) { + $where[] = ['pmt.task_id', '=', $task_id]; + } + $user_id = $request->get('user_id'); + if ($user_id) { + $where[] = ['pmt.uid', '=', $user_id]; + } + $list = PluginModelVoice::alias('pmt') + ->join('plugin_model pm', 'pmt.model_id = pm.id') + ->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }, 'result' => function ($query) { + $query->field('task_id,result,image,image_path,video_path,message,channels_uid'); + }, 'channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->field('pmt.*,pm.name as model_name') + ->where($where) + ->order('pmt.id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/model/app/api/controller/ModelController.php b/fastmovie-admin/plugin/model/app/api/controller/ModelController.php new file mode 100644 index 0000000..1ad22ab --- /dev/null +++ b/fastmovie-admin/plugin/model/app/api/controller/ModelController.php @@ -0,0 +1,43 @@ + State::YES['value']])->order('sort asc')->field('id,channels_uid,icon,name,point,point_type,scene,description,form')->select()->each(function ($item) { + $item->unit_point = $item->point; + if ($item->point_type == ModelPointType::FIELD_RULE['value']) { + $formData = [ + 'resolution' => '720P', + 'duration' => 5, + ]; + $calculator = new PricingCalculator($item->form, $formData, $item->point); + $item->point = $calculator->calculate() / 5; + } + }); + $models = []; + foreach ($PluginModel as $model) { + $models[$model->scene][] = [ + 'id' => $model->id, + 'icon' => $model->icon, + 'name' => $model->name, + 'point' => $model->point, + 'scene' => $model->scene, + 'description' => $model->description, + 'form' => $model->form, + 'unit_point' => $model->unit_point, + 'unit' => ModelPointType::get($model->point_type, 'unit') + ]; + } + return $this->resData($models); + } +} diff --git a/fastmovie-admin/plugin/model/app/api/controller/TaskController.php b/fastmovie-admin/plugin/model/app/api/controller/TaskController.php new file mode 100644 index 0000000..fc05e2c --- /dev/null +++ b/fastmovie-admin/plugin/model/app/api/controller/TaskController.php @@ -0,0 +1,44 @@ +get('limit', 10); + $where = []; + $where[] = ['uid', '=', $request->uid]; + $where[] = ['status', 'in', [ModelTaskStatus::WAIT['value'], ModelTaskStatus::PROCESSING['value'], ModelTaskStatus::WAIT_DOWNLOAD['value'], ModelTaskStatus::DOWNLOADING['value'], ModelTaskStatus::UPLOADING['value'], ModelTaskStatus::SUCCESS['value']]]; + $scene = $request->get('scene', 'all'); + if ($scene != 'all') { + $where[] = ['scene', '=', $scene]; + } + $scenes=$request->get('scenes'); + if ($scenes) { + $where[] = ['scene', 'in', $scenes]; + } + $model_type = $request->get('model_type', 'all'); + if ($model_type != 'all') { + $where[] = ['model_type', '=', $model_type]; + } + $alias_id = $request->get('alias_id'); + if ($alias_id) { + $where[] = ['alias_id', '=', $alias_id]; + } + $list = PluginModelTask::where($where) + ->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }, 'result' => function ($query) { + $query->field('task_id,result,image,image_path,video_path,message,channels_uid'); + }]) + ->order('id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/model/app/api/controller/VoiceController.php b/fastmovie-admin/plugin/model/app/api/controller/VoiceController.php new file mode 100644 index 0000000..82a1480 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/api/controller/VoiceController.php @@ -0,0 +1,166 @@ +post(); + if (empty($D['text_id'])) { + return $this->fail('请选择文案'); + } + if (empty($D['model_id'])) { + return $this->fail('请选择模型'); + } + $PluginModelVoiceText = PluginModelVoiceText::where(['id' => $D['text_id'], 'channels_uid' => $request->channels_uid])->find(); + if (!$PluginModelVoiceText) { + return $this->fail('文案不存在'); + } + $PluginModel = PluginModel::where(['id' => $D['model_id'], 'channels_uid' => $request->channels_uid, 'state' => State::YES['value'], 'scene' => ModelScene::DIALOGUE_VOICE['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $audio_url = ''; + $file = $request->file('file'); + if (empty($file)) { + return $this->fail('请录制语音'); + } + $fileName = uniqid('voice_') . '.wav'; + $temp_path = runtime_path('temp/' . $fileName); + // 判断文件的mime是否为MP3或WAV + if (!str_starts_with($file->getUploadMimeType(), 'audio/wav')) { + // 使用phpffmpeg转换为MP3 + $ffmpeg = \FFMpeg\FFMpeg::create([ + 'ffmpeg.binaries' => '/www/server/ffmpeg/ffmpeg-6.1/ffmpeg', + 'ffprobe.binaries' => '/www/server/ffmpeg/ffmpeg-6.1/ffprobe', + ]); + $media = $ffmpeg->open($file->getRealPath()); + $wav = new Wav(); + $media->save($wav, $temp_path); + $file = new UploadFile($temp_path, $fileName, 'audio/wav', $file->getUploadErrorCode()); + } + if ($file->getSize() > 1024 * 1024 * 10) { + unlink($temp_path); + return $this->fail('语音文件大小不能超过10MB,请重新录制'); + } + $result = Uploads::upload($request->channels_uid, $file, null, 'uploads/voice_audio'); + if (file_exists($temp_path)) { + unlink($temp_path); + } + $audio_url = $result['url']; + $PluginModelVoice = new PluginModelVoice(); + $PluginModelVoice->uid = $request->uid; + $PluginModelVoice->channels_uid = $request->channels_uid; + if (!empty($D['voice_id'])) { + $PluginModelVoice = PluginModelVoice::where(['id' => $D['voice_id'], 'channels_uid' => $request->channels_uid, 'uid' => $request->uid])->find(); + if (!$PluginModelVoice) { + return $this->fail('音色不存在'); + } + $audio_url = $PluginModelVoice->audio_url; + } + $PluginModelVoice->text_id = $D['text_id']; + $PluginModelVoice->model_id = $D['model_id']; + if ($audio_url) { + $PluginModelVoice->audio = $audio_url; + } + $PluginModelVoice->status = ModelVoiceStatus::WAIT['value']; + $PluginModelVoice->save(); + $ids = [ + 'id' => $PluginModelVoice->id, + 'uid' => $request->uid, + 'channels_uid' => $request->channels_uid, + ]; + $data = [ + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/voiceClone', + 'form_data' => [ + 'audio' => $audio_url, + ] + ]; + Coroutine::create(function () use ($ids, $data) { + $status = ModelVoiceStatus::CLONING['value']; + try { + $result = Yidevs::AudioVoiceClone($ids['channels_uid'], $data); + $PluginModelVoice = PluginModelVoice::where(['id' => $ids['id'], 'channels_uid' => $ids['channels_uid']])->find(); + $PluginModelVoice->status = ModelVoiceStatus::CLONING['value']; + $PluginModelVoice->task_id = $result['task_id']; + $PluginModelVoice->save(); + } catch (\Throwable $th) { + $PluginModelVoice = PluginModelVoice::where(['id' => $ids['id'], 'channels_uid' => $ids['channels_uid']])->find(); + $PluginModelVoice->status = ModelVoiceStatus::FAIL['value']; + $PluginModelVoice->message = $th->getMessage(); + $PluginModelVoice->save(); + $status = ModelVoiceStatus::FAIL['value']; + } + Push::send([ + 'uid' => $ids['uid'], + 'event' => 'clonevoice', + ], [ + 'id' => $PluginModelVoice->id, + 'status' => $status, + ]); + }); + return $this->resData(['voice_id' => $PluginModelVoice->id]); + } + public function update(Request $request) + { + $id = $request->post('id'); + $PluginModelVoice = PluginModelVoice::where(['id' => $id, 'channels_uid' => $request->channels_uid, 'uid' => $request->uid])->find(); + if (!$PluginModelVoice) { + return $this->fail('音色不存在'); + } + $name = $request->post('name'); + if ($name) { + $PluginModelVoice->name = $name; + } + $description = $request->post('description'); + if ($description) { + $PluginModelVoice->description = $description; + } + $image = $request->post('image'); + if ($image) { + $PluginModelVoice->image = $image; + } + $headimg = $request->post('headimg'); + if ($headimg) { + $PluginModelVoice->headimg = $headimg; + } + $gender = $request->post('gender'); + if ($gender) { + $PluginModelVoice->gender = $gender; + } + $age = $request->post('age'); + if ($age) { + $PluginModelVoice->age = $age; + } + $PluginModelVoice->save(); + return $this->success('更新成功'); + } + public function detail(Request $request) + { + $id = $request->get('id'); + $PluginModelVoice = PluginModelVoice::where(['id' => $id, 'channels_uid' => $request->channels_uid, 'uid' => $request->uid])->find(); + if (!$PluginModelVoice) { + return $this->fail('音色不存在'); + } + return $this->resData($PluginModelVoice); + } +} diff --git a/fastmovie-admin/plugin/model/app/api/controller/VoiceTextController.php b/fastmovie-admin/plugin/model/app/api/controller/VoiceTextController.php new file mode 100644 index 0000000..c59778d --- /dev/null +++ b/fastmovie-admin/plugin/model/app/api/controller/VoiceTextController.php @@ -0,0 +1,24 @@ +channels_uid]; + $where[] = ['state', '=', State::YES['value']]; + $text = $request->get('text'); + if ($text) { + $where[] = ['text', 'like', "%{$text}%"]; + } + $list = PluginModelVoiceText::where($where)->order('id desc')->select(); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/model/app/control/controller/ModelController.php b/fastmovie-admin/plugin/model/app/control/controller/ModelController.php new file mode 100644 index 0000000..ae72a63 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/control/controller/ModelController.php @@ -0,0 +1,765 @@ +model = new PluginModel(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/model/control/Model/update', + 'props' => [ + 'title' => '编辑《{name}》' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/model/control/Model/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{name}》吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/model/control/Model/create', + 'props' => [ + 'title' => '创建' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $builder->addHeaderAction('初始化', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/model/control/Model/init', + 'props' => [ + 'title' => '初始化' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('name', '模型名称', 'input', '', [ + 'props' => [ + 'placeholder' => '模型名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('model_type', '模型类型', 'select', '', [ + 'options' => ModelType::getOptions(), + 'props' => [ + 'placeholder' => '模型类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('model_name', '模型名称(壹定)', 'input', '', [ + 'props' => [ + 'placeholder' => '模型名称(壹定)搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('assistant_name', '助手名称', 'input', '', [ + 'props' => [ + 'placeholder' => '助手名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'options' => ModelScene::getOptions(), + 'props' => [ + 'placeholder' => '场景搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('icon', '模型图标', [ + 'props' => [ + 'width' => '100px' + ], + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width:40px;height:40px;' + ] + ] + ]); + $builder->add('name', '模型名称(本地)', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_type', '模型类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelType::getOptions() + ], + 'props' => [ + 'width' => '140px' + ] + ]); + $builder->add('model_name', '模型名称(壹定)', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('assistant_name', '助手名称', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('sort', '排序(小到大)', [ + 'props' => [ + 'width' => '130px' + ] + ]); + $builder->add('point', '积分', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('scene', '场景', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelScene::getOptions() + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/model/control/Model/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + ]); + $builder->add('description', '描述', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $model_name = $request->get('model_name'); + if ($model_name) { + $where[] = ['model_name', 'like', "%{$model_name}%"]; + } + $assistant_name = $request->get('assistant_name'); + if ($assistant_name) { + $where[] = ['assistant_name', 'like', "%{$assistant_name}%"]; + } + $scene = $request->get('scene'); + if ($scene) { + $where[] = ['scene', '=', $scene]; + } + $model_type = $request->get('model_type'); + if ($model_type) { + $where[] = ['model_type', '=', $model_type]; + } + $list = PluginModel::where($where)->order('id desc')->paginate($limit); + return $this->resData($list); + } + + + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $D['channels_uid'] = $request->channels_uid; + $model = new PluginModel(); + $model_type = $D['model_type']; + switch ($model_type) { + case ModelType::CHAT['value']: + $ydModel = Yidevs::ChatModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::ChatAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + case ModelType::DRAW['value']: + $ydModel = Yidevs::DrawModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::DrawAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + case ModelType::TOVIDEO['value']: + $ydModel = Yidevs::VideoModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::VideoAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + case ModelType::AUDIO['value']: + $ydModel = Yidevs::AudioModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::AudioAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + } + if (!isset($ydModel[0])) { + return $this->fail('模型不存在'); + } + if ($ydModel[0]['model_id'] != $D['model_id']) { + return $this->fail('模型不存在'); + } + if (!isset($ydAssistant[0])) { + return $this->fail('助手不存在'); + } + if ($ydAssistant[0]['assistant_id'] != $D['assistant_id']) { + return $this->fail('助手不存在'); + } + $modelSelected = $ydModel[0]; + $D['model_name'] = $modelSelected['name']; + if (!empty($modelSelected['form'])) { + $D['form'] = $modelSelected['form']; + } + $assistantSelected = $ydAssistant[0]; + $D['assistant_name'] = $assistantSelected['name']; + if ($model->save($D)) { + return $this->success('创建成功'); + } + return $this->fail('创建失败'); + } + $builder = $this->getFormBuilder($request); + $builder->addValue('id', null); + return $this->resData($builder); + } + + + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $model = PluginModel::where(['id' => $D['id']])->find(); + $model_type = $D['model_type']; + switch ($model_type) { + case ModelType::CHAT['value']: + $ydModel = Yidevs::ChatModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::ChatAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + case ModelType::DRAW['value']: + $ydModel = Yidevs::DrawModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::DrawAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + case ModelType::TOVIDEO['value']: + $ydModel = Yidevs::VideoModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::VideoAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + case ModelType::AUDIO['value']: + $ydModel = Yidevs::AudioModels($request->channels_uid, ['model_id' => $D['model_id']]); + $ydAssistant = Yidevs::AudioAssistantlist($request->channels_uid, ['assistant_id' => $D['assistant_id']]); + break; + } + if (!isset($ydModel[0])) { + return $this->fail('模型不存在'); + } + if ($ydModel[0]['model_id'] != $D['model_id']) { + return $this->fail('模型不存在'); + } + if (!isset($ydAssistant[0])) { + return $this->fail('助手不存在'); + } + if ($ydAssistant[0]['assistant_id'] != $D['assistant_id']) { + return $this->fail('助手不存在'); + } + $modelSelected = $ydModel[0]; + $D['model_name'] = $modelSelected['name']; + if (!empty($modelSelected['form'])) { + $D['form'] = $modelSelected['form']; + } + $assistantSelected = $ydAssistant[0]; + $D['assistant_name'] = $assistantSelected['name']; + if ($model->save($D)) { + return $this->success('更新成功'); + } + return $this->fail('更新失败'); + } + $id = $request->get('id'); + $model = PluginModel::where(['id' => $id])->find(); + $builder = $this->getFormBuilder($request); + $builder->setData($model->toArray()); + return $this->resData($builder); + } + + public function getFormBuilder(Request $request) + { + $Component = new ComponentBuilder(); + $formBuilder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large' + ]); + $formBuilder->add('icon', '模型图标', 'bundle', '', [ + 'required' => true, + 'props' => [ + 'accept' => 'image/jpeg,image/png,image/jpg', + 'multiple' => 1, + 'size' => 2 + ] + ]); + $formBuilder->add('name', '模型名称', 'input', '', [ + 'required' => true, + 'props' => [ + 'placeholder' => '模型名称' + ] + ]); + $formBuilder->add('model_type', '模型类别', 'radio', ModelType::CHAT['value'], [ + 'required' => true, + 'options' => ModelType::getOptions(), + 'subProps' => [ + 'border' => true + ], + 'where' => [ + ['id', 'null', null] + ] + ]); + # 对话模型 + $ydModel = Yidevs::ChatModels($request->channels_uid); + $modelList = $assistantList = []; + foreach ($ydModel as $model) { + $modelList[] = [ + 'value' => $model['model_id'], + 'tips' => $model['integral'] . $model['integral_unit'], + 'label' => $model['name'] + ]; + } + $ydAssistant = Yidevs::ChatAssistantlist($request->channels_uid); + foreach ($ydAssistant as $assistant) { + $assistantList[] = [ + 'value' => $assistant['assistant_id'], + 'tips' => $assistant['integral'] . $assistant['integral_unit'], + 'label' => $assistant['name'] + ]; + } + $formBuilder->add('model_id', '绑定模型', 'select', '', [ + 'required' => true, + 'options' => $modelList, + 'where' => [ + ['model_type', '=', ModelType::CHAT['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + $formBuilder->add('assistant_id', '绑定助手', 'select', '', [ + 'required' => true, + 'options' => $assistantList, + 'where' => [ + ['model_type', '=', ModelType::CHAT['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + # 绘图模型 + $ydModel = Yidevs::DrawModels($request->channels_uid); + $modelList = $assistantList = []; + foreach ($ydModel as $model) { + $modelList[] = [ + 'value' => $model['model_id'], + 'tips' => $model['integral'] . $model['integral_unit'], + 'label' => $model['name'] + ]; + } + $ydAssistant = Yidevs::DrawAssistantlist($request->channels_uid); + foreach ($ydAssistant as $assistant) { + $assistantList[] = [ + 'value' => $assistant['assistant_id'], + 'tips' => $assistant['integral'] . $assistant['integral_unit'], + 'label' => $assistant['name'] + ]; + } + $formBuilder->add('model_id', '绑定模型', 'select', '', [ + 'required' => true, + 'options' => $modelList, + 'where' => [ + ['model_type', '=', ModelType::DRAW['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + $formBuilder->add('assistant_id', '绑定助手', 'select', '', [ + 'required' => true, + 'options' => $assistantList, + 'where' => [ + ['model_type', '=', ModelType::DRAW['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + # 视频模型 + $ydModel = Yidevs::VideoModels($request->channels_uid); + $modelList = $assistantList = []; + foreach ($ydModel as $model) { + $modelList[] = [ + 'value' => $model['model_id'], + 'tips' => $model['integral'] . $model['integral_unit'], + 'label' => $model['name'] + ]; + } + $ydAssistant = Yidevs::VideoAssistantlist($request->channels_uid); + foreach ($ydAssistant as $assistant) { + $assistantList[] = [ + 'value' => $assistant['assistant_id'], + 'tips' => $assistant['integral'] . $assistant['integral_unit'], + 'label' => $assistant['name'] + ]; + } + $formBuilder->add('model_id', '绑定模型', 'select', '', [ + 'required' => true, + 'options' => $modelList, + 'where' => [ + ['model_type', '=', ModelType::TOVIDEO['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + $formBuilder->add('assistant_id', '绑定助手', 'select', '', [ + 'required' => true, + 'options' => $assistantList, + 'where' => [ + ['model_type', '=', ModelType::TOVIDEO['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + # 音频模型 + $ydModel = Yidevs::AudioModels($request->channels_uid); + $modelList = $assistantList = []; + foreach ($ydModel as $model) { + $modelList[] = [ + 'value' => $model['model_id'], + 'tips' => $model['integral'] . $model['integral_unit'], + 'label' => $model['name'] + ]; + } + $ydAssistant = Yidevs::AudioAssistantlist($request->channels_uid); + foreach ($ydAssistant as $assistant) { + $assistantList[] = [ + 'value' => $assistant['assistant_id'], + 'tips' => $assistant['integral'] . $assistant['integral_unit'], + 'label' => $assistant['name'] + ]; + } + $formBuilder->add('model_id', '绑定模型', 'select', '', [ + 'required' => true, + 'options' => $modelList, + 'where' => [ + ['model_type', '=', ModelType::AUDIO['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + $formBuilder->add('assistant_id', '绑定助手', 'select', '', [ + 'required' => true, + 'options' => $assistantList, + 'where' => [ + ['model_type', '=', ModelType::AUDIO['value']] + ], + 'props' => [ + 'filterable' => true + ] + ]); + + $formBuilder->add('state', '状态', 'switch', State::YES['value'], [ + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $formBuilder->add('point', '扣除数量', 'input-number', '', [ + 'required' => true, + ]); + $formBuilder->add('point_type', '计费类型', 'radio', ModelPointType::TIMES['value'], [ + 'col' => [ + 'xs' => 24, + 'sm' => 12, + 'md' => 16, + 'lg' => 18, + 'xl' => 20 + ], + 'required' => true, + 'options' => ModelPointType::getOptions(), + 'prompt' => [ + $Component->add('text', ['default' => '计费类型'], ['type' => 'info', 'size' => 'small']) + ->add('text', ['default' => ModelPointType::FIELD_RULE['label'] . ':通过表单中,组件选择器、单选框、多选框的值中的基础点数和倍数计算点数'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'subProps' => [ + 'border' => true + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'required' => true, + 'options' => ModelScene::getOptions() + ]); + $formBuilder->add('sort', '排序(小到大)', 'input-number', '', [ + 'required' => true, + 'props' => [ + 'placeholder' => '排序(小到大)' + ] + ]); + $formBuilder->add('description', '描述', 'input', '', [ + 'required' => true, + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ], + 'placeholder' => '描述' + ] + ]); + return $formBuilder; + } + + public function delete(Request $request) + { + $id = $request->post('id'); + $model = PluginModel::where(['id' => $id])->find(); + if ($model->delete()) { + return $this->success('删除成功'); + } + return $this->fail('删除失败'); + } + + /** + * 初始化数据方法 + * 从 JSON 文件读取数据并写入数据库 + * + * @param Request $request + * @return \support\Response + */ + public function init(Request $request) + { + // 从请求中获取 uid(channels_uid) + $uid = $request->channels_uid; + if (empty($uid)) { + return $this->fail('缺少渠道参数'); + } + + // JSON 文件路径(放在 plugin/model 目录下) + $jsonFile = base_path('plugin/model/modal.json'); + + // 检查文件是否存在 + if (!file_exists($jsonFile)) { + return $this->fail('初始化数据文件不存在:' . $jsonFile); + } + + // 读取 JSON 文件内容 + $jsonContent = file_get_contents($jsonFile); + if ($jsonContent === false) { + return $this->fail('读取初始化数据文件失败'); + } + + // 解析 JSON 数据 + $dataList = json_decode($jsonContent, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return $this->fail('JSON 数据格式错误:' . json_last_error_msg()); + } + + // 确保是数组格式 + if (!is_array($dataList)) { + return $this->fail('JSON 数据格式错误:必须是数组格式'); + } + + // 定义需要排除的字段(id 和 channels_uid) + $excludeFields = ['id', 'channels_uid']; + + // 定义允许的字段(除了 id 和 channels_uid 之外的所有字段) + $allowedFields = [ + 'name', + 'icon', + 'model_type', + 'model_id', + 'model_name', + 'assistant_id', + 'assistant_name', + 'scene', + 'point', + 'state', + 'sort', + 'description', + 'create_time', + 'update_time' + ]; + + $successCount = 0; + $failCount = 0; + $skipCount = 0; + $errors = []; + + // 遍历数据并插入数据库 + foreach ($dataList as $index => $data) { + try { + // 过滤掉不需要的字段 + $insertData = []; + foreach ($allowedFields as $field) { + if (isset($data[$field])) { + $insertData[$field] = $data[$field]; + } + } + + // 检查必填字段 + if (!isset($insertData['model_id']) || !isset($insertData['assistant_id']) || !isset($insertData['scene'])) { + $failCount++; + $errors[] = "第 " . ($index + 1) . " 条数据缺少必填字段(model_id、assistant_id、scene)"; + continue; + } + + // 设置 channels_uid + $insertData['channels_uid'] = $uid; + + // 检查是否存在相同的数据(model_id、assistant_id、scene、channels_uid) + $exists = PluginModel::where([ + 'model_id' => $insertData['model_id'], + 'assistant_id' => $insertData['assistant_id'], + 'scene' => $insertData['scene'], + 'channels_uid' => $uid + ])->find(); + + // 如果存在相同数据,跳过 + if ($exists) { + $skipCount++; + continue; + } + + // 如果没有设置 create_time,则使用当前时间 + if (!isset($insertData['create_time'])) { + $insertData['create_time'] = date('Y-m-d H:i:s'); + } + + // 如果没有设置 update_time,则使用当前时间 + if (!isset($insertData['update_time'])) { + $insertData['update_time'] = date('Y-m-d H:i:s'); + } + + // 如果没有设置 state,则默认为 1 + if (!isset($insertData['state'])) { + $insertData['state'] = 1; + } + + // 如果没有设置 sort,则默认为 0 + if (!isset($insertData['sort'])) { + $insertData['sort'] = 0; + } + + // 创建模型实例并保存 + $model = new PluginModel(); + if ($model->save($insertData)) { + $successCount++; + } else { + $failCount++; + $errors[] = "第 " . ($index + 1) . " 条数据保存失败"; + } + } catch (\Exception $e) { + $failCount++; + $errors[] = "第 " . ($index + 1) . " 条数据保存失败:" . $e->getMessage(); + } + } + + // 返回结果 + $message = "初始化完成,成功:{$successCount} 条"; + if ($skipCount > 0) { + $message .= ",跳过:{$skipCount} 条(已存在相同数据)"; + } + if ($failCount > 0) { + $message .= ",失败:{$failCount} 条"; + if (!empty($errors)) { + $message .= "。" . implode('; ', array_slice($errors, 0, 5)); // 只显示前5个错误 + if (count($errors) > 5) { + $message .= "(还有 " . (count($errors) - 5) . " 个错误)"; + } + } + return $this->fail($message); + } + + return $this->success($message); + } +} diff --git a/fastmovie-admin/plugin/model/app/control/controller/TaskController.php b/fastmovie-admin/plugin/model/app/control/controller/TaskController.php new file mode 100644 index 0000000..470b393 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/control/controller/TaskController.php @@ -0,0 +1,294 @@ +model = new PluginModelTask(); + } + + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $builder->addTableAction('重试', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/model/control/Task/retry', + 'where' => [ + ['retry', '=', 1] + ], + 'props' => [ + 'type' => 'warning', + 'message' => '确定要重试《{task_id}》任务吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ] + ]); + $formBuilder->add('task_id', '任务ID', 'input', '', [ + 'props' => [ + 'placeholder' => '任务ID搜索', + ] + ]); + $formBuilder->add('user_id', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + ] + ]); + $formBuilder->add('model_type', '模型类型', 'select', '', [ + 'options' => ModelType::getOptions(), + 'props' => [ + 'placeholder' => '模型类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('status', '状态', 'select', '', [ + 'options' => ModelTaskStatus::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + + $formBuilder->add('model_name', '模型名称(壹定)', 'input', '', [ + 'props' => [ + 'placeholder' => '模型名称(壹定)搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'options' => ModelScene::getOptions(), + 'props' => [ + 'placeholder' => '场景搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + // $builder->addAction('操作', [ + // 'width' => '200px', + // 'fixed' => 'right' + // ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('task_id', '任务ID', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_name', '模型名称', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model_type', '模型类型', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelType::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('status', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelTaskStatus::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('scene', '场景', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelScene::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('result.image_path', '图片', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width:40px;height:40px;' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('result.video_path', '视频', [ + 'component' => [ + 'name' => 'link', + 'props' => [ + 'target' => '_blank' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('result.audio_path', '音频', [ + 'component' => [ + 'name' => 'link', + 'props' => [ + 'target' => '_blank' + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('result.message', '错误消息', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'text', + 'props' => [ + 'type' => 'danger' + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['pmt.channels_uid', '=', $request->channels_uid]; + $model_name = $request->get('model_name'); + if ($model_name) { + $where[] = ['pm.name', 'like', "%{$model_name}%"]; + } + $model_type = $request->get('model_type'); + if ($model_type) { + $where[] = ['pm.model_type', '=', $model_type]; + } + $scene = $request->get('scene'); + if ($scene) { + $where[] = ['pm.scene', '=', $scene]; + } + $status = $request->get('status'); + if ($status) { + $where[] = ['pmt.status', '=', $status]; + } + $task_id = $request->get('task_id'); + if ($task_id) { + $where[] = ['pmt.task_id', '=', $task_id]; + } + $user_id = $request->get('user_id'); + if ($user_id) { + $where[] = ['pmt.uid', '=', $user_id]; + } + $list = PluginModelTask::alias('pmt') + ->join('plugin_model pm', 'pmt.model_id = pm.id') + ->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }, 'result' => function ($query) { + $query->field('task_id,result,image,image_path,video_path,message,channels_uid'); + }]) + ->field('pmt.*,pm.name as model_name') + ->where($where) + ->order('pmt.id desc') + ->paginate($limit)->each(function ($item) { + $item->retry = 0; + if (in_array($item->status, [ModelTaskStatus::PROCESSING['value'], ModelTaskStatus::DOWNLOADING['value'], ModelTaskStatus::UPLOADING['value']]) && $item->update_time < date('Y-m-d H:i:s', strtotime('-30 minutes'))) { + $item->retry = 1; + } + }); + return $this->resData($list); + } + public function retry(Request $request) + { + $id = $request->post('id'); + $PluginModelTask = PluginModelTask::where('id', $id)->find(); + if (!$PluginModelTask) { + return $this->fail('任务不存在'); + } + switch ($PluginModelTask->status) { + case ModelTaskStatus::PROCESSING['value']: + $PluginModelTask->status = ModelTaskStatus::WAIT['value']; + break; + case ModelTaskStatus::DOWNLOADING['value']: + case ModelTaskStatus::UPLOADING['value']: + $PluginModelTask->status = ModelTaskStatus::WAIT_DOWNLOAD['value']; + break; + } + $PluginModelTask->save(); + return $this->success('重试成功'); + } +} diff --git a/fastmovie-admin/plugin/model/app/control/controller/VoiceController.php b/fastmovie-admin/plugin/model/app/control/controller/VoiceController.php new file mode 100644 index 0000000..e78af79 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/control/controller/VoiceController.php @@ -0,0 +1,173 @@ +model = new PluginModelVoice(); + } + + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('task_id', '任务ID', 'input', '', [ + 'props' => [ + 'placeholder' => '任务ID搜索', + ] + ]); + $formBuilder->add('user_id', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + ] + ]); + $formBuilder->add('model_id', '模型', 'select', '', [ + 'options' => PluginModel::options(['scene' => ModelScene::DIALOGUE_VOICE['value']]), + 'props' => [ + 'placeholder' => '模型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('status', '状态', 'select', '', [ + 'options' => ModelVoiceStatus::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + // $builder->addAction('操作', [ + // 'width' => '200px', + // 'fixed' => 'right' + // ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('task_id', '任务ID', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('model.name', '模型名称', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('status', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => ModelVoiceStatus::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('message', '错误消息', [ + 'props' => [ + 'minWidth' => '200px' + ], + 'component' => [ + 'name' => 'text', + 'props' => [ + 'type' => 'danger' + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $model_id = $request->get('model_id'); + if ($model_id) { + $where[] = ['model_id', '=', $model_id]; + } + $status = $request->get('status'); + if ($status) { + $where[] = ['status', '=', $status]; + } + $task_id = $request->get('task_id'); + if ($task_id) { + $where[] = ['task_id', '=', $task_id]; + } + $user_id = $request->get('user_id'); + if ($user_id) { + $where[] = ['uid', '=', $user_id]; + } + $list = PluginModelVoice::where($where) + ->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }, 'model' => function ($query) { + $query->field('id,name'); + }]) + ->order('id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/model/app/control/controller/VoiceTextController.php b/fastmovie-admin/plugin/model/app/control/controller/VoiceTextController.php new file mode 100644 index 0000000..2b028ec --- /dev/null +++ b/fastmovie-admin/plugin/model/app/control/controller/VoiceTextController.php @@ -0,0 +1,203 @@ +model = new PluginModelVoiceText(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/model/control/VoiceText/update', + 'props' => [ + 'title' => '编辑《{name}》' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/model/control/VoiceText/delete', + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{name}》吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/model/control/VoiceText/create', + 'props' => [ + 'title' => '创建' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('text', '文案', 'input', '', [ + 'props' => [ + 'placeholder' => '文案搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('text', '文案', []); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/model/control/VoiceText/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $text = $request->get('text'); + if ($text) { + $where[] = ['text', 'like', "%{$text}%"]; + } + $list = PluginModelVoiceText::where($where)->order('id desc')->paginate($limit); + return $this->resData($list); + } + + + public function create(Request $request) + { + if ($request->method() === 'POST') { + $data = $request->post(); + $data['channels_uid'] = $request->channels_uid; + $model = new PluginModelVoiceText(); + if ($model->save($data)) { + return $this->success('创建成功'); + } + return $this->fail('创建失败'); + } + $builder = $this->getFormBuilder($request); + return $this->resData($builder); + } + + + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $model = PluginModelVoiceText::where(['id' => $D['id']])->find(); + if ($model->save($D)) { + return $this->success('更新成功'); + } + return $this->fail('更新失败'); + } + $id = $request->get('id'); + $model = PluginModelVoiceText::where(['id' => $id])->find(); + $builder = $this->getFormBuilder($request); + $builder->setData($model->toArray()); + return $this->resData($builder); + } + + public function getFormBuilder(Request $request) + { + $formBuilder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large' + ]); + $formBuilder->add('state', '状态', 'switch', State::YES['value'], [ + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $formBuilder->add('text', '文案', 'input', '', [ + 'required' => true, + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ], + 'placeholder' => '文案' + ] + ]); + return $formBuilder; + } + + public function delete(Request $request) + { + $id = $request->post('id'); + $model = PluginModelVoiceText::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if ($model->delete()) { + return $this->success('删除成功'); + } + return $this->fail('删除失败'); + } +} diff --git a/fastmovie-admin/plugin/model/app/controller/NotifyController.php b/fastmovie-admin/plugin/model/app/controller/NotifyController.php new file mode 100644 index 0000000..1052dd2 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/controller/NotifyController.php @@ -0,0 +1,242 @@ +post())); + $taskId = $request->post('task_id'); + $PluginModelTask = PluginModelTask::where(['task_id' => $taskId, 'model_type' => ModelType::DRAW['value']])->with(['result'])->find(); + if (!$PluginModelTask) { + return 'success'; + } + $status = $request->post('status'); + if ($status == ModelTaskStatus::SUCCESS['value']) { + $PluginModelTask->status = ModelTaskStatus::WAIT_DOWNLOAD['value']; + $PluginModelTask->result->result = $request->post(); + $PluginModelTask->result->image = $request->post('url'); + $PluginModelTask->together(['result'])->save(); + } elseif ($status == ModelTaskStatus::FAIL['value']) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->result->result = $request->post(); + $PluginModelTask->result->message = $request->post('message'); + $PluginModelTask->together(['result'])->save(); + if (!empty($PluginModelTask->consume_ids)) { + Db::startTrans(); + try { + Account::refund($PluginModelTask->uid, $PluginModelTask->channels_uid, $PluginModelTask->consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + $pushData = [ + 'id' => $PluginModelTask->alias_id + ]; + switch ($PluginModelTask->scene) { + case ModelScene::ACTOR_IMAGE['value']: + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'status' => ModelTaskStatus::WAIT['value']])->find(); + if ($PluginModelTask) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + if (!empty($PluginModelTask->consume_ids)) { + Db::startTrans(); + try { + Account::refund($PluginModelTask->uid, $PluginModelTask->channels_uid, $PluginModelTask->consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + } + $pushData['status'] = ActorStatus::INITIALIZING; + break; + case ModelScene::PROP_IMAGE['value']: + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'status' => ModelTaskStatus::WAIT['value']])->find(); + if ($PluginModelTask) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + if (!empty($PluginModelTask->consume_ids)) { + Db::startTrans(); + try { + Account::refund($PluginModelTask->uid, $PluginModelTask->channels_uid, $PluginModelTask->consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + } + $pushData['status'] = PropStatus::INITIALIZING; + break; + case ModelScene::ACTOR_COSTUME['value']: + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'status' => ModelTaskStatus::WAIT['value']])->find(); + if ($PluginModelTask) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + if (!empty($PluginModelTask->consume_ids)) { + Db::startTrans(); + try { + Account::refund($PluginModelTask->uid, $PluginModelTask->channels_uid, $PluginModelTask->consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + } + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where(['id' => $PluginModelTask->alias_id])->find(); + $PluginShortplayActorCharacterLook->delete(); + $pushData['storyboard_id'] = $PluginShortplayActorCharacterLook->storyboard_id; + $pushData['episode_id'] = $PluginShortplayActorCharacterLook->episode_id; + $pushData['drama_id'] = $PluginShortplayActorCharacterLook->drama_id; + $pushData['actor_id'] = $PluginShortplayActorCharacterLook->actor_id; + break; + case ModelScene::ACTOR_THREE_VIEW_IMAGE['value']: + $pushData['status'] = ActorStatus::INITIALIZING; + break; + case ModelScene::PROP_THREE_VIEW_IMAGE['value']: + $pushData['status'] = PropStatus::INITIALIZING; + break; + case ModelScene::ACTOR_COSTUME_THREE_VIEW['value']: + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where(['id' => $PluginModelTask->alias_id])->find(); + $PluginShortplayActorCharacterLook->delete(); + $pushData['storyboard_id'] = $PluginShortplayActorCharacterLook->storyboard_id; + $pushData['episode_id'] = $PluginShortplayActorCharacterLook->episode_id; + $pushData['drama_id'] = $PluginShortplayActorCharacterLook->drama_id; + $pushData['actor_id'] = $PluginShortplayActorCharacterLook->actor_id; + break; + } + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => 'generate' . strtolower(str_replace('_', '', $PluginModelTask->scene)), + ], $pushData); + } + return 'success'; + } + public function video(Request $request) + { + Log::error('video' . json_encode($request->post())); + $taskId = $request->post('task_id'); + $PluginModelTask = PluginModelTask::where(['task_id' => $taskId, 'model_type' => ModelType::TOVIDEO['value']])->with(['result'])->find(); + if (!$PluginModelTask) { + return 'success'; + } + $status = $request->post('status'); + if ($status == ModelTaskStatus::SUCCESS['value']) { + $PluginModelTask->status = ModelTaskStatus::WAIT_DOWNLOAD['value']; + $PluginModelTask->result->result = $request->post(); + $PluginModelTask->result->video = $request->post('url'); + $PluginModelTask->together(['result'])->save(); + } elseif ($status == ModelTaskStatus::FAIL['value']) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->result->result = $request->post(); + $PluginModelTask->result->message = $request->post('message'); + $PluginModelTask->together(['result'])->save(); + if (!empty($PluginModelTask->consume_ids)) { + Db::startTrans(); + try { + Account::refund($PluginModelTask->uid, $PluginModelTask->channels_uid, $PluginModelTask->consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + $pushData = [ + 'id' => $PluginModelTask->alias_id + ]; + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => 'generate' . strtolower(str_replace('_', '', $PluginModelTask->scene)), + ], $pushData); + } + return 'success'; + } + public function audio(Request $request) + { + Log::error('audio' . json_encode($request->post())); + $taskId = $request->post('task_id'); + $PluginModelTask = PluginModelTask::where(['task_id' => $taskId, 'model_type' => ModelType::AUDIO['value']])->with(['result'])->find(); + $status = $request->post('status'); + if ($status == ModelTaskStatus::SUCCESS['value']) { + $PluginModelTask->status = ModelTaskStatus::WAIT_DOWNLOAD['value']; + $PluginModelTask->result->result = $request->post(); + $PluginModelTask->result->audio = $request->post('url'); + $PluginModelTask->together(['result'])->save(); + } elseif ($status == ModelTaskStatus::FAIL['value']) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->result->result = $request->post(); + $PluginModelTask->result->message = $request->post('message'); + $PluginModelTask->together(['result'])->save(); + if (!empty($PluginModelTask->consume_ids)) { + Db::startTrans(); + try { + Account::refund($PluginModelTask->uid, $PluginModelTask->channels_uid, $PluginModelTask->consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + $pushData = [ + 'id' => $PluginModelTask->alias_id + ]; + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => 'generate' . strtolower(str_replace('_', '', $PluginModelTask->scene)), + ], $pushData); + } + return 'success'; + } + public function voiceClone(Request $request) + { + Log::error('voiceClone' . json_encode($request->post())); + $taskId = $request->post('task_id'); + $PluginModelVoice = PluginModelVoice::where(['task_id' => $taskId])->find(); + if (!$PluginModelVoice) { + return 'success'; + } + $status = $request->post('status'); + if ($status == ModelTaskStatus::SUCCESS['value']) { + $PluginModelVoice->status = ModelVoiceStatus::SUCCESS['value']; + $PluginModelVoice->voice_id = $request->post('voice_id'); + $PluginModelVoice->save(); + $pushData = [ + 'id' => $PluginModelVoice->id, + 'status' => ModelVoiceStatus::SUCCESS['value'], + ]; + } elseif ($status == ModelTaskStatus::FAIL['value']) { + $PluginModelVoice->status = ModelVoiceStatus::FAIL['value']; + $PluginModelVoice->message = $request->post('message'); + $PluginModelVoice->save(); + $pushData = [ + 'id' => $PluginModelVoice->id, + 'status' => ModelVoiceStatus::FAIL['value'], + ]; + } + Push::send([ + 'uid' => $PluginModelVoice->uid, + 'event' => 'clonevoice', + ], $pushData); + return 'success'; + } +} diff --git a/fastmovie-admin/plugin/model/app/functions.php b/fastmovie-admin/plugin/model/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/model/app/functions.php @@ -0,0 +1,6 @@ + [ + 'form' => 'json', + ] + ]; + } + public function getIconAttr($value,$data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setIconAttr($value,$data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function channels() + { + return $this->hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public static function options($where = []) + { + $models = self::where($where)->field('id,name')->select(); + $data = []; + foreach ($models as $item) { + $data[] = [ + 'label' => $item->name, + 'value' => $item->id, + ]; + } + return $data; + } +} diff --git a/fastmovie-admin/plugin/model/app/model/PluginModelTask.php b/fastmovie-admin/plugin/model/app/model/PluginModelTask.php new file mode 100644 index 0000000..5606ae6 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/model/PluginModelTask.php @@ -0,0 +1,38 @@ + [ + 'consume_ids' => 'json', + ] + ]; + } + public function result() + { + return $this->hasOne(PluginModelTaskResult::class, 'task_id', 'id'); + } + + public function user() + { + return $this->hasOne(PluginUser::class, 'id', 'uid'); + } + + public function channels() + { + return $this->hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public static function processing($where = []) + { + return self::where($where)->whereIn('status', [ModelTaskStatus::WAIT['value'],ModelTaskStatus::PROCESSING['value'], ModelTaskStatus::WAIT_DOWNLOAD['value'], ModelTaskStatus::DOWNLOADING['value'], ModelTaskStatus::UPLOADING['value']])->count(); + } +} diff --git a/fastmovie-admin/plugin/model/app/model/PluginModelTaskResult.php b/fastmovie-admin/plugin/model/app/model/PluginModelTaskResult.php new file mode 100644 index 0000000..6589144 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/model/PluginModelTaskResult.php @@ -0,0 +1,44 @@ + [ + // 设置JSON字段的类型 + 'params' => 'json', + 'result' => 'json', + ] + ]; + } + public function getImagePathAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setImagePathAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getAudioPathAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setAudioPathAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getVideoPathAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setVideoPathAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } +} diff --git a/fastmovie-admin/plugin/model/app/model/PluginModelVoice.php b/fastmovie-admin/plugin/model/app/model/PluginModelVoice.php new file mode 100644 index 0000000..495a973 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/model/PluginModelVoice.php @@ -0,0 +1,32 @@ +hasOne(PluginModel::class, 'id', 'model_id'); + } + public function channels() + { + return $this->hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public function user() + { + return $this->hasOne(PluginUser::class, 'id', 'uid'); + } +} diff --git a/fastmovie-admin/plugin/model/app/model/PluginModelVoiceText.php b/fastmovie-admin/plugin/model/app/model/PluginModelVoiceText.php new file mode 100644 index 0000000..2d8e528 --- /dev/null +++ b/fastmovie-admin/plugin/model/app/model/PluginModelVoiceText.php @@ -0,0 +1,14 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } +} diff --git a/fastmovie-admin/plugin/model/config/app.php b/fastmovie-admin/plugin/model/config/app.php new file mode 100644 index 0000000..bc3fd0f --- /dev/null +++ b/fastmovie-admin/plugin/model/config/app.php @@ -0,0 +1,9 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/model/config/autoload.php b/fastmovie-admin/plugin/model/config/autoload.php new file mode 100644 index 0000000..e9f8ff1 --- /dev/null +++ b/fastmovie-admin/plugin/model/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/model/app/functions.php', + ] +]; diff --git a/fastmovie-admin/plugin/model/config/container.php b/fastmovie-admin/plugin/model/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/model/config/container.php @@ -0,0 +1,2 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/model/config/middleware.php b/fastmovie-admin/plugin/model/config/middleware.php new file mode 100644 index 0000000..c9cc79c --- /dev/null +++ b/fastmovie-admin/plugin/model/config/middleware.php @@ -0,0 +1,19 @@ + [ + app\middleware\Template::class, + app\middleware\Platform::class + ], + 'control' => [ + plugin\control\expose\middleware\ControlAuth::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ] +]; diff --git a/fastmovie-admin/plugin/model/config/process.php b/fastmovie-admin/plugin/model/config/process.php new file mode 100644 index 0000000..ffb2a32 --- /dev/null +++ b/fastmovie-admin/plugin/model/config/process.php @@ -0,0 +1,30 @@ + [ + 'eventLoop' => Select::class, + 'handler' => Submit::class, + 'count' => 5 + ], + 'ImageTransfer' => [ + 'eventLoop' => Select::class, + 'handler' => ImageTransfer::class, + 'count' => 5 + ], + 'VideoTransfer' => [ + 'eventLoop' => Select::class, + 'handler' => VideoTransfer::class, + 'count' => 5 + ], + 'AudioTransfer' => [ + 'eventLoop' => Select::class, + 'handler' => AudioTransfer::class, + 'count' => 5 + ], +]; diff --git a/fastmovie-admin/plugin/model/config/settings/basic.php b/fastmovie-admin/plugin/model/config/settings/basic.php new file mode 100644 index 0000000..6ac6ef9 --- /dev/null +++ b/fastmovie-admin/plugin/model/config/settings/basic.php @@ -0,0 +1,40 @@ + '配置项名称', + #配置项字段 + 'field' => 'web_name', + #配置项值 + 'value' => 'FastMovie', + #配置项组件 + 'component' => 'input', + #配置项额外属性 + 'extra' => [ + #是否必填 + 'required' => true, + #element-plus表单中的rules规则 + 'rules' => [ + ['type'=>'string','required'=>true,'message'=>'请输入配置项值'] + ], + #使用接口验证 + 'rules_api' => [ + 'url' => '/app/model/example/test' + ], + #提示 + 'prompt' => [ + $Component->add('text', ['default' => '提示信息'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + #组件属性 + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入应用名称' + ] + ] + ] +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/config/translation.php b/fastmovie-admin/plugin/model/config/translation.php new file mode 100644 index 0000000..820b904 --- /dev/null +++ b/fastmovie-admin/plugin/model/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/model/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/config/view.php b/fastmovie-admin/plugin/model/config/view.php new file mode 100644 index 0000000..fe78682 --- /dev/null +++ b/fastmovie-admin/plugin/model/config/view.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +// use support\view\ThinkPHP; +use support\ThinkPHP; + +return [ + 'handler' => ThinkPHP::class, +]; diff --git a/fastmovie-admin/plugin/model/control.json b/fastmovie-admin/plugin/model/control.json new file mode 100644 index 0000000..71f2836 --- /dev/null +++ b/fastmovie-admin/plugin/model/control.json @@ -0,0 +1,88 @@ +{ + "title": "模型", + "icon": "Grid", + "path": "/app/Model/control", + "sort": 999, + "children": [ + { + "title": "模型管理", + "path": "/app/model/control/Model/index", + "component": "table", + "children": [ + { + "title": "创建模型", + "path": "/app/model/control/Model/create", + "component": "form", + "show": false + }, + { + "title": "编辑模型", + "path": "/app/model/control/Model/update", + "component": "form", + "show": false + }, + { + "title": "删除模型", + "path": "/app/model/control/Model/delete", + "component": "form", + "show": false + } + ] + }, + { + "title": "任务列表", + "path": "/app/model/control/Task/index", + "component": "table", + "children": [ + { + "title": "编辑模型", + "path": "/app/model/control/Task/update", + "component": "form", + "show": false + }, + { + "title": "删除模型", + "path": "/app/model/control/Task/delete", + "component": "form", + "show": false + } + ] + }, + { + "title": "音色", + "path": "/app/model/control/Voice/group", + "children": [ + { + "title": "音色列表", + "path": "/app/model/control/Voice/index", + "component": "table" + }, + { + "title": "音色文案", + "path": "/app/model/control/VoiceText/index", + "component": "table", + "children": [ + { + "title": "创建音色文案", + "path": "/app/model/control/VoiceText/create", + "component": "form", + "show": false + }, + { + "title": "编辑音色文案", + "path": "/app/model/control/VoiceText/update", + "component": "form", + "show": false + }, + { + "title": "删除音色文案", + "path": "/app/model/control/VoiceText/delete", + "component": "form", + "show": false + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/modal.json b/fastmovie-admin/plugin/model/modal.json new file mode 100644 index 0000000..25e43fa --- /dev/null +++ b/fastmovie-admin/plugin/model/modal.json @@ -0,0 +1,275 @@ +[ + { + "name": "通义千问-Plus", + "model_type": "chat", + "model_id": 2, + "model_name": "Yc-reasoner", + "assistant_id": 1, + "assistant_name": "编剧助手", + "scene": "creative_script", + "point": 1, + "state": 1, + "sort": 1, + "description": "Qwen3系列Plus模型,实现思考模式和非思考模式的有效融合,可在对话中切换模式。" + }, + { + "name": "千问3-Max", + "model_type": "chat", + "model_id": 2, + "model_name": "Yc-reasoner", + "assistant_id": 1, + "assistant_name": "编剧助手", + "scene": "creative_script", + "point": 0, + "state": 1, + "sort": 0, + "description": "通义千问3系列Max模型,适配场景更加复杂的智能体需求。" + }, + { + "name": "生成封面", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 2, + "assistant_name": "封面制作", + "scene": "drama_cover", + "point": 1, + "state": 1, + "sort": 0, + "description": "AI生成封面" + }, + { + "name": "美术指导", + "model_type": "chat", + "model_id": 2, + "model_name": "Yc-reasoner", + "assistant_id": 2, + "assistant_name": "场景分镜设计", + "scene": "creative_scenes", + "point": 1, + "state": 1, + "sort": 0, + "description": "擦我擦" + }, + { + "name": "续写分集", + "model_type": "chat", + "model_id": 2, + "model_name": "Yc-reasoner", + "assistant_id": 3, + "assistant_name": "分集助手", + "scene": "creative_episode", + "point": 1, + "state": 1, + "sort": 0, + "description": "续写" + }, + { + "name": "分镜师", + "model_type": "chat", + "model_id": 2, + "model_name": "Yc-reasoner", + "assistant_id": 4, + "assistant_name": "分镜设计", + "scene": "creative_storyboards", + "point": 1, + "state": 1, + "sort": 0, + "description": "分镜" + }, + { + "name": "banono-pro", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 3, + "assistant_name": "场景图片", + "scene": "scene_image", + "point": 60, + "state": 1, + "sort": 0, + "description": "根据场景提示词生成场景图片" + }, + { + "name": "角色形象", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 5, + "assistant_name": "角色主图", + "scene": "actor_image", + "point": 1, + "state": 1, + "sort": 0, + "description": "创建角色形象" + }, + { + "name": "角色三视图", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 4, + "assistant_name": "角色三视图", + "scene": "actor_three_view_image", + "point": 1, + "state": 1, + "sort": 0, + "description": "角色三视图" + }, + { + "name": "分镜图", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 6, + "assistant_name": "分镜图", + "scene": "storyboard_image", + "point": 1, + "state": 1, + "sort": 0, + "description": "分镜图" + }, + { + "name": "分镜合成视频", + "model_type": "tovideo", + "model_id": 8, + "model_name": "万相2.2极速版(无声视频)", + "assistant_id": 7, + "assistant_name": "分镜转视频", + "scene": "storyboard_video", + "point": 1, + "state": 1, + "sort": 0, + "description": "分镜合成视频" + }, + { + "name": "形象服饰", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 7, + "assistant_name": "形象助手", + "scene": "character_look_costume", + "point": 1, + "state": 1, + "sort": 1, + "description": "形象服饰" + }, + { + "name": "角色换装bannono", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 8, + "assistant_name": "重塑角色", + "scene": "actor_costume", + "point": 1, + "state": 1, + "sort": 0, + "description": "角色换装" + }, + { + "name": "角色换装三视图", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 4, + "assistant_name": "角色三视图", + "scene": "actor_costume_three_view", + "point": 1, + "state": 1, + "sort": 0, + "description": "角色换装三视图" + }, + { + "name": "物品图", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 9, + "assistant_name": "物品主图", + "scene": "prop_image", + "point": 1, + "state": 1, + "sort": 0, + "description": "物品图" + }, + { + "name": "物品三视图", + "model_type": "draw", + "model_id": 4, + "model_name": "Banono-Pro", + "assistant_id": 10, + "assistant_name": "物品三视图", + "scene": "prop_three_view_image", + "point": 1, + "state": 1, + "sort": 0, + "description": "物品三视图" + }, + { + "name": "DeepSeek", + "model_type": "chat", + "model_id": 2, + "model_name": "Yc-reasoner", + "assistant_id": 1, + "assistant_name": "编剧助手", + "scene": "creative_script", + "point": 1, + "state": 1, + "sort": 0, + "description": "1" + }, + { + "name": "音频合成", + "model_type": "audio", + "model_id": 9, + "model_name": "cosyvoice-v3-flash", + "assistant_id": 8, + "assistant_name": "文字转语音", + "scene": "dialogue_voice", + "point": 1, + "state": 1, + "sort": 0, + "description": "音频合成" + }, + { + "name": "旁白合成", + "model_type": "audio", + "model_id": 9, + "model_name": "cosyvoice-v3-flash", + "assistant_id": 8, + "assistant_name": "文字转语音", + "scene": "storyboard_narration_voice", + "point": 0, + "state": 1, + "sort": 0, + "description": "旁白合成" + }, + { + "name": "阿里云", + "model_type": "draw", + "model_id": 7, + "model_name": "万相2.6 image", + "assistant_id": 6, + "assistant_name": "分镜图", + "scene": "storyboard_image", + "point": 100, + "state": 1, + "sort": 0, + "description": "图像处理,生图模型" + }, + { + "name": "香蕉", + "model_type": "draw", + "model_id": 2, + "model_name": "Banono", + "assistant_id": 3, + "assistant_name": "场景图片", + "scene": "scene_image", + "point": 100, + "state": 1, + "sort": 0, + "description": "用于生成场景图片" + } +] \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/package.json b/fastmovie-admin/plugin/model/package.json new file mode 100644 index 0000000..c496739 --- /dev/null +++ b/fastmovie-admin/plugin/model/package.json @@ -0,0 +1,10 @@ +{ + "name": "interface-example", + "title": "应用示例项目", + "version": "0.0.1", + "description": "应用市场", + "author": { + "name": "余心", + "email": "unknown.renloong@gmail.com" + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/process/audio/AudioTransfer.php b/fastmovie-admin/plugin/model/process/audio/AudioTransfer.php new file mode 100644 index 0000000..8f46b1c --- /dev/null +++ b/fastmovie-admin/plugin/model/process/audio/AudioTransfer.php @@ -0,0 +1,101 @@ +id; + new Crontab('*/5 * * * * *', function () use ($id) { + try { + if ($id) { + Timer::sleep(0.3 * $id); + } + Coroutine::create(function () { + $PluginModelTask = PluginModelTask::where(['status' => ModelTaskStatus::WAIT_DOWNLOAD['value'], 'model_type' => ModelType::AUDIO['value']])->order('last_heartbeat asc,id asc')->lock(true)->find(); + if (!$PluginModelTask) { + return; + } + $PluginModelTask->status = ModelTaskStatus::DOWNLOADING['value']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+5 seconds')); + $PluginModelTask->save(); + $PluginModelTaskResult = PluginModelTaskResult::where('task_id', $PluginModelTask->id)->find(); + if (!$PluginModelTaskResult) { + return; + } + try { + $ModelScene = ModelScene::get($PluginModelTask->scene); + $classify = Uploads::getClassify($PluginModelTask->channels_uid, 'uploads/' . $PluginModelTask->scene, $ModelScene['label']); + $result = Uploads::download($PluginModelTask->channels_uid, $PluginModelTaskResult->audio, $classify); + } catch (\Throwable $th) { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->status = ModelTaskStatus::WAIT_DOWNLOAD['value']; + $PluginModelTask->save(); + Log::error("音频转存处理失败:" . $th->getMessage(), $th->getTrace()); + return; + } + $pushData = [ + 'task_id' => $PluginModelTask->id, + 'id' => $PluginModelTask->alias_id, + 'audio' => $result->file_url + ]; + $event = 'generate' . strtolower(str_replace('_', '', $PluginModelTask->scene)); + Db::startTrans(); + try { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->save(); + $PluginModelTaskResult = PluginModelTaskResult::where('task_id', $PluginModelTask->id)->find(); + $PluginModelTaskResult->audio_path = $result->file_name; + $PluginModelTaskResult->save(); + switch ($PluginModelTask->scene) { + case ModelScene::DIALOGUE_VOICE['value']: + $PluginShortplayDramaStoryboardDialogue = PluginShortplayDramaStoryboardDialogue::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayDramaStoryboardDialogue->audio = $result->file_name; + $PluginShortplayDramaStoryboardDialogue->save(); + $event = 'generatestoryboard'; + $pushData['event'] = ModelScene::DIALOGUE_VOICE['value']; + $pushData['id']=$PluginShortplayDramaStoryboardDialogue->storyboard_id; + $pushData['dialogue_id']=$PluginShortplayDramaStoryboardDialogue->id; + break; + case ModelScene::STORYBOARD_NARRATION_VOICE['value']: + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayDramaStoryboard->narration_audio = $result->file_name; + $PluginShortplayDramaStoryboard->save(); + $event = 'generatestoryboard'; + $pushData['event'] = ModelScene::STORYBOARD_NARRATION_VOICE['value']; + break; + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error("音频转存保存失败:" . $th->getMessage(), $th->getTrace()); + } + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => $event + ], $pushData); + }); + } catch (\Throwable $th) { + Log::error("音频转存定时任务失败:" . $th->getMessage(), $th->getTrace()); + } + }); + } +} diff --git a/fastmovie-admin/plugin/model/process/chat/Submit.php b/fastmovie-admin/plugin/model/process/chat/Submit.php new file mode 100644 index 0000000..39cf434 --- /dev/null +++ b/fastmovie-admin/plugin/model/process/chat/Submit.php @@ -0,0 +1,450 @@ +id; + new Crontab('*/5 * * * * *', function () use (&$id) { + try { + if ($id) { + Timer::sleep(0.3 * $id); + } + Coroutine::create(function () { + $PluginModelTask = PluginModelTask::where(['status' => ModelTaskStatus::WAIT['value'], 'model_type' => ModelType::CHAT['value']])->order('last_heartbeat asc,id asc')->lock(true)->find(); + if (!$PluginModelTask) { + return; + } + if ($PluginModelTask->expectation_execution_count !== null) { + $PluginModelTask->execution_count = Db::raw('execution_count + 1'); + } + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+5 seconds')); + $PluginModelTask->save(); + p($PluginModelTask->id); + $event = 'generate' . strtolower(str_replace('_', '', $PluginModelTask->scene)); + $PluginModelTaskResult = PluginModelTaskResult::where('task_id', $PluginModelTask->id)->find(); + if (!$PluginModelTaskResult) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => $event + ], [ + 'task_id' => $PluginModelTask->id, + 'id' => $PluginModelTask->alias_id + ]); + return; + } + $data = $PluginModelTaskResult->params; + switch ($PluginModelTask->scene) { + case ModelScene::CREATIVE_EPISODE['value']: + try { + $data['params']['form_data']['episode_no'] = $data['params']['form_data']['episode_no'] + 1; + p($PluginModelTask->id, $data['params']['form_data']['episode_no'], 'start'); + $result = Yidevs::ChatAssistantCompletions($data['channels_uid'], $data['params']); + p($PluginModelTask->id, $data['params']['form_data']['episode_no'], 'success'); + } catch (\Throwable $th) { + Log::error('CREATIVE_EPISODE Error:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + $taksStatus = ModelTaskStatus::FAIL['value']; + if (!empty($result)) { + Db::startTrans(); + try { + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $data['drama_id']])->find(); + $episode_num = $PluginShortplayDrama->episode_num + 1; + $PluginShortplayDrama->episode_num = Db::raw('episode_num + 1'); + $PluginShortplayDrama->save(); + $PluginShortplayDramaEpisode = new PluginShortplayDramaEpisode(); + $PluginShortplayDramaEpisode->channels_uid = $data['channels_uid']; + $PluginShortplayDramaEpisode->drama_id = $data['drama_id']; + $PluginShortplayDramaEpisode->episode_no = $episode_num; + $PluginShortplayDramaEpisode->title = $result['title']; + $PluginShortplayDramaEpisode->outline = $result['description']; + $PluginShortplayDramaEpisode->content = $result['content']; + $PluginShortplayDramaEpisode->save(); + if (!empty($result['actor'])) { + $PluginShortplayActors = PluginShortplayActor::whereIn('actor_id', $result['actor'])->field('id,actor_id')->select(); + foreach ($PluginShortplayActors as $PluginShortplayActor) { + if (!PluginShortplayDramaActor::where(['drama_id' => $PluginShortplayDrama->id, 'actor_id' => $PluginShortplayActor->id])->count()) { + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayDramaActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaActor->save(); + } + $PluginShortplayDramaEpisodeActor = new PluginShortplayDramaEpisodeActor(); + $PluginShortplayDramaEpisodeActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaEpisodeActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayDramaEpisodeActor->episode_id = $PluginShortplayDramaEpisode->id; + $PluginShortplayDramaEpisodeActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaEpisodeActor->save(); + } + } + $updateActors = []; + if (!empty($result['create_actor'])) { + foreach ($result['create_actor'] as $actor) { + $PluginShortplayActor = PluginShortplayActor::where(['name' => $actor['name'], 'channels_uid' => $data['channels_uid'], 'drama_id' => $PluginShortplayDrama->id])->field('id,actor_id')->find(); + if (!$PluginShortplayActor) { + if (empty($actor['description'])) { + continue; + } + $PluginShortplayActor = new PluginShortplayActor(); + $PluginShortplayActor->channels_uid = $data['channels_uid']; + $PluginShortplayActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayActor->episode_id = $PluginShortplayDramaEpisode->id; + $PluginShortplayActor->uid = $data['uid']; + $PluginShortplayActor->actor_id = uniqid(); + $PluginShortplayActor->name = $actor['name']; + $PluginShortplayActor->species_type = $actor['species']; + $PluginShortplayActor->gender = $actor['gender']; + $PluginShortplayActor->age = $actor['age']; + $PluginShortplayActor->remarks = $actor['description']; + $PluginShortplayActor->status = ActorStatus::INITIALIZING['value']; + $PluginShortplayActor->save(); + $updateActors[] = "{$PluginShortplayActor->name}{{$PluginShortplayActor->actor_id}}"; + } + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayDramaActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaActor->save(); + $PluginShortplayDramaEpisodeActor = new PluginShortplayDramaEpisodeActor(); + $PluginShortplayDramaEpisodeActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaEpisodeActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayDramaEpisodeActor->episode_id = $PluginShortplayDramaEpisode->id; + $PluginShortplayDramaEpisodeActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaEpisodeActor->save(); + } + } + $PluginModelTask = PluginModelTask::where(['id' => $PluginModelTask->id])->with(['result'])->find(); + if ( + $PluginModelTask->expectation_execution_count === null + || ($PluginModelTask->expectation_execution_count !== null && $PluginModelTask->success_execution_count + 1 >= $PluginModelTask->expectation_execution_count) + ) { + $PluginModelTask->status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->success_execution_count = Db::raw('success_execution_count + 1'); + $PluginModelTask->result->result = $result; + $PluginModelTask->together(['result'])->save(); + } else { + $PluginModelTask->status = ModelTaskStatus::WAIT['value']; + $PluginModelTask->success_execution_count = Db::raw('success_execution_count + 1'); + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+10 seconds')); + $data['params']['form_data']['pre_episode'] = $result['content']; + if (!empty($updateActors)) { + $data['params']['form_data']['actors'] .= "," . implode(',', $updateActors); + } + $PluginModelTask->result->params = $data; + $PluginModelTask->together(['result'])->save(); + } + $taksStatus = ModelTaskStatus::SUCCESS['value']; + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('续写分集失败 Error:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + } + if ($taksStatus == ModelTaskStatus::FAIL['value']) { + $PluginModelTask = PluginModelTask::where(['id' => $PluginModelTask->id])->find(); + if ( + $PluginModelTask->expectation_execution_count === null + || ($PluginModelTask->expectation_execution_count !== null && $PluginModelTask->success_execution_count + 1 >= $PluginModelTask->expectation_execution_count) + || ($PluginModelTask->expectation_execution_count !== null && $PluginModelTask->execution_count >= $PluginModelTask->expectation_execution_count * 2) + ) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'continueepisode', + ], [ + 'drama_id' => $data['drama_id'] + ]); + } else { + $PluginModelTask->status = ModelTaskStatus::WAIT['value']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+10 seconds')); + $PluginModelTask->save(); + } + } else { + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'continueepisode', + ], [ + 'drama_id' => $data['drama_id'] + ]); + } + break; + case ModelScene::CREATIVE_STORYBOARDS['value']: + try { + p('start'); + $result = Yidevs::ChatAssistantCompletions($data['channels_uid'], $data['params']); + p($result); + } catch (\Throwable $th) { + Log::error('CREATIVE_STORYBOARDS Error:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + $taksStatus = ModelTaskStatus::FAIL['value']; + if (!empty($result)) { + Db::startTrans(); + try { + $previous_dialogues = ''; + $whereOr = []; + $whereOr[] = ['uid', '=', null]; + $whereOr[] = ['uid', '=', $data['uid']]; + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $data['drama_id']])->find(); + $sort = PluginShortplayDramaStoryboard::where(['drama_id' => $data['drama_id'], 'episode_id' => $data['episode_id']])->max('sort') + 1 ?? 1; + $PluginShortplayDramaStoryboard = new PluginShortplayDramaStoryboard; + $PluginShortplayDramaStoryboard->channels_uid = $data['channels_uid']; + $PluginShortplayDramaStoryboard->drama_id = $data['drama_id']; + $PluginShortplayDramaStoryboard->episode_id = $data['episode_id']; + $PluginShortplayDramaStoryboard->scene_id = $result['scene_id']; + $PluginShortplayDramaStoryboard->sort = $sort; + $PluginShortplayDramaStoryboard->description = $result['description']; + if (!empty($result['image_prompt'])) { + $PluginShortplayDramaStoryboard->image_prompt = $result['image_prompt']; + } + if (!empty($result['video_prompt'])) { + $PluginShortplayDramaStoryboard->video_prompt = $result['video_prompt']; + } + $PluginShortplayDramaStoryboard->shot_type = $result['shot_type']; + $PluginShortplayDramaStoryboard->shot_angle = $result['shot_angle']; + $PluginShortplayDramaStoryboard->shot_motion = $result['shot_motion']; + if (empty($result['narration'])) { + $PluginShortplayDramaStoryboard->narration = $result['narration']; + } + // $PluginShortplayDramaStoryboard->sfx = $storyboard['sfx']; + $PluginShortplayDramaStoryboard->duration = $result['duration']; + $PluginShortplayDramaStoryboard->save(); + $updateActors = []; + if (!empty($result['actor'])) { + foreach ($result['actor'] as $actor_key => $actor) { + $PluginShortplayActor = null; + if (!empty($actor['actor_id'])) { + $PluginShortplayActor = PluginShortplayActor::where(['actor_id' => $actor['actor_id']])->find(); + } + if (!$PluginShortplayActor) { + $PluginShortplayActor = PluginShortplayActor::where(['channels_uid' => $data['channels_uid'], 'name' => $actor['name']])->whereOr($whereOr)->find(); + } + if (!$PluginShortplayActor && !empty($actor['description'])) { + $PluginShortplayActor = new PluginShortplayActor; + $PluginShortplayActor->channels_uid = $data['channels_uid']; + $PluginShortplayActor->uid = $data['uid']; + $PluginShortplayActor->drama_id = $data['drama_id']; + $PluginShortplayActor->episode_id = $data['episode_id']; + $PluginShortplayActor->actor_id = uniqid(); + $PluginShortplayActor->name = $actor['name']; + $PluginShortplayActor->remarks = $actor['description']; + $PluginShortplayActor->status = ActorStatus::INITIALIZING['value']; + $PluginShortplayActor->save(); + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaActor->drama_id = $data['drama_id']; + $PluginShortplayDramaActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaActor->save(); + $PluginShortplayDramaEpisodeActor = new PluginShortplayDramaEpisodeActor; + $PluginShortplayDramaEpisodeActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaEpisodeActor->drama_id = $data['drama_id']; + $PluginShortplayDramaEpisodeActor->episode_id = $data['episode_id']; + $PluginShortplayDramaEpisodeActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaEpisodeActor->save(); + $updateActors[] = "{$PluginShortplayActor->name}{{$PluginShortplayActor->actor_id}}"; + } + if ($PluginShortplayActor) { + $PluginShortplayDramaStoryboardActor = new PluginShortplayDramaStoryboardActor(); + $PluginShortplayDramaStoryboardActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaStoryboardActor->drama_id = $data['drama_id']; + $PluginShortplayDramaStoryboardActor->episode_id = $data['episode_id']; + $PluginShortplayDramaStoryboardActor->storyboard_id = $PluginShortplayDramaStoryboard->id; + $PluginShortplayDramaStoryboardActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaStoryboardActor->save(); + } + } + } + $PluginShortplayActor = null; + if (!empty($result['dialogues'])) { + foreach ($result['dialogues'] as $dialogue_key => $dialogue) { + if (!empty($dialogue['actor']['actor_id'])) { + $PluginShortplayActor = PluginShortplayActor::where(['actor_id' => $dialogue['actor']['actor_id']])->find(); + } + if (!$PluginShortplayActor) { + $PluginShortplayActor = PluginShortplayActor::where(['channels_uid' => $data['channels_uid'], 'name' => $dialogue['actor']['name']])->whereOr($whereOr)->find(); + } + if (!$PluginShortplayActor) { + continue; + } + $PluginShortplayDramaStoryboardDialogue = new PluginShortplayDramaStoryboardDialogue(); + $PluginShortplayDramaStoryboardDialogue->channels_uid = $data['channels_uid']; + $PluginShortplayDramaStoryboardDialogue->storyboard_id = $PluginShortplayDramaStoryboard->id; + $PluginShortplayDramaStoryboardDialogue->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaStoryboardDialogue->prosody_speed = $dialogue['prosody_speed']; + $PluginShortplayDramaStoryboardDialogue->prosody_volume = $dialogue['prosody_volume']; + $PluginShortplayDramaStoryboardDialogue->emotion = $dialogue['emotion']; + $PluginShortplayDramaStoryboardDialogue->start_time = $dialogue['start_time']; + $PluginShortplayDramaStoryboardDialogue->end_time = $dialogue['end_time']; + $PluginShortplayDramaStoryboardDialogue->inner_monologue = empty($dialogue['inner_monologue']) ? 0 : 1; + $content = $dialogue['content']; + if (empty($content)) { + $content = '……'; + } + $PluginShortplayDramaStoryboardDialogue->content = $content; + $PluginShortplayDramaStoryboardDialogue->save(); + $previous_dialogues .= "{$PluginShortplayActor->name}:{$content}\n"; + } + } + $updateProps = []; + if (!empty($result['prop'])) { + foreach ($result['prop'] as $prop_key => $prop) { + $PluginShortplayProp = null; + if (!empty($prop['prop_id'])) { + $PluginShortplayProp = PluginShortplayProp::where(['prop_id' => $prop['prop_id']])->find(); + } + if (!$PluginShortplayProp) { + $PluginShortplayProp = PluginShortplayProp::where(['name' => $prop['name'], 'channels_uid' => $data['channels_uid'], 'drama_id' => $data['drama_id']])->find(); + } + if (!$PluginShortplayProp && !empty($prop['description'])) { + $PluginShortplayProp = new PluginShortplayProp; + $PluginShortplayProp->channels_uid = $data['channels_uid']; + $PluginShortplayProp->uid = $data['uid']; + $PluginShortplayProp->drama_id = $data['drama_id']; + $PluginShortplayProp->episode_id = $data['episode_id']; + $PluginShortplayProp->prop_id = uniqid(); + $PluginShortplayProp->name = $prop['name']; + $PluginShortplayProp->description = $prop['description']; + $PluginShortplayProp->status = PropStatus::INITIALIZING['value']; + $PluginShortplayProp->save(); + $updateProps[] = "{$PluginShortplayProp->name}{{$PluginShortplayProp->prop_id}}"; + } + if ($PluginShortplayProp) { + $PluginShortplayDramaStoryboardProp = new PluginShortplayDramaStoryboardProp(); + $PluginShortplayDramaStoryboardProp->channels_uid = $data['channels_uid']; + $PluginShortplayDramaStoryboardProp->storyboard_id = $PluginShortplayDramaStoryboard->id; + $PluginShortplayDramaStoryboardProp->prop_id = $PluginShortplayProp->id; + $PluginShortplayDramaStoryboardProp->save(); + } + } + } + $success_execution_count = $PluginModelTask->success_execution_count + 1; + $PluginModelTask = PluginModelTask::where(['id' => $PluginModelTask->id])->with(['result'])->find(); + if ($result['is_end']) { + $PluginModelTask->success_execution_count = $PluginModelTask->expectation_execution_count; + } + if ( + $PluginModelTask->expectation_execution_count === null + || ($PluginModelTask->expectation_execution_count !== null && $PluginModelTask->success_execution_count + 1 >= $PluginModelTask->expectation_execution_count) + ) { + $PluginModel = PluginModel::where(['id' => $PluginModelTask->model_id])->find(); + $point = $PluginModel->point * $success_execution_count; + Account::decPoints($PluginShortplayDrama->uid, $PluginShortplayDrama->channels_uid, $point > 200 ? 200 : $point, PointsBillScene::CONSUME['value'], null, '生成分镜', true, true); + $PluginModelTask->status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->success_execution_count = Db::raw('success_execution_count + 1'); + $PluginModelTask->result->result = $result; + $PluginModelTask->together(['result'])->save(); + } else { + $storyboards = ''; + if (!empty($data['params']['form_data']['storyboards'])) { + $storyboards = $data['params']['form_data']['storyboards']; + } + $storyboards .= "- 序号:" . $PluginShortplayDramaStoryboard->sort . "\n- 画面描述:" . $PluginShortplayDramaStoryboard->description . "\n"; + $storyboards .= "- 对话:" . $previous_dialogues . "\n\n"; + $PluginModelTask->status = ModelTaskStatus::WAIT['value']; + $PluginModelTask->success_execution_count = Db::raw('success_execution_count + 1'); + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+10 seconds')); + if (!empty($updateActors)) { + $data['params']['form_data']['actors'] .= "," . implode(',', $updateActors); + } + if (!empty($updateProps)) { + $data['params']['form_data']['props'] .= "," . implode(',', $updateProps); + } + $data['params']['form_data']['previous_description'] = $PluginShortplayDramaStoryboard->description; + $data['params']['form_data']['previous_dialogues'] = $previous_dialogues; + $data['params']['form_data']['previous_sort'] = $PluginShortplayDramaStoryboard->sort; + $data['params']['form_data']['sort'] = $sort + 1; + $data['params']['form_data']['storyboards'] = $storyboards; + $PluginModelTask->result->params = $data; + $PluginModelTask->result->result = $result; + $PluginModelTask->together(['result'])->save(); + } + $taksStatus = ModelTaskStatus::SUCCESS['value']; + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('CREATIVE_STORYBOARDS RESULT:', $result); + Log::error('Generate Storyboard Error:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + } + if ($taksStatus == ModelTaskStatus::FAIL['value']) { + $PluginModelTask = PluginModelTask::where(['id' => $PluginModelTask->id])->find(); + if ( + $PluginModelTask->expectation_execution_count === null + || ($PluginModelTask->expectation_execution_count !== null && $PluginModelTask->success_execution_count + 1 >= $PluginModelTask->expectation_execution_count) + || ($PluginModelTask->expectation_execution_count !== null && $PluginModelTask->execution_count >= $PluginModelTask->expectation_execution_count * 2) + ) { + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'hash' => $data['drama_id'], + 'event' => 'generatescenestoryboard', + ], [ + 'drama_id' => $data['drama_id'], + 'episode_id' => $data['episode_id'], + ]); + } else { + $PluginModelTask->status = ModelTaskStatus::WAIT['value']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+10 seconds')); + $PluginModelTask->save(); + } + } else { + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'hash' => $data['drama_id'], + 'event' => 'generatescenestoryboard', + ], [ + 'drama_id' => $data['drama_id'], + 'episode_id' => $data['episode_id'], + ]); + } + break; + default: + break; + } + }); + } catch (\Throwable $th) { + Log::error("Chat Submit Process Error:" . $th->getMessage(), $th->getTrace()); + } + }); + } +} diff --git a/fastmovie-admin/plugin/model/process/draw/ImageTransfer.php b/fastmovie-admin/plugin/model/process/draw/ImageTransfer.php new file mode 100644 index 0000000..514be09 --- /dev/null +++ b/fastmovie-admin/plugin/model/process/draw/ImageTransfer.php @@ -0,0 +1,271 @@ +id; + new Crontab('*/5 * * * * *', function () use ($id) { + try { + if ($id) { + Timer::sleep(0.3 * $id); + } + Coroutine::create(function () { + $PluginModelTask = PluginModelTask::where(['status' => ModelTaskStatus::WAIT_DOWNLOAD['value'], 'model_type' => ModelType::DRAW['value']])->order('last_heartbeat asc,id asc')->lock(true)->find(); + if (!$PluginModelTask) { + return; + } + $PluginModelTask->status = ModelTaskStatus::DOWNLOADING['value']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+5 seconds')); + $PluginModelTask->save(); + $event = 'generate' . strtolower(str_replace('_', '', $PluginModelTask->scene)); + $pushData = [ + 'task_id' => $PluginModelTask->id, + 'id' => $PluginModelTask->alias_id + ]; + $PluginModelTaskResult = PluginModelTaskResult::where('task_id', $PluginModelTask->id)->find(); + if (!$PluginModelTaskResult) { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => $event + ], $pushData); + return; + } + try { + $ModelScene = ModelScene::get($PluginModelTask->scene); + $classify = Uploads::getClassify($PluginModelTask->channels_uid, 'uploads/' . $PluginModelTask->scene, $ModelScene['label']); + $result = Uploads::download($PluginModelTask->channels_uid, $PluginModelTaskResult->image, $classify); + } catch (\Throwable $th) { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->status = ModelTaskStatus::WAIT_DOWNLOAD['value']; + $PluginModelTask->save(); + Log::error("图片转存处理失败:" . $th->getMessage(), $th->getTrace()); + return; + } + $pushData['image'] = $result->file_url; + Db::startTrans(); + try { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->save(); + $PluginModelTaskResult = PluginModelTaskResult::where('task_id', $PluginModelTask->id)->find(); + $PluginModelTaskResult->image_path = $result->file_name; + $PluginModelTaskResult->save(); + switch ($PluginModelTask->scene) { + case ModelScene::DRAMA_COVER['value']: + $PluginShortplayDrama = PluginShortplayDrama::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayDrama->cover = $result->file_name; + $PluginShortplayDrama->save(); + break; + case ModelScene::SCENE_IMAGE['value']: + $PluginShortplayDramaScene = PluginShortplayDramaScene::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayDramaScene->image = $result->file_name; + $PluginShortplayDramaScene->save(); + break; + case ModelScene::ACTOR_IMAGE['value']: + $PluginShortplayActor = PluginShortplayActor::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayActor->headimg = $result->file_name; + $PluginShortplayActor->status = $PluginShortplayActor->headimg && $PluginShortplayActor->three_view_image ? ActorStatus::GENERATED['value'] : ActorStatus::INITIALIZING['value']; + $PluginShortplayActor->save(); + $threeViewImageState = PluginModelTask::processing(['alias_id' => $PluginShortplayActor->id, 'scene' => ModelScene::ACTOR_THREE_VIEW_IMAGE['value']]); + if ($threeViewImageState > 0) { + $pushData['status'] = ActorStatus::PENDING; + } else { + $pushData['status'] = ActorStatus::get($PluginShortplayActor->status); + } + break; + case ModelScene::ACTOR_THREE_VIEW_IMAGE['value']: + $PluginShortplayActor = PluginShortplayActor::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayActor->three_view_image = $result->file_name; + $PluginShortplayActor->status = $PluginShortplayActor->headimg && $PluginShortplayActor->three_view_image ? ActorStatus::GENERATED['value'] : ActorStatus::INITIALIZING['value']; + $PluginShortplayActor->save(); + $pushData['status'] = ActorStatus::get($PluginShortplayActor->status); + break; + case ModelScene::STORYBOARD_IMAGE['value']: + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayDramaStoryboard->image = $result->file_name; + $PluginShortplayDramaStoryboard->use_material_type='image'; + $PluginShortplayDramaStoryboard->save(); + $event = 'generatestoryboard'; + $pushData['event'] = ModelScene::STORYBOARD_IMAGE['value']; + break; + case ModelScene::ACTOR_COSTUME['value']: + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayActorCharacterLook->headimg = $result->file_name; + $PluginShortplayActorCharacterLook->save(); + $event = 'generatestoryboard'; + $pushData['storyboard_id'] = $PluginShortplayActorCharacterLook->storyboard_id; + $pushData['episode_id'] = $PluginShortplayActorCharacterLook->episode_id; + $pushData['drama_id'] = $PluginShortplayActorCharacterLook->drama_id; + $pushData['id'] = $PluginShortplayActorCharacterLook->actor_id; + $pushData['event'] = ModelScene::ACTOR_COSTUME['value']; + break; + case ModelScene::ACTOR_COSTUME_THREE_VIEW['value']: + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayActorCharacterLook->three_view_image = $result->file_name; + $PluginShortplayActorCharacterLook->status = ActorStatus::GENERATED['value']; + $PluginShortplayActorCharacterLook->save(); + $event = 'generatestoryboard'; + $pushData['event'] = ModelScene::ACTOR_COSTUME_THREE_VIEW['value']; + $pushData['storyboard_id'] = $PluginShortplayActorCharacterLook->storyboard_id; + $pushData['episode_id'] = $PluginShortplayActorCharacterLook->episode_id; + $pushData['drama_id'] = $PluginShortplayActorCharacterLook->drama_id; + $pushData['id'] = $PluginShortplayActorCharacterLook->actor_id; + + if ($PluginShortplayActorCharacterLook->storyboard_id) { + $PluginShortplayDramaStoryboardActor = PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $PluginShortplayActorCharacterLook->storyboard_id, 'actor_id' => $PluginShortplayActorCharacterLook->actor_id])->find(); + $PluginShortplayDramaStoryboardActor->character_look_id = $PluginShortplayActorCharacterLook->id; + $PluginShortplayDramaStoryboardActor->headimg = $PluginShortplayActorCharacterLook->headimg; + $PluginShortplayDramaStoryboardActor->three_view_image = $PluginShortplayActorCharacterLook->three_view_image; + $PluginShortplayDramaStoryboardActor->save(); + } elseif ($PluginShortplayActorCharacterLook->episode_id) { + $PluginShortplayDramaEpisodeActor = PluginShortplayDramaEpisodeActor::where(['episode_id' => $PluginShortplayActorCharacterLook->episode_id, 'actor_id' => $PluginShortplayActorCharacterLook->actor_id])->find(); + $PluginShortplayDramaEpisodeActor->character_look_id = $PluginShortplayActorCharacterLook->id; + $PluginShortplayDramaEpisodeActor->headimg = $PluginShortplayActorCharacterLook->headimg; + $PluginShortplayDramaEpisodeActor->three_view_image = $PluginShortplayActorCharacterLook->three_view_image; + $PluginShortplayDramaEpisodeActor->save(); + } elseif ($PluginShortplayActorCharacterLook->drama_id) { + $PluginShortplayDramaActor = PluginShortplayDramaActor::where(['drama_id' => $PluginShortplayActorCharacterLook->drama_id, 'actor_id' => $PluginShortplayActorCharacterLook->actor_id])->find(); + $PluginShortplayDramaActor->character_look_id = $PluginShortplayActorCharacterLook->id; + $PluginShortplayDramaActor->headimg = $PluginShortplayActorCharacterLook->headimg; + $PluginShortplayDramaActor->three_view_image = $PluginShortplayActorCharacterLook->three_view_image; + $PluginShortplayDramaActor->save(); + } + break; + case ModelScene::PROP_IMAGE['value']: + $PluginShortplayProp = PluginShortplayProp::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayProp->image = $result->file_name; + $PluginShortplayProp->status = $PluginShortplayProp->image && $PluginShortplayProp->three_view_image ? PropStatus::GENERATED['value'] : PropStatus::INITIALIZING['value']; + $PluginShortplayProp->save(); + $threeViewImageState = PluginModelTask::processing(['alias_id' => $PluginShortplayProp->id, 'scene' => ModelScene::PROP_THREE_VIEW_IMAGE['value']]); + if ($threeViewImageState > 0) { + $pushData['status'] = PropStatus::PENDING; + } else { + $pushData['status'] = PropStatus::get($PluginShortplayProp->status); + } + break; + case ModelScene::PROP_THREE_VIEW_IMAGE['value']: + $PluginShortplayProp = PluginShortplayProp::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayProp->three_view_image = $result->file_name; + $PluginShortplayProp->status = $PluginShortplayProp->image && $PluginShortplayProp->three_view_image ? PropStatus::GENERATED['value'] : PropStatus::INITIALIZING['value']; + $PluginShortplayProp->save(); + $pushData['status'] = PropStatus::get($PluginShortplayProp->status); + break; + case ModelScene::CHARACTER_LOOK_COSTUME['value']: + $PluginShortplayCharacterLook = PluginShortplayCharacterLook::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayCharacterLook->costume_url = $result->file_name; + $PluginShortplayCharacterLook->status = PropStatus::GENERATED['value']; + $PluginShortplayCharacterLook->save(); + $pushData['status'] = PropStatus::get($PluginShortplayCharacterLook->status); + break; + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error("图片转存保存失败:" . $th->getMessage(), $th->getTrace()); + return; + } + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => $event + ], $pushData); + switch ($PluginModelTask->scene) { + case ModelScene::ACTOR_IMAGE['value']: + try { + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'status' => ModelTaskStatus::WAIT['value']])->with(['result'])->find(); + if ($PluginModelTask) { + $data = $PluginModelTask->result->params; + $data['form_data']['images'] = [$result->file_url]; + $result = Yidevs::DrawAssistantTIGI($PluginModelTask->channels_uid, $data); + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->save(); + } + } catch (\Throwable $e) { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->pre_task_status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->save(); + Log::error('GenerateActorThreeView Error:' . $e->getMessage()); + } + break; + case ModelScene::PROP_IMAGE['value']: + try { + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'status' => ModelTaskStatus::WAIT['value']])->with(['result'])->find(); + if ($PluginModelTask) { + $data = $PluginModelTask->result->params; + $data['form_data']['images'] = [$result->file_url]; + $result = Yidevs::DrawAssistantTIGI($PluginModelTask->channels_uid, $data); + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->save(); + } + } catch (\Throwable $e) { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->pre_task_status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->save(); + Log::error('GeneratePropThreeView Error:' . $e->getMessage()); + } + break; + case ModelScene::ACTOR_COSTUME['value']: + try { + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'status' => ModelTaskStatus::WAIT['value']])->with(['result'])->find(); + if ($PluginModelTask) { + $data = $PluginModelTask->result->params; + $data['form_data']['images'] = [$result->file_url]; + $result = Yidevs::DrawAssistantTIGI($PluginModelTask->channels_uid, $data); + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->save(); + } + } catch (\Throwable $e) { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->pre_task_status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->save(); + Log::error('GenerateActorThreeView Error:' . $e->getMessage()); + } + break; + } + }); + } catch (\Throwable $th) { + Log::error("图片转存定时任务失败:" . $th->getMessage(), $th->getTrace()); + } + }); + } +} diff --git a/fastmovie-admin/plugin/model/process/video/VideoTransfer.php b/fastmovie-admin/plugin/model/process/video/VideoTransfer.php new file mode 100644 index 0000000..148783b --- /dev/null +++ b/fastmovie-admin/plugin/model/process/video/VideoTransfer.php @@ -0,0 +1,92 @@ +id; + new Crontab('*/5 * * * * *', function () use ($id) { + try { + if ($id) { + Timer::sleep(0.3 * $id); + } + Coroutine::create(function () { + $PluginModelTask = PluginModelTask::where(['status' => ModelTaskStatus::WAIT_DOWNLOAD['value'], 'model_type' => ModelType::TOVIDEO['value']])->order('last_heartbeat asc,id asc')->lock(true)->find(); + if (!$PluginModelTask) { + return; + } + $PluginModelTask->status = ModelTaskStatus::DOWNLOADING['value']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+5 seconds')); + $PluginModelTask->save(); + $PluginModelTaskResult = PluginModelTaskResult::where('task_id', $PluginModelTask->id)->find(); + if (!$PluginModelTaskResult) { + return; + } + try { + $ModelScene = ModelScene::get($PluginModelTask->scene); + $classify = Uploads::getClassify($PluginModelTask->channels_uid, 'uploads/' . $PluginModelTask->scene, $ModelScene['label']); + $result = Uploads::download($PluginModelTask->channels_uid, $PluginModelTaskResult->video, $classify); + } catch (\Throwable $th) { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->status = ModelTaskStatus::WAIT_DOWNLOAD['value']; + $PluginModelTask->save(); + Log::error("视频转存处理失败:" . $th->getMessage(), $th->getTrace()); + return; + } + $pushData = [ + 'task_id' => $PluginModelTask->id, + 'id' => $PluginModelTask->alias_id, + 'video' => $result->file_url + ]; + $event = 'generate' . strtolower(str_replace('_', '', $PluginModelTask->scene)); + Db::startTrans(); + try { + $PluginModelTask = PluginModelTask::where('id', $PluginModelTask->id)->find(); + $PluginModelTask->status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->save(); + $PluginModelTaskResult = PluginModelTaskResult::where('task_id', $PluginModelTask->id)->find(); + $PluginModelTaskResult->video_path = $result->file_name; + $PluginModelTaskResult->save(); + switch ($PluginModelTask->scene) { + case ModelScene::STORYBOARD_VIDEO['value']: + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where('id', $PluginModelTask->alias_id)->find(); + $PluginShortplayDramaStoryboard->video = $result->file_name; + $PluginShortplayDramaStoryboard->use_material_type='video'; + $PluginShortplayDramaStoryboard->save(); + $event = 'generatestoryboard'; + $pushData['event'] = ModelScene::STORYBOARD_VIDEO['value']; + break; + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error("视频转存保存失败:" . $th->getMessage(), $th->getTrace()); + } + Push::send([ + 'uid' => $PluginModelTask->uid, + 'channels_uid' => $PluginModelTask->channels_uid, + 'event' => $event + ], $pushData); + }); + } catch (\Throwable $th) { + Log::error("视频转存定时任务失败:" . $th->getMessage(), $th->getTrace()); + } + }); + } +} diff --git a/fastmovie-admin/plugin/model/resource/translations/en/admin.php b/fastmovie-admin/plugin/model/resource/translations/en/admin.php new file mode 100644 index 0000000..5e9f768 --- /dev/null +++ b/fastmovie-admin/plugin/model/resource/translations/en/admin.php @@ -0,0 +1,5 @@ +'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/resource/translations/en/api.php b/fastmovie-admin/plugin/model/resource/translations/en/api.php new file mode 100644 index 0000000..45a7854 --- /dev/null +++ b/fastmovie-admin/plugin/model/resource/translations/en/api.php @@ -0,0 +1,6 @@ + 'Success', + 'fail' => 'Fail', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/resource/translations/en/messages.php b/fastmovie-admin/plugin/model/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/model/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/model/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..c9d49f9 --- /dev/null +++ b/fastmovie-admin/plugin/model/resource/translations/zh_CN/admin.php @@ -0,0 +1,5 @@ +'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/resource/translations/zh_CN/api.php b/fastmovie-admin/plugin/model/resource/translations/zh_CN/api.php new file mode 100644 index 0000000..40bd25b --- /dev/null +++ b/fastmovie-admin/plugin/model/resource/translations/zh_CN/api.php @@ -0,0 +1,6 @@ + '成功', + 'fail' => '失败', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/model/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/model/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/model/utils/enum/ModelPointType.php b/fastmovie-admin/plugin/model/utils/enum/ModelPointType.php new file mode 100644 index 0000000..52b8bfd --- /dev/null +++ b/fastmovie-admin/plugin/model/utils/enum/ModelPointType.php @@ -0,0 +1,19 @@ + '次', + 'value' => 'times', + 'unit' => '次' + ]; + const FIELD_RULE = [ + 'label' => '字段规则', + 'value' => 'field_rule', + 'unit' => '秒起' + ]; +} diff --git a/fastmovie-admin/plugin/model/utils/enum/ModelScene.php b/fastmovie-admin/plugin/model/utils/enum/ModelScene.php new file mode 100644 index 0000000..2b2e21f --- /dev/null +++ b/fastmovie-admin/plugin/model/utils/enum/ModelScene.php @@ -0,0 +1,199 @@ + '创意剧本', + 'value' => 'creative_script', + 'props' => [ + 'type' => 'primary', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const CREATIVE_EPISODE = [ + 'label' => '续写分集', + 'value' => 'creative_episode', + 'props' => [ + 'type' => 'primary', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const CREATIVE_SCENES = [ + 'label' => '创作场景', + 'value' => 'creative_scenes', + 'props' => [ + 'type' => 'primary', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const CREATIVE_STORYBOARDS = [ + 'label' => '创作分镜', + 'value' => 'creative_storyboards', + 'props' => [ + 'type' => 'primary', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const DRAMA_COVER = [ + 'label' => '短剧封面', + 'value' => 'drama_cover', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const SCENE_IMAGE = [ + 'label' => '场景图片', + 'value' => 'scene_image', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const ACTOR_IMAGE = [ + 'label' => '角色形象', + 'value' => 'actor_image', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const ACTOR_THREE_VIEW_IMAGE = [ + 'label' => '角色三视图', + 'value' => 'actor_three_view_image', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const STORYBOARD_IMAGE = [ + 'label' => '分镜图片', + 'value' => 'storyboard_image', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const CHARACTER_LOOK_COSTUME = [ + 'label' => '角色服饰', + 'value' => 'character_look_costume', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const ACTOR_COSTUME = [ + 'label' => '角色换装', + 'value' => 'actor_costume', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const ACTOR_COSTUME_THREE_VIEW = [ + 'label' => '角色换装三视图', + 'value' => 'actor_costume_three_view', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const PROP_IMAGE = [ + 'label' => '物品图片', + 'value' => 'prop_image', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const PROP_THREE_VIEW_IMAGE = [ + 'label' => '物品三视图', + 'value' => 'prop_three_view_image', + 'props' => [ + 'type' => 'warning', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const STORYBOARD_VIDEO = [ + 'label' => '分镜视频', + 'value' => 'storyboard_video', + 'props' => [ + 'type' => 'success', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const DIALOGUE_VOICE = [ + 'label' => '台词语音', + 'value' => 'dialogue_voice', + 'props' => [ + 'type' => 'danger', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const STORYBOARD_NARRATION_VOICE = [ + 'label' => '分镜旁白语音', + 'value' => 'storyboard_narration_voice', + 'props' => [ + 'type' => 'danger', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const STORYBOARD_SFX_VOICE = [ + 'label' => '分镜音效', + 'value' => 'storyboard_sfx_voice', + 'props' => [ + 'type' => 'danger', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; + const STORYBOARD_MUSIC_VOICE = [ + 'label' => '分镜音乐', + 'value' => 'storyboard_music_voice', + 'props' => [ + 'type' => 'danger', + ], + 'extra' => [ + 'charge_unit' => '次', + ] + ]; +} diff --git a/fastmovie-admin/plugin/model/utils/enum/ModelTaskStatus.php b/fastmovie-admin/plugin/model/utils/enum/ModelTaskStatus.php new file mode 100644 index 0000000..5da0c6e --- /dev/null +++ b/fastmovie-admin/plugin/model/utils/enum/ModelTaskStatus.php @@ -0,0 +1,58 @@ + '待处理', + 'value' => 'wait', + 'props' => [ + 'type' => 'info' + ] + ]; + const PROCESSING = [ + 'label' => '处理中', + 'value' => 'processing', + 'props' => [ + 'type' => 'primary' + ] + ]; + const WAIT_DOWNLOAD = [ + 'label' => '等待下载', + 'value' => 'wait_download', + 'props' => [ + 'type' => 'primary' + ] + ]; + const DOWNLOADING = [ + 'label' => '下载中', + 'value' => 'downloading', + 'props' => [ + 'type' => 'primary' + ] + ]; + const UPLOADING = [ + 'label' => '上传中', + 'value' => 'uploading', + 'props' => [ + 'type' => 'primary' + ] + ]; + const SUCCESS = [ + 'label' => '成功', + 'value' => 'success', + 'props' => [ + 'type' => 'success' + ] + ]; + const FAIL = [ + 'label' => '失败', + 'value' => 'fail', + 'props' => [ + 'type' => 'danger' + ] + ]; +} diff --git a/fastmovie-admin/plugin/model/utils/enum/ModelType.php b/fastmovie-admin/plugin/model/utils/enum/ModelType.php new file mode 100644 index 0000000..8eb23ef --- /dev/null +++ b/fastmovie-admin/plugin/model/utils/enum/ModelType.php @@ -0,0 +1,37 @@ + '对话', + 'value' => 'chat', + 'props' => [ + 'type' => 'primary', + ] + ]; + const DRAW = [ + 'label' => '绘图', + 'value' => 'draw', + 'props' => [ + 'type' => 'warning', + ] + ]; + const TOVIDEO = [ + 'label' => '视频生成', + 'value' => 'tovideo', + 'props' => [ + 'type' => 'success', + ] + ]; + const AUDIO = [ + 'label' => '音频生成', + 'value' => 'audio', + 'props' => [ + 'type' => 'danger', + ] + ]; +} diff --git a/fastmovie-admin/plugin/model/utils/enum/ModelVoiceStatus.php b/fastmovie-admin/plugin/model/utils/enum/ModelVoiceStatus.php new file mode 100644 index 0000000..b298572 --- /dev/null +++ b/fastmovie-admin/plugin/model/utils/enum/ModelVoiceStatus.php @@ -0,0 +1,37 @@ + '待提交', + 'value' => 'wait', + 'props' => [ + 'type' => 'info' + ] + ]; + const CLONING = [ + 'label' => '克隆中', + 'value' => 'cloning', + 'props' => [ + 'type' => 'primary' + ] + ]; + const SUCCESS = [ + 'label' => '成功', + 'value' => 'success', + 'props' => [ + 'type' => 'success' + ] + ]; + const FAIL = [ + 'label' => '失败', + 'value' => 'fail', + 'props' => [ + 'type' => 'danger' + ] + ]; +} diff --git a/fastmovie-admin/plugin/notification/admin.json b/fastmovie-admin/plugin/notification/admin.json new file mode 100644 index 0000000..b97fc7e --- /dev/null +++ b/fastmovie-admin/plugin/notification/admin.json @@ -0,0 +1,57 @@ +{ + "title": "Notification", + "icon": "Bell", + "path": "/app/notification/admin", + "sort": 8000, + "children": [ + { + "title": "Message List", + "icon": "Message", + "path": "/app/notification/admin/Message/index", + "component": "table", + "children": [ + { + "title": "Message List", + "path": "/app/notification/admin/Message/index", + "component": "table" + }, + { + "title": "Push Test", + "path": "/app/notification/admin/Message/pushTest", + "show": false, + "component": "form" + } + ] + }, + { + "title": "System Configuration", + "icon": "Setting", + "path": "/app/notification/admin/System", + "children": [ + { + "title": "Notification Strategy", + "path": "/app/notification/admin/System/strategy", + "show": false, + "component": "form" + }, + { + "title": "Site Push", + "path": "/app/notification/admin/System/push", + "component": "form" + }, + { + "title": "WeChat Template", + "path": "/app/notification/admin/System/wechat_template", + "show": false, + "component": "form" + }, + { + "title": "SMS Template", + "path": "/app/notification/admin/System/sms_template", + "show": false, + "component": "form" + } + ] + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/api/Install.php b/fastmovie-admin/plugin/notification/api/Install.php new file mode 100644 index 0000000..e3cfeba --- /dev/null +++ b/fastmovie-admin/plugin/notification/api/Install.php @@ -0,0 +1,15 @@ +plugin='notification'; + $this->path = __DIR__; + } +} diff --git a/fastmovie-admin/plugin/notification/api/Install.sql b/fastmovie-admin/plugin/notification/api/Install.sql new file mode 100644 index 0000000..1e72a77 --- /dev/null +++ b/fastmovie-admin/plugin/notification/api/Install.sql @@ -0,0 +1,35 @@ +/* + Navicat Premium Data Transfer + + Source Server : Loong + Source Server Type : MySQL + Source Server Version : 80024 (8.0.24) + Source Host : 127.0.0.1:3306 + Source Schema : cloud_xhadmin_cn + + Target Server Type : MySQL + Target Server Version : 80024 (8.0.24) + File Encoding : 65001 + + Date: 18/05/2024 18:25:14 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for php_plugin_notification_log +-- ---------------------------- +DROP TABLE IF EXISTS `php_plugin_notification_log`; +CREATE TABLE `php_plugin_notification_log` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` int NULL DEFAULT NULL, + `admin_uid` int NULL DEFAULT NULL, + `scene` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `num` int NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/fastmovie-admin/plugin/notification/app/Basic.php b/fastmovie-admin/plugin/notification/app/Basic.php new file mode 100644 index 0000000..1345a5a --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/Basic.php @@ -0,0 +1,10 @@ +model = new PluginNotificationMessage(); + } + + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addHeader(); + $builder->addHeaderAction('推送测试', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/notification/admin/Message/pushTest', + 'props' => [ + 'title' => '推送测试' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'options' => MessageScene::getOptions(), + 'props' => [ + 'placeholder' => '场景搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'select', '', [ + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + // $builder->addAction('操作', [ + // 'width' => '200px', + // 'fixed' => 'right' + // ]); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'where' => [ + ['channels_uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels.headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('title', '标题', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('scene', '场景', [ + 'component' => [ + 'name' => 'tag', + 'options' => MessageScene::getOptions() + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/notification/admin/Message/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '120px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $scene = $request->get('scene'); + if ($scene) { + $where[] = ['scene', '=', $scene]; + } + $state = $request->get('state'); + if ($state) { + $where[] = ['state', '=', $state]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $list = PluginNotificationMessage::with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }, 'channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->where($where) + ->order('id desc') + ->paginate($limit); + return $this->resData($list); + } + + public function pushTest(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $Message = new Message(); + if ($D['channels_uid']) { + $Message->setChannelsUid($D['channels_uid']); + } + if ($D['uid']) { + $Message->setUid($D['uid']); + } + if ($D['scene']) { + $Message->setScene($D['scene']); + } + if ($D['title']) { + $Message->setTitle($D['title']); + } + if ($D['content']) { + $Message->setContent($D['content']); + } + $Message->save(); + return $this->success('推送成功'); + } + $formBuilder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large' + ]); + $channelList = PluginChannelsUser::options(); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => '推送指定UID', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '状态', 'switch', State::YES['value'], [ + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $formBuilder->add('scene', '场景', 'select', '', [ + 'required' => true, + 'options' => MessageScene::getOptions() + ]); + $formBuilder->add('title', '标题', 'input', '', [ + 'required' => true, + 'props' => [ + 'placeholder' => '标题' + ] + ]); + $formBuilder->add('content', '内容', 'input', '', [ + 'required' => true, + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ], + 'placeholder' => '内容' + ] + ]); + return $this->resData($formBuilder); + } +} diff --git a/fastmovie-admin/plugin/notification/app/admin/controller/SystemController.php b/fastmovie-admin/plugin/notification/app/admin/controller/SystemController.php new file mode 100644 index 0000000..9019335 --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/admin/controller/SystemController.php @@ -0,0 +1,28 @@ +builder(); + } + public function push(Request $request) + { + return $this->builder(); + } + public function wechat_template(Request $request) + { + return $this->builder(); + } + public function sms_template(Request $request) + { + return $this->builder(); + } +} diff --git a/fastmovie-admin/plugin/notification/app/admin/controller/WechatTemplateController.php b/fastmovie-admin/plugin/notification/app/admin/controller/WechatTemplateController.php new file mode 100644 index 0000000..f04b200 --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/admin/controller/WechatTemplateController.php @@ -0,0 +1,31 @@ +post('field'); + $access_token = Wechat::getAccessToken(); + $url = "https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=" . $access_token; + $params = [ + 'template_id_short' => $request->post('template_id_short'), + 'keyword_name_list' => $request->post('keywords'), + ]; + $client = new Client([ + 'content_type' => 'application/json', + ]); + $response = $client->post($url, ['body' => json_encode($params, JSON_UNESCAPED_UNICODE)]); + $data = json_decode($response->getBody()->getContents(), true); + if (empty($data['template_id'])) { + throw new \Exception("[{$data['errcode']}]" . $data['errmsg']); + } + return $this->success('获取成功',[$field=>$data['template_id']]); + } +} diff --git a/fastmovie-admin/plugin/notification/app/api/controller/MessageController.php b/fastmovie-admin/plugin/notification/app/api/controller/MessageController.php new file mode 100644 index 0000000..dfc1633 --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/api/controller/MessageController.php @@ -0,0 +1,71 @@ +resData([ + 'unread' => PluginNotificationMessage::where(['uid' => $request->uid, 'read_state' => State::NO['value']])->count(), + 'read' => PluginNotificationMessage::where(['uid' => $request->uid, 'read_state' => State::YES['value']])->count(), + ]); + } + public function list(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $read=$request->get('read', 'all'); + if($read !== 'all'){ + $where[] = ['read_state', '=', $read == 'read' ? State::YES['value'] : State::NO['value']]; + } + $where[] = ['uid', '=', $request->uid]; + $model = PluginNotificationMessage::where($where); + $list = $model->order('id desc')->paginate($limit)->each(function ($item) { + if($item->effect=='danger'){ + $item->effect = 'error'; + } + $item->scene_enum = MessageScene::get($item->scene); + // switch($item->scene){ + // case MessageScene::INVITE_JOIN_TEAM['value']: + // $item->state_enum = InviteState::get($item->state); + // break; + // default: + // $item->state_enum = State::get($item->state); + // if($item->state==State::YES['value']){ + // $item->effect = 'info'; + // } + // break; + // } + }); + return $this->resData($list); + } + + /** + * 详情 + * @author:1950781041@qq.com + * @Date:2026-01-22 + */ + public function detail(Request $request) + { + $id = $request->get('id'); + $message = PluginNotificationMessage::with('content')->where(['id' => $id])->find(); + $message->read_state=1; + $message->save(); + if($message){ + return $this->resData($message); + } + return $this->fail('消息不存在'); + } +} diff --git a/fastmovie-admin/plugin/notification/app/api/controller/PushController.php b/fastmovie-admin/plugin/notification/app/api/controller/PushController.php new file mode 100644 index 0000000..64233d3 --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/api/controller/PushController.php @@ -0,0 +1,60 @@ +post('channel_name'); + $authType = AuthType::getValues(); + $match = preg_match('/^private-(' . implode('|', $authType) . ')-(.+)$/', $channel_name, $matches); + if (!$match) { + return $this->fail('Forbidden'); + } + $header = $matches[1]; + $AuthTypeAction = AuthType::get($header); + $hash = $matches[2]; + $has_authority = false; + switch ($AuthTypeAction['action']) { + case 'user': + $uid = PluginUser::getUidByUser($hash); + if ($uid != $request->uid) { + return $this->fail('Forbidden'); + } + $PluginUser = PluginUser::where('id', $request->uid)->find(); + $has_authority = $PluginUser && $PluginUser->state == State::YES['value']; + break; + case 'orders': + $PluginFinanceOrders = PluginFinanceOrders::where('trade_no', $hash)->find(); + if (!$PluginFinanceOrders) { + return $this->fail('Forbidden'); + } + $has_authority = $PluginFinanceOrders->uid == $request->uid; + break; + case 'drama': + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $hash, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('Forbidden'); + } + $has_authority = true; + break; + } + if ($has_authority) { + return $this->resData(json_decode($pusher->socketAuth($channel_name, $request->post('socket_id')), true)); + } else { + return $this->fail('Forbidden'); + } + } +} diff --git a/fastmovie-admin/plugin/notification/app/controller/PushController.php b/fastmovie-admin/plugin/notification/app/controller/PushController.php new file mode 100644 index 0000000..d534c2d --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/controller/PushController.php @@ -0,0 +1,101 @@ +header('x-pusher-signature')) { + return response('401 Not authenticated', 401); + } + + $body = $request->rawBody(); + + // 计算签名,$app_secret 是双方使用的密钥,是保密的,外部无从得知 + $expected_signature = hash_hmac('sha256', $body, config('plugin.webman.push.app.app_secret'), false); + + // 安全校验,如果签名不一致可能是伪造的请求,返回401状态码 + if ($webhook_signature !== $expected_signature) { + return response('401 Not authenticated', 401); + } + + // 这里存储这上线 下线的channel数据 + $payload = json_decode($body, true); + $channels_online = $channels_offline = []; + + foreach ($payload['events'] as $event) { + if ($event['name'] === 'channel_added') { + $channels_online[] = $event['channel']; + } else if ($event['name'] === 'channel_removed') { + $channels_offline[] = $event['channel']; + } + } + + // 业务根据需要处理上下线的channel,例如将在线状态写入数据库,通知其它channel等 + // 上线的所有channel + // echo 'online channels: ' . implode(',', $channels_online) . "\n"; + foreach ($channels_online as $channel) { + $PluginNotificationOnline = PluginNotificationOnline::where(['channel' => $channel])->find(); + if ($PluginNotificationOnline) { + continue; + } + $authType = AuthType::getValues(); + $match = preg_match('/^private-(' . implode('|', $authType) . ')-(.+)$/', $channel, $matches); + if (!$match) { + continue; + } + $header = $matches[1]; + $hash = $matches[2]; + $PluginNotificationOnline = new PluginNotificationOnline(); + $PluginNotificationOnline->channel = $channel; + $PluginNotificationOnline->event = $header; + $PluginNotificationOnline->hash = $hash; + + $AuthTypeAction = AuthType::get($header); + switch ($AuthTypeAction['action']) { + case 'user': + $PluginNotificationOnline->uid = PluginUser::getUidByUser($hash); + $PluginUser = PluginUser::where('id', $PluginNotificationOnline->uid)->find(); + $PluginNotificationOnline->channels_uid = $PluginUser->channels_uid; + break; + case 'orders': + $PluginFinanceOrders = PluginFinanceOrders::where('trade_no', $hash)->find(); + if (!$PluginFinanceOrders) { + break; + } + $PluginNotificationOnline->uid = $PluginFinanceOrders->uid; + $PluginNotificationOnline->channels_uid = $PluginFinanceOrders->channels_uid; + break; + case 'drama': + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $hash])->find(); + if (!$PluginShortplayDrama) { + break; + } + $PluginNotificationOnline->uid = $PluginShortplayDrama->uid; + $PluginNotificationOnline->channels_uid = $PluginShortplayDrama->channels_uid; + break; + } + $PluginNotificationOnline->save(); + } + // 下线的所有channel + // echo 'offline channels: ' . implode(',', $channels_offline) . "\n"; + foreach ($channels_offline as $channel) { + $PluginNotificationOnline = PluginNotificationOnline::where(['channel' => $channel])->find(); + if (!$PluginNotificationOnline) { + continue; + } + $PluginNotificationOnline->delete(); + } + return 'OK'; + } +} diff --git a/fastmovie-admin/plugin/notification/app/functions.php b/fastmovie-admin/plugin/notification/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/functions.php @@ -0,0 +1,6 @@ + [ + 'extra' => 'json' + ], + ]; + } + public function user() + { + return $this->hasOne(PluginUser::class, 'id', 'uid'); + } + public function channels() + { + return $this->hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public function content() + { + return $this->hasOne(PluginNotificationMessageContent::class, 'message_id', 'id'); + } +} diff --git a/fastmovie-admin/plugin/notification/app/model/PluginNotificationMessageContent.php b/fastmovie-admin/plugin/notification/app/model/PluginNotificationMessageContent.php new file mode 100644 index 0000000..65cf7d7 --- /dev/null +++ b/fastmovie-admin/plugin/notification/app/model/PluginNotificationMessageContent.php @@ -0,0 +1,11 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/notification/config/autoload.php b/fastmovie-admin/plugin/notification/config/autoload.php new file mode 100644 index 0000000..12db641 --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/notification/app/functions.php', + ] +]; diff --git a/fastmovie-admin/plugin/notification/config/container.php b/fastmovie-admin/plugin/notification/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/container.php @@ -0,0 +1,2 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/notification/config/middleware.php b/fastmovie-admin/plugin/notification/config/middleware.php new file mode 100644 index 0000000..cda3050 --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/middleware.php @@ -0,0 +1,15 @@ + [ + app\expose\middleware\Platform::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ], +]; diff --git a/fastmovie-admin/plugin/notification/config/process.php b/fastmovie-admin/plugin/notification/config/process.php new file mode 100644 index 0000000..a72a250 --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/process.php @@ -0,0 +1,5 @@ + '开关', + 'field' => 'state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '是否开启站内推送'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [] + ] + ], + [ + 'title' => 'wss地址', + 'field' => 'wss_url', + 'value' => 'wss://{HOST}', + 'component' => 'input', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'col'=>[ + 'span'=>12 + ], + 'prompt' => [ + $Component->add('text', ['default' => '只需要填写wss://+域名,不需要填写路径'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => 'wss://+域名' + ] + ] + ], +]; diff --git a/fastmovie-admin/plugin/notification/config/settings/sms_template.php b/fastmovie-admin/plugin/notification/config/settings/sms_template.php new file mode 100644 index 0000000..0b83a92 --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/settings/sms_template.php @@ -0,0 +1,24 @@ + $value) { + $temp=[ + 'title' => $value['label'].'模板或模板ID', + 'field' => $value['value'], + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => $value['label'].'场景使用的短信推送模板或模板ID,为空则关闭该场景通知'], ['type' => 'info', 'size' => 'small']) + ->builder() + ] + ] + ]; + $data[]=$temp; +} +return $data; diff --git a/fastmovie-admin/plugin/notification/config/settings/strategy.php b/fastmovie-admin/plugin/notification/config/settings/strategy.php new file mode 100644 index 0000000..a4b8ee1 --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/settings/strategy.php @@ -0,0 +1,94 @@ + '开关', + 'field' => 'state', + 'value' => false, + 'component' => 'switch', + 'extra' => [ + 'required' => true, + 'prompt' => [ + $Component->add('text', ['default' => '是否开启通知'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [] + ] + ], + [ + 'title' => '同一用户每日通知次数', + 'field' => 'user_limit', + 'value' => 0, + 'component' => 'input-number', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'col'=>[ + 'xs' => 24, + 'sm' => 12, + 'md' => 6 + ], + 'prompt' => [ + $Component->add('text', ['default' => '同一用户所有场景每日通知次数,0:不限制'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'min' => 0, + 'controls'=>false, + 'placeholder' => '次数' + ] + ] + ], + [ + 'title' => '同一用户同一场景每日通知次数', + 'field' => 'user_scene_limit', + 'value' => 1, + 'component' => 'input-number', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'col'=>[ + 'xs' => 24, + 'sm' => 12, + 'md' => 6 + ], + 'prompt' => [ + $Component->add('text', ['default' => '同一用户同一场景每日通知次数,0:不限制'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'min' => 0, + 'controls'=>false, + 'placeholder' => '次数' + ] + ] + ], + [ + 'title' => '通知方式', + 'field' => 'method', + 'value' => Method::WECHAT_AND_SMS['value'], + 'component' => 'radio', + 'extra' => [ + 'required' => true, + 'where' => [ + ['state', '=', true] + ], + 'prompt' => [ + $Component->add('text', ['default' => '通知方式'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'options'=>Method::getOptions(), + 'subProps' => [ + 'border'=>true + ] + ] + ] +]; diff --git a/fastmovie-admin/plugin/notification/config/settings/wechat_template.php b/fastmovie-admin/plugin/notification/config/settings/wechat_template.php new file mode 100644 index 0000000..4a74fa3 --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/settings/wechat_template.php @@ -0,0 +1,50 @@ + $value) { + $temp=[ + 'title' => $value['label'].'模板ID', + 'field' => $value['value'], + 'value' => '', + 'component' => 'input', + 'extra' => [ + 'prompt' => [ + $Component->add('text', ['default' => $value['label'].'场景使用的微信公众号推送模板ID,为空则关闭该场景通知'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [], + 'children'=>[ + 'append'=>[ + 'component'=>'el-button', + 'extra'=>[ + 'model'=>Action::COMFIRM['value'], + 'path'=>'/app/notification/admin/WechatTemplate/getTemplateId', + 'props'=>[ + 'type'=>'warning', + 'message'=>'点击确定立即获取模板ID', + ] + ], + 'children'=>[ + 'default'=>'一键获取模板ID' + ] + ] + ] + ] + ]; + if(isset($value['params'])){ + foreach ($value['params'] as $k => $v) { + if(!isset($temp['extra']['children']['append']['extra']['params'])){ + $temp['extra']['children']['append']['extra']['params']=[]; + } + $temp['extra']['children']['append']['extra']['params'][$k]=$v; + } + } + $data[]=$temp; +} +return $data; diff --git a/fastmovie-admin/plugin/notification/config/static.php b/fastmovie-admin/plugin/notification/config/static.php new file mode 100644 index 0000000..90006e7 --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/static.php @@ -0,0 +1,6 @@ + true, + 'middleware' => [ + ], +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/config/translation.php b/fastmovie-admin/plugin/notification/config/translation.php new file mode 100644 index 0000000..734fc5d --- /dev/null +++ b/fastmovie-admin/plugin/notification/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/notification/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/expose/helper/Message.php b/fastmovie-admin/plugin/notification/expose/helper/Message.php new file mode 100644 index 0000000..2e91dfb --- /dev/null +++ b/fastmovie-admin/plugin/notification/expose/helper/Message.php @@ -0,0 +1,134 @@ +channels_uid = $channels_uid; + return $this; + } + public function setUid(int $uid) + { + $this->uid = $uid; + return $this; + } + public function setFormUid(int $form_uid) + { + $this->form_uid = $form_uid; + return $this; + } + public function setFormId(int $form_id) + { + $this->form_id = $form_id; + return $this; + } + public function setScene(string $scene) + { + $this->scene = $scene; + return $this; + } + public function setAliasId(int $alias_id) + { + $this->alias_id = $alias_id; + return $this; + } + public function setExtra(array $extra) + { + $this->extra = $extra; + return $this; + } + public function setTitle(string $title) + { + $this->title = $title; + return $this; + } + public function setEffect(string $effect) + { + $this->effect = $effect; + return $this; + } + public function setContent(string $content) + { + $this->content = $content; + return $this; + } + public function setSubtitle(string $subtitle) + { + $this->subtitle = $subtitle; + return $this; + } + public function save() + { + $messageId = null; + if ($this->uid) { + $Message = new PluginNotificationMessage(); + $Message->channels_uid = $this->channels_uid; + $Message->uid = $this->uid; + $Message->form_uid = $this->form_uid; + $Message->form_id = $this->form_id; + $Message->scene = $this->scene; + $Message->alias_id = $this->alias_id; + $Message->extra = $this->extra; + $Message->title = $this->title; + $Message->subtitle = $this->subtitle; + $Message->effect = $this->effect; + $Message->save(); + $MessageContent = new PluginNotificationMessageContent(); + $MessageContent->message_id = $Message->id; + $MessageContent->content = $this->content; + $MessageContent->save(); + $messageId = $Message->id; + } + + $pushConfig = new Config('push', 'notification'); + if ($pushConfig->state) { + try { + if ($this->uid) { + Push::send(['uid' => $this->uid, 'event' => 'notify','channels_uid' => $this->channels_uid], [ + 'unread' => PluginNotificationMessage::where(['uid' => $this->uid, 'read_state' => State::NO['value'], 'channels_uid' => $this->channels_uid])->count(), + 'read' => PluginNotificationMessage::where(['uid' => $this->uid, 'read_state' => State::YES['value'], 'channels_uid' => $this->channels_uid])->count(), + 'list' => [ + [ + 'id' => $messageId, + 'scene' => MessageScene::get($this->scene), + 'title' => $this->title, + ] + ] + ]); + } else { + Push::trigger('notify', 'message', [ + 'id' => $messageId, + 'scene' => MessageScene::get($this->scene), + 'title' => $this->title, + 'content' => $this->content, + 'extra' => $this->extra, + 'effect' => $this->effect, + 'alias_id'=>$this->alias_id, + ]); + } + } catch (\Throwable $th) { + Log::error('Message save push error: ' . $th->getMessage(), $th->getTrace()); + } + } + } +} diff --git a/fastmovie-admin/plugin/notification/expose/helper/Notice.php b/fastmovie-admin/plugin/notification/expose/helper/Notice.php new file mode 100644 index 0000000..4a7e6e2 --- /dev/null +++ b/fastmovie-admin/plugin/notification/expose/helper/Notice.php @@ -0,0 +1,163 @@ +method = $method; + } + public function setScene($scene) + { + $this->scene = $scene; + } + public function setData($data) + { + $this->data = $data; + } + public function addMobile($mobile) + { + $this->mobiles[] = $mobile; + } + public function addOpenid($openid) + { + $this->openids[] = $openid; + } + public function builderSms() + { + $config = new Config('sms_template', 'notification'); + if (isset($config[$this->scene]) && $config[$this->scene]) { + $sms_template = $config[$this->scene]; + //发送短信 + switch ($this->scene) { + case Scene::TODO['value']: + break; + case Scene::WARNING['value']: + break; + } + foreach ($this->data as $key => $value) { + $sms_template = str_replace('{' . $key . '}', $value, $sms_template); + } + foreach ($this->mobiles as $mobile) { + try { + //发送短信 + $Sms = new Sms; + $Sms->mobile = $mobile; + $SmsNotice = new SmsNotice; + $SmsNotice->setTemplateCode($sms_template); + $Sms->setTemplate($SmsNotice); + $Sms->setData($this->data); + $Sms->send(); + } catch (\Throwable $th) { + Log::error("短信通知失败(手机号:mobile):" . $th->getMessage(), $th->getTrace()); + } + } + } + } + public function builderWechat() + { + $config = new Config('wechat_template', 'notification'); + if (isset($config[$this->scene]) && $config[$this->scene]) { + $template_id = $config[$this->scene]; + //发送微信 + switch ($this->scene) { + case Scene::TODO['value']: + break; + case Scene::WARNING['value']: + $data['template_id'] = $template_id; + $data['data'] = []; + foreach ($this->data as $key => $value) { + $data['data'][$key] = ['value' => $value]; + } + break; + } + if (isset($data['template_id'])) { + foreach ($this->openids as $openid) { + $data['touser'] = $openid; + try { + Wechat::sendTemplate($data); + } catch (\Throwable $th) { + Log::error("微信通知失败(openid:{$openid}):" . $th->getMessage(), $th->getTrace()); + } + } + } + } + } + public function send() + { + $this->mobiles = array_unique($this->mobiles); + $this->openids = array_unique($this->openids); + switch ($this->method) { + case Method::WECHAT_FIRST: + if (!empty($this->openids)) { + try { + $this->builderWechat(); + } catch (\Throwable $th) { + Log::error('微信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + } else { + try { + $this->builderSms(); + } catch (\Throwable $th) { + Log::error('短信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + } + break; + case Method::SMS_FIRST: + if (!empty($this->mobiles)) { + try { + $this->builderSms(); + } catch (\Throwable $th) { + Log::error('短信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + } else { + try { + $this->builderWechat(); + } catch (\Throwable $th) { + Log::error('微信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + } + break; + case Method::WECHAT_ONLY: + try { + $this->builderWechat(); + } catch (\Throwable $th) { + Log::error('微信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + break; + case Method::SMS_ONLY: + try { + $this->builderSms(); + } catch (\Throwable $th) { + Log::error('短信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + break; + default: + try { + $this->builderSms(); + } catch (\Throwable $th) { + Log::error('短信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + try { + $this->builderWechat(); + } catch (\Throwable $th) { + Log::error('微信通知发送失败:' . $th->getMessage(), $th->getTrace()); + } + break; + } + } +} diff --git a/fastmovie-admin/plugin/notification/expose/helper/Online.php b/fastmovie-admin/plugin/notification/expose/helper/Online.php new file mode 100644 index 0000000..0901246 --- /dev/null +++ b/fastmovie-admin/plugin/notification/expose/helper/Online.php @@ -0,0 +1,14 @@ +column('channel'); + if (!empty($PluginNotificationOnline)) { + return self::trigger($PluginNotificationOnline, $event, $data); + } + return false; + } + public static function trigger(string|array $channel, string $event, array $data): bool + { + $api = new Api( + config('plugin.webman.push.app.api'), + config('plugin.webman.push.app.app_key'), + config('plugin.webman.push.app.app_secret') + ); + return $api->trigger($channel, $event, $data); + } +} diff --git a/fastmovie-admin/plugin/notification/expose/template/sms/Notice.php b/fastmovie-admin/plugin/notification/expose/template/sms/Notice.php new file mode 100644 index 0000000..28dfb5b --- /dev/null +++ b/fastmovie-admin/plugin/notification/expose/template/sms/Notice.php @@ -0,0 +1,16 @@ +data['templateCode'] = $templateCode; + } + public function builder() + { + } +} diff --git a/fastmovie-admin/plugin/notification/package.json b/fastmovie-admin/plugin/notification/package.json new file mode 100644 index 0000000..24a23ad --- /dev/null +++ b/fastmovie-admin/plugin/notification/package.json @@ -0,0 +1,12 @@ +{ + "name": "notification", + "title": "通知系统", + "version": "0.0.1", + "description": "通知系统", + "author": { + "name": "余心", + "email": "unknown.renloong@gmail.com" + }, + "plugin":{ + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/resource/translations/en/admin.php b/fastmovie-admin/plugin/notification/resource/translations/en/admin.php new file mode 100644 index 0000000..447ed56 --- /dev/null +++ b/fastmovie-admin/plugin/notification/resource/translations/en/admin.php @@ -0,0 +1,10 @@ + 'Notification', + 'System Configuration' => 'System Configuration', + 'Notification Strategy' => 'Notification Strategy', + 'Site Push' => 'Site Push', + 'WeChat Template' => 'WeChat Template', + 'SMS Template' => 'SMS Template', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/resource/translations/en/api.php b/fastmovie-admin/plugin/notification/resource/translations/en/api.php new file mode 100644 index 0000000..d6506e3 --- /dev/null +++ b/fastmovie-admin/plugin/notification/resource/translations/en/api.php @@ -0,0 +1,7 @@ +'Forbidden', + 'success' => 'Success', + 'fail' => 'Fail', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/resource/translations/en/messages.php b/fastmovie-admin/plugin/notification/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/notification/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/notification/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..15e74f6 --- /dev/null +++ b/fastmovie-admin/plugin/notification/resource/translations/zh_CN/admin.php @@ -0,0 +1,10 @@ + '通知', + 'System Configuration' => '系统配置', + 'Notification Strategy' => '通知策略', + 'Site Push' => '站内推送', + 'WeChat Template' => '微信公众号模板', + 'SMS Template' => '短信模板', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/resource/translations/zh_CN/api.php b/fastmovie-admin/plugin/notification/resource/translations/zh_CN/api.php new file mode 100644 index 0000000..1adbf7b --- /dev/null +++ b/fastmovie-admin/plugin/notification/resource/translations/zh_CN/api.php @@ -0,0 +1,7 @@ +'无权限', + 'success' => '成功', + 'fail' => '失败', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/notification/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/notification/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/notification/utils/Api.php b/fastmovie-admin/plugin/notification/utils/Api.php new file mode 100644 index 0000000..a2f3441 --- /dev/null +++ b/fastmovie-admin/plugin/notification/utils/Api.php @@ -0,0 +1,237 @@ + 2, + ]; + + /** + * @param $api_address + * @param $auth_key + * @param $secret + * @throws PushException + */ + public function __construct($api_address, $auth_key, $secret) + { + $this->checkCompatibility(); + $this->_settings['api_address'] = $api_address; + $this->_settings['auth_key'] = $auth_key; + $this->_settings['secret'] = $secret; + $this->_settings['base_path'] = '/apps/1024'; + } + + /** + * trigger an event by providing event name and payload. + * Optionally provide a socket ID to exclude a client (most likely the sender). + * + * @param array|string $channels An array of channel names to publish the event on. + * @param string $event + * @param mixed $data Event data + * @param string $socket_id [optional] + * @return bool|string + */ + public function trigger($channels, $event, $data, $socket_id = null) + { + if (is_string($channels)) { + $channels = array($channels); + } + $query_params = array(); + $s_url = $this->_settings['base_path'] . '/events'; + $data_encoded = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $post_params = array(); + $post_params['name'] = $event; + $post_params['data'] = $data_encoded; + $post_params['channels'] = $channels; + if ($socket_id !== null) { + $post_params['socket_id'] = $socket_id; + } + $post_value = json_encode($post_params); + $query_params['body_md5'] = md5($post_value); + $ch = $this->createCurl($this->_settings['api_address'], $s_url, 'POST', $query_params); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_value); + $response = $this->execCurl($ch); + if ($response['status'] === 200) { + return true; + } else { + return false; + } + } + + public function getChannelInfo($channel, $params = array()) + { + $this->validateChannel($channel); + + $response = $this->get('/channels/' . $channel, $params); + + if ($response['status'] === 200) { + $response = json_decode($response['body']); + } else { + $response = false; + } + + return $response; + } + + public function getChannels($params = array()) + { + return $this->get('/channels', $params); + } + + private function checkCompatibility() + { + if (!in_array('sha256', hash_algos())) { + throw new PushException('SHA256 appears to be unsupported - make sure you have support for it, or upgrade your version of PHP.'); + } + } + + private function validateChannel($channel) + { + if (!preg_match('/\A[-a-zA-Z0-9_=@,.;]+\z/', $channel)) { + throw new PushException('Invalid channel name ' . $channel); + } + } + + private function validateSocketId($socket_id) + { + if ($socket_id !== null && !preg_match('/\A\d+\.\d+\z/', $socket_id)) { + throw new PushException('Invalid socket ID ' . $socket_id); + } + } + + private function createCurl($domain, $s_url, $request_method = 'GET', $query_params = array()) + { + static $ch = null; + $signed_query = self::buildAuthQueryString( + $this->_settings['auth_key'], + $this->_settings['secret'], + $request_method, + $s_url, + $query_params); + + $full_url = $domain . $s_url . '?' . $signed_query; + + if (null === $ch) { + $ch = curl_init(); + if ($ch === false) { + throw new PushException('Could not initialise cURL!'); + } + } + + if (function_exists('curl_reset')) { + curl_reset($ch); + } + + curl_setopt($ch, CURLOPT_URL, $full_url); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Expect:', + )); + curl_setopt($ch, CURLOPT_PROXY, ""); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->_settings['timeout']); + if ($request_method === 'POST') { + curl_setopt($ch, CURLOPT_POST, 1); + } elseif ($request_method === 'GET') { + curl_setopt($ch, CURLOPT_POST, 0); + } // Otherwise let the user configure it + + return $ch; + } + + private function execCurl($ch) + { + $response = array(); + $response['body'] = curl_exec($ch); + $response['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE); + return $response; + } + + public static function buildAuthQueryString($auth_key, $auth_secret, $request_method, $request_path, + $query_params = array()) + { + $params = array(); + $params['auth_key'] = $auth_key; + $params['auth_timestamp'] = time(); + + $params = array_merge($params, $query_params); + ksort($params); + + $string_to_sign = "$request_method\n" . $request_path . "\n" . self::arrayImplode('=', '&', $params); + + $auth_signature = hash_hmac('sha256', $string_to_sign, $auth_secret, false); + + $params['auth_signature'] = $auth_signature; + ksort($params); + + $auth_query_string = self::arrayImplode('=', '&', $params); + + return $auth_query_string; + } + + public static function arrayImplode($glue, $separator, $array) + { + if (!is_array($array)) { + return $array; + } + $string = array(); + foreach ($array as $key => $val) { + if (is_array($val)) { + $val = implode(',', $val); + } + $string[] = "{$key}{$glue}{$val}"; + } + + return implode($separator, $string); + } + + public function get($path, $params = array()) + { + $ch = $this->createCurl($this->_settings['api_address'], $this->_settings['base_path'] . $path, 'GET', $params); + + $response = $this->execCurl($ch); + + if ($response['status'] === 200) { + $response['result'] = json_decode($response['body'], true); + } + + return $response; + } + + public function socketAuth($channel, $socket_id, $custom_data = null) + { + $this->validateChannel($channel); + $this->validateSocketId($socket_id); + + if ($custom_data) { + $signature = hash_hmac('sha256', $socket_id . ':' . $channel . ':' . $custom_data, $this->_settings['secret'], false); + } else { + $signature = hash_hmac('sha256', $socket_id . ':' . $channel, $this->_settings['secret'], false); + } + + $signature = array('auth' => $this->_settings['auth_key'] . ':' . $signature); + if ($custom_data) { + $signature['channel_data'] = $custom_data; + } + + return json_encode($signature); + } + + public function presenceAuth($channel, $socket_id, $user_id, $user_info = null) + { + $user_data = array('user_id' => $user_id); + if ($user_info) { + $user_data['user_info'] = $user_info; + } + + return $this->socketAuth($channel, $socket_id, json_encode($user_data)); + } +} diff --git a/fastmovie-admin/plugin/notification/utils/PushException.php b/fastmovie-admin/plugin/notification/utils/PushException.php new file mode 100644 index 0000000..bbf2cde --- /dev/null +++ b/fastmovie-admin/plugin/notification/utils/PushException.php @@ -0,0 +1,7 @@ + '用户', + 'value' => 'user', + 'action' => 'user', + ]; + const WALLET = [ + 'label' => '钱包', + 'value' => 'wallet', + 'action' => 'user', + ]; + const NOTIFY = [ + 'label' => '通知', + 'value' => 'notify', + 'action' => 'user', + ]; + const ORDERS = [ + 'label' => '订单', + 'value' => 'orders', + 'action' => 'orders', + ]; + const CONTINUE_EPISODE = [ + 'label' => '续写分集', + 'value' => 'continueepisode', + 'action' => 'drama', + ]; + const GENERATE_SCENE_STORYBOARD = [ + 'label' => '创作分镜', + 'value' => 'generatescenestoryboard', + 'action' => 'drama', + ]; + const GENERATE_DRAMA_COVER = [ + 'label' => '创作短剧封面', + 'value' => 'generatedramacover', + 'action' => 'drama', + ]; + const GENERATE_SCENE_IMAGE = [ + 'label' => '创作场景', + 'value' => 'generatesceneimage', + 'action' => 'user', + ]; + const GENERATE_ACTOR_IMAGE = [ + 'label' => '创作角色形象', + 'value' => 'generateactorimage', + 'action' => 'user', + ]; + const GENERATE_ACTOR_THREE_VIEW_IMAGE = [ + 'label' => '创作角色三视图', + 'value' => 'generateactorthreeviewimage', + 'action' => 'user', + ]; + const GENERATE_STORYBOARD_IMAGE = [ + 'label' => '创作分镜图片', + 'value' => 'generatestoryboardimage', + 'action' => 'user', + ]; + const GENERATE_STORYBOARD = [ + 'label' => '创作分镜', + 'value' => 'generatestoryboard', + 'action' => 'user', + ]; + const GENERATE_CHARACTER_LOOK_COSTUME = [ + 'label' => '创作角色服饰', + 'value' => 'generatecharacterlookcostume', + 'action' => 'user', + ]; + const GENERATE_PROP_IMAGE = [ + 'label' => '创作物品图片', + 'value' => 'generatepropimage', + 'action' => 'user', + ]; + const GENERATE_PROP_THREE_VIEW_IMAGE = [ + 'label' => '创作物品三视图', + 'value' => 'generatepropthreeviewimage', + 'action' => 'user', + ]; + const CREATE_DRAMA = [ + 'label' => '创建短剧', + 'value' => 'generatecreatedrama', + 'action' => 'user', + ]; + const CLONE_VOICE = [ + 'label' => '克隆音色', + 'value' => 'clonevoice', + 'action' => 'user', + ]; + const DOWNLOAD_PACKAGE = [ + 'label' => '打包ZIP包', + 'value' => 'downloadpackage', + 'action' => 'user', + ]; +} diff --git a/fastmovie-admin/plugin/notification/utils/enum/MessageScene.php b/fastmovie-admin/plugin/notification/utils/enum/MessageScene.php new file mode 100644 index 0000000..3571c7a --- /dev/null +++ b/fastmovie-admin/plugin/notification/utils/enum/MessageScene.php @@ -0,0 +1,13 @@ + '公告', + 'value' => 'announcement' + ]; +} diff --git a/fastmovie-admin/plugin/notification/utils/enum/Method.php b/fastmovie-admin/plugin/notification/utils/enum/Method.php new file mode 100644 index 0000000..95b9611 --- /dev/null +++ b/fastmovie-admin/plugin/notification/utils/enum/Method.php @@ -0,0 +1,30 @@ + '微信+短信', + 'value' => 'wechat_and_sms' + ]; + const WECHAT_FIRST = [ + 'label' => '微信优先', + 'value' => 'wechat_first' + ]; + const SMS_FIRST = [ + 'label' => '短信优先', + 'value' => 'sms_first' + ]; + const WECHAT_ONLY = [ + 'label' => '仅微信', + 'value' => 'wechat_only' + ]; + const SMS_ONLY = [ + 'label' => '仅短信', + 'value' => 'sms_only' + ]; +} diff --git a/fastmovie-admin/plugin/notification/utils/enum/Scene.php b/fastmovie-admin/plugin/notification/utils/enum/Scene.php new file mode 100644 index 0000000..58e6f70 --- /dev/null +++ b/fastmovie-admin/plugin/notification/utils/enum/Scene.php @@ -0,0 +1,34 @@ + '待办通知', + 'value' => 'todo', + 'sms_template' => [ + 'admin' => '您有新的待办事项,请登录控制台查看。', + 'user' => '您有新的待办事项,请登录控制台查看。' + ], + 'params' => [ + 'template_id_short' => '52305', + 'keywords' => ['项目名称', '客户名称', '提醒时间'] + ] + ]; + const WARNING = [ + 'label' => '预警通知', + 'value' => 'warning', + 'sms_template' => [ + 'admin' => '您有新的待办事项,请登录控制台查看。', + 'user' => '您有新的待办事项,请登录控制台查看。' + ], + 'params' => [ + 'template_id_short' => '52305', + 'keywords' => ['项目名称', '客户名称', '提醒时间'] + ] + ]; +} diff --git a/fastmovie-admin/plugin/pluginExample/admin.json b/fastmovie-admin/plugin/pluginExample/admin.json new file mode 100644 index 0000000..9e9292f --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/admin.json @@ -0,0 +1,56 @@ +{ + "title": "示例", + "icon": "Operation", + "path": "/app/pluginExample/admin", + "sort": 3999, + "show": false, + "children": [ + { + "title": "大屏", + "path": "/app/pluginExample/admin/Databoard/index", + "show": false, + "component": "databoard" + }, + { + "title": "表单", + "path": "/app/pluginExample/admin/Form/index", + "show": false, + "component": "form" + }, + { + "title": "分组表单", + "path": "/app/pluginExample/admin/GroupForm/index", + "show": false, + "component": "form" + }, + { + "title": "审核表单", + "path": "/app/pluginExample/admin/Examine/index", + "show": false, + "component": "examine" + }, + { + "title": "表格", + "path": "/app/pluginExample/admin/Table/index", + "show": false, + "component": "table" + }, + { + "title": "图片", + "path": "/app/pluginExample/admin/Images/index", + "component": "images" + }, + { + "title": "信息展示", + "path": "/app/pluginExample/admin/Info/index", + "show": false, + "component": "info" + }, + { + "title": "配置", + "path": "/app/pluginExample/admin/Settings/basic", + "show": false, + "component": "form" + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/api/Install.php b/fastmovie-admin/plugin/pluginExample/api/Install.php new file mode 100644 index 0000000..e9f1aa1 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/api/Install.php @@ -0,0 +1,15 @@ +plugin='pluginExample'; + $this->path = __DIR__; + } +} diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/DataboardController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/DataboardController.php new file mode 100644 index 0000000..365443a --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/DataboardController.php @@ -0,0 +1,163 @@ + 6 + ]); + $today=100; + $yesterday=124; + $statistic = new Statistic; + $statistic->setLabel('今日新注册用户') + ->setUnit('人') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday?round(($today-$yesterday)/$yesterday*100,2):0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + $statistic = new Statistic; + $statistic->setLabel('快捷操作') + ->setFooterText('操作提示') + ->setData([ + 'today' => 100, + 'yesterday' => 124, + 'growth_rate' => 100, + 'footer' => 100 + ]) + ->setAction([ + 'model' => Action::REDIRECT['value'], + 'path' => '/app/pluginExample/admin/Table/index', + 'component' => [ + 'name' => 'button', + 'label' => '去操作', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 12, + 'lg' => 6, + 'xl' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + $Domain = new Domain; + $Domain->setLabel('复制链接') + ->setTips('GitHub') + ->setDomain('https://github.com/RenLoong/loong-admin.git'); + $Domain->setAgreement([ + 'title' => 'LoongAdmin', + 'url' => 'https://github.com/RenLoong/loong-admin.git' + ]); + $builder->add($Domain, [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ], [ + 'xs' => 1, + 'sm' => 1, + 'md' => 1, + 'lg' => 1, + 'xl' => 2, + ]); + $this->echarts($builder); + return $this->resData($builder); + } + public function echarts(DataboardBuilder $builder) + { + $request=request(); + $echarts = new Echarts; + $echarts->setClass('bg-white p-4 rounded-4 shadow-lighter vh-65'); + $EchartsData=$this->getEchartsData($request); + $echarts->setData($EchartsData); + $builder->add($echarts); + } + public function getEchartsData(Request $request) + { + $data=[ + 'color' => ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'], + 'title' => [ + 'text' => '数据可视化' + ], + 'tooltip' => [ + 'trigger' => 'axis', + 'axisPointer' => [ + 'type' => 'cross', + 'label' => [ + 'backgroundColor' => '#6a7985' + ] + ] + ], + 'legend' => [ + 'data' => ["注册人数"] + ], + 'toolbox' => [ + 'show' => false + ], + 'grid' => [ + 'left' => '3%', + 'right' => '4%', + 'bottom' => '3%', + 'containLabel' => true + ], + 'xAxis' => [ + [ + 'type' => 'category', + 'boundaryGap' => false, + 'data' => [] + ] + ], + 'yAxis' => [ + [ + 'type' => 'value' + ] + ], + 'series' => [ + [ + 'name' => "注册人数", + 'type' => 'line', + 'smooth' => true, + 'data' => [] + ] + ] + ]; + for($i=7;$i>0;$i--){ + $date=date('Y-m-d',strtotime("-$i day")); + $data['xAxis'][0]['data'][]=$date; + $Statistic=rand(100,1000); + if($Statistic){ + $data['series'][0]['data'][]=$Statistic; + }else{ + $data['series'][0]['data'][]=0; + } + } + return $data; + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/ExamineController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/ExamineController.php new file mode 100644 index 0000000..4491eb8 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/ExamineController.php @@ -0,0 +1,90 @@ +[ + 'label'=>'原始数据', + 'type'=>'info' + ], + 'new'=>[ + 'label'=>'更新数据', + 'type'=>'success' + ] + ]); + $builder->add('text', 'Text', 'text', [ + 'props' => [ + ] + ]); + $builder->add('avatar', 'Avatar', 'avatar', [ + 'props' => [ + ] + ]); + $builder->add('tag', 'Tag', 'tag', [ + 'props' => [ + ], + 'options' => [ + [ + 'label' => '选项一', + 'value' => '选项一' + ], + [ + 'label' => '选项二', + 'value' => '选项二' + ] + ] + ]); + $builder->add('text', 'Text', 'text', [ + 'props' => [ + ] + ]); + $builder->add('text', 'Text', 'text', [ + 'props' => [ + ] + ]); + $builder->add('text', 'Text', 'text', [ + 'props' => [ + ] + ]); + $builder->setOldData([ + 'text'=>'文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字', + 'avatar'=>'头像', + 'tag'=>'选项一' + ]); + $builder->setNewData([ + 'text'=>'文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字', + 'avatar'=>'头像', + 'tag'=>'选项一' + ]); + $formBuilder=new FormBuilder(); + $formBuilder->add('examine', '审核状态', 'radio', '', [ + 'required' => true, + 'options' => Examine::getOptions(), + 'subProps' => [ + 'border' => true + ] + ]); + $formBuilder->add('remarks', '驳回理由', 'marked-editor', '', [ + 'where' => [ + ['examine', '=', Examine::REJECT['value']] + ], + 'required' => true, + 'props'=>[ + 'bundle'=>true + ] + ]); + $builder->addForm($formBuilder); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/FormController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/FormController.php new file mode 100644 index 0000000..f708d8b --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/FormController.php @@ -0,0 +1,141 @@ +add('input', 'Input', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Input Placeholder', + 'clearable' => true + ] + ]); + $builder->add('select', 'Select', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Select Placeholder', + 'clearable' => true + ], + 'options' => [ + [ + 'label' => '选项一', + 'value' => '选项一' + ], + [ + 'label' => '选项二', + 'value' => '选项二', + 'tips' => '选项二Tips' + ] + ] + ]); + $builder->add('remote_select', 'Remote Select', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'remote' => [ + 'url' => '/app/pluginExample/admin/Form/remote_select' + ], + 'props' => [ + 'placeholder' => 'Remote Select Placeholder', + 'clearable' => true + ], + 'options' => [] + ]); + $builder->add('mention', 'Mention', 'mention', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'remote' => [ + 'url' => '/app/pluginExample/admin/Form/remote_select' + ], + 'props' => [ + 'placeholder' => 'Mention Placeholder', + 'clearable' => true + ], + 'options' => [] + ]); + $builder->add('autocomplete', 'Autocomplete', 'autocomplete', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'remote' => [ + 'url' => '/app/pluginExample/admin/Form/remote_select' + ], + 'props' => [ + 'placeholder' => 'Autocomplete Placeholder', + 'clearable' => true + ], + 'options' => [] + ]); + $builder->add('switch', '状态', 'switch', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $builder->add('bundle', '附件库', 'bundle', '', [ + 'props' => [ + // 'accept' => 'image/*', + 'multiple' => 1 + ] + ]); + return $this->resData($builder); + } + public function remote_select(Request $request) + { + $keyword = $request->post('keyword'); + $form = $request->post('form'); + $options = [ + [ + 'label' => '选项一', + 'value' => '选项一' + ], + [ + 'label' => '选项二', + 'value' => '选项二', + 'tips' => '选项二Tips' + ] + ]; + return $this->resData($options); + } +} diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/GroupFormController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/GroupFormController.php new file mode 100644 index 0000000..5bb6f33 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/GroupFormController.php @@ -0,0 +1,184 @@ +true + ]); + $builder->setTranslations(); + $builder->add('input', 'Input', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Input Placeholder', + 'clearable' => true + ] + ]); + $builder->add('select', 'Select', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Select Placeholder', + 'clearable' => true + ], + 'options' => [ + [ + 'label' => '选项一', + 'value' => '选项一' + ], + [ + 'label' => '选项二', + 'value' => '选项二', + 'tips' => '选项二Tips' + ] + ] + ]); + $builder->add('remote_select', 'Remote Select', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'remote' => [ + 'url' => '/app/pluginExample/admin/Form/remote_select' + ], + 'props' => [ + 'placeholder' => 'Remote Select Placeholder', + 'clearable' => true + ], + 'options' => [] + ]); + $builder->add('mention', 'Mention', 'mention', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'remote' => [ + 'url' => '/app/pluginExample/admin/Form/remote_select' + ], + 'props' => [ + 'placeholder' => 'Mention Placeholder', + 'clearable' => true + ], + 'options' => [] + ]); + $builder->add('autocomplete', 'Autocomplete', 'autocomplete', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'remote' => [ + 'url' => '/app/pluginExample/admin/Form/remote_select' + ], + 'props' => [ + 'placeholder' => 'Autocomplete Placeholder', + 'clearable' => true + ], + 'options' => [] + ]); + $builder->add('switch', '状态', 'switch', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ]); + $builder->add('bundle', '附件库', 'bundle', '', [ + 'props' => [ + // 'accept' => 'image/*', + 'multiple' => 1 + ] + ]); + + $subBuilder = new FormBuilder('group1', 'Group1'); + $subBuilder->add('input', 'Input', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Input Placeholder', + 'clearable' => true + ] + ]); + $subBuilder->add('select', 'Select', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Select Placeholder', + 'clearable' => true + ], + 'options' => [ + [ + 'label' => '选项一', + 'value' => '选项一' + ], + [ + 'label' => '选项二', + 'value' => '选项二', + 'tips' => '选项二Tips' + ] + ] + ]); + $builder->addGroupForm($subBuilder); + return $this->resData($builder); + } + public function remote_select(Request $request) + { + $keyword = $request->post('keyword'); + $form = $request->post('form'); + $options = [ + [ + 'label' => '选项一', + 'value' => '选项一' + ], + [ + 'label' => '选项二', + 'value' => '选项二', + 'tips' => '选项二Tips' + ] + ]; + return $this->resData($options); + } +} diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/ImagesController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/ImagesController.php new file mode 100644 index 0000000..c0a5267 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/ImagesController.php @@ -0,0 +1,112 @@ +addAction('编辑', [ + 'path' => '/app/pluginExample/admin/Form/index', + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{nickname}》' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/pluginExample/admin/Table/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{nickname}》吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建', [ + 'path' => '/app/pluginExample/admin/Form/create', + 'props' => [ + 'type' => 'success', + 'title' => '创建' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $builder->addFooter(); + $builder->addFooterAction('移动到', [ + 'model' => Action::DIALOG['value'], + 'path' => 'Admin/moveRole', + 'props' => [ + 'title' => '移动到', + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->input('limit', 10); + $data = [ + 'total' => 100, + 'data' => [] + ]; + for ($i = 0; $i < $limit; $i++) { + $data['data'][] = [ + 'id' => $i, + 'title' => '图片' . $i, + 'url' => 'https://picsum.photos/1920/1080?random=' . $i, + ]; + } + return $this->resData($data); + } +} diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/InfoController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/InfoController.php new file mode 100644 index 0000000..b7d7e5c --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/InfoController.php @@ -0,0 +1,50 @@ +add('input', 'Input', 'span', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Input Placeholder', + 'clearable' => true + ] + ]); + $subBuilder = new InfoBuilder('subInfo','Sub Info'); + $subBuilder->add('input', 'Input', 'span', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 24, + 'lg' => 24, + 'xl' => 8, + ], + 'props' => [ + 'placeholder' => 'Input Placeholder', + 'clearable' => true + ] + ]); + $builder->addGroupInfo($subBuilder); + $builder->setData([ + 'input' => 'Input Value', + 'subInfo' => [ + 'input' => 'Sub Info Input Value' + ] + ]); + return $this->resData($builder); + } +} diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/SettingsController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/SettingsController.php new file mode 100644 index 0000000..593c035 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/SettingsController.php @@ -0,0 +1,16 @@ +builder(); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/app/admin/controller/TableController.php b/fastmovie-admin/plugin/pluginExample/app/admin/controller/TableController.php new file mode 100644 index 0000000..f096bc6 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/admin/controller/TableController.php @@ -0,0 +1,269 @@ +addAction('操作', [ + 'width' => '200px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'path' => '/app/pluginExample/admin/Form/index', + 'props' => [ + 'type' => 'primary', + 'title' => '编辑《{nickname}》' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('删除', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/pluginExample/admin/Table/delete', + 'where' => [ + ['is_system', '!=', 1] + ], + 'props' => [ + 'type' => 'error', + 'message' => '确定要删除《{nickname}》吗?', + 'confirmButtonClass' => 'el-button--danger' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建', [ + 'path' => '/app/pluginExample/admin/Form/create', + 'props' => [ + 'type' => 'success', + 'title' => '创建' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null,null,[ + 'inline' => true + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '80px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'danger', + 'size' => 'small' + ] + ], + [ + 'field' => 'new_text1', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + 'tags' => [ + [ + 'field' => 'role_name', + 'props' => [ + 'type' => 'success' + ] + ] + ], + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ], + [ + 'field' => 'wx_openid', + 'label' => 'OpenID' + ] + ] + ] + ] + ]); + $builder->add('allow_week', '工作日', [ + 'component' => [ + 'name' => 'tag', + 'options' => Week::getOptions() + ], + 'props' => [ + 'width' => '240px' + ] + ]); + $builder->add('allow_work', '工作时间', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'index' => 0, + 'props' => [ + 'type' => 'success' + ] + ], + [ + 'index' => 1, + 'props' => [ + 'type' => 'danger' + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('online_time', '活动', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'online_time', + 'label' => '在线' + ], + [ + 'field' => 'login_time', + 'label' => '登录' + ], + [ + 'component' => 'tag', + 'field' => 'login_ip', + 'label' => '登录IP', + 'props' => [ + 'size' => 'small', + ] + ] + ] + ] + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/pluginExample/admin/Table/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $data=[ + 'total'=>1, + 'data'=>[ + [ + 'id' => 1, + 'contact' => '13800138000', + 'allow_week' => '周一', + 'allow_work' => ['9:00','18:00'], + 'online_time' => '2021-01-01 10:00:00', + 'state' => 1, + 'new_text' => '新', + 'new_text1' => 'T', + 'nickname' => '张三', + 'headimg' => '', + 'username' => 'admin', + 'role_name' => '管理员', + ] + ] + ]; + return $this->resData($data); + } +} diff --git a/fastmovie-admin/plugin/pluginExample/app/functions.php b/fastmovie-admin/plugin/pluginExample/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/app/functions.php @@ -0,0 +1,6 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/pluginExample/config/autoload.php b/fastmovie-admin/plugin/pluginExample/config/autoload.php new file mode 100644 index 0000000..5c689a5 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/pluginExample/app/functions.php', + ] +]; diff --git a/fastmovie-admin/plugin/pluginExample/config/container.php b/fastmovie-admin/plugin/pluginExample/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/config/container.php @@ -0,0 +1,2 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/pluginExample/config/log.php b/fastmovie-admin/plugin/pluginExample/config/log.php new file mode 100644 index 0000000..ef19fec --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/config/log.php @@ -0,0 +1,20 @@ + [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/api.log', + 7, + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/fastmovie-admin/plugin/pluginExample/config/middleware.php b/fastmovie-admin/plugin/pluginExample/config/middleware.php new file mode 100644 index 0000000..76fb115 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/config/middleware.php @@ -0,0 +1,11 @@ + [ + app\middleware\Template::class, + app\middleware\Platform::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ] +]; diff --git a/fastmovie-admin/plugin/pluginExample/config/process.php b/fastmovie-admin/plugin/pluginExample/config/process.php new file mode 100644 index 0000000..881ab67 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/config/process.php @@ -0,0 +1,2 @@ + '配置项名称', + #配置项字段 + 'field' => 'web_name', + #配置项值 + 'value' => 'FastMovie', + #配置项组件 + 'component' => 'input', + #配置项额外属性 + 'extra' => [ + #是否必填 + 'required' => true, + #element-plus表单中的rules规则 + 'rules' => [ + ['type'=>'string','required'=>true,'message'=>'请输入配置项值'] + ], + #使用接口验证 + 'rules_api' => [ + 'url' => '/app/pluginExample/example/test' + ], + #提示 + 'prompt' => [ + $Component->add('text', ['default' => '提示信息'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + #组件属性 + 'props' => [ + 'type' => 'text', + 'placeholder' => '请输入应用名称' + ] + ] + ] +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/config/translation.php b/fastmovie-admin/plugin/pluginExample/config/translation.php new file mode 100644 index 0000000..280fbf5 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/pluginExample/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/config/view.php b/fastmovie-admin/plugin/pluginExample/config/view.php new file mode 100644 index 0000000..fe78682 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/config/view.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +// use support\view\ThinkPHP; +use support\ThinkPHP; + +return [ + 'handler' => ThinkPHP::class, +]; diff --git a/fastmovie-admin/plugin/pluginExample/package.json b/fastmovie-admin/plugin/pluginExample/package.json new file mode 100644 index 0000000..c496739 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/package.json @@ -0,0 +1,10 @@ +{ + "name": "interface-example", + "title": "应用示例项目", + "version": "0.0.1", + "description": "应用市场", + "author": { + "name": "余心", + "email": "unknown.renloong@gmail.com" + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/resource/translations/en/admin.php b/fastmovie-admin/plugin/pluginExample/resource/translations/en/admin.php new file mode 100644 index 0000000..5e9f768 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/resource/translations/en/admin.php @@ -0,0 +1,5 @@ +'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/resource/translations/en/messages.php b/fastmovie-admin/plugin/pluginExample/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..c9d49f9 --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/admin.php @@ -0,0 +1,5 @@ +'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/pluginExample/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/admin.json b/fastmovie-admin/plugin/shortplay/admin.json new file mode 100644 index 0000000..bd421ba --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/admin.json @@ -0,0 +1,98 @@ +{ + "title": "短剧", + "icon": "VideoCamera", + "path": "/app/shortplay/admin", + "sort": 2000, + "children": [ + { + "title": "剧本列表", + "icon": "Tickets", + "path": "/app/shortplay/admin/Drama/index", + "component": "table", + "children": [ + { + "title": "更新状态", + "show": false, + "path": "/app/shortplay/admin/Drama/indexUpdateState" + } + ] + }, + { + "title": "分集列表", + "icon": "Tickets", + "path": "/app/shortplay/admin/DramaEpisode/index", + "component": "table", + "children": [ + { + "title": "创建分集", + "path": "/app/shortplay/admin/DramaEpisode/create", + "show": false, + "component": "form" + }, + { + "title": "编辑分集", + "show": false, + "path": "/app/shortplay/admin/DramaEpisode/update", + "component": "form" + } + ] + }, + { + "title": "演员列表", + "icon": "User", + "path": "/app/shortplay/admin/Actor/index", + "component": "table", + "children": [ + { + "title": "创建演员", + "path": "/app/shortplay/admin/Actor/create", + "show": false, + "component": "form" + }, + { + "title": "编辑演员", + "show": false, + "path": "/app/shortplay/admin/Actor/update", + "component": "form" + }, + { + "title": "更新演员状态", + "show": false, + "path": "/app/shortplay/admin/Actor/indexUpdateState" + } + ] + }, + { + "title": "风格列表", + "icon": "MagicStick", + "path": "/app/shortplay/admin/Style/index", + "component": "table", + "children": [ + { + "title": "创建风格", + "path": "/app/shortplay/admin/Style/create", + "show": false, + "component": "form" + }, + { + "title": "编辑风格", + "show": false, + "path": "/app/shortplay/admin/Style/update", + "component": "form" + }, + { + "title": "更新风格状态", + "show": false, + "path": "/app/shortplay/admin/Style/indexUpdateState" + } + ] + }, + { + "title": "Settings", + "icon": "Setting", + "path": "/app/shortplay/admin/Setting/basic", + "component": "form", + "show": false + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/api/Control.php b/fastmovie-admin/plugin/shortplay/api/Control.php new file mode 100644 index 0000000..88c6c85 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/api/Control.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'shortplay'; + } +} diff --git a/fastmovie-admin/plugin/shortplay/api/Install.php b/fastmovie-admin/plugin/shortplay/api/Install.php new file mode 100644 index 0000000..2c52ab2 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/api/Install.php @@ -0,0 +1,15 @@ +plugin='shortplay'; + $this->path = __DIR__; + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/admin/controller/ActorController.php b/fastmovie-admin/plugin/shortplay/app/admin/controller/ActorController.php new file mode 100644 index 0000000..ac01414 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/admin/controller/ActorController.php @@ -0,0 +1,225 @@ +model = new PluginShortplayActor; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('name', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('actor_id', '演员ID', 'input', '', [ + 'props' => [ + 'placeholder' => '演员ID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('actorinfo', '演员', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'name', + 'avatar' => 'headimg', + 'info' => 'actor_id', + 'preview' => true + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('three_view_image', '三视图', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width: 100px; height: 100px;', + 'fit' => 'cover' + ] + ], + 'props' => [ + 'width' => '133px' + ] + ]); + $builder->add('species_type', '物种', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorSpeciesType::getOptions(), + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('gender', '性别', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorGender::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('age', '年龄', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorAge::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('status', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorStatus::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('remarks', '人物描述', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function query(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['name|actor_id|remarks', 'like', "%{$query}%"]; + return $this->resData(PluginShortplayActor::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $actor_id = $request->get('actor_id'); + if ($actor_id) { + $where[] = ['actor_id', 'like', "%{$actor_id}%"]; + } + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $list = PluginShortplayActor::where($where)->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }, 'channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/admin/controller/DramaController.php b/fastmovie-admin/plugin/shortplay/app/admin/controller/DramaController.php new file mode 100644 index 0000000..331d32a --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/admin/controller/DramaController.php @@ -0,0 +1,180 @@ +addHeader(); + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('title', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('title', '剧本名称', [ + 'props' => [ + 'minWidth' => '200px' + ] + ]); + $builder->add('description', '剧本简介', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('cover', '封面', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width: 100px; height: 100px;', + 'fit' => 'cover' + ] + ], + 'props' => [ + 'width' => '133px' + ] + ]); + + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => State::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $list = PluginShortplayDrama::where($where) + ->with(['channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->order('id desc')->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/admin/controller/DramaEpisodeController.php b/fastmovie-admin/plugin/shortplay/app/admin/controller/DramaEpisodeController.php new file mode 100644 index 0000000..9e134d9 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/admin/controller/DramaEpisodeController.php @@ -0,0 +1,176 @@ + 'max-height: 200px;' + ] + ); + $builder->addHeader(); + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('title', '分集名称', 'input', '', [ + 'props' => [ + 'placeholder' => '分集名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('title', '分集名称', [ + 'props' => [ + 'minWidth' => '200px', + ] + ]); + $builder->add('content', '分集内容', [ + 'props' => [ + 'minWidth' => '300px', + 'show-overflow-tooltip'=>true + ] + ]); + $builder->add('cover', '封面', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width: 100px; height: 100px;', + 'fit' => 'cover' + ] + ], + 'props' => [ + 'width' => '133px' + ] + ]); + + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $list = PluginShortplayDramaEpisode::where($where) + ->with(['channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->order('id desc')->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/admin/controller/StyleController.php b/fastmovie-admin/plugin/shortplay/app/admin/controller/StyleController.php new file mode 100644 index 0000000..129c075 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/admin/controller/StyleController.php @@ -0,0 +1,171 @@ +model = new PluginShortplayStyle; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('name', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('classify', '风格分类', 'select', '', [ + 'options' => StyleClassify::getOptions(), + 'required' => true, + 'props' => [ + 'placeholder' => '风格分类搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('classify', '风格分类', [ + 'component' => [ + 'name' => 'tag', + 'options' => StyleClassify::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('styleinfo', '风格', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'name', + 'avatar' => 'image', + 'preview' => true + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => State::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('prompts', '提示词', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function query(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['name|prompts', 'like', "%{$query}%"]; + return $this->resData(PluginShortplayStyle::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $classify = $request->get('classify'); + if ($classify) { + $where[] = ['classify', '=', $classify]; + } + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $list = PluginShortplayStyle::where($where) + ->with(['channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/ActorController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/ActorController.php new file mode 100644 index 0000000..08055ca --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/ActorController.php @@ -0,0 +1,632 @@ +get('limit', 2000); + $page = $request->get('page'); + $type = $request->get('type'); + $where = []; + // $where[] = ['status', '=', ActorStatus::GENERATED['value']]; + $whereOr = []; + $params = []; + $uid = $request->uid; + switch ($type) { + case 'public': + $where[] = ['actor.uid', '=', null]; + break; + case 'personal': + if (!$request->uid) { + return $this->code(ResponseCode::NEED_LOGIN, '请先登录'); + } + $where[] = ['actor.uid', '=', $request->uid]; + $where[] = ['actor.drama_id', '=', null]; + break; + case 'self': + if (!$request->uid) { + return $this->code(ResponseCode::NEED_LOGIN, '请先登录'); + } + $where[] = ['actor.uid', '=', $request->uid]; + break; + case 'storyboard': + $storyboard_id = $request->get('storyboard_id'); + if (!$storyboard_id) { + return $this->fail('分镜ID不能为空'); + } + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $PluginShortplayDramaStoryboard->episode_id, 'drama_id' => $PluginShortplayDramaStoryboard->drama_id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $PluginShortplayDramaStoryboard->drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $whereOr[] = ['actor.uid', '=', null]; + $whereOr[] = ['actor.uid', '=', $request->uid]; + $params['drama_id'] = $PluginShortplayDrama->id; + $params['episode_id'] = $PluginShortplayDramaEpisode->id; + $params['storyboard_id'] = $PluginShortplayDramaStoryboard->id; + break; + case 'episode': + $episode_id = $request->get('episode_id'); + if (!$episode_id) { + return $this->fail('分集ID不能为空'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $PluginShortplayDramaEpisode->drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $whereOr[] = ['actor.uid', '=', null]; + $whereOr[] = ['actor.uid', '=', $request->uid]; + $params['drama_id'] = $PluginShortplayDrama->id; + $params['episode_id'] = $PluginShortplayDramaEpisode->id; + $params['storyboard_id'] = null; + break; + case 'drama': + $drama_id = $request->get('drama_id'); + if (!$drama_id) { + return $this->fail('短剧ID不能为空'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $whereOr[] = ['actor.uid', '=', null]; + $whereOr[] = ['actor.uid', '=', $request->uid]; + $params['drama_id'] = $PluginShortplayDrama->id; + $params['episode_id'] = null; + $params['storyboard_id'] = null; + break; + default: + $whereOr[] = ['actor.uid', '=', null]; + $whereOr[] = ['actor.uid', '=', $request->uid]; + break; + } + $name = $request->get('name'); + if ($name) { + $where[] = ['actor.name', 'like', '%' . $name . '%']; + } + $actor_id = $request->get('actor_id'); + if ($actor_id) { + $where[] = ['actor.actor_id', 'like', '%' . $actor_id . '%']; + } + $species_type = $request->get('species_type'); + if ($species_type) { + $where[] = ['actor.species_type', '=', $species_type]; + } + $gender = $request->get('gender'); + if ($gender) { + $where[] = ['actor.gender', '=', $gender]; + } + $age = $request->get('age'); + if ($age) { + $where[] = ['actor.age', '=', $age]; + } + $PluginShortplayActor = PluginShortplayActor::actorQuery($params)->where($where)->whereOr($whereOr)->order('actor.id', 'desc')->limit($limit); + if ($page) { + $PluginShortplayActor = $PluginShortplayActor->paginate($limit, $page); + } else { + $PluginShortplayActor = $PluginShortplayActor->select(); + } + $list = $PluginShortplayActor->each(function ($item) use ($uid, $params) { + $item->species_type_enum = ActorSpeciesType::get($item->species_type); + $item->gender_enum = ActorGender::get($item->gender); + $item->age_enum = ActorAge::get($item->age); + $item->is_edit = $item->uid == $uid && $uid; + $params['actor_id'] = $item->id; + $item->character_look_state = PluginShortplayActorCharacterLook::processing($params); + if ($item->character_look_id) { + $characterLook = PluginShortplayActorCharacterLook::where(['id' => $item->character_look_id])->with('characterLook')->find(); + if ($characterLook) { + $item->characterLook = $characterLook->characterLook; + } + } + }); + return $this->resData($list); + } + public function initializing(Request $request) + { + $id = $request->post('id'); + + $PluginShortplayActor = PluginShortplayActor::where([ + 'id' => $id, + 'uid' => $request->uid + ])->find(); + + if (!$PluginShortplayActor) { + return $this->fail('演员不存在'); + } + $name = $request->post('name'); + if ($name) { + $PluginShortplayActor->name = $name; + } + $actor_id = uniqid(); + if ($actor_id) { + $PluginShortplayActor->actor_id = $actor_id; + } + $species_type = $request->post('species_type'); + if ($species_type) { + $PluginShortplayActor->species_type = $species_type; + } + $gender = $request->post('gender'); + if ($gender) { + $PluginShortplayActor->gender = $gender; + } + $age = $request->post('age'); + if ($age) { + $PluginShortplayActor->age = $age; + } + $remarks = $request->post('remarks'); + if ($remarks) { + $PluginShortplayActor->remarks = $remarks; + } + $imageState = PluginModelTask::processing(['alias_id' => $PluginShortplayActor->id, 'scene' => ModelScene::ACTOR_IMAGE['value']]); + $threeViewImageState = PluginModelTask::processing(['alias_id' => $PluginShortplayActor->id, 'scene' => ModelScene::ACTOR_THREE_VIEW_IMAGE['value']]); + if ($imageState > 0 || $threeViewImageState > 0) { + return $this->fail('演员正在生成中'); + } + + // ===== 表单参数 ===== + $image_model_id = $request->post('image_model_id'); + $three_view_model_id = $request->post('three_view_model_id'); + $image_state = (bool)$request->post('image_state'); + $three_view_image_state = (bool)$request->post('three_view_image_state'); + $image = $request->post('image'); + $three_view_image = $request->post('three_view_image'); + $image_reference_state = (bool)$request->post('image_reference_state'); + $reference_headimg = $request->post('reference_headimg'); + if ($image_reference_state && empty($reference_headimg)) { + return $this->fail('请上传参考图片'); + } + + // ===== 是否需要 AI 生成 ===== + $needAiImage = ($PluginShortplayActor->headimg && $image_state) ? empty($image) : true; + $needSkipImage = $PluginShortplayActor->headimg && !$image_state; + $needAiThreeView = ($PluginShortplayActor->three_view_image && $three_view_image_state) ? empty($three_view_image) : true; + $needSkipThreeView = $PluginShortplayActor->three_view_image && !$three_view_image_state; + $isDoubleAi = ($needAiImage && !$needSkipImage) && ($needAiThreeView && !$needSkipThreeView); + + $ImagePluginModel = null; + $ThreeViewPluginModel = null; + + // ===================================================== + // 一、模型前置校验(任何 AI 调用前) + // ===================================================== + + // 双 AI:必须一次性校验两个模型 + if ($isDoubleAi) { + $ImagePluginModel = PluginModel::where([ + 'id' => $image_model_id, + 'scene' => ModelScene::ACTOR_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + $ThreeViewPluginModel = PluginModel::where([ + 'id' => $three_view_model_id, + 'scene' => ModelScene::ACTOR_THREE_VIEW_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + if (!$ImagePluginModel || !$ThreeViewPluginModel) { + return $this->fail('模型不存在'); + } + } else { + // 单独 image AI + if ($needAiImage && !$needSkipImage) { + if (!$image_model_id) { + return $this->fail('请选择角色形象模型'); + } + $ImagePluginModel = PluginModel::where([ + 'id' => $image_model_id, + 'scene' => ModelScene::ACTOR_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + if (!$ImagePluginModel) { + return $this->fail('角色形象模型不存在'); + } + } + + // 单独 three view AI + if ($needAiThreeView && !$needSkipThreeView) { + if (!$three_view_model_id) { + return $this->fail('请选择角色三视图模型'); + } + $ThreeViewPluginModel = PluginModel::where([ + 'id' => $three_view_model_id, + 'scene' => ModelScene::ACTOR_THREE_VIEW_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + if (!$ThreeViewPluginModel) { + return $this->fail('角色三视图模型不存在'); + } + } + } + + // ===================================================== + // 二、角色形象 AI 生成 + // ===================================================== + + $lockImage = false; + $taskId = null; + if (!$needSkipImage) { + if ($needAiImage) { + $lockImage = true; + $data = [ + 'assistant' => $ImagePluginModel->assistant_id, + 'model' => $ImagePluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'prompt' => $PluginShortplayActor->remarks, + 'species' => ActorSpeciesType::getText($PluginShortplayActor->species_type), + 'gender' => ActorGender::getText($PluginShortplayActor->gender), + 'age' => ActorAge::getText($PluginShortplayActor->age), + 'aspect_ratio' => '1:1', + ] + ]; + if ($image_reference_state) { + $data['form_data']['images'] = [$reference_headimg]; + } + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $ImagePluginModel->point, PointsBillScene::CONSUME['value'], null, '生成角色形象图', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + + Db::startTrans(); + try { + $task = new PluginModelTask(); + $task->channels_uid = $request->channels_uid; + $task->uid = $request->uid; + $task->model_id = $ImagePluginModel->id; + $task->model_type = ModelType::DRAW['value']; + $task->alias_id = $PluginShortplayActor->id; + $task->scene = ModelScene::ACTOR_IMAGE['value']; + $task->status = ModelTaskStatus::PROCESSING['value']; + $task->task_id = $result['task_id']; + $task->last_heartbeat = date('Y-m-d H:i:s'); + $task->consume_ids = $consume_ids; + $task->save(); + $taskId = $task->id; + $taskResult = new PluginModelTaskResult(); + $taskResult->task_id = $task->id; + $taskResult->channels_uid = $request->channels_uid; + $taskResult->params = $data; + $taskResult->save(); + + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } catch (\Throwable $e) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + Log::error('GenerateActorImage Error:' . $e->getMessage()); + return $this->fail('角色形象生成失败'); + } + $PluginShortplayActor->headimg = null; + } + } + + // ===================================================== + // 三、角色三视图 AI 生成 + // ===================================================== + if (!$needSkipThreeView) { + if ($needAiThreeView) { + $data = [ + 'assistant' => $ThreeViewPluginModel->assistant_id, + 'model' => $ThreeViewPluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'prompt' => $PluginShortplayActor->remarks, + 'species' => ActorSpeciesType::getText($PluginShortplayActor->species_type), + 'gender' => ActorGender::getText($PluginShortplayActor->gender), + 'age' => ActorAge::getText($PluginShortplayActor->age), + 'aspect_ratio' => '1:1', + ] + ]; + + // 非 image AI 场景,必须依赖已有形象 + if (!$lockImage) { + if (!$PluginShortplayActor->headimg) { + return $this->fail('请先生成角色形象,再生成角色三视图'); + } + $data['form_data']['images'] = [$PluginShortplayActor->headimg]; + } + + try { + if (!$lockImage) { + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $ThreeViewPluginModel->point, PointsBillScene::CONSUME['value'], null, '生成角色三视图', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + } + + Db::startTrans(); + try { + $task = new PluginModelTask(); + $task->channels_uid = $request->channels_uid; + $task->uid = $request->uid; + $task->model_id = $ThreeViewPluginModel->id; + $task->model_type = ModelType::DRAW['value']; + $task->alias_id = $PluginShortplayActor->id; + $task->scene = ModelScene::ACTOR_THREE_VIEW_IMAGE['value']; + $task->pre_task_id = $taskId; + $task->status = $lockImage + ? ModelTaskStatus::WAIT['value'] + : ModelTaskStatus::PROCESSING['value']; + $task->task_id = $lockImage ? null : $result['task_id']; + $task->last_heartbeat = date('Y-m-d H:i:s'); + $task->consume_ids = $consume_ids; + $task->save(); + + $taskResult = new PluginModelTaskResult(); + $taskResult->task_id = $task->id; + $taskResult->channels_uid = $request->channels_uid; + $taskResult->params = $data; + $taskResult->save(); + + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } catch (\Throwable $e) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + Log::error('GenerateActorThreeView Error:' . $e->getMessage()); + return $this->fail('角色三视图生成失败'); + } + $PluginShortplayActor->three_view_image = null; + } + } + $PluginShortplayActor->save(); + $PluginShortplayActor = PluginShortplayActor::where(['id' => $PluginShortplayActor->id])->find(); + return $this->resData($PluginShortplayActor); + } + public function update(Request $request) + { + $msg = '更新成功'; + Db::startTrans(); + try { + $id = $request->post('id'); + $PluginShortplayDramaActor = null; + $PluginShortplayDramaEpisodeActor = null; + if ($id) { + $PluginShortplayActor = PluginShortplayActor::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayActor) { + throw new \Exception('演员不存在'); + } + } else { + $PluginShortplayActor = new PluginShortplayActor(); + $PluginShortplayActor->uid = $request->uid; + $PluginShortplayActor->channels_uid = $request->channels_uid; + $drama_id = $request->post('drama_id'); + if ($drama_id) { + $PluginShortplayActor->drama_id = $drama_id; + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $request->channels_uid; + $PluginShortplayDramaActor->drama_id = $drama_id; + } + $episode_id = $request->post('episode_id'); + if ($episode_id) { + $PluginShortplayActor->episode_id = $episode_id; + $PluginShortplayDramaEpisodeActor = new PluginShortplayDramaEpisodeActor(); + $PluginShortplayDramaEpisodeActor->channels_uid = $request->channels_uid; + $PluginShortplayDramaEpisodeActor->drama_id = $drama_id; + $PluginShortplayDramaEpisodeActor->episode_id = $episode_id; + } + $PluginShortplayActor->status = ActorStatus::INITIALIZING['value']; + $msg = '创建成功'; + $PluginShortplayActor->actor_id = uniqid(); + } + $PluginShortplayActor->name = $request->post('name'); + $PluginShortplayActor->headimg = $request->post('headimg'); + $PluginShortplayActor->species_type = $request->post('species_type'); + $PluginShortplayActor->gender = $request->post('gender'); + $PluginShortplayActor->age = $request->post('age'); + $PluginShortplayActor->remarks = $request->post('remarks'); + $PluginShortplayActor->three_view_image = $request->post('three_view_image'); + if ($PluginShortplayActor->headimg && $PluginShortplayActor->three_view_image) { + $PluginShortplayActor->status = ActorStatus::GENERATED['value']; + } + $PluginShortplayActor->save(); + if ($PluginShortplayDramaActor) { + $PluginShortplayDramaActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaActor->save(); + } + if ($PluginShortplayDramaEpisodeActor) { + $PluginShortplayDramaEpisodeActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaEpisodeActor->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success($msg, $PluginShortplayActor); + } + public function ReplaceActor(Request $request) + { + $id = $request->post('id'); + $task_id = $request->post('task_id'); + $PluginShortplayActor = PluginShortplayActor::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayActor) { + return $this->fail('演员不存在'); + } + $PluginModelTask = PluginModelTask::where(['id' => $task_id, 'alias_id' => $PluginShortplayActor->id, 'scene' => ModelScene::ACTOR_IMAGE['value'], 'status' => ModelTaskStatus::SUCCESS['value']])->find(); + if (!$PluginModelTask) { + return $this->fail('任务不存在'); + } + $PluginShortplayActor->headimg = $PluginModelTask->result->image_path; + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'alias_id' => $PluginShortplayActor->id, 'scene' => ModelScene::ACTOR_THREE_VIEW_IMAGE['value'], 'status' => ModelTaskStatus::SUCCESS['value']])->find(); + if ($PluginModelTask) { + $PluginShortplayActor->three_view_image = $PluginModelTask->result->image_path; + $PluginShortplayActor->status = ActorStatus::GENERATED['value']; + } else { + $PluginShortplayActor->status = ActorStatus::INITIALIZING['value']; + $PluginShortplayActor->three_view_image = null; + } + $PluginShortplayActor->save(); + $PluginShortplayActor = PluginShortplayActor::where(['id' => $id])->find(); + return $this->resData($PluginShortplayActor); + } + public function voice(Request $request) + { + $id = $request->post('id'); + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $storyboard_id = $request->post('storyboard_id'); + $scene_id = $request->post('scene_id'); + $dialogue_id = $request->post('dialogue_id'); + $apply_scope = $request->post('apply_scope'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + Db::startTrans(); + try { + $voice = $request->post('voice'); + if (!empty($voice['emotions_enum']) && empty($voice['selected_emotion'])) { + $voice['selected_emotion'] = $voice['emotions_enum'][0]; + } + if (empty($voice['selected_language'])) { + $voice['selected_language'] = VoiceLanguage::ZH; + } + if ($apply_scope === 'scene') { + $PluginShortplayDramaStoryboards = PluginShortplayDramaStoryboard::where(['scene_id' => $scene_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if ($PluginShortplayDramaStoryboards->isEmpty()) { + throw new \Exception('该场景下无分镜'); + } + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + $PluginShortplayDramaStoryboardActor = PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id, 'actor_id' => $id])->find(); + if ($PluginShortplayDramaStoryboardActor) { + $PluginShortplayDramaStoryboardActor->voice = $voice; + $PluginShortplayDramaStoryboardActor->save(); + } + } + } else { + $ActorModel = null; + switch ($apply_scope) { + case 'drama': + $ActorModel = PluginShortplayDramaActor::where(['drama_id' => $PluginShortplayDrama->id, 'actor_id' => $id])->find(); + break; + case 'episode': + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + throw new \Exception('分集不存在'); + } + $ActorModel = PluginShortplayDramaEpisodeActor::where(['episode_id' => $PluginShortplayDramaEpisode->id, 'actor_id' => $id])->find(); + break; + case 'storyboard': + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaStoryboard) { + throw new \Exception('分镜不存在'); + } + $ActorModel = PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id, 'actor_id' => $id])->find(); + break; + case 'dialogue': + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaStoryboard) { + throw new \Exception('分镜不存在'); + } + $ActorModel = PluginShortplayDramaStoryboardDialogue::where(['id' => $dialogue_id, 'storyboard_id' => $PluginShortplayDramaStoryboard->id])->find(); + break; + } + if (!$ActorModel) { + throw new \Exception('演员不存在'); + } + $ActorModel->voice = $voice; + $ActorModel->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('更新成功'); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $PluginShortplayActor = PluginShortplayActor::where(['id' => $id, 'uid' => $request->uid])->whereNull('drama_id')->find(); + if (!$PluginShortplayActor) { + return $this->fail('演员不存在'); + } + $PluginShortplayActor->delete(); + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/CharacterLookController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/CharacterLookController.php new file mode 100644 index 0000000..80868e4 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/CharacterLookController.php @@ -0,0 +1,189 @@ +get('type'); + $where = []; + switch ($type) { + case 'episode': + $episode_id = $request->get('episode_id'); + if (!$episode_id) { + return $this->fail('分集ID不能为空'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $PluginShortplayDramaEpisode->drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $where[] = ['episode_id', '=', $PluginShortplayDramaEpisode->id]; + break; + case 'drama': + $drama_id = $request->get('drama_id'); + if (!$drama_id) { + return $this->fail('短剧ID不能为空'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $where[] = ['drama_id', '=', $PluginShortplayDrama->id]; + break; + case 'actor': + $actor_id = $request->get('actor_id'); + if (!$actor_id) { + return $this->fail('演员ID不能为空'); + } + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where(['actor_id' => $actor_id, 'uid' => $request->uid])->order('id', 'desc')->with('characterLook')->select()->each(function ($item) { + $item->component = 'actor'; + }); + return $this->resData($PluginShortplayActorCharacterLook); + default: + $where[] = ['uid', '=', $request->uid]; + break; + } + $keywords = $request->get('keywords'); + if ($keywords) { + $where[] = ['title|overall_style|makeup|hair_style|status_note', 'like', '%' . $keywords . '%']; + } + $PluginShortplayCharacterLook = PluginShortplayCharacterLook::where($where)->order('id', 'desc')->select()->each(function ($item) {}); + return $this->resData($PluginShortplayCharacterLook); + } + public function update(Request $request) + { + $drama_id = $request->post('drama_id'); + if ($drama_id) { + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + } + $episode_id = $request->post('episode_id'); + if ($episode_id) { + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + } + $id = $request->post('id'); + if ($id) { + $PluginShortplayCharacterLook = PluginShortplayCharacterLook::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayCharacterLook) { + return $this->fail('装扮不存在'); + } + } else { + $PluginShortplayCharacterLook = new PluginShortplayCharacterLook(); + $PluginShortplayCharacterLook->channels_uid = $request->channels_uid; + $PluginShortplayCharacterLook->uid = $request->uid; + if ($drama_id) { + $PluginShortplayCharacterLook->drama_id = $PluginShortplayDrama->id; + } + if ($episode_id) { + $PluginShortplayCharacterLook->drama_id = $PluginShortplayDramaEpisode->drama_id; + $PluginShortplayCharacterLook->episode_id = $PluginShortplayDramaEpisode->id; + } + } + $costume_reference_state = (bool)$request->post('costume_reference_state'); + $D = $request->post(); + if (!empty($D['title'])) { + $PluginShortplayCharacterLook->title = $D['title']; + } + if (!empty($D['overall_style'])) { + $PluginShortplayCharacterLook->overall_style = $D['overall_style']; + } + if (!empty($D['makeup'])) { + $PluginShortplayCharacterLook->makeup = $D['makeup']; + } + if (!empty($D['hair_style'])) { + $PluginShortplayCharacterLook->hair_style = $D['hair_style']; + } + if (!empty($D['status_note'])) { + $PluginShortplayCharacterLook->status_note = $D['status_note']; + } + if (!empty($D['costume'])) { + $PluginShortplayCharacterLook->costume = $D['costume']; + } + $PluginShortplayCharacterLook->status = ActorStatus::INITIALIZING['value']; + if (!empty($D['costume_url'])) { + $PluginShortplayCharacterLook->costume_url = $D['costume_url']; + if (!$costume_reference_state) { + $PluginShortplayCharacterLook->status = ActorStatus::GENERATED['value']; + } + } + $PluginShortplayCharacterLook->save(); + $model_id = $request->post('model_id'); + if ($model_id && $PluginShortplayCharacterLook->status === ActorStatus::INITIALIZING['value']) { + $data = [ + 'uid' => $request->uid, + 'channels_uid' => $request->channels_uid, + 'token' => $request->token, + 'origin' => $request->header('origin'), + 'referer' => $request->header('referer'), + 'host' => $request->host(), + 'form_data' => [ + 'id' => $PluginShortplayCharacterLook->id, + 'model_id' => $model_id, + 'costume_url' => $PluginShortplayCharacterLook->costume_url, + 'costume_reference_state' => $costume_reference_state + ], + ]; + Coroutine::create(function () use ($data) { + try { + $client = new Client([ + 'base_uri' => 'http://127.0.0.1:' . getenv('SERVER_PORT'), + 'verify' => false, + 'proxy' => false + ]); + $response = $client->post('/app/shortplay/api/Generate/characterLookCostume', [ + 'json' => $data['form_data'], + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Authorization' => $data['token'], + 'Origin' => $data['origin'], + 'Referer' => $data['referer'], + 'Host' => $data['host'], + ], + ]); + $result = json_decode($response->getBody()->getContents(), true); + if (!isset($result['code']) || $result['code'] != 200) { + Log::error('初始化装扮失败:' . $result['msg']); + } else { + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'generatecharacterlookcostume' + ], [ + 'id' => $data['form_data']['id'], + 'status' => ActorStatus::PENDING, + ]); + } + } catch (TransferException $e) { + Log::error('初始化装扮失败:' . $e->getMessage() . PHP_EOL . $e->getTraceAsString()); + } catch (\Throwable $th) { + Log::error('初始化装扮失败:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + }); + } + return $this->resData($PluginShortplayCharacterLook); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/DramaController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/DramaController.php new file mode 100644 index 0000000..623144e --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/DramaController.php @@ -0,0 +1,290 @@ +post('drama_id'); + $episode_id = $request->post('episode_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + // 保存分片目录 + $tempDir = runtime_path('temp/chunks/' . $drama_id . '_' . $episode_id); + if (!is_dir($tempDir)) { + mkdir($tempDir, 0777, true); + } else { + # 清空目录 + shell_exec('rm -rf ' . $tempDir . '/*'); + } + return $this->success(); + } + public function uploadChunk(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $chunkIndex = intval($request->post('chunkIndex', 0)); + $totalChunks = intval($request->post('totalChunks', 0)); + + if (!$drama_id || !$episode_id || $totalChunks <= 0) { + return $this->fail('参数错误'); + } + + // 上传的文件片段 + $chunkFile = $request->file('chunkData'); + if (!$chunkFile || $chunkFile->isValid() !== true) { + return $this->fail('分片上传失败'); + } + + // 保存分片目录 + $tempDir = runtime_path('temp/chunks/' . $drama_id . '_' . $episode_id); + if (!is_dir($tempDir)) { + mkdir($tempDir, 0777, true); + } + + // 分片保存路径 + $chunkPath = $tempDir . '/' . $chunkIndex; + $chunkFile->move($chunkPath); + + return $this->success('分片上传成功'); + } + public function mergeChunks(Request $request) + { + Coroutine::create(function () use ($request) { + try { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $totalChunks = intval($request->post('totalChunks', 0)); + $channels_uid = (int)$request->channels_uid; + if (empty($channels_uid)) { + throw new \Exception('渠道用户ID不存在'); + } + + if (!$drama_id || !$episode_id || $totalChunks <= 0) { + throw new \Exception('参数错误'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + throw new \Exception('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + throw new \Exception('分集不存在'); + } + + $tempDir = runtime_path('temp/chunks/' . $drama_id . '_' . $episode_id); + if (!is_dir($tempDir)) { + throw new \Exception('分片目录不存在'); + } + + $targetDir = runtime_path('temp/videos/'); + if (!is_dir($targetDir)) mkdir($targetDir, 0777, true); + + $targetPath = $targetDir . $drama_id . '_' . $episode_id . '.mp4'; + if (file_exists($targetPath)) { + \unlink($targetPath); + } + + $out = fopen($targetPath, 'ab'); + if (!$out) { + throw new \Exception('打开文件失败'); + } + + // 合并分片 + for ($i = 0; $i < $totalChunks; $i++) { + $chunkPath = $tempDir . '/' . $i; + if (!file_exists($chunkPath)) { + fclose($out); + throw new \Exception("分片 {$i} 不存在"); + } + $in = fopen($chunkPath, 'rb'); + stream_copy_to_stream($in, $out); + fclose($in); + // 删除分片 + unlink($chunkPath); + } + fclose($out); + + // 删除分片目录 + rmdir($tempDir); + try { + $file = new UploadFile($targetPath, $drama_id . '_' . $episode_id . '.mp4', 'video/mp4', 0); + $config = new Config('filesystem', '', $channels_uid); + $classify = Uploads::getClassify($channels_uid, 'uploads/drama/video', '短剧', $config->default); + $Storage = new Storage($config->toArray()); + $date_path = date('Ymd'); + $result = $Storage->adapter($classify->channels)->path($classify->dir_name . '/' . $date_path)->upload($file); + \unlink($targetPath); + $Uploads = new ModelUploads(); + $Uploads->classify_id = $classify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $classify->channels; + $Uploads->channels_uid = $channels_uid; + $Uploads->save(); + } catch (\Throwable $th) { + \unlink($targetPath); + throw $th; + } + $PluginShortplayDramaEpisode->video_path = $result->file_url; + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['drama_id' => $PluginShortplayDrama->id, 'episode_id' => $PluginShortplayDramaEpisode->id])->whereNotNull('image')->order('sort ASC')->find(); + $PluginShortplayDramaEpisode->cover = $PluginShortplayDramaStoryboard->image; + $PluginShortplayDramaEpisode->save(); + } catch (\Throwable $th) { + Log::error('合并分片失败:' . $th->getMessage(), $th->getTrace()); + } + }); + return $this->success('合并成功'); + } + public function update(Request $request) + { + $id = $request->post('id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $D = $request->post(); + if (!empty($D['style_id'])) { + $PluginShortplayDrama->style_id = $D['style_id']; + } + if (!empty($D['aspect_ratio'])) { + $PluginShortplayDrama->aspect_ratio = $D['aspect_ratio']; + } + if (!empty($D['title'])) { + $PluginShortplayDrama->title = $D['title']; + } + if (!empty($D['overall_hook'])) { + $PluginShortplayDrama->overall_hook = $D['overall_hook']; + } + if (!empty($D['core_catharsis_mechanism'])) { + $PluginShortplayDrama->core_catharsis_mechanism = $D['core_catharsis_mechanism']; + } + if (!empty($D['main_conflict'])) { + $PluginShortplayDrama->main_conflict = $D['main_conflict']; + } + if (!empty($D['relationship_mainline'])) { + $PluginShortplayDrama->relationship_mainline = $D['relationship_mainline']; + } + if (!empty($D['description'])) { + $PluginShortplayDrama->description = $D['description']; + } + if (!empty($D['background_description'])) { + $PluginShortplayDrama->background_description = $D['background_description']; + } + if (!empty($D['outline'])) { + $PluginShortplayDrama->outline = $D['outline']; + } + $PluginShortplayDrama->save(); + return $this->success('更新成功'); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + Db::startTrans(); + try { + PluginShortplayDramaActor::where(['drama_id' => $PluginShortplayDrama->id])->delete(); + $PluginShortplayDramaEpisodes = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id])->select(); + foreach ($PluginShortplayDramaEpisodes as $PluginShortplayDramaEpisode) { + PluginShortplayDramaEpisodeActor::where(['episode_id' => $PluginShortplayDramaEpisode->id])->delete(); + $PluginShortplayDramaEpisode->delete(); + } + PluginShortplayDramaScene::where(['drama_id' => $PluginShortplayDrama->id])->delete(); + $PluginShortplayDramaStoryboards = PluginShortplayDramaStoryboard::where(['drama_id' => $PluginShortplayDrama->id])->select(); + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + PluginShortplayDramaStoryboardProp::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + $PluginShortplayDramaStoryboard->delete(); + } + $PluginShortplayDrama->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('删除成功'); + } + public function voice(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + Db::startTrans(); + try { + $voice = $request->post('voice'); + if (!empty($voice['emotions_enum']) && empty($voice['selected_emotion'])) { + $voice['selected_emotion'] = $voice['emotions_enum'][0]; + } + if (empty($voice['selected_language'])) { + $voice['selected_language'] = VoiceLanguage::ZH; + } + $PluginShortplayDrama->voice = $voice; + $PluginShortplayDrama->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('更新成功'); + } + public function deleteActor(Request $request) + { + $id = $request->post('id'); + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaActor = PluginShortplayDramaActor::where(['actor_id' => $id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaActor) { + return $this->fail('演员不存在'); + } + Db::startTrans(); + try { + $PluginShortplayDramaActor->delete(); + PluginShortplayDramaEpisodeActor::where(['drama_id' => $PluginShortplayDrama->id, 'actor_id' => $PluginShortplayDramaActor->actor_id])->delete(); + PluginShortplayDramaStoryboardActor::where(['drama_id' => $PluginShortplayDrama->id, 'actor_id' => $PluginShortplayDramaActor->actor_id])->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/DramaEpisodeController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/DramaEpisodeController.php new file mode 100644 index 0000000..6b77778 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/DramaEpisodeController.php @@ -0,0 +1,213 @@ +get('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->get('episode_id'); + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->with('drama')->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDramaEpisode->is_share = PluginShortplayShareEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'episode_id' => $episode_id])->count(); + return $this->resData($PluginShortplayDramaEpisode); + } + public function share(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->post('episode_id'); + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + Db::startTrans(); + try { + $PluginShortplayShare = PluginShortplayShare::where(['drama_id' => $PluginShortplayDrama->id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayShare) { + $PluginShortplayShare = new PluginShortplayShare(); + $PluginShortplayShare->drama_id = $PluginShortplayDrama->id; + $PluginShortplayShare->uid = $request->uid; + $PluginShortplayShare->channels_uid = $request->channels_uid; + $PluginShortplayShare->save(); + } + $PluginShortplayShareEpisode = PluginShortplayShareEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'episode_id' => $PluginShortplayDramaEpisode->id])->find(); + if (!$PluginShortplayShareEpisode) { + $PluginShortplayShareEpisode = new PluginShortplayShareEpisode(); + $PluginShortplayShareEpisode->share_id = $PluginShortplayShare->id; + $PluginShortplayShareEpisode->drama_id = $PluginShortplayDrama->id; + $PluginShortplayShareEpisode->episode_id = $PluginShortplayDramaEpisode->id; + $PluginShortplayShareEpisode->channels_uid = $request->channels_uid; + $PluginShortplayShareEpisode->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('分享成功'); + } + public function episodes(Request $request) + { + $drama_id = $request->get('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id])->with(['scenes' => function ($query) { + $query->order('sort', 'asc'); + }, 'storyboards' => function ($query) { + $query->order('sort', 'asc'); + }])->select(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + return $this->resData($PluginShortplayDramaEpisode); + } + public function create(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + Db::startTrans(); + try { + $id = $request->post('id'); + if ($id) { + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + throw new \Exception('分集不存在'); + } + $msg = '更新成功'; + } else { + $PluginShortplayDramaEpisode = new PluginShortplayDramaEpisode(); + $PluginShortplayDramaEpisode->channels_uid = $request->channels_uid; + $PluginShortplayDramaEpisode->uid = $request->uid; + $PluginShortplayDramaEpisode->drama_id = $PluginShortplayDrama->id; + $PluginShortplayDramaEpisode->episode_no = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id])->max('episode_no') + 1; + $msg = '创建成功'; + $PluginShortplayDrama->episode_num = Db::raw('episode_num + 1'); + $PluginShortplayDrama->save(); + } + $PluginShortplayDramaEpisode->title = $request->post('title'); + $PluginShortplayDramaEpisode->content = $request->post('content'); + $PluginShortplayDramaEpisode->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success($msg); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + Db::startTrans(); + try { + $PluginShortplayDramaEpisode->delete(); + # 重排分集 + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $drama_id])->order('episode_no asc')->select(); + $episode_no = 1; + foreach ($PluginShortplayDramaEpisode as $PluginShortplayDramaEpisode) { + $PluginShortplayDramaEpisode->episode_no = $episode_no; + $PluginShortplayDramaEpisode->save(); + $episode_no++; + } + $PluginShortplayDrama->episode_num = Db::raw('episode_num - 1'); + $PluginShortplayDrama->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('删除成功'); + } + public function deleteActor(Request $request) + { + $id = $request->post('id'); + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->post('episode_id'); + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDramaEpisodeActor = PluginShortplayDramaEpisodeActor::where(['actor_id' => $id, 'episode_id' => $PluginShortplayDramaEpisode->id])->find(); + if (!$PluginShortplayDramaEpisodeActor) { + return $this->fail('演员不存在'); + } + Db::startTrans(); + try { + $PluginShortplayDramaEpisodeActor->delete(); + PluginShortplayDramaStoryboardActor::where(['episode_id' => $PluginShortplayDramaEpisode->id, 'actor_id' => $PluginShortplayDramaEpisodeActor->actor_id])->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('删除成功'); + } + public function CharacterLook(Request $request) + { + $actor_id = $request->post('actor_id'); + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $character_look_id = $request->post('character_look_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDramaEpisodeActor = PluginShortplayDramaEpisodeActor::where(['episode_id' => $PluginShortplayDramaEpisode->id, 'actor_id' => $actor_id])->find(); + if (!$PluginShortplayDramaEpisodeActor) { + return $this->fail('演员不存在'); + } + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where(['id' => $character_look_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayActorCharacterLook) { + return $this->fail('演员妆容不存在'); + } + $PluginShortplayDramaEpisodeActor->character_look_id = $PluginShortplayActorCharacterLook->id; + $PluginShortplayDramaEpisodeActor->headimg = $PluginShortplayActorCharacterLook->headimg; + $PluginShortplayDramaEpisodeActor->three_view_image = $PluginShortplayActorCharacterLook->three_view_image; + $PluginShortplayDramaEpisodeActor->save(); + return $this->success('设置成功'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/GenerateController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/GenerateController.php new file mode 100644 index 0000000..3f77c01 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/GenerateController.php @@ -0,0 +1,1470 @@ +post('id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $id, 'uid' => $request->uid])->with('style')->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::DRAMA_COVER['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $whereOr = []; + $whereOr[] = ['actor.uid', '=', null]; + $whereOr[] = ['actor.uid', '=', $request->uid]; + $params = []; + $params['drama_id'] = $PluginShortplayDrama->id; + $PluginShortplayActor = PluginShortplayActor::actorQuery($params)->whereOr($whereOr)->order('d_actor.id', 'asc')->limit(2)->select(); + $images = []; + foreach ($PluginShortplayActor as $item) { + if ($item->status == ActorStatus::GENERATED['value']) { + $images[] = $item->headimg; + }else{ + $images=[]; + break; + } + } + $prompts = []; + $prompts[] = '剧名:' . $PluginShortplayDrama->title; + $prompts[] = '简介:' . $PluginShortplayDrama->description; + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'images' => $images, + 'prompt' => implode(";\n", $prompts) . ";\n", + 'style' => $PluginShortplayDrama->style->prompts, + 'aspect_ratio' => '2:3' + ] + ]; + + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成封面', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $model_id; + $PluginModelTask->model_type = ModelType::DRAW['value']; + $PluginModelTask->alias_id = $PluginShortplayDrama->id; + $PluginModelTask->scene = ModelScene::DRAMA_COVER['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s'); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + return $this->fail($th->getMessage()); + } + return $this->success('生成中'); + } + public function continueEpisode(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::CREATIVE_EPISODE['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $episode_sum = (int)$request->post('episode_sum'); + if (!$episode_sum) { + return $this->fail('请输入需要生成的集数'); + } + $continue_episode_state = PluginModelTask::processing(['alias_id' => $PluginShortplayDrama->id, 'scene' => ModelScene::CREATIVE_EPISODE['value']]); + if ($continue_episode_state > 0) { + return $this->fail('正在生成中'); + } + Db::startTrans(); + try { + $PluginShortplayDramaActor = PluginShortplayDramaActor::where('drama_id', $PluginShortplayDrama->id)->with('actor')->select(); + $actors = []; + foreach ($PluginShortplayDramaActor as $PluginShortplayActor) { + $actors[] = "{$PluginShortplayActor->actor->name}{{$PluginShortplayActor->actor->actor_id}}"; + } + $background_description = $PluginShortplayDrama->background_description; + $outline = $PluginShortplayDrama->outline; + $pre_episode = ''; + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where('drama_id', $PluginShortplayDrama->id)->order('episode_no desc')->find(); + if ($PluginShortplayDramaEpisode) { + // $pre_episode[] = "{$PluginShortplayDramaEpisode->episode_no}集大纲:{$PluginShortplayDramaEpisode->outline}"; + $pre_episode = $PluginShortplayDramaEpisode->content; + } + $task_id = uniqid('local-'); + $species = []; + $gender = []; + $age = []; + foreach (ActorSpeciesType::getOptions() as $value) { + $species[] = $value['label'] . ':' . $value['value'] . ''; + } + foreach (ActorGender::getOptions() as $value) { + $gender[] = $value['label'] . ':' . $value['value'] . ''; + } + foreach (ActorAge::getOptions() as $value) { + $age[] = $value['label'] . ':' . $value['value'] . ''; + } + $data = [ + 'channels_uid' => $request->channels_uid, + 'uid' => $request->uid, + 'drama_id' => $PluginShortplayDrama->id, + 'task_id' => $task_id, + 'params' => [ + 'model' => $PluginModel->model_id, + 'assistant' => $PluginModel->assistant_id, + 'form_data' => [ + 'background_description' => $background_description, + 'episode_duration' => $PluginShortplayDrama->episode_duration, + 'outline' => $outline, + 'pre_episode' => $pre_episode, + 'episode_sum' => $PluginShortplayDrama->episode_sum, + 'actors' => implode(',', $actors), + 'episode_no' => $PluginShortplayDrama->episode_num, + 'species' => implode(',', $species), + 'gender' => implode(',', $gender), + 'age' => implode(',', $age), + ] + ] + ]; + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $PluginModel->id; + $PluginModelTask->model_type = ModelType::CHAT['value']; + $PluginModelTask->alias_id = $PluginShortplayDrama->id; + $PluginModelTask->scene = ModelScene::CREATIVE_EPISODE['value']; + $PluginModelTask->status = ModelTaskStatus::WAIT['value']; + $PluginModelTask->task_id = $task_id; + $PluginModelTask->execution_count = 0; + $PluginModelTask->expectation_execution_count = $episode_sum; + if ($PluginModelTask->expectation_execution_count > $PluginShortplayDrama->episode_sum - $PluginShortplayDrama->episode_num) { + $PluginModelTask->expectation_execution_count = $PluginShortplayDrama->episode_sum - $PluginShortplayDrama->episode_num; + } + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+10 seconds')); + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point * $PluginModelTask->expectation_execution_count, PointsBillScene::CONSUME['value'], null, '续写分集', true); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('续写分集失败:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + return $this->fail($th->getMessage()); + } + return $this->success('生成中'); + } + public function scene(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $episode_id])->with('actors')->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::CREATIVE_SCENES['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $actors = []; + foreach ($PluginShortplayDramaEpisode->actors as $item) { + $actors[] = "{$item->actor->name}{{$item->actor->actor_id}}"; + } + try { + $prompt = $PluginShortplayDramaEpisode->content; + $NextPluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'episode_no' => $PluginShortplayDramaEpisode->episode_no + 1])->find(); + if ($NextPluginShortplayDramaEpisode) { + $next_episode_content = $NextPluginShortplayDramaEpisode->content; + } else { + $next_episode_content = ''; + } + $task_id = uniqid('local-'); + $data = [ + 'channels_uid' => $request->channels_uid, + 'uid' => $request->uid, + 'drama_id' => $PluginShortplayDrama->id, + 'episode_id' => $PluginShortplayDramaEpisode->id, + 'task_id' => $task_id, + 'params' => [ + 'model' => $PluginModel->model_id, + 'assistant' => $PluginModel->assistant_id, + 'form_data' => [ + 'prompt' => $prompt, + 'next_episode_content' => $next_episode_content, + 'episode_duration' => $PluginShortplayDrama->episode_duration, + // 'episode_outline' => $outline, + 'episode_num' => $PluginShortplayDrama->episode_num, + 'episode_no' => $PluginShortplayDramaEpisode->episode_no, + 'drama_outline' => $PluginShortplayDrama->outline, + 'actors' => implode(',', $actors) + ] + ] + ]; + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $model_id; + $PluginModelTask->model_type = ModelType::CHAT['value']; + $PluginModelTask->alias_id = $PluginShortplayDramaEpisode->id; + $PluginModelTask->scene = ModelScene::CREATIVE_SCENES['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $task_id; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+30 seconds')); + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + $consume_ids = Account::decPoints($PluginModelTask->uid, $PluginModelTask->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成场景', true); + $data['consume_ids'] = $consume_ids; + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + Coroutine::create(function () use ($data) { + try { + p('start'); + $result = Yidevs::ChatAssistantCompletions($data['channels_uid'], $data['params']); + p($result); + } catch (\Throwable $th) { + Log::error('GenerateScene Error:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + $taksStatus = ModelTaskStatus::FAIL['value']; + if (!empty($result)) { + Db::startTrans(); + try { + $wherePublic[] = ['uid', '=', null]; + $whereSelf[] = ['uid', '=', $data['uid']]; + foreach ($result as $scene_key => $scene) { + $PluginShortplayDramaScene = new PluginShortplayDramaScene(); + $PluginShortplayDramaScene->channels_uid = $data['channels_uid']; + $PluginShortplayDramaScene->uid = $data['uid']; + $PluginShortplayDramaScene->drama_id = $data['drama_id']; + $PluginShortplayDramaScene->episode_id = $data['episode_id']; + $PluginShortplayDramaScene->sort = $scene_key + 1; + $PluginShortplayDramaScene->title = $scene['title']; + $PluginShortplayDramaScene->scene_space = $scene['scene_space']; + $PluginShortplayDramaScene->scene_location = $scene['scene_location']; + $PluginShortplayDramaScene->scene_time = $scene['scene_time']; + $PluginShortplayDramaScene->scene_weather = $scene['scene_weather']; + $PluginShortplayDramaScene->description = $scene['description']; + if (!empty($scene['atmosphere'])) { + $PluginShortplayDramaScene->atmosphere = $scene['atmosphere']; + } + $PluginShortplayDramaScene->save(); + if (empty($scene['storyboards'])) { + continue; + } + $sort = PluginShortplayDramaStoryboard::where(['drama_id' => $data['drama_id'], 'episode_id' => $data['episode_id']])->max('sort') + 1 ?? 1; + foreach ($scene['storyboards'] as $storyboard) { + $PluginShortplayDramaStoryboard = new PluginShortplayDramaStoryboard(); + $PluginShortplayDramaStoryboard->channels_uid = $data['channels_uid']; + $PluginShortplayDramaStoryboard->drama_id = $data['drama_id']; + $PluginShortplayDramaStoryboard->episode_id = $data['episode_id']; + $PluginShortplayDramaStoryboard->scene_id = $PluginShortplayDramaScene->id; + $PluginShortplayDramaStoryboard->sort = $sort; + $PluginShortplayDramaStoryboard->description = $storyboard; + $PluginShortplayDramaStoryboard->save(); + $sort++; + } + } + $PluginModelTask = PluginModelTask::where(['task_id' => $data['task_id'], 'model_type' => ModelType::CHAT['value']])->with(['result'])->find(); + $PluginModelTask->status = ModelTaskStatus::SUCCESS['value']; + $PluginModelTask->result->result = $result; + $PluginModelTask->together(['result'])->save(); + $taksStatus = ModelTaskStatus::SUCCESS['value']; + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('GenerateScene Error:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + } else { + if (!empty($data['consume_ids'])) { + Db::startTrans(); + try { + Account::refund($data['uid'], $data['channels_uid'], $data['consume_ids']); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + } + if ($taksStatus == ModelTaskStatus::FAIL['value']) { + $PluginModelTask = PluginModelTask::where(['task_id' => $data['task_id'], 'model_type' => ModelType::CHAT['value']])->find(); + $PluginModelTask->status = ModelTaskStatus::FAIL['value']; + $PluginModelTask->save(); + } + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'hash' => $data['drama_id'], + 'event' => 'generatescenestoryboard', + ], [ + 'drama_id' => $data['drama_id'], + 'episode_id' => $data['episode_id'], + ]); + }); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('生成中'); + } + public function storyboard(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $episode_id])->with('actors')->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::CREATIVE_STORYBOARDS['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $actors = []; + foreach ($PluginShortplayDramaEpisode->actors as $item) { + $actors[] = "{$item->actor->name}{{$item->actor->actor_id}}"; + } + $props = []; + $PluginShortplayProp = PluginShortplayProp::where(['episode_id' => $PluginShortplayDramaEpisode->id])->select(); + foreach ($PluginShortplayProp as $item) { + $props[] = "{$item->name}{{$item->prop_id}}"; + } + $scene_list = []; + $PluginShortplayDramaScene = PluginShortplayDramaScene::where(['drama_id' => $PluginShortplayDrama->id, 'episode_id' => $PluginShortplayDramaEpisode->id])->select(); + foreach ($PluginShortplayDramaScene as $item) { + $scene_list[] = "{$item->scene_space}·{$item->scene_location}·{$item->scene_time}·{$item->scene_weather}{{$item->id}}"; + } + $prompt = $PluginShortplayDramaEpisode->content; + $NextPluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'episode_no' => $PluginShortplayDramaEpisode->episode_no + 1])->find(); + if ($NextPluginShortplayDramaEpisode) { + $next_episode_content = $NextPluginShortplayDramaEpisode->content; + } else { + $next_episode_content = ''; + } + $species_type = []; + foreach (ActorSpeciesType::getOptions() as $item) { + $species_type[] = $item['label'] . ':' . $item['value'] . ''; + } + $gender = []; + foreach (ActorGender::getOptions() as $item) { + $gender[] = $item['label'] . ':' . $item['value'] . ''; + } + $age = []; + foreach (ActorAge::getOptions() as $item) { + $age[] = $item['label'] . ':' . $item['value'] . ''; + } + $task_id = uniqid('local-'); + $data = [ + 'channels_uid' => $request->channels_uid, + 'uid' => $request->uid, + 'drama_id' => $PluginShortplayDrama->id, + 'episode_id' => $PluginShortplayDramaEpisode->id, + 'task_id' => $task_id, + 'params' => [ + 'model' => $PluginModel->model_id, + 'assistant' => $PluginModel->assistant_id, + 'form_data' => [ + 'prompt' => $prompt, + 'next_episode_content' => $next_episode_content, + 'episode_num' => $PluginShortplayDrama->episode_num, + 'episode_duration' => $PluginShortplayDrama->episode_duration, + 'episode_no' => $PluginShortplayDramaEpisode->episode_no, + 'sort' => 1, + 'scene_list' => implode(',', $scene_list), + 'actors' => implode(',', $actors), + 'props' => implode(',', $props), + 'species_type' => implode(',', $species_type), + 'gender' => implode(',', $gender), + 'age' => implode(',', $age), + ] + ] + ]; + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $model_id; + $PluginModelTask->model_type = ModelType::CHAT['value']; + $PluginModelTask->alias_id = $PluginShortplayDramaEpisode->id; + $PluginModelTask->scene = ModelScene::CREATIVE_STORYBOARDS['value']; + $PluginModelTask->status = ModelTaskStatus::WAIT['value']; + $PluginModelTask->task_id = $task_id; + $PluginModelTask->execution_count = 0; + $PluginModelTask->expectation_execution_count = 999; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s', strtotime('+10 seconds')); + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->success('生成中'); + } + public function sceneImage(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->with('style')->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $episode_id])->with('actors')->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $id = $request->post('id'); + $PluginShortplayDramaScene = PluginShortplayDramaScene::where(['id' => $id, 'drama_id' => $PluginShortplayDrama->id, 'episode_id' => $PluginShortplayDramaEpisode->id])->find(); + if (!$PluginShortplayDramaScene) { + return $this->fail('场景不存在'); + } + $title = $request->post('title'); + if (!empty($title)) { + $PluginShortplayDramaScene->title = $title; + } + $scene_space = $request->post('scene_space'); + if (!empty($scene_space)) { + $PluginShortplayDramaScene->scene_space = $scene_space; + } + $scene_location = $request->post('scene_location'); + if (!empty($scene_location)) { + $PluginShortplayDramaScene->scene_location = $scene_location; + } + $scene_time = $request->post('scene_time'); + if (!empty($scene_time)) { + $PluginShortplayDramaScene->scene_time = $scene_time; + } + $scene_weather = $request->post('scene_weather'); + if (!empty($scene_weather)) { + $PluginShortplayDramaScene->scene_weather = $scene_weather; + } + $description = $request->post('description'); + if (!empty($description)) { + $PluginShortplayDramaScene->description = $description; + } + $atmosphere = $request->post('atmosphere'); + if (!empty($atmosphere)) { + $PluginShortplayDramaScene->atmosphere = $atmosphere; + } + $PluginShortplayDramaScene->save(); + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::SCENE_IMAGE['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'id' => $PluginShortplayDramaScene->id, + 'title' => $PluginShortplayDramaScene->title, + 'scene_space' => $PluginShortplayDramaScene->scene_space, + 'scene_location' => $PluginShortplayDramaScene->scene_location, + 'scene_time' => $PluginShortplayDramaScene->scene_time, + 'scene_weather' => $PluginShortplayDramaScene->scene_weather, + 'description' => $PluginShortplayDramaScene->description, + 'atmosphere' => $PluginShortplayDramaScene->atmosphere, + 'style' => $PluginShortplayDrama->style->prompts, + 'aspect_ratio' => $PluginShortplayDrama->aspect_ratio + ] + ]; + $reference_image = $request->post('reference_image'); + if (!empty($reference_image)) { + $data['form_data']['images'] = [$reference_image]; + } + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成场景图片', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $model_id; + $PluginModelTask->model_type = ModelType::DRAW['value']; + $PluginModelTask->alias_id = $PluginShortplayDramaScene->id; + $PluginModelTask->scene = ModelScene::SCENE_IMAGE['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s'); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + Log::error('GenerateSceneImage Error:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + } + return $this->success(); + } + public function storyboardImage(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->with('style')->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::STORYBOARD_IMAGE['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $images = []; + $PluginShortplayDramaScene = PluginShortplayDramaScene::where(['id' => $PluginShortplayDramaStoryboard->scene_id])->find(); + if (!$PluginShortplayDramaScene) { + return $this->fail("分镜{$PluginShortplayDramaStoryboard->sort}:场景不存在"); + } + if (!$PluginShortplayDramaScene->image) { + return $this->fail("分镜{$PluginShortplayDramaStoryboard->sort}:场景图片不存在"); + } + $storyboards = $request->post('storyboards'); + if ($storyboards) { + $QuotePluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['drama_id' => $drama_id])->whereIn('id', $storyboards)->order('sort', 'asc')->select(); + foreach ($QuotePluginShortplayDramaStoryboard as $item) { + $images[] = $item->image; + } + } else { + $images[] = $PluginShortplayDramaScene->image; + } + $whereOr = []; + $whereOr[] = ['actor.uid', '=', null]; + $whereOr[] = ['actor.uid', '=', $request->uid]; + $params = []; + $params['drama_id'] = $PluginShortplayDrama->id; + $params['episode_id'] = $PluginShortplayDramaStoryboard->episode_id; + $params['storyboard_id'] = $PluginShortplayDramaStoryboard->id; + $PluginShortplayActor = PluginShortplayActor::actorQuery($params)->whereOr($whereOr)->order('actor.id', 'desc')->select(); + // $PluginShortplayDramaStoryboardActor = PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $storyboard_id])->with('actor')->select(); + foreach ($PluginShortplayActor as $item) { + if ($item->status != ActorStatus::GENERATED['value']) { + return $this->fail("分镜{$PluginShortplayDramaStoryboard->sort}:演员状态异常[" . $item->name . "]"); + } + $images[] = $item->headimg; + } + $PluginShortplayDramaStoryboardProp = PluginShortplayDramaStoryboardProp::where(['storyboard_id' => $storyboard_id])->with('prop')->select(); + foreach ($PluginShortplayDramaStoryboardProp as $item) { + if ($item->prop->status != PropStatus::GENERATED['value']) { + return $this->fail("分镜{$PluginShortplayDramaStoryboard->sort}:物品状态异常[" . $item->prop->name . "]"); + } + if ($item->prop->image) { + $images[] = $item->prop->image; + } + } + $prompt = $request->post('prompt'); + if (!$prompt) { + $prompts = []; + $prompts[] = '镜头设计:'; + $prompts[] = '-景别:' . $PluginShortplayDramaStoryboard->shot_type; + $prompts[] = '-镜头视角:' . $PluginShortplayDramaStoryboard->shot_angle; + $prompts[] = "\n\n画面描述:\n" . $PluginShortplayDramaStoryboard->description; + $prompt = implode("\n", $prompts) . "\n"; + } + $PluginShortplayDramaStoryboard->image_prompt = $prompt; + $PluginShortplayDramaStoryboard->save(); + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'images' => $images, + 'prompt' => $prompt, + 'style' => $PluginShortplayDrama->style->prompts, + 'aspect_ratio' => $PluginShortplayDrama->aspect_ratio + ] + ]; + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成分镜图片', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $PluginModel->id; + $PluginModelTask->model_type = ModelType::DRAW['value']; + $PluginModelTask->alias_id = $PluginShortplayDramaStoryboard->id; + $PluginModelTask->scene = ModelScene::STORYBOARD_IMAGE['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s'); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + return $this->fail($th->getMessage()); + } + return $this->success('生成中'); + } + public function OptimizeStoryboardPrompt(Request $request) + { + $prompt = $request->post('prompt'); + if (!$prompt) { + return $this->fail('优化提示词不能为空'); + } + $data = [ + 'model' => 2, + "messages" => [ + [ + "content" => "你是一位分镜大师,擅长使用AI工具", + "role" => "system" + ], + ] + ]; + $type = $request->post('type'); + if ($type == ModelScene::STORYBOARD_IMAGE['value']) { + $data['messages'][] = [ + "content" => "优化以下提示词用于生成高质量图片:\n" . $prompt . "\n\n只需要输出优化后的提示词,不要输出其他内容", + "role" => "user" + ]; + } else if ($type == ModelScene::STORYBOARD_VIDEO['value']) { + $data['messages'][] = [ + "content" => "优化以下提示词用于生成高质量视频:\n" . $prompt . "\n\n只需要输出优化后的提示词,不要输出其他内容", + "role" => "user" + ]; + } + try { + $result = []; + Yidevs::ChatCompletions($request->channels_uid, $data, function ($ch, $data) use (&$result) { + if (empty($data['choices'][0])) { + return $data; + } + $choices = $data['choices'][0]; + if (!empty($choices['message']['content'])) { + $result[] = $choices['message']['content']; + } elseif (!empty($choices['delta']['content'])) { + $result[] = $choices['delta']['content']; + } + return $data; + }); + return $this->resData(['prompt' => implode('', $result)]); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + return $this->fail('优化失败'); + } + public function characterLookCostume(Request $request) + { + $id = $request->post('id'); + $PluginShortplayCharacterLook = PluginShortplayCharacterLook::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayCharacterLook) { + return $this->fail('装扮不存在'); + } + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::CHARACTER_LOOK_COSTUME['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $images = []; + $costume_url = $request->post('costume_url'); + $costume_reference_state = (bool)$request->post('costume_reference_state'); + if ($costume_reference_state && $costume_url) { + $images[] = $costume_url; + } + $style = $request->post('style'); + if ($style) { + $PluginShortplayStyle = PluginShortplayStyle::where(['id' => $style])->find(); + if ($PluginShortplayStyle) { + $style = $PluginShortplayStyle->prompts; + } + } else { + if ($PluginShortplayCharacterLook->drama_id) { + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $PluginShortplayCharacterLook->drama_id])->with('style')->find(); + if ($PluginShortplayDrama) { + $style = $PluginShortplayDrama->style->prompts; + } + } + } + if (!$style) { + return $this->fail('风格不存在'); + } + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'images' => $images, + 'hair_style' => $PluginShortplayCharacterLook->hair_style, + 'costume' => $PluginShortplayCharacterLook->costume, + 'style' => $style, + 'aspect_ratio' => '1:1', + ] + ]; + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成角色换装', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $PluginModel->id; + $PluginModelTask->model_type = ModelType::DRAW['value']; + $PluginModelTask->alias_id = $PluginShortplayCharacterLook->id; + $PluginModelTask->scene = ModelScene::CHARACTER_LOOK_COSTUME['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s'); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + return $this->fail($th->getMessage()); + } + return $this->success('生成中'); + } + public function characterLook(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->with('style')->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->post('episode_id'); + $storyboard_id = $request->post('storyboard_id'); + $actor_id = $request->post('actor_id'); + $PluginShortplayActor = PluginShortplayActor::where(['id' => $actor_id])->whereOr([['uid', '=', $request->uid], ['uid', '=', null]])->find(); + if (!$PluginShortplayActor) { + return $this->fail('演员不存在'); + } + $character_look_id = $request->post('character_look_id'); + $PluginShortplayCharacterLook = PluginShortplayCharacterLook::where(['id' => $character_look_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayCharacterLook) { + return $this->fail('装扮不存在'); + } + if ($PluginShortplayCharacterLook->status !== ActorStatus::GENERATED['value']) { + return $this->fail('装扮还未完成初始化'); + } + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where(['actor_id' => $actor_id, 'character_look_id' => $PluginShortplayCharacterLook->id, 'uid' => $request->uid, 'status' => ActorStatus::PENDING['value']])->find(); + if ($PluginShortplayActorCharacterLook) { + return $this->fail('演员换装中'); + } + $actor_costume_model_id = $request->post('actor_costume_model_id'); + $PluginModel = PluginModel::where(['id' => $actor_costume_model_id, 'scene' => ModelScene::ACTOR_COSTUME['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('角色换装模型不存在'); + } + $actor_costume_three_view_model_id = $request->post('actor_costume_three_view_model_id'); + $ThreeViewPluginModel = PluginModel::where(['id' => $actor_costume_three_view_model_id, 'scene' => ModelScene::ACTOR_COSTUME_THREE_VIEW['value'], 'state' => State::YES['value']])->find(); + if (!$ThreeViewPluginModel) { + return $this->fail('角色换装三视图模型不存在'); + } + $ids = [ + 'channels_uid' => $request->channels_uid, + 'uid' => $request->uid, + 'model_id' => $PluginModel->id, + 'model_model_id' => $PluginModel->model_id, + 'model_points' => $PluginModel->point, + 'three_view_model_points' => $ThreeViewPluginModel->point, + 'assistant_id' => $PluginModel->assistant_id, + 'drama_id' => $drama_id, + 'episode_id' => $episode_id, + 'storyboard_id' => $storyboard_id, + 'actor_id' => $actor_id, + 'three_view_model_id' => $ThreeViewPluginModel->id, + 'three_view_model_model_id' => $ThreeViewPluginModel->model_id, + 'three_view_assistant_id' => $ThreeViewPluginModel->assistant_id, + 'character_look_id' => $PluginShortplayCharacterLook->id, + 'host' => $request->host(), + ]; + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $ids['host'] . '/app/model/Notify/draw', + 'form_data' => [ + 'prompt' => $PluginShortplayActor->remarks, + 'species' => ActorSpeciesType::getText($PluginShortplayActor->species_type), + 'gender' => ActorGender::getText($PluginShortplayActor->gender), + 'age' => ActorAge::getText($PluginShortplayActor->age), + 'overall_style' => $PluginShortplayCharacterLook->overall_style, + 'status_note' => $PluginShortplayCharacterLook->status_note, + 'makeup' => $PluginShortplayCharacterLook->makeup, + 'hair_style' => $PluginShortplayCharacterLook->hair_style, + 'costume' => $PluginShortplayCharacterLook->costume, + 'style' => $PluginShortplayDrama->style->prompts, + 'aspect_ratio' => '1:1', + 'images' => [$PluginShortplayActor->headimg, $PluginShortplayActor->three_view_image, $PluginShortplayCharacterLook->costume_url] + ] + ]; + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成角色换装', true); + $three_view_consume_ids = Account::decPoints($request->uid, $request->channels_uid, $ThreeViewPluginModel->point, PointsBillScene::CONSUME['value'], null, '生成角色换装三视图', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + Coroutine::create(function () use ($ids, $data, $consume_ids, $three_view_consume_ids) { + try { + + $result = Yidevs::DrawAssistantTIGI($ids['channels_uid'], $data); + + Db::startTrans(); + try { + $PluginShortplayActorCharacterLook = new PluginShortplayActorCharacterLook(); + $PluginShortplayActorCharacterLook->channels_uid = $ids['channels_uid']; + $PluginShortplayActorCharacterLook->uid = $ids['uid']; + $PluginShortplayActorCharacterLook->actor_id = $ids['actor_id']; + $PluginShortplayActorCharacterLook->status = ActorStatus::PENDING['value']; + $PluginShortplayActorCharacterLook->character_look_id = $ids['character_look_id']; + if ($ids['drama_id']) { + $PluginShortplayActorCharacterLook->drama_id = $ids['drama_id']; + } + if ($ids['episode_id']) { + $PluginShortplayActorCharacterLook->episode_id = $ids['episode_id']; + } + if ($ids['storyboard_id']) { + $PluginShortplayActorCharacterLook->storyboard_id = $ids['storyboard_id']; + } + $PluginShortplayActorCharacterLook->save(); + $task = new PluginModelTask(); + $task->channels_uid = $ids['channels_uid']; + $task->uid = $ids['uid']; + $task->model_id = $ids['model_id']; + $task->model_type = ModelType::DRAW['value']; + $task->alias_id = $PluginShortplayActorCharacterLook->id; + $task->scene = ModelScene::ACTOR_COSTUME['value']; + $task->status = ModelTaskStatus::PROCESSING['value']; + $task->task_id = $result['task_id']; + $task->last_heartbeat = date('Y-m-d H:i:s'); + $task->consume_ids = $consume_ids; + $task->save(); + $taskId = $task->id; + $taskResult = new PluginModelTaskResult(); + $taskResult->task_id = $task->id; + $taskResult->channels_uid = $ids['channels_uid']; + $taskResult->params = $data; + $taskResult->save(); + + $data = [ + 'assistant' => $ids['three_view_assistant_id'], + 'model' => $ids['three_view_model_model_id'], + 'notify_url' => 'https://' . $ids['host'] . '/app/model/Notify/draw', + 'form_data' => [ + 'images' => [], + 'aspect_ratio' => '1:1', + 'style' => $data['form_data']['style'], + ] + ]; + $task = new PluginModelTask(); + $task->channels_uid = $ids['channels_uid']; + $task->uid = $ids['uid']; + $task->model_id = $ids['three_view_model_id']; + $task->model_type = ModelType::DRAW['value']; + $task->alias_id = $PluginShortplayActorCharacterLook->id; + $task->scene = ModelScene::ACTOR_COSTUME_THREE_VIEW['value']; + $task->pre_task_id = $taskId; + $task->status = ModelTaskStatus::WAIT['value']; + $task->task_id = null; + $task->last_heartbeat = date('Y-m-d H:i:s', strtotime('+30 seconds')); + $task->consume_ids = $three_view_consume_ids; + $task->save(); + + $taskResult = new PluginModelTaskResult(); + $taskResult->task_id = $task->id; + $taskResult->channels_uid = $ids['channels_uid']; + $taskResult->params = $data; + $taskResult->save(); + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } catch (\Throwable $e) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($ids['uid'], $ids['channels_uid'], $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + if (!empty($three_view_consume_ids)) { + Db::startTrans(); + try { + Account::refund($ids['uid'], $ids['channels_uid'], $three_view_consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + Log::error('GenerateActorImage Error:' . $e->getMessage()); + Push::send([ + 'uid' => $ids['uid'], + 'channels_uid' => $ids['channels_uid'], + 'event' => 'generate' . strtolower(str_replace('_', '', ModelScene::ACTOR_COSTUME['value'])), + ], [ + 'actor_id' => $ids['actor_id'], + 'drama_id' => $ids['drama_id'], + 'episode_id' => $ids['episode_id'], + 'storyboard_id' => $ids['storyboard_id'], + ]); + } + }); + return $this->resData($PluginShortplayActor); + } + public function storyboardVideo(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $model_id = $request->post('model_id'); + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => ModelScene::STORYBOARD_VIDEO['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $prompt = $request->post('prompt'); + if (!$prompt) { + return $this->fail('提示词不能为空'); + } + $PluginShortplayDramaStoryboard->video_prompt = $prompt; + $first_image = $request->post('first_image'); + if (!$first_image) { + return $this->fail('首帧图不能为空'); + } + $last_image = $request->post('last_image'); + $negative_prompt = $request->post('negative_prompt'); + $duration = (int)$request->post('duration'); + if ($duration) { + $PluginShortplayDramaStoryboard->duration = $duration; + } + $duration = max(2, min(15, floor($PluginShortplayDramaStoryboard->duration / 1000))); + $PluginShortplayDramaStoryboard->save(); + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/video', + 'form_data' => [ + 'prompt' => $prompt, + 'negative_prompt' => $negative_prompt, + 'first_image' => $first_image, + 'last_image' => $last_image, + 'duration' => $duration, + 'resolution'=>'720P', + 'aspect_ratio' => $PluginShortplayDrama->aspect_ratio + ] + ]; + Db::startTrans(); + try { + if ($PluginModel->point_type == ModelPointType::TIMES['value']) { + $point = $PluginModel->point; + } else { + $calculator = new PricingCalculator($PluginModel->form, $data['form_data'], $PluginModel->point); + $point = $calculator->calculate(); + } + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $point, PointsBillScene::CONSUME['value'], null, '生成分镜视频', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::VideoAssistantTITV($request->channels_uid, $data); + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $PluginModel->id; + $PluginModelTask->model_type = ModelType::TOVIDEO['value']; + $PluginModelTask->alias_id = $PluginShortplayDramaStoryboard->id; + $PluginModelTask->scene = ModelScene::STORYBOARD_VIDEO['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s'); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->image_path = $first_image; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + return $this->fail($th->getMessage()); + } + return $this->success('生成中'); + } + public function storyboardDialogueVoice(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $dialogue_id = $request->post('dialogue_id'); + $PluginShortplayDramaStoryboardDialogue = PluginShortplayDramaStoryboardDialogue::where(['id' => $dialogue_id, 'storyboard_id' => $PluginShortplayDramaStoryboard->id])->with('actor')->find(); + if (!$PluginShortplayDramaStoryboardDialogue) { + return $this->fail('台词不存在'); + } + $voice = $request->post('voice'); + if (empty($voice)) { + $voice = $PluginShortplayDramaStoryboardDialogue->voice; + } + if (empty($voice)) { + $ActorVoiceLevel = [ + [ + 'model' => PluginShortplayDramaStoryboardActor::class, + 'where' => [ + 'storyboard_id' => $PluginShortplayDramaStoryboard->id, + 'actor_id' => $PluginShortplayDramaStoryboardDialogue->actor_id + ] + ], + [ + 'model' => PluginShortplayDramaEpisodeActor::class, + 'where' => [ + 'episode_id' => $PluginShortplayDramaStoryboard->episode_id, + 'actor_id' => $PluginShortplayDramaStoryboardDialogue->actor_id + ] + ], + [ + 'model' => PluginShortplayDramaActor::class, + 'where' => [ + 'drama_id' => $PluginShortplayDrama->id, + 'actor_id' => $PluginShortplayDramaStoryboardDialogue->actor_id + ] + ], + [ + 'level' => 'actor', + 'model' => PluginShortplayActor::class, + 'where' => [ + 'actor_id' => $PluginShortplayDramaStoryboardDialogue->actor_id + ] + ] + ]; + foreach ($ActorVoiceLevel as $item) { + $PluginActorVoice = $item['model']::where($item['where'])->find(); + if ($PluginActorVoice && $PluginActorVoice->voice) { + $voice = $PluginActorVoice->voice; + break; + } + } + } + $prosody_volume = $request->post('prosody_volume', null); + $prosody_speed = $request->post('prosody_speed', null); + if ($prosody_volume !== null) { + $PluginShortplayDramaStoryboardDialogue->prosody_volume = $prosody_volume; + } + if ($prosody_speed !== null) { + $PluginShortplayDramaStoryboardDialogue->prosody_speed = $prosody_speed; + } + $content = $request->post('content'); + if ($content) { + $PluginShortplayDramaStoryboardDialogue->content = $content; + } + $PluginShortplayDramaStoryboardDialogue->save(); + $PluginModel = PluginModel::where(['id' => $voice['model_id'], 'scene' => ModelScene::DIALOGUE_VOICE['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $voice_id = $voice['voice_id']; + $voice_channel = $voice['voice_channel']; + $clone_voice_id = ''; + if ($voice_channel === 'self') { + $PluginModelVoice = PluginModelVoice::where(['id' => $voice['id']])->find(); + if (!$PluginModelVoice) { + return $this->fail('音色不存在'); + } + $voice_id = ''; + $clone_voice_id = $PluginModelVoice->voice_id; + } + if (!$clone_voice_id && !$voice_id) { + return $this->fail('未找到音色'); + } + $language = empty($voice['selected_language']['value']) ? VoiceLanguage::ZH['value'] : $voice['selected_language']['value']; + $emotion = empty($voice['selected_emotion']['value']) ? VoiceEmotion::NEUTRAL['value'] : $voice['selected_emotion']['value']; + $volume = $PluginShortplayDramaStoryboardDialogue->prosody_volume; + $speed = $PluginShortplayDramaStoryboardDialogue->prosody_speed; + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/audio', + 'form_data' => [ + 'text' => $PluginShortplayDramaStoryboardDialogue->content, + 'voice_id' => $voice_id, + 'clone_voice_id' => $clone_voice_id, + 'emotion' => $emotion, + 'volume' => $volume, + 'speed' => $speed, + 'language' => [$language], + ] + ]; + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成台词语音', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::AudioAssistantTTS($request->channels_uid, $data); + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $PluginModel->id; + $PluginModelTask->model_type = ModelType::AUDIO['value']; + $PluginModelTask->alias_id = $PluginShortplayDramaStoryboardDialogue->id; + $PluginModelTask->scene = ModelScene::DIALOGUE_VOICE['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s'); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + return $this->fail($th->getMessage()); + } + return $this->success(); + } + public function storyboardNarrationVoice(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $voice = $request->post('voice'); + if (!empty($voice)) { + if (!empty($voice['emotions_enum']) && empty($voice['selected_emotion'])) { + $voice['selected_emotion'] = $voice['emotions_enum'][0]; + } + if (empty($voice['selected_language'])) { + $voice['selected_language'] = VoiceLanguage::ZH; + } + $PluginShortplayDrama->voice = $voice; + } + $voice = $PluginShortplayDrama->voice; + $prosody_volume = $request->post('prosody_volume', null); + $prosody_speed = $request->post('prosody_speed', null); + if ($prosody_volume !== null) { + $PluginShortplayDrama->prosody_volume = $prosody_volume; + } + if ($prosody_speed !== null) { + $PluginShortplayDrama->prosody_speed = $prosody_speed; + } + $narration = $request->post('narration'); + if ($narration) { + $PluginShortplayDramaStoryboard->narration = $narration; + $PluginShortplayDramaStoryboard->save(); + } + $PluginShortplayDrama->save(); + $PluginModel = PluginModel::where(['id' => $voice['model_id'], 'scene' => ModelScene::STORYBOARD_NARRATION_VOICE['value'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $voice_id = $voice['voice_id']; + $voice_channel = $voice['voice_channel']; + $clone_voice_id = ''; + if ($voice_channel === 'self') { + $PluginModelVoice = PluginModelVoice::where(['id' => $voice['id']])->find(); + if (!$PluginModelVoice) { + return $this->fail('音色不存在'); + } + $voice_id = ''; + $clone_voice_id = $PluginModelVoice->voice_id; + } + if (!$clone_voice_id && !$voice_id) { + return $this->fail('未找到音色'); + } + $language = empty($voice['selected_language']['value']) ? VoiceLanguage::ZH['value'] : $voice['selected_language']['value']; + $emotion = empty($voice['selected_emotion']['value']) ? VoiceEmotion::NEUTRAL['value'] : $voice['selected_emotion']['value']; + $volume = $PluginShortplayDrama->prosody_volume; + $speed = $PluginShortplayDrama->prosody_speed; + $data = [ + 'assistant' => $PluginModel->assistant_id, + 'model' => $PluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/audio', + 'form_data' => [ + 'text' => $PluginShortplayDramaStoryboard->narration, + 'voice_id' => $voice_id, + 'clone_voice_id' => $clone_voice_id, + 'emotion' => $emotion, + 'volume' => $volume, + 'speed' => $speed, + 'language' => [$language], + ] + ]; + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $PluginModel->point, PointsBillScene::CONSUME['value'], null, '生成分镜旁白语音', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::AudioAssistantTTS($request->channels_uid, $data); + Db::startTrans(); + try { + $PluginModelTask = new PluginModelTask(); + $PluginModelTask->channels_uid = $request->channels_uid; + $PluginModelTask->uid = $request->uid; + $PluginModelTask->model_id = $PluginModel->id; + $PluginModelTask->model_type = ModelType::AUDIO['value']; + $PluginModelTask->alias_id = $PluginShortplayDramaStoryboard->id; + $PluginModelTask->scene = ModelScene::STORYBOARD_NARRATION_VOICE['value']; + $PluginModelTask->status = ModelTaskStatus::PROCESSING['value']; + $PluginModelTask->task_id = $result['task_id']; + $PluginModelTask->last_heartbeat = date('Y-m-d H:i:s'); + $PluginModelTask->consume_ids = $consume_ids; + $PluginModelTask->save(); + $PluginModelTaskResult = new PluginModelTaskResult(); + $PluginModelTaskResult->task_id = $PluginModelTask->id; + $PluginModelTaskResult->channels_uid = $request->channels_uid; + $PluginModelTaskResult->params = $data; + $PluginModelTaskResult->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + return $this->fail($th->getMessage()); + } + return $this->success(); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/IndexController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/IndexController.php new file mode 100644 index 0000000..5475f1e --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/IndexController.php @@ -0,0 +1,258 @@ +success('Hello World'); + } + public function submit(Request $request) + { + $post = $request->post(); + if (empty($post['script'])) { + return $this->fail('请选择你要创建短视频或短剧'); + } + if (empty($post['style'])) { + return $this->fail('请选择短剧风格画风'); + } + if (empty($post['aspect_ratio'])) { + return $this->fail('请选择短剧比例'); + } + if (empty($post['episode_sum'])) { + return $this->fail('请选择短剧集数'); + } + if (empty($post['episode_duration'])) { + return $this->fail('请选择短剧每集时长'); + } + $episode_sum = abs($post['episode_sum']); + $episode_duration = abs($post['episode_duration']); + if ($episode_sum <= 0 || $episode_sum > 500) { + switch ($post['script']) { + case 'script': + $episode_sum = 1; + $episode_duration = 300; + break; + case 'drama': + $episode_sum = 80; + break; + } + } + if ($episode_duration < 60 || $episode_duration > 300) { + $episode_duration = 60; + } + if (empty($post['prompt'])) { + return $this->fail('请提供一个创意想法'); + } + if (empty($post['model'])) { + return $this->fail('请选择模型'); + } + $PluginModel = PluginModel::where(['id' => $post['model'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $prompt = $post['prompt']; + $species = []; + $gender = []; + $age = []; + foreach (ActorSpeciesType::getOptions() as $value) { + $species[] = $value['label'] . ':' . $value['value'] . ''; + } + foreach (ActorGender::getOptions() as $value) { + $gender[] = $value['label'] . ':' . $value['value'] . ''; + } + foreach (ActorAge::getOptions() as $value) { + $age[] = $value['label'] . ':' . $value['value'] . ''; + } + $PluginShortplayDrama = new PluginShortplayDrama(); + $PluginShortplayDrama->channels_uid = $request->channels_uid; + $PluginShortplayDrama->uid = $request->uid; + $PluginShortplayDrama->model_id = $PluginModel->id; + $PluginShortplayDrama->style_id = $post['style']; + $PluginShortplayDrama->script = $post['script']; + $PluginShortplayDrama->aspect_ratio = $post['aspect_ratio']; + $PluginShortplayDrama->episode_sum = $episode_sum; + $PluginShortplayDrama->episode_duration = $episode_duration; + $PluginShortplayDrama->state = State::NO['value']; + $PluginShortplayDrama->save(); + $data = [ + 'channels_uid' => $request->channels_uid, + 'uid' => $request->uid, + 'model_id' => $PluginModel->id, + 'amount' => $PluginModel->point, + 'uuid' => $PluginShortplayDrama->id, + 'params' => [ + 'model' => $PluginModel->model_id, + 'assistant' => $PluginModel->assistant_id, + 'form_data' => [ + 'prompt' => $prompt, + 'species' => implode(',', $species), + 'gender' => implode(',', $gender), + 'age' => implode(',', $age), + 'episode_sum' => $episode_sum, + 'episode_duration' => $episode_duration, + ] + ], + ]; + Coroutine::create(function () use ($data) { + try { + Db::startTrans(); + try { + $ids = Account::decPoints($data['uid'], $data['channels_uid'], $data['amount'], PointsBillScene::CONSUME['value'], null, '创建短剧', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'generatecreatedrama' + ], [ + 'uuid' => $data['uuid'], + 'msg' => $th->getMessage() + ]); + PluginShortplayDrama::where(['id' => $data['uuid']])->delete(); + return; + } + $result = Yidevs::ChatAssistantCompletions($data['channels_uid'], $data['params']); + if (empty($result)) { + throw new \Exception('创建短剧失败'); + } + Db::startTrans(); + try { + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $data['uuid']])->find(); + $PluginShortplayDrama->state = State::YES['value']; + $PluginShortplayDrama->title = $result['title']; + $PluginShortplayDrama->description = $result['description']; + $PluginShortplayDrama->background_description = $result['background_description']; + $PluginShortplayDrama->outline = $result['outline']; + $PluginShortplayDrama->overall_hook = $result['overall_hook']; + $PluginShortplayDrama->core_catharsis_mechanism = $result['core_catharsis_mechanism']; + $PluginShortplayDrama->main_conflict = $result['main_conflict']; + $PluginShortplayDrama->relationship_mainline = $result['relationship_mainline']; + $PluginShortplayDrama->episode_num = 0; + $PluginShortplayDrama->save(); + if (!empty($result['actor'])) { + $PluginShortplayActors = PluginShortplayActor::whereIn('actor_id', $result['actor'])->field('id,actor_id')->select(); + foreach ($PluginShortplayActors as $PluginShortplayActor) { + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayDramaActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaActor->save(); + } + } + if (!empty($result['create_actor'])) { + foreach ($result['create_actor'] as $actor) { + $PluginShortplayActor = PluginShortplayActor::where(['name' => $actor['name'], 'channels_uid' => $data['channels_uid'], 'drama_id' => $PluginShortplayDrama->id])->field('id,actor_id')->find(); + if (!$PluginShortplayActor) { + $PluginShortplayActor = new PluginShortplayActor(); + $PluginShortplayActor->channels_uid = $data['channels_uid']; + $PluginShortplayActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayActor->uid = $data['uid']; + $PluginShortplayActor->actor_id = uniqid(); + $PluginShortplayActor->name = $actor['name']; + $PluginShortplayActor->species_type = $actor['species']; + $PluginShortplayActor->gender = $actor['gender']; + $PluginShortplayActor->age = $actor['age']; + $PluginShortplayActor->remarks = $actor['description']; + $PluginShortplayActor->status = ActorStatus::INITIALIZING['value']; + $PluginShortplayActor->save(); + } + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $data['channels_uid']; + $PluginShortplayDramaActor->drama_id = $PluginShortplayDrama->id; + $PluginShortplayDramaActor->actor_id = $PluginShortplayActor->id; + $PluginShortplayDramaActor->save(); + } + } + Db::commit(); + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'generatecreatedrama' + ], [ + 'uuid' => $data['uuid'], + 'drama_id' => $PluginShortplayDrama->id, + 'msg' => '创建短剧成功' + ]); + } catch (\Throwable $th) { + Db::rollback(); + Log::error('创建短剧失败:' . $th->getMessage() . PHP_EOL . $th->getTraceAsString()); + throw $th; + } + } catch (\Throwable $th) { + if (!empty($ids)) { + Db::startTrans(); + try { + Account::refund($data['uid'], $data['channels_uid'], $ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + PluginShortplayDrama::where(['id' => $data['uuid']])->delete(); + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'generatecreatedrama' + ], [ + 'uuid' => $data['uuid'], + 'msg' => '创建短剧失败' + ]); + } + }); + return $this->resData(['uuid' => $data['uuid']]); + /* if ($post['script'] == 'script') { + } else { + if (empty($post['title'])) { + return $this->fail('请输入短剧标题'); + } + if (empty($post['description'])) { + return $this->fail('请输入短剧描述'); + } + $PluginShortplayDrama = new PluginShortplayDrama(); + $PluginShortplayDrama->channels_uid = $request->channels_uid; + $PluginShortplayDrama->uid = $request->uid; + $PluginShortplayDrama->style_id = $post['style']; + $PluginShortplayDrama->script = $post['script']; + $PluginShortplayDrama->description = $post['description']; + $PluginShortplayDrama->title = $post['title']; + $PluginShortplayDrama->aspect_ratio = $post['aspect_ratio']; + $PluginShortplayDrama->episode_sum = $episode_sum; + $PluginShortplayDrama->episode_duration = $episode_duration; + $PluginShortplayDrama->episode_num = 0; + if (!empty($post['model'])) { + $PluginModel = PluginModel::where(['id' => $post['model'], 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + $PluginShortplayDrama->model_id = $PluginModel->id; + } + if (!empty($post['cover'])) { + $PluginShortplayDrama->cover = $post['cover']; + } + $PluginShortplayDrama->save(); + } + return $this->resData(['drama_id' => $PluginShortplayDrama->id]); */ + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/PropController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/PropController.php new file mode 100644 index 0000000..9d9d8fb --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/PropController.php @@ -0,0 +1,415 @@ +get('limit', 2000); + $type = $request->get('type'); + $where = []; + // $where[] = ['status', '=', PropStatus::GENERATED['value']]; + $where[] = ['uid', '=', $request->uid]; + switch ($type) { + case 'episode': + $episode_id = $request->get('episode_id'); + if (!$episode_id) { + return $this->fail('分集ID不能为空'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $PluginShortplayDramaEpisode->drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $where[] = ['episode_id', '=', $PluginShortplayDramaEpisode->id]; + break; + case 'drama': + $drama_id = $request->get('drama_id'); + if (!$drama_id) { + return $this->fail('短剧ID不能为空'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $where[] = ['drama_id', '=', $PluginShortplayDrama->id]; + break; + default: + break; + } + $name = $request->get('name'); + if ($name) { + $where[] = ['name|prop_id', 'like', '%' . $name . '%']; + } + $PluginShortplayProp = PluginShortplayProp::where($where)->order('id', 'desc')->limit($limit)->select()->each(function ($item) { + // $item->status_enum = PropStatus::get($item->status); + }); + return $this->resData($PluginShortplayProp); + } + public function initializing(Request $request) + { + $id = $request->post('id'); + + $PluginShortplayProp = PluginShortplayProp::where([ + 'id' => $id, + 'uid' => $request->uid + ])->find(); + if (!$PluginShortplayProp) { + return $this->fail('物品不存在'); + } + $description = $request->post('description'); + if ($description) { + $PluginShortplayProp->description = $description; + } + + $imageState = PluginModelTask::processing(['alias_id' => $PluginShortplayProp->id, 'scene' => ModelScene::PROP_IMAGE['value']]); + $threeViewImageState = PluginModelTask::processing(['alias_id' => $PluginShortplayProp->id, 'scene' => ModelScene::PROP_THREE_VIEW_IMAGE['value']]); + if ($imageState > 0 || $threeViewImageState > 0) { + return $this->fail('物品正在生成中'); + } + + // ===== 表单参数 ===== + $image_model_id = $request->post('image_model_id'); + $three_view_model_id = $request->post('three_view_model_id'); + $image_state = (bool)$request->post('image_state'); + $three_view_image_state = (bool)$request->post('three_view_image_state'); + $image = $request->post('image'); + $three_view_image = $request->post('three_view_image'); + $image_reference_state = (bool)$request->post('image_reference_state'); + $reference_image = $request->post('reference_image'); + if ($image_reference_state && empty($reference_image)) { + return $this->fail('请上传参考图片'); + } + + // ===== 是否需要 AI 生成 ===== + $needAiImage = ($PluginShortplayProp->image && $image_state) ? empty($image) : true; + $needSkipImage = $PluginShortplayProp->image && !$image_state; + $needAiThreeView = ($PluginShortplayProp->three_view_image && $three_view_image_state) ? empty($three_view_image) : true; + $needSkipThreeView = $PluginShortplayProp->three_view_image && !$three_view_image_state; + $isDoubleAi = ($needAiImage && !$needSkipImage) && ($needAiThreeView && !$needSkipThreeView); + + $ImagePluginModel = null; + $ThreeViewPluginModel = null; + + // ===================================================== + // 一、模型前置校验(任何 AI 调用前) + // ===================================================== + + // 双 AI:必须一次性校验两个模型 + if ($isDoubleAi) { + $ImagePluginModel = PluginModel::where([ + 'id' => $image_model_id, + 'scene' => ModelScene::PROP_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + $ThreeViewPluginModel = PluginModel::where([ + 'id' => $three_view_model_id, + 'scene' => ModelScene::PROP_THREE_VIEW_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + if (!$ImagePluginModel || !$ThreeViewPluginModel) { + return $this->fail('模型不存在'); + } + } else { + // 单独 image AI + if ($needAiImage && !$needSkipImage) { + $ImagePluginModel = PluginModel::where([ + 'id' => $image_model_id, + 'scene' => ModelScene::PROP_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + if (!$ImagePluginModel) { + return $this->fail('物品图片模型不存在'); + } + } + + // 单独 three view AI + if ($needAiThreeView && !$needSkipThreeView) { + $ThreeViewPluginModel = PluginModel::where([ + 'id' => $three_view_model_id, + 'scene' => ModelScene::PROP_THREE_VIEW_IMAGE['value'], + 'state' => State::YES['value'] + ])->find(); + + if (!$ThreeViewPluginModel) { + return $this->fail('物品三视图模型不存在'); + } + } + } + + // ===================================================== + // 二、物品图片 AI 生成 + // ===================================================== + + $lockImage = false; + $taskId = null; + if (!$needSkipImage) { + if ($needAiImage) { + $lockImage = true; + $data = [ + 'assistant' => $ImagePluginModel->assistant_id, + 'model' => $ImagePluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'prompt' => $PluginShortplayProp->description, + 'aspect_ratio' => '1:1', + ] + ]; + if ($image_reference_state) { + $data['form_data']['images'] = [$reference_image]; + } + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $ImagePluginModel->point, PointsBillScene::CONSUME['value'], null, '生成物品图片', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + + Db::startTrans(); + try { + $task = new PluginModelTask(); + $task->channels_uid = $request->channels_uid; + $task->uid = $request->uid; + $task->model_id = $ImagePluginModel->id; + $task->model_type = ModelType::DRAW['value']; + $task->alias_id = $PluginShortplayProp->id; + $task->scene = ModelScene::PROP_IMAGE['value']; + $task->status = ModelTaskStatus::PROCESSING['value']; + $task->task_id = $result['task_id']; + $task->last_heartbeat = date('Y-m-d H:i:s'); + $task->consume_ids = $consume_ids; + $task->save(); + $taskId = $task->id; + $taskResult = new PluginModelTaskResult(); + $taskResult->task_id = $task->id; + $taskResult->channels_uid = $request->channels_uid; + $taskResult->params = $data; + $taskResult->save(); + + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } catch (\Throwable $e) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + Log::error('GeneratePropImage Error:' . $e->getMessage()); + return $this->fail('物品图片生成失败'); + } + $PluginShortplayProp->image = null; + } + } + + // ===================================================== + // 三、物品三视图 AI 生成 + // ===================================================== + if (!$needSkipThreeView) { + if ($needAiThreeView) { + $data = [ + 'assistant' => $ThreeViewPluginModel->assistant_id, + 'model' => $ThreeViewPluginModel->model_id, + 'notify_url' => 'https://' . $request->host() . '/app/model/Notify/draw', + 'form_data' => [ + 'prompt' => $PluginShortplayProp->description, + 'aspect_ratio' => '1:1', + ] + ]; + + // 非 image AI 场景,必须依赖已有形象 + if (!$lockImage) { + if (!$PluginShortplayProp->image) { + return $this->fail('请先生成物品图片,再生成物品三视图'); + } + $data['form_data']['images'] = [$PluginShortplayProp->image]; + } + + Db::startTrans(); + try { + $consume_ids = Account::decPoints($request->uid, $request->channels_uid, $ThreeViewPluginModel->point, PointsBillScene::CONSUME['value'], null, '生成物品三视图', true); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + try { + if (!$lockImage) { + $result = Yidevs::DrawAssistantTIGI($request->channels_uid, $data); + } + + Db::startTrans(); + try { + $task = new PluginModelTask(); + $task->channels_uid = $request->channels_uid; + $task->uid = $request->uid; + $task->model_id = $ThreeViewPluginModel->id; + $task->model_type = ModelType::DRAW['value']; + $task->alias_id = $PluginShortplayProp->id; + $task->scene = ModelScene::PROP_THREE_VIEW_IMAGE['value']; + $task->pre_task_id = $taskId; + $task->status = $lockImage + ? ModelTaskStatus::WAIT['value'] + : ModelTaskStatus::PROCESSING['value']; + $task->task_id = $lockImage ? null : $result['task_id']; + $task->last_heartbeat = date('Y-m-d H:i:s'); + $task->consume_ids = $consume_ids; + $task->save(); + + $taskResult = new PluginModelTaskResult(); + $taskResult->task_id = $task->id; + $taskResult->channels_uid = $request->channels_uid; + $taskResult->params = $data; + $taskResult->save(); + + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } catch (\Throwable $e) { + if (!empty($consume_ids)) { + Db::startTrans(); + try { + Account::refund($request->uid, $request->channels_uid, $consume_ids); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + } + } + Log::error('GeneratePropThreeView Error:' . $e->getMessage()); + return $this->fail('物品三视图生成失败'); + } + $PluginShortplayProp->three_view_image = null; + } + } + $PluginShortplayProp->save(); + + $PluginShortplayProp = PluginShortplayProp::where(['id' => $PluginShortplayProp->id])->find(); + return $this->resData($PluginShortplayProp); + } + public function update(Request $request) + { + $msg = '更新成功'; + Db::startTrans(); + try { + $id = $request->post('id'); + if ($id) { + $PluginShortplayProp = PluginShortplayProp::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayProp) { + throw new \Exception('物品不存在'); + } + } else { + $PluginShortplayProp = new PluginShortplayProp(); + $PluginShortplayProp->uid = $request->uid; + $PluginShortplayProp->channels_uid = $request->channels_uid; + $PluginShortplayProp->status = PropStatus::INITIALIZING['value']; + $drama_id = $request->post('drama_id'); + if ($drama_id) { + $PluginShortplayProp->drama_id = $drama_id; + } + $episode_id = $request->post('episode_id'); + if ($episode_id) { + $PluginShortplayProp->episode_id = $episode_id; + } + $msg = '创建成功'; + $PluginShortplayProp->prop_id = uniqid(); + } + $PluginShortplayProp->name = $request->post('name'); + $PluginShortplayProp->image = $request->post('image'); + $PluginShortplayProp->description = $request->post('description'); + $PluginShortplayProp->three_view_image = $request->post('three_view_image'); + $PluginShortplayProp->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success($msg, $PluginShortplayProp); + } + public function delete(Request $request) + { + $id = $request->post('id'); + $PluginShortplayProp = PluginShortplayProp::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayProp) { + return $this->fail('物品不存在'); + } + Db::startTrans(); + try { + PluginShortplayDramaStoryboardProp::where(['prop_id' => $PluginShortplayProp->id])->delete(); + $PluginShortplayProp->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('删除成功'); + } + public function ReplaceProp(Request $request) + { + $id = $request->post('id'); + $task_id = $request->post('task_id'); + $PluginShortplayProp = PluginShortplayProp::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayProp) { + return $this->fail('物品不存在'); + } + $PluginModelTask = PluginModelTask::where(['id' => $task_id, 'alias_id' => $PluginShortplayProp->id, 'scene' => ModelScene::PROP_IMAGE['value'], 'status' => ModelTaskStatus::SUCCESS['value']])->find(); + if (!$PluginModelTask) { + return $this->fail('任务不存在'); + } + $PluginShortplayProp->image = $PluginModelTask->result->image_path; + $PluginModelTask = PluginModelTask::where(['pre_task_id' => $PluginModelTask->id, 'alias_id' => $PluginShortplayProp->id, 'scene' => ModelScene::PROP_THREE_VIEW_IMAGE['value'], 'status' => ModelTaskStatus::SUCCESS['value']])->find(); + if ($PluginModelTask) { + $PluginShortplayProp->three_view_image = $PluginModelTask->result->image_path; + $PluginShortplayProp->status = PropStatus::GENERATED['value']; + } else { + $PluginShortplayProp->status = PropStatus::INITIALIZING['value']; + $PluginShortplayProp->three_view_image = null; + } + $PluginShortplayProp->save(); + $PluginShortplayProp = PluginShortplayProp::where(['id' => $id])->find(); + return $this->resData($PluginShortplayProp); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/SceneController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/SceneController.php new file mode 100644 index 0000000..cfb1306 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/SceneController.php @@ -0,0 +1,280 @@ +get('drama_id'); + $episode_id = $request->get('episode_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaScenes = PluginShortplayDramaScene::where(['drama_id' => $PluginShortplayDrama->id, 'episode_id' => $episode_id])->order('sort asc')->select(); + return $this->resData($PluginShortplayDramaScenes); + } + public function updateSort(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $scenes = $request->post('scenes'); + if (empty($scenes)) { + $id = $request->post('id'); + $sort = $request->post('sort'); + $PluginShortplayDramaScene = PluginShortplayDramaScene::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $id])->find(); + if (!$PluginShortplayDramaScene) { + return $this->fail('场景不存在'); + } + $PluginShortplayDramaScene->sort = $sort; + $PluginShortplayDramaScene->save(); + } else { + Db::startTrans(); + try { + foreach ($scenes as $scene) { + $PluginShortplayDramaScene = PluginShortplayDramaScene::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $scene['id']])->find(); + if (!$PluginShortplayDramaScene) { + continue; + } + $PluginShortplayDramaScene->sort = $scene['sort']; + $PluginShortplayDramaScene->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + } + return $this->success('更新成功'); + } + public function copyScene(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $scene_id = $request->post('scene_id'); + + // 校验短剧 + $PluginShortplayDrama = PluginShortplayDrama::where([ + 'id' => $drama_id, + 'uid' => $request->uid + ])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + + // 校验原场景 + $origin = PluginShortplayDramaScene::where([ + 'drama_id' => $drama_id, + 'id' => $scene_id + ])->find(); + if (!$origin) { + return $this->fail('原场景不存在'); + } + + Db::startTrans(); + try { + // 3. 插入复制后的场景 + unset($origin['id']); // 删除原场景ID + $origin['sort'] = PluginShortplayDramaScene::where(['drama_id' => $drama_id, 'episode_id' => $episode_id])->max('sort') + 1 ?? 1; + $origin['create_time'] = time(); + $origin['update_time'] = time(); + $origin['episode_id'] = $episode_id; + $PluginShortplayDramaScene = new PluginShortplayDramaScene; + $PluginShortplayDramaScene->save($origin); + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + + return $this->resData($PluginShortplayDramaScene); + } + public function deleteScene(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $id = $request->post('id'); + + // 校验短剧是否存在 + $PluginShortplayDrama = PluginShortplayDrama::where([ + 'id' => $drama_id, + 'uid' => $request->uid + ])->find(); + + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + if ($id) { + // 校验场景是否存在 + $PluginShortplayDramaScene = PluginShortplayDramaScene::where([ + 'id' => $id, + 'drama_id' => $drama_id, + 'episode_id' => $episode_id + ])->find(); + + if (!$PluginShortplayDramaScene) { + return $this->fail('场景不存在'); + } + } + + Db::startTrans(); + try { + if ($id) { + // 1. 删除该场景 + PluginShortplayDramaScene::where([ + 'id' => $id, + 'drama_id' => $drama_id + ])->delete(); + PluginShortplayDramaStoryboard::where([ + 'drama_id' => $drama_id, + 'scene_id' => $PluginShortplayDramaScene->id + ])->update(['scene_id' => null]); + + + // 2. 重新拉取该 episode 的所有场景并重排 sort + $PluginShortplayDramaScenes = PluginShortplayDramaScene::where([ + 'drama_id' => $drama_id, + 'episode_id' => $episode_id + ]) + ->order('sort asc') + ->select() + ->toArray(); + + $sort = 1; + foreach ($PluginShortplayDramaScenes as $sb) { + PluginShortplayDramaScene::where(['id' => $sb['id']]) + ->update(['sort' => $sort++]); + } + } else { + $PluginShortplayDramaScenes = PluginShortplayDramaScene::where(['drama_id' => $drama_id, 'episode_id' => $episode_id])->order('sort asc')->select(); + foreach ($PluginShortplayDramaScenes as $scene) { + PluginShortplayDramaStoryboard::where(['drama_id' => $drama_id, 'scene_id' => $scene->id])->update(['scene_id' => null]); + $scene->delete(); + } + } + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + + return $this->success('删除成功'); + } + public function insertAfter(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $after_id = $request->post('after_id'); + + $drama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$drama) { + return $this->fail('短剧不存在'); + } + + $afterSort = 0; + if ($after_id) { + $after = PluginShortplayDramaScene::where([ + 'drama_id' => $drama->id, + 'id' => $after_id, + 'episode_id' => $episode_id + ])->find(); + + if (!$after) { + return $this->fail('指定的场景不存在'); + } + $afterSort = $after->sort; + } + + Db::startTrans(); + try { + // 从 after.sort + 1 开始,所有后续 +1 + PluginShortplayDramaScene::where([ + 'drama_id' => $drama_id, + 'episode_id' => $episode_id + ]) + ->where('sort', '>', $afterSort) + ->inc('sort', 1) + ->update(); + + // 插入新的空白场景 + $new = new PluginShortplayDramaScene(); + $new->channels_uid = $request->channels_uid; + $new->drama_id = $drama->id; + $new->episode_id = $episode_id; + $new->sort = $afterSort + 1; + $new->save(); + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + + return $this->success('插入成功', ['id' => $new->id]); + } + public function update(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->post('episode_id'); + $id = $request->post('id'); + $D = $request->post(); + if ($id) { + $PluginShortplayDramaScene = PluginShortplayDramaScene::where(['id' => $id, 'drama_id' => $drama_id, 'episode_id' => $episode_id])->find(); + if (!$PluginShortplayDramaScene) { + return $this->fail('场景不存在'); + } + } else { + $PluginShortplayDramaScene = new PluginShortplayDramaScene(); + $PluginShortplayDramaScene->channels_uid = $request->channels_uid; + $PluginShortplayDramaScene->uid = $request->uid; + $PluginShortplayDramaScene->drama_id = $drama_id; + $PluginShortplayDramaScene->episode_id = $episode_id; + $PluginShortplayDramaScene->sort = PluginShortplayDramaScene::where(['drama_id' => $drama_id, 'episode_id' => $episode_id])->max('sort') + 1 ?? 1; + } + if (!empty($D['title'])) { + $PluginShortplayDramaScene->title = $D['title']; + } + if (!empty($D['scene_space'])) { + $PluginShortplayDramaScene->scene_space = $D['scene_space']; + } + if (!empty($D['scene_location'])) { + $PluginShortplayDramaScene->scene_location = $D['scene_location']; + } + if (!empty($D['scene_time'])) { + $PluginShortplayDramaScene->scene_time = $D['scene_time']; + } + if (!empty($D['scene_weather'])) { + $PluginShortplayDramaScene->scene_weather = $D['scene_weather']; + } + if (!empty($D['description'])) { + $PluginShortplayDramaScene->description = $D['description']; + } + if (!empty($D['atmosphere'])) { + $PluginShortplayDramaScene->atmosphere = $D['atmosphere']; + } + if (!empty($D['image'])) { + $PluginShortplayDramaScene->image = $D['image']; + } + $PluginShortplayDramaScene->save(); + return $this->resData($PluginShortplayDramaScene); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/SquareController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/SquareController.php new file mode 100644 index 0000000..48e3daa --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/SquareController.php @@ -0,0 +1,108 @@ +get('limit', 10); + $uid = $request->uid; + $PluginShortplayShare = PluginShortplayShare::with(['drama' => function ($query) { + $query->field('id,channels_uid,title,cover'); + }, 'user' => function ($query) { + $query->field('id,channels_uid,nickname,headimg'); + }])->order('update_time desc,id desc')->paginate($limit)->each(function ($item) use ($uid) { + $item->is_likes = false; + if ($uid) { + $item->is_likes = PluginShortplayShareLikes::where(['share_id' => $item->id, 'uid' => $uid])->count() > 0; + } + $item->episode = PluginShortplayShareEpisode::where(['share_id' => $item->id])->find(); + $item->episode_num = PluginShortplayShareEpisode::where(['share_id' => $item->id])->count(); + }); + if ($PluginShortplayShare->isEmpty()) { + return $this->fail('暂无数据'); + } + return $this->resData($PluginShortplayShare); + } + public function details(Request $request) + { + $drama_id = $request->get('drama_id'); + $episode_id = $request->get('episode_id'); + $PluginShortplayShare = PluginShortplayShare::where(['drama_id' => $drama_id])->find(); + if (!$PluginShortplayShare) { + return $this->fail('分享不存在'); + } + $PluginShortplayShareEpisode = PluginShortplayShareEpisode::where(['share_id' => $PluginShortplayShare->id, 'episode_id' => $episode_id])->find(); + if (!$PluginShortplayShareEpisode) { + return $this->fail('分享分集不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $PluginShortplayShareEpisode->episode_id, 'drama_id' => $PluginShortplayShare->drama_id])->with(['drama' => function ($query) { + $query->field('id,channels_uid,title,cover,description'); + }])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDramaEpisode->user = PluginUser::where(['id' => $PluginShortplayShare->uid])->field('channels_uid,headimg,nickname')->find(); + $PluginShortplayDramaEpisode->share = $PluginShortplayShare; + $PluginShortplayDramaEpisode->is_likes = false; + if ($request->uid) { + $PluginShortplayDramaEpisode->is_likes = PluginShortplayShareLikes::where(['share_id' => $PluginShortplayShare->id, 'uid' => $request->uid])->count() > 0; + } + return $this->resData($PluginShortplayDramaEpisode); + } + public function episodes(Request $request) + { + $drama_id = $request->get('drama_id'); + $PluginShortplayShare = PluginShortplayShare::where(['drama_id' => $drama_id])->find(); + if (!$PluginShortplayShare) { + return $this->fail('分享不存在'); + } + $PluginShortplayShareEpisode = PluginShortplayShareEpisode::alias('a')->where(['a.share_id' => $PluginShortplayShare->id]) + ->join('PluginShortplayDramaEpisode e', 'e.id=a.episode_id') + ->field('a.*,e.episode_no') + ->order('e.episode_no asc') + ->select(); + return $this->resData($PluginShortplayShareEpisode); + } + public function likes(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayShare = PluginShortplayShare::where(['drama_id' => $drama_id])->find(); + if (!$PluginShortplayShare) { + return $this->fail('分享不存在'); + } + Db::startTrans(); + try { + $PluginShortplayShareLikes = PluginShortplayShareLikes::where(['share_id' => $PluginShortplayShare->id, 'uid' => $request->uid])->find(); + if ($PluginShortplayShareLikes) { + $PluginShortplayShare->likes = Db::raw('likes-1'); + $PluginShortplayShare->save(); + $PluginShortplayShareLikes->delete(); + } else { + $PluginShortplayShare->likes = Db::raw('likes+1'); + $PluginShortplayShare->save(); + $PluginShortplayShareLikes = new PluginShortplayShareLikes(); + $PluginShortplayShareLikes->share_id = $PluginShortplayShare->id; + $PluginShortplayShareLikes->uid = $request->uid; + $PluginShortplayShareLikes->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $PluginShortplayShare = PluginShortplayShare::where(['id' => $PluginShortplayShare->id])->find(); + return $this->resData($PluginShortplayShare); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardController.php new file mode 100644 index 0000000..9d4d39f --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardController.php @@ -0,0 +1,780 @@ +get('type'); + $where = []; + switch ($type) { + case 'episode': + $episode_id = $request->get('episode_id'); + if (!$episode_id) { + return $this->fail('分集ID不能为空'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $PluginShortplayDramaEpisode->drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $where[] = ['episode_id', '=', $PluginShortplayDramaEpisode->id]; + break; + case 'drama': + break; + default: + $drama_id = $request->get('drama_id'); + if (!$drama_id) { + return $this->fail('短剧ID不能为空'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $where[] = ['drama_id', '=', $PluginShortplayDrama->id]; + break; + } + $description = $request->get('description'); + if ($description) { + $where[] = ['description', 'like', '%' . $description . '%']; + } + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where($where)->with(['sceneFind', 'props', 'dialogues'])->order('sort asc,id asc')->select()->each(function ($item) {}); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function updateSort(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboards = $request->post('storyboards'); + if (empty($storyboards)) { + $id = $request->post('id'); + $sort = $request->post('sort'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $PluginShortplayDramaStoryboard->sort = $sort; + $PluginShortplayDramaStoryboard->save(); + } else { + Db::startTrans(); + try { + foreach ($storyboards as $storyboard) { + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $storyboard['id']])->find(); + if (!$PluginShortplayDramaStoryboard) { + continue; + } + $PluginShortplayDramaStoryboard->sort = $storyboard['sort']; + $PluginShortplayDramaStoryboard->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + } + return $this->success('更新成功'); + } + public function copyStoryboard(Request $request) + { + $drama_id = $request->post('drama_id'); + $copy_id = $request->post('copy_id'); // 要复制的 storyboard ID + $new_sort = $request->post('new_sort'); + + // 校验短剧 + $PluginShortplayDrama = PluginShortplayDrama::where([ + 'id' => $drama_id, + 'uid' => $request->uid + ])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + + // 校验原分镜 + $origin = PluginShortplayDramaStoryboard::where([ + 'drama_id' => $drama_id, + 'id' => $copy_id + ])->find(); + if (!$origin) { + return $this->fail('原分镜不存在'); + } + + // 校验 new_sort + if (!$new_sort || !is_numeric($new_sort)) { + return $this->fail('new_sort 不合法'); + } + + Db::startTrans(); + try { + // 1. 查询当前 scene 内所有分镜 + $sceneStoryboards = PluginShortplayDramaStoryboard::where([ + 'drama_id' => $drama_id, + 'episode_id' => $origin->episode_id, + ]) + ->order('sort asc') + ->select() + ->toArray(); + + // 2. 将 sort >= new_sort 的分镜全部 +1 + foreach ($sceneStoryboards as $sb) { + if ($sb['sort'] >= $new_sort) { + PluginShortplayDramaStoryboard::where(['id' => $sb['id']]) + ->update(['sort' => $sb['sort'] + 1]); + } + } + + // 3. 插入复制后的分镜 + unset($origin['id']); // 删除原分镜ID + $origin['sort'] = $new_sort; + $origin['create_time'] = time(); + $origin['update_time'] = time(); + $PluginShortplayDramaStoryboard = new PluginShortplayDramaStoryboard; + $PluginShortplayDramaStoryboard->save($origin); + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $PluginShortplayDramaStoryboard->id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function deleteStoryboard(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $id = $request->post('id'); + + // 校验短剧是否存在 + $PluginShortplayDrama = PluginShortplayDrama::where([ + 'id' => $drama_id, + 'uid' => $request->uid + ])->find(); + + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where([ + 'id' => $episode_id, + 'drama_id' => $drama_id + ])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + if ($id) { + // 校验分镜是否存在 + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where([ + 'id' => $id, + 'drama_id' => $drama_id, + ])->find(); + + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + } + + Db::startTrans(); + try { + if ($id) { + PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + PluginShortplayDramaStoryboardDialogue::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + $PluginShortplayDramaStoryboardProps = PluginShortplayDramaStoryboardProp::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->select(); + foreach ($PluginShortplayDramaStoryboardProps as $PluginShortplayDramaStoryboardProp) { + PluginShortplayProp::where(['id' => $PluginShortplayDramaStoryboardProp->prop_id, 'status' => PropStatus::INITIALIZING['value']])->delete(); + $PluginShortplayDramaStoryboardProp->delete(); + } + $PluginShortplayDramaStoryboard->delete(); + $PluginShortplayDramaStoryboards = PluginShortplayDramaStoryboard::where([ + 'drama_id' => $drama_id, + 'episode_id' => $episode_id, + ]) + ->order('sort asc') + ->select(); + + $sort = 1; + foreach ($PluginShortplayDramaStoryboards as $sb) { + $sb->sort = $sort++; + $sb->save(); + } + } else { + $PluginShortplayDramaStoryboards = PluginShortplayDramaStoryboard::where([ + 'drama_id' => $drama_id, + 'episode_id' => $episode_id, + ])->select(); + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + PluginShortplayDramaStoryboardDialogue::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + PluginShortplayDramaStoryboardProp::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + $PluginShortplayDramaStoryboard->delete(); + } + PluginShortplayProp::where(['episode_id' => $PluginShortplayDramaEpisode->id, 'status' => PropStatus::INITIALIZING['value']])->delete(); + } + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + + return $this->success('删除成功'); + } + public function insertAfter(Request $request) + { + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $after_id = $request->post('after_id'); + + $drama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$drama) { + return $this->fail('短剧不存在'); + } + $afterSort = 0; + if ($after_id) { + $after = PluginShortplayDramaStoryboard::where([ + 'drama_id' => $drama->id, + 'id' => $after_id, + ])->find(); + + if (!$after) { + return $this->fail('指定的分镜不存在'); + } + $afterSort = $after->sort; + } + + Db::startTrans(); + try { + // 从 after.sort + 1 开始,所有后续 +1 + PluginShortplayDramaStoryboard::where([ + 'drama_id' => $drama_id, + 'episode_id' => $episode_id, + ]) + ->where('sort', '>', $afterSort) + ->inc('sort', 1) + ->update(); + + // 插入新的空白分镜 + $new = new PluginShortplayDramaStoryboard(); + $new->channels_uid = $request->channels_uid; + $new->drama_id = $drama->id; + $new->episode_id = $episode_id; + $new->sort = $afterSort + 1; + $new->duration = 5000; + $new->save(); + + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $new->id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function update(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->post('episode_id'); + $id = $request->post('id'); + $D = $request->post(); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $id, 'drama_id' => $drama_id, 'episode_id' => $episode_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + if (!empty($D['scene_id'])) { + $PluginShortplayDramaStoryboard->scene_id = $D['scene_id']; + } + if (!empty($D['description'])) { + $PluginShortplayDramaStoryboard->description = $D['description']; + } + if (!empty($D['shot_type'])) { + $PluginShortplayDramaStoryboard->shot_type = $D['shot_type']; + } + if (!empty($D['shot_angle'])) { + $PluginShortplayDramaStoryboard->shot_angle = $D['shot_angle']; + } + if (!empty($D['shot_motion'])) { + $PluginShortplayDramaStoryboard->shot_motion = $D['shot_motion']; + } + if (!empty($D['image'])) { + $PluginShortplayDramaStoryboard->image = $D['image']; + $PluginShortplayDramaStoryboard->use_material_type = 'image'; + } + if (!empty($D['video'])) { + $PluginShortplayDramaStoryboard->video = $D['video']; + $PluginShortplayDramaStoryboard->use_material_type = 'video'; + } + if (isset($D['narration'])) { + $PluginShortplayDramaStoryboard->narration = $D['narration']; + } + if (isset($D['sfx'])) { + $PluginShortplayDramaStoryboard->sfx = $D['sfx']; + } + $PluginShortplayDramaStoryboard->save(); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $PluginShortplayDramaStoryboard->id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function joinActor(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $actor_id = $request->post('actor_id'); + $PluginShortplayActor = PluginShortplayActor::where(['id' => $actor_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayActor) { + $PluginShortplayActor = PluginShortplayActor::where(['id' => $actor_id, 'channels_uid' => $request->channels_uid])->whereNull('uid')->find(); + } + if (!$PluginShortplayActor) { + return $this->fail('演员不存在'); + } + Db::startTrans(); + try { + if (!PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $storyboard_id, 'actor_id' => $actor_id])->count()) { + $PluginShortplayDramaStoryboardActor = new PluginShortplayDramaStoryboardActor(); + $PluginShortplayDramaStoryboardActor->channels_uid = $request->channels_uid; + $PluginShortplayDramaStoryboardActor->drama_id = $PluginShortplayDramaStoryboard->drama_id; + $PluginShortplayDramaStoryboardActor->episode_id = $PluginShortplayDramaStoryboard->episode_id; + $PluginShortplayDramaStoryboardActor->storyboard_id = $storyboard_id; + $PluginShortplayDramaStoryboardActor->actor_id = $actor_id; + $PluginShortplayDramaStoryboardActor->save(); + } + if (!PluginShortplayDramaEpisodeActor::where(['episode_id' => $PluginShortplayDramaStoryboard->episode_id, 'actor_id' => $actor_id])->count()) { + $PluginShortplayDramaEpisodeActor = new PluginShortplayDramaEpisodeActor(); + $PluginShortplayDramaEpisodeActor->channels_uid = $request->channels_uid; + $PluginShortplayDramaEpisodeActor->drama_id = $PluginShortplayDramaStoryboard->drama_id; + $PluginShortplayDramaEpisodeActor->episode_id = $PluginShortplayDramaStoryboard->episode_id; + $PluginShortplayDramaEpisodeActor->actor_id = $actor_id; + $PluginShortplayDramaEpisodeActor->save(); + } + if (!PluginShortplayDramaActor::where(['drama_id' => $drama_id, 'actor_id' => $actor_id])->count()) { + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $request->channels_uid; + $PluginShortplayDramaActor->drama_id = $drama_id; + $PluginShortplayDramaActor->actor_id = $actor_id; + $PluginShortplayDramaActor->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function deleteActor(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $id = $request->post('id'); + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + Db::startTrans(); + try { + PluginShortplayDramaStoryboardActor::where(['id' => $id, 'storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function joinProp(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $prop_id = $request->post('prop_id'); + $PluginShortplayProp = PluginShortplayProp::where(['id' => $prop_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayProp) { + return $this->fail('物品不存在'); + } + Db::startTrans(); + try { + if (!PluginShortplayDramaStoryboardProp::where(['storyboard_id' => $storyboard_id, 'prop_id' => $PluginShortplayProp->id])->count()) { + $PluginShortplayDramaStoryboardProp = new PluginShortplayDramaStoryboardProp(); + $PluginShortplayDramaStoryboardProp->channels_uid = $request->channels_uid; + $PluginShortplayDramaStoryboardProp->storyboard_id = $storyboard_id; + $PluginShortplayDramaStoryboardProp->prop_id = $PluginShortplayProp->id; + $PluginShortplayDramaStoryboardProp->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function deleteProp(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $id = $request->post('id'); + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + Db::startTrans(); + try { + PluginShortplayDramaStoryboardProp::where(['id' => $id, 'storyboard_id' => $PluginShortplayDramaStoryboard->id])->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function ReplaceStoryboard(Request $request) + { + $id = $request->post('id'); + $task_id = $request->post('task_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $PluginShortplayDramaStoryboard->drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginModelTask = PluginModelTask::where(['id' => $task_id, 'alias_id' => $PluginShortplayDramaStoryboard->id, 'status' => ModelTaskStatus::SUCCESS['value']])->with(['result'])->find(); + if (!$PluginModelTask) { + return $this->fail('任务不存在'); + } + if ($PluginModelTask->scene == ModelScene::STORYBOARD_IMAGE['value']) { + $PluginShortplayDramaStoryboard->image = $PluginModelTask->result->image_path; + $PluginShortplayDramaStoryboard->use_material_type = 'image'; + } + if ($PluginModelTask->scene == ModelScene::STORYBOARD_VIDEO['value']) { + $PluginShortplayDramaStoryboard->video = $PluginModelTask->result->video_path; + $PluginShortplayDramaStoryboard->use_material_type = 'video'; + } + $PluginShortplayDramaStoryboard->save(); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $id])->with(['actors', 'sceneFind', 'props', 'dialogues'])->find(); + return $this->resData($PluginShortplayDramaStoryboard); + } + public function CharacterLook(Request $request) + { + $actor_id = $request->post('actor_id'); + $drama_id = $request->post('drama_id'); + $episode_id = $request->post('episode_id'); + $storyboard_id = $request->post('storyboard_id'); + $character_look_id = $request->post('character_look_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id, 'episode_id' => $episode_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $PluginShortplayDramaStoryboardActor = PluginShortplayDramaStoryboardActor::where(['storyboard_id' => $PluginShortplayDramaStoryboard->id, 'actor_id' => $actor_id])->find(); + if (!$PluginShortplayDramaStoryboardActor) { + return $this->fail('演员不存在'); + } + $PluginShortplayActorCharacterLook = PluginShortplayActorCharacterLook::where(['id' => $character_look_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayActorCharacterLook) { + return $this->fail('演员妆容不存在'); + } + $PluginShortplayDramaStoryboardActor->character_look_id = $PluginShortplayActorCharacterLook->id; + $PluginShortplayDramaStoryboardActor->headimg = $PluginShortplayActorCharacterLook->headimg; + $PluginShortplayDramaStoryboardActor->three_view_image = $PluginShortplayActorCharacterLook->three_view_image; + $PluginShortplayDramaStoryboardActor->save(); + return $this->success('设置成功'); + } + public function downloadPackage(Request $request) + { + $drama_id = $request->get('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->get('episode_id'); + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + # 初始化打包 + $uuid = uniqid(); + $data = [ + 'uuid' => $uuid, + 'uid' => $PluginShortplayDrama->uid, + 'channels_uid' => $PluginShortplayDrama->channels_uid, + 'drama_id' => $PluginShortplayDrama->id, + 'episode_id' => $PluginShortplayDramaEpisode->id, + 'url' => $request->header('x-forwarded-proto') . '://' . $request->host() . '/app/shortplay/download/storyboard?uuid=' . $uuid, + ]; + Coroutine::create(function () use ($data) { + try { + sleep(2); + $PluginShortplayDramaStoryboards = PluginShortplayDramaStoryboard::where(['drama_id' => $data['drama_id'], 'episode_id' => $data['episode_id']])->with(['dialogues'])->order('sort', 'asc')->select(); + $storyboardSum = count($PluginShortplayDramaStoryboards); + $zip = new \ZipArchive(); + $filename = $data['drama_id'] . '_' . $data['episode_id'] . '.zip'; + $filepath = runtime_path('temp/storyboard/'); + if (!is_dir($filepath)) { + mkdir($filepath, 0777, true); + } + if (!$zip->open($filepath . $filename, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { + throw new \Exception('打包失败'); + } + $localFiles = []; + # 打包图片素材 + $index = 0; + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + if (!empty($PluginShortplayDramaStoryboard->image)) { + $localfile = Uploads::downloadTemp($PluginShortplayDramaStoryboard->image); + if (!file_exists($localfile)) { + throw new \Exception('图片素材下载失败'); + } + $localFiles[] = $localfile; + $ext = pathinfo($localfile, PATHINFO_EXTENSION); + $zip->addFile($localfile, $PluginShortplayDramaStoryboard->sort . '/image.' . $ext); + $progress = 5 + round(($index / $storyboardSum) * 10); + $index++; + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 1, 'progress' => $progress]); + } + } + # 打包视频素材 + $index = 0; + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + if (!empty($PluginShortplayDramaStoryboard->video)) { + $localfile = Uploads::downloadTemp($PluginShortplayDramaStoryboard->video); + if (!file_exists($localfile)) { + throw new \Exception('视频素材下载失败'); + } + $ext = pathinfo($localfile, PATHINFO_EXTENSION); + $zip->addFile($localfile, $PluginShortplayDramaStoryboard->sort . '/video.' . $ext); + $localFiles[] = $localfile; + $progress = 15 + round(($index / $storyboardSum) * 40); + $index++; + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 2, 'progress' => $progress]); + } + } + # 打包对话素材 + $srtContent = []; + + /** + * 毫秒 → SRT 时间格式 + */ + function msToSrtTime(int $ms): string + { + $hours = floor($ms / 3600000); + $minutes = floor(($ms % 3600000) / 60000); + $seconds = floor(($ms % 60000) / 1000); + $milliseconds = $ms % 1000; + + return sprintf( + '%02d:%02d:%02d,%03d', + $hours, + $minutes, + $seconds, + $milliseconds + ); + } + + $index = 1; // SRT 序号必须从 1 开始 + $currentTimeline = 0; // 全局时间轴(毫秒) + + foreach ($PluginShortplayDramaStoryboards as $storyboard) { + if (empty($storyboard->dialogues)) { + continue; + } + $dialogueText = ''; + $maxEnd = 0; + foreach ($storyboard->dialogues as $dialogue) { + + // 当前字幕在全局时间轴中的开始 / 结束时间 + $startMs = $currentTimeline + $dialogue->start_time; + $endMs = $currentTimeline + $dialogue->end_time; + + $temp = + $index . "\n" . + msToSrtTime($startMs) . " --> " . msToSrtTime($endMs) . "\n" . + trim($dialogue->content) . "\n"; + $dialogueText .= $temp . "\n"; + $srtContent[] = $temp; + $index++; + $maxEnd = max($maxEnd, $endMs); + } + + $currentTimeline += $maxEnd; + if (!empty($dialogueText)) { + $zip->addFromString($storyboard->sort . '/对话.txt', $dialogueText); + } + } + + // 最终 SRT 文本 + $srtText = implode("\n", $srtContent); + $zip->addFromString('字幕.srt', $srtText); + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 3, 'progress' => 55]); + # 打包音频素材 + $index = 0; + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + if (empty($PluginShortplayDramaStoryboard->dialogues)) { + continue; + } + $n = 0; + foreach ($PluginShortplayDramaStoryboard->dialogues as $PluginShortplayDramaStoryboardDialogue) { + if (!empty($PluginShortplayDramaStoryboardDialogue->audio)) { + $localfile = Uploads::downloadTemp($PluginShortplayDramaStoryboardDialogue->audio); + if (!file_exists($localfile)) { + throw new \Exception('音频素材下载失败'); + } + $ext = pathinfo($localfile, PATHINFO_EXTENSION); + $zip->addFile($localfile, $PluginShortplayDramaStoryboard->sort . "/{$n}-{$PluginShortplayDramaStoryboardDialogue->actor->name}." . $ext); + $localFiles[] = $localfile; + } + $n++; + } + $progress = 55 + round(($index / $storyboardSum) * 10); + $index++; + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 4, 'progress' => $progress]); + } + # 打包旁白素材 + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + if (!empty($PluginShortplayDramaStoryboard->narration)) { + $zip->addFromString($PluginShortplayDramaStoryboard->sort . '/旁白.txt', $PluginShortplayDramaStoryboard->narration); + } + } + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 5, 'progress' => 75]); + # 打包旁白音频素材 + $index = 0; + foreach ($PluginShortplayDramaStoryboards as $PluginShortplayDramaStoryboard) { + if (!empty($PluginShortplayDramaStoryboard->narration_audio)) { + $localfile = Uploads::downloadTemp($PluginShortplayDramaStoryboard->narration_audio); + if (!file_exists($localfile)) { + throw new \Exception('旁白音频素材下载失败'); + } + $ext = pathinfo($localfile, PATHINFO_EXTENSION); + $zip->addFile($localfile, $PluginShortplayDramaStoryboard->sort . '/旁白.' . $ext); + $localFiles[] = $localfile; + } + $progress = 75 + round(($index / $storyboardSum) * 10); + $index++; + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 6, 'progress' => $progress]); + } + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 7, 'progress' => 95]); + $zip->close(); + $cacheData = [ + 'uuid' => $data['uuid'], + 'filename' => $filename, + 'filepath' => $filepath, + ]; + Cache::set('downloadPackage_' . $data['uuid'], $cacheData, 60 * 60 * 24); + foreach ($localFiles as $localFile) { + if (file_exists($localFile)) { + unlink($localFile); + } + } + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'action' => 'process', 'index' => 7, 'progress' => 100, 'url' => $data['url'], 'filename' => $filename]); + } catch (\Throwable | ClientException | RequestException $th) { + Log::error("下载打包异常:" . $th->getMessage(), $th->getTrace()); + Push::send([ + 'uid' => $data['uid'], + 'channels_uid' => $data['channels_uid'], + 'event' => 'downloadPackage', + ], ['uuid' => $data['uuid'], 'msg' => $th->getMessage()]); + } + }); + return $this->resData(['uuid' => $uuid]); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardDialogueController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardDialogueController.php new file mode 100644 index 0000000..5c48dd0 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/StoryboardDialogueController.php @@ -0,0 +1,186 @@ +get('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $episode_id = $request->get('episode_id'); + if ($episode_id) { + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['id' => $episode_id, 'drama_id' => $PluginShortplayDrama->id])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + } + $where = []; + $storyboard_id = $request->get('storyboard_id'); + if ($storyboard_id) { + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $where[] = ['storyboard_id', '=', $PluginShortplayDramaStoryboard->id]; + } else { + $ids = PluginShortplayDramaStoryboard::where(['episode_id' => $PluginShortplayDramaEpisode->id, 'drama_id' => $drama_id])->column('id'); + if (empty($ids)) { + return $this->resData([]); + } + $where[] = ['storyboard_id', 'in', $ids]; + } + $PluginShortplayDramaStoryboardDialogue = PluginShortplayDramaStoryboardDialogue::where($where)->with(['actor', 'storyboard'])->order('start_time asc,id asc')->select()->each(function ($item) { + $item->voice_level = 'dialogue'; + if (!$item->voice) { + $ActorVoiceLevel = [ + [ + 'level' => 'storyboard', + 'model' => PluginShortplayDramaStoryboardActor::class, + 'where' => [ + 'storyboard_id' => $item->storyboard_id, + 'actor_id' => $item->actor_id + ] + ], + [ + 'level' => 'episode', + 'model' => PluginShortplayDramaEpisodeActor::class, + 'where' => [ + 'episode_id' => $item->storyboard->episode_id, + 'actor_id' => $item->actor_id + ] + ], + [ + 'level' => 'drama', + 'model' => PluginShortplayDramaActor::class, + 'where' => [ + 'drama_id' => $item->storyboard->drama_id, + 'actor_id' => $item->actor_id + ] + ], + [ + 'level' => 'actor', + 'model' => PluginShortplayActor::class, + 'where' => [ + 'actor_id' => $item->actor_id + ] + ] + ]; + foreach ($ActorVoiceLevel as $ActorVoiceLevelItem) { + $PluginActorVoice = $ActorVoiceLevelItem['model']::where($ActorVoiceLevelItem['where'])->find(); + if ($PluginActorVoice && $PluginActorVoice->voice) { + $item->voice = $PluginActorVoice->voice; + $item->voice_level = $ActorVoiceLevelItem['level']; + break; + } + } + } + }); + return $this->resData($PluginShortplayDramaStoryboardDialogue); + } + public function delete(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $dialogue_id = $request->post('dialogue_id'); + $PluginShortplayDramaStoryboardDialogue = PluginShortplayDramaStoryboardDialogue::where(['id' => $dialogue_id, 'storyboard_id' => $PluginShortplayDramaStoryboard->id])->find(); + if (!$PluginShortplayDramaStoryboardDialogue) { + return $this->success('已删除'); + } + if ($PluginShortplayDramaStoryboardDialogue->delete()) { + return $this->success('删除成功'); + } else { + return $this->fail('删除失败'); + } + } + public function save(Request $request) + { + $drama_id = $request->post('drama_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $storyboard_id = $request->post('storyboard_id'); + $PluginShortplayDramaStoryboard = PluginShortplayDramaStoryboard::where(['id' => $storyboard_id, 'drama_id' => $drama_id])->find(); + if (!$PluginShortplayDramaStoryboard) { + return $this->fail('分镜不存在'); + } + $actor_id = $request->post('actor_id'); + $PluginShortplayActor = PluginShortplayActor::where(['id' => $actor_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayActor) { + $PluginShortplayActor = PluginShortplayActor::where(['id' => $actor_id, 'channels_uid' => $request->channels_uid])->whereNull('uid')->find(); + } + if (!$PluginShortplayActor) { + return $this->fail('演员不存在'); + } + $id = $request->post('id'); + if ($id) { + $PluginShortplayDramaStoryboardDialogue = PluginShortplayDramaStoryboardDialogue::where(['id' => $id, 'storyboard_id' => $PluginShortplayDramaStoryboard->id])->find(); + if (!$PluginShortplayDramaStoryboardDialogue) { + return $this->fail('对话不存在'); + } + } else { + $PluginShortplayDramaStoryboardDialogue = new PluginShortplayDramaStoryboardDialogue(); + $PluginShortplayDramaStoryboardDialogue->channels_uid = $request->channels_uid; + $PluginShortplayDramaStoryboardDialogue->storyboard_id = $PluginShortplayDramaStoryboard->id; + $PluginShortplayDramaStoryboardDialogue->actor_id = $PluginShortplayActor->id; + } + Db::startTrans(); + try { + $D = $request->post(); + $PluginShortplayDramaStoryboardDialogue->prosody_speed = $D['prosody_speed']; + $PluginShortplayDramaStoryboardDialogue->prosody_volume = $D['prosody_volume']; + $PluginShortplayDramaStoryboardDialogue->emotion = $D['emotion']; + $PluginShortplayDramaStoryboardDialogue->start_time = $D['start_time']; + $PluginShortplayDramaStoryboardDialogue->end_time = $D['end_time']; + $PluginShortplayDramaStoryboardDialogue->content = $D['content']; + $PluginShortplayDramaStoryboardDialogue->save(); + if (!PluginShortplayDramaEpisodeActor::where(['episode_id' => $PluginShortplayDramaStoryboard->episode_id, 'actor_id' => $actor_id])->count()) { + $PluginShortplayDramaEpisodeActor = new PluginShortplayDramaEpisodeActor(); + $PluginShortplayDramaEpisodeActor->channels_uid = $request->channels_uid; + $PluginShortplayDramaEpisodeActor->drama_id = $PluginShortplayDramaStoryboard->drama_id; + $PluginShortplayDramaEpisodeActor->episode_id = $PluginShortplayDramaStoryboard->episode_id; + $PluginShortplayDramaEpisodeActor->actor_id = $actor_id; + $PluginShortplayDramaEpisodeActor->save(); + } + if (!PluginShortplayDramaActor::where(['drama_id' => $PluginShortplayDrama->id, 'actor_id' => $actor_id])->count()) { + $PluginShortplayDramaActor = new PluginShortplayDramaActor(); + $PluginShortplayDramaActor->channels_uid = $request->channels_uid; + $PluginShortplayDramaActor->drama_id = $drama_id; + $PluginShortplayDramaActor->actor_id = $actor_id; + $PluginShortplayDramaActor->save(); + } + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + $PluginShortplayDramaStoryboardDialogue = PluginShortplayDramaStoryboardDialogue::where(['id' => $PluginShortplayDramaStoryboardDialogue->id])->with(['actor'])->find(); + return $this->resData($PluginShortplayDramaStoryboardDialogue); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/StyleController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/StyleController.php new file mode 100644 index 0000000..a91c3dd --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/StyleController.php @@ -0,0 +1,28 @@ +get('name'); + if ($name) { + $where[] = ['name', 'like', '%' . $name . '%']; + } + $classify = $request->get('classify','all'); + if ($classify != 'all') { + $where[] = ['classify', '=', $classify]; + } + $PluginShortplayStyle = PluginShortplayStyle::where($where)->order('id', 'desc')->select(); + return $this->resData($PluginShortplayStyle); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/UploadsController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/UploadsController.php new file mode 100644 index 0000000..b2055ad --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/UploadsController.php @@ -0,0 +1,17 @@ +uid = $request->uid; + $this->channels_uid = $request->channels_uid; + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/VoiceController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/VoiceController.php new file mode 100644 index 0000000..b1ab52e --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/VoiceController.php @@ -0,0 +1,49 @@ +get('model_id'); + $scene = $request->get('scene'); + $action = $request->get('action', 'yidevs'); + if ($action == 'yidevs') { + $PluginModel = PluginModel::where(['id' => $model_id, 'scene' => $scene, 'state' => State::YES['value']])->find(); + if (!$PluginModel) { + return $this->fail('模型不存在'); + } + try { + $result = Yidevs::AudioVoiceList($PluginModel->channels_uid, ['model' => $PluginModel->model_id]); + return $this->resData($result); + } catch (\Throwable $th) { + return $this->exception($th); + } + } else { + $model_id = $request->get('model_id'); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $where[] = ['uid', '=', $request->uid]; + if ($model_id) { + $where[] = ['model_id', '=', $model_id]; + } + $where[] = ['status', '=', ModelVoiceStatus::SUCCESS['value']]; + $voice = PluginModelVoice::where($where)->order('id desc')->select()->each(function ($item) { + $item->gender_enum = ActorGender::get($item->gender); + $item->age_enum = ActorAge::get($item->age); + }); + return $this->resData($voice); + } + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/api/controller/WorksController.php b/fastmovie-admin/plugin/shortplay/app/api/controller/WorksController.php new file mode 100644 index 0000000..fd68e45 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/api/controller/WorksController.php @@ -0,0 +1,118 @@ +get('limit', 10); + $where = []; + $where[] = ['uid', '=', $request->uid]; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', '%' . $name . '%']; + } + $script = $request->get('script', 'all'); + if ($script != 'all') { + $where[] = ['script', '=', $script]; + } + $PluginShortplayDrama = PluginShortplayDrama::where($where)->order('state asc ,id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($PluginShortplayDrama); + } + public function updateCover(Request $request) + { + $id = $request->post('id'); + $cover = $request->post('cover'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDrama->cover = $cover; + $PluginShortplayDrama->save(); + return $this->success('更新成功'); + } + public function details(Request $request) + { + $id = $request->get('id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $id, 'uid' => $request->uid])->with(['episodes', 'style'])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + if ($PluginShortplayDrama->cover_state > 0) { + $PluginShortplayDrama->cover = null; + } + return $this->resData($PluginShortplayDrama); + } + public function episode(Request $request) + { + $drama_id = $request->get('drama_id'); + $episode_id = $request->get('episode_id'); + $PluginShortplayDrama = PluginShortplayDrama::where(['id' => $drama_id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayDrama) { + return $this->fail('短剧不存在'); + } + $PluginShortplayDramaEpisode = PluginShortplayDramaEpisode::where(['drama_id' => $PluginShortplayDrama->id, 'id' => $episode_id])->with(['scenes' => function ($query) { + $query->order('sort', 'asc'); + }, 'storyboards' => function ($query) { + $query->order('sort', 'asc'); + }])->find(); + if (!$PluginShortplayDramaEpisode) { + return $this->fail('分集不存在'); + } + return $this->resData($PluginShortplayDramaEpisode); + } + + public function share(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['psd.uid', '=', $request->uid]; + $name = $request->get('name'); + if ($name) { + $where[] = ['psd.name', 'like', '%' . $name . '%']; + } + $script = $request->get('script', 'all'); + if ($script != 'all') { + $where[] = ['psd.script', '=', $script]; + } + $PluginShortplayDrama = PluginShortplayDrama::alias('psd') + ->join('plugin_shortplay_share pss', 'psd.id = pss.drama_id') + ->where($where) + ->order('pss.update_time desc,pss.id desc') + ->field('psd.*,pss.likes,pss.id as share_id') + ->paginate($limit) + ->each(function ($item) {}); + return $this->resData($PluginShortplayDrama); + } + + public function deleteShare(Request $request) + { + $id = $request->post('id'); + $PluginShortplayShare = PluginShortplayShare::where(['id' => $id, 'uid' => $request->uid])->find(); + if (!$PluginShortplayShare) { + return $this->fail('分享不存在'); + } + Db::startTrans(); + try { + PluginShortplayShareEpisode::where(['share_id' => $PluginShortplayShare->id])->delete(); + PluginShortplayShareLikes::where(['share_id' => $PluginShortplayShare->id])->delete(); + $PluginShortplayShare->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->fail($th->getMessage()); + } + return $this->success('删除成功'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/control/controller/ActorController.php b/fastmovie-admin/plugin/shortplay/app/control/controller/ActorController.php new file mode 100644 index 0000000..7d96329 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/control/controller/ActorController.php @@ -0,0 +1,441 @@ +model = new PluginShortplayActor; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '180px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/shortplay/control/Actor/update', + 'props' => [ + 'title' => '编辑《ID:{id}》演员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addTableAction('初始化', [ + 'model' => Action::COMFIRM['value'], + 'path' => '/app/shortplay/control/Actor/submitInit', + 'props' => [ + 'title' => '确定要初始化《{name}》演员吗?' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建演员', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/shortplay/control/Actor/create', + 'props' => [ + 'title' => '创建演员' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('name', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('actor_id', '演员ID', 'input', '', [ + 'props' => [ + 'placeholder' => '演员ID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('drama_id','公共演员','select','',[ + 'options' => [ + ['label' => '是', 'value' => 1], + ['label' => '否', 'value' => 0] + ], + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('actorinfo', '演员', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'name', + 'avatar' => 'headimg', + 'info' => 'actor_id', + 'preview' => true + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('three_view_image', '三视图', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width: 100px; height: 100px;', + 'fit' => 'cover' + ] + ], + 'props' => [ + 'width' => '133px' + ] + ]); + $builder->add('species_type', '物种', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorSpeciesType::getOptions(), + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('gender', '性别', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorGender::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('age', '年龄', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorAge::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('status', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => ActorStatus::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('remarks', '人物描述', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function query(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['name|actor_id|remarks', 'like', "%{$query}%"]; + return $this->resData(PluginShortplayActor::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $actor_id = $request->get('actor_id'); + if ($actor_id) { + $where[] = ['actor_id', 'like', "%{$actor_id}%"]; + } + $drama_id = $request->get('drama_id'); + if ($drama_id==1) { + $where[] = ['uid', '=', null]; + } else { + $where[] = ['uid', '>', 0]; + } + $list = PluginShortplayActor::where($where)->with(['user' => function ($query) { + $query->field('id,nickname,headimg,mobile,channels_uid'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $PluginShortplayActor = new PluginShortplayActor; + $PluginShortplayActor->channels_uid = $request->channels_uid; + $PluginShortplayActor->name = $D['name']; + $PluginShortplayActor->headimg = $D['headimg']; + $PluginShortplayActor->species_type = $D['species_type']; + $PluginShortplayActor->gender = $D['gender']; + $PluginShortplayActor->age = $D['age']; + $PluginShortplayActor->status = $D['status']; + $PluginShortplayActor->three_view_image = $D['three_view_image']; + $PluginShortplayActor->remarks = $D['remarks']; + $PluginShortplayActor->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $PluginShortplayActor = PluginShortplayActor::where(['id' => $D['id'], 'channels_uid' => $request->channels_uid])->find(); + $PluginShortplayActor->channels_uid = $request->channels_uid; + $PluginShortplayActor->name = $D['name']; + $PluginShortplayActor->headimg = $D['headimg']; + $PluginShortplayActor->species_type = $D['species_type']; + $PluginShortplayActor->gender = $D['gender']; + $PluginShortplayActor->age = $D['age']; + $PluginShortplayActor->status = $D['status']; + $PluginShortplayActor->three_view_image = $D['three_view_image']; + $PluginShortplayActor->remarks = $D['remarks']; + $PluginShortplayActor->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('更新成功'); + } + $id = $request->get('id'); + $Actor = PluginShortplayActor::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + $builder = $this->getFormBuilder(); + $builder->setData($Actor->toArray()); + return $this->resData($builder); + } + private function getFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $Component = new ComponentBuilder; + $builder->add('headimg', '形象', 'bundle', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 8 + ], + 'prompt' => [ + $Component->add('text', ['default' => '支持jpg、png、jpeg格式,大小不超过3M'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'accept' => 'image/jpeg,image/png,image/jpg', + 'multiple' => 1, + 'size' => 3, + 'customStyle' => '--el-upload-list-picture-card-height:200px;--el-upload-list-picture-card-width:160px;' + ] + ]); + $builder->add('three_view_image', '三视图', 'bundle', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 8 + ], + 'prompt' => [ + $Component->add('text', ['default' => '支持jpg、png、jpeg格式,大小不超过3M,推荐1:1比例'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'accept' => 'image/jpeg,image/png,image/jpg', + 'multiple' => 1, + 'size' => 3, + 'customStyle' => '--el-upload-list-picture-card-height:200px;--el-upload-list-picture-card-width:200px;' + ] + ]); + $builder->add('name', '名称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true, + ]); + $builder->add('species_type', '物种', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 8 + ], + 'options' => ActorSpeciesType::getOptions(), + 'required' => true, + ]); + $builder->add('gender', '性别', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 8 + ], + 'options' => ActorGender::getOptions(), + 'required' => true, + ]); + $builder->add('age', '年龄', 'select', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 8 + ], + 'options' => ActorAge::getOptions(), + 'required' => true, + ]); + $builder->add('status', '状态', 'radio', ActorStatus::INITIALIZING['value'], [ + 'options' => ActorStatus::getOptions(), + 'subProps' => [ + 'border' => true + ], + 'required' => true, + ]); + $builder->add('remarks', '人物描述', 'input', '', [ + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ] + ] + ]); + return $builder; + } + public function generateHeadimg(Request $request) + { + $prompt = $request->post('prompt'); + if (empty($prompt)) { + return $this->fail('AI提示词不能为空'); + } + $actionBuilder = new ActionBuilder(); + $headimg = 'https://' . $request->host() . '/uploads/default/20251122/5b8dcf4cbec3ba58057884929194349c_69217f42205c5.jfif'; + $actionBuilder->setData([ + 'headimg' => $headimg + ]); + $actionBuilder->setDataAction('append'); + return $this->success('生成成功', $actionBuilder); + } + public function generateThreeViewImage(Request $request) + { + $prompt = $request->post('prompt'); + if (empty($prompt)) { + return $this->fail('AI提示词不能为空'); + } + $actionBuilder = new ActionBuilder(); + $headimg = 'https://' . $request->host() . '/uploads/default/20251122/5b8dcf4cbec3ba58057884929194349c_69217f42205c5.jfif'; + $actionBuilder->setData([ + 'three_view_image' => $headimg + ]); + $actionBuilder->setDataAction('append'); + return $this->success('生成成功', $actionBuilder); + } + public function submitInit(Request $request) + { + $id = $request->post('id'); + $Actor = PluginShortplayActor::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + if (!$Actor) { + return $this->fail('演员不存在'); + } + $Actor->status = ActorStatus::PENDING['value']; + $Actor->actor_id = uniqid(); + $Actor->three_view_image = $Actor->headimg; + $Actor->save(); + return $this->success('初始化成功'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/control/controller/DramaController.php b/fastmovie-admin/plugin/shortplay/app/control/controller/DramaController.php new file mode 100644 index 0000000..c206701 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/control/controller/DramaController.php @@ -0,0 +1,165 @@ +addAction('操作', [ + // 'width' => '180px', + // 'fixed' => 'right' + // ]); + // $builder->addTableAction('编辑', [ + // 'model' => Action::DIALOG['value'], + // 'path' => '/app/shortplay/control/Actor/update', + // 'props' => [ + // 'title' => '编辑《ID:{id}》演员' + // ], + // 'component' => [ + // 'name' => 'button', + // 'props' => [ + // 'type' => 'primary', + // 'size' => 'small' + // ] + // ] + // ]); + $builder->addHeader(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('title', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('title', '剧本名称', [ + 'props' => [ + 'minWidth' => '200px' + ] + ]); + $builder->add('description', '剧本简介', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('cover', '封面', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width: 100px; height: 100px;', + 'fit' => 'cover' + ] + ], + 'props' => [ + 'width' => '133px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/shortplay/control/Drama/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $list = PluginShortplayDrama::where($where) + ->order('id desc')->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/control/controller/DramaEpisodeController.php b/fastmovie-admin/plugin/shortplay/app/control/controller/DramaEpisodeController.php new file mode 100644 index 0000000..106a57b --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/control/controller/DramaEpisodeController.php @@ -0,0 +1,167 @@ +select(); + $builder = new TableBuilder( ); + $builder->addHeader(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('drama_id', '剧本', 'select', '', [ + 'options' => $list, + 'props' => [ + 'placeholder' => '剧本搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('title', '分集名称', 'input', '', [ + 'props' => [ + 'placeholder' => '分集名称搜索', + 'clearable' => true + ] + ]); + + $formBuilder->add('uid', '用户ID', 'input', '', [ + 'props' => [ + 'placeholder' => '用户ID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('user', '用户', [ + 'where' => [ + ['uid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.mobile', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('drama.title', '剧本名称', [ + 'props' => [ + 'minWidth' => '200px', + ] + ]); + $builder->add('title', '分集名称', [ + 'props' => [ + 'minWidth' => '200px', + ] + ]); + $builder->add('content', '分集内容', [ + 'props' => [ + 'minWidth' => '300px', + 'show-overflow-tooltip'=>true + ] + ]); + $builder->add('cover', '封面', [ + 'component' => [ + 'name' => 'image', + 'props' => [ + 'style' => 'width: 100px; height: 100px;', + 'fit' => 'cover' + ] + ], + 'props' => [ + 'width' => '133px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/shortplay/control/Drama/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $title = $request->get('title'); + if ($title) { + $where[] = ['title', 'like', "%{$title}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $drama_id = $request->get('drama_id'); + if ($drama_id) { + $where[] = ['drama_id', '=', $drama_id]; + } + $list = PluginShortplayDramaEpisode::where($where) + ->with(['drama']) + ->order('id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/control/controller/StyleController.php b/fastmovie-admin/plugin/shortplay/app/control/controller/StyleController.php new file mode 100644 index 0000000..a26002d --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/control/controller/StyleController.php @@ -0,0 +1,319 @@ +model = new PluginShortplayStyle; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '180px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/shortplay/control/Style/update', + 'props' => [ + 'title' => '编辑《ID:{id}》风格' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建风格', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/shortplay/control/Style/create', + 'props' => [ + 'title' => '创建风格' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('name', '名称', 'input', '', [ + 'props' => [ + 'placeholder' => '名称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('classify', '风格分类', 'select', '', [ + 'options' => StyleClassify::getOptions(), + 'required' => true, + 'props' => [ + 'placeholder' => '风格分类搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('classify', '风格分类', [ + 'component' => [ + 'name' => 'tag', + 'options' => StyleClassify::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('styleinfo', '风格', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'name', + 'avatar' => 'image', + 'preview' => true + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/shortplay/control/Style/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('prompts', '提示词', [ + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function query(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['name|prompts', 'like', "%{$query}%"]; + return $this->resData(PluginShortplayStyle::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $name = $request->get('name'); + if ($name) { + $where[] = ['name', 'like', "%{$name}%"]; + } + $classify = $request->get('classify'); + if ($classify) { + $where[] = ['classify', '=', $classify]; + } + $list = PluginShortplayStyle::where($where) + ->order('id desc')->paginate($limit)->each(function ($item) {}); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $PluginShortplayStyle = new PluginShortplayStyle; + $PluginShortplayStyle->channels_uid = $request->channels_uid; + $PluginShortplayStyle->name = $D['name']; + $PluginShortplayStyle->classify = $D['classify']; + $PluginShortplayStyle->image = $D['image']; + $PluginShortplayStyle->state = $D['state']; + $PluginShortplayStyle->prompts = $D['prompts']; + $PluginShortplayStyle->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $PluginShortplayStyle = PluginShortplayStyle::where(['id' => $D['id'], 'channels_uid' => $request->channels_uid])->find(); + $PluginShortplayStyle->name = $D['name']; + $PluginShortplayStyle->classify = $D['classify']; + $PluginShortplayStyle->image = $D['image']; + $PluginShortplayStyle->state = $D['state']; + $PluginShortplayStyle->prompts = $D['prompts']; + $PluginShortplayStyle->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('更新成功'); + } + $id = $request->get('id'); + $Style = PluginShortplayStyle::where(['id' => $id, 'channels_uid' => $request->channels_uid])->find(); + $builder = $this->getFormBuilder(); + $builder->setData($Style->toArray()); + return $this->resData($builder); + } + private function getFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'translations' => true, + 'size' => 'large', + ]); + $Component = new ComponentBuilder; + $builder->add('classify', '风格分类', 'radio', StyleClassify::ANIME['value'], [ + 'options' => StyleClassify::getOptions(), + 'required' => true, + 'subProps' => [ + 'border' => true + ], + ]); + $builder->add('image', '风格图', 'bundle', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 8, + 'xl' => 8 + ], + 'prompt' => [ + $Component->add('text', ['default' => '支持jpg、png、jpeg格式,大小不超过2M'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'required' => true, + 'props' => [ + 'accept' => 'image/jpeg,image/png,image/jpg', + 'multiple' => 1, + 'size' => 2, + 'customStyle' => '--el-upload-list-picture-card-height:200px;--el-upload-list-picture-card-width:160px;' + ] + ]); + $actionBuilder = new ActionBuilder('', [ + 'class' => 'mt-4' + ]); + $actionBuilder->add('AI 生成风格图', [ + 'model' => Action::REQUEST['value'], + 'path' => '/app/shortplay/control/Style/generateImage', + 'field' => '*', + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success', + 'size' => 'small', + 'bg' => true, + 'text' => true, + 'icon' => 'Refresh', + ] + ] + ]); + $builder->add('prompt', 'AI 提示词', 'input', '', [ + 'col' => [ + 'xs' => 24, + 'sm' => 24, + 'md' => 12, + 'lg' => 16, + 'xl' => 16 + ], + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ] + ] + ], $actionBuilder); + $builder->add('name', '名称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true, + ]); + $builder->add('state', '状态', 'radio', State::YES['value'], [ + 'options' => State::getOptions(), + 'subProps' => [ + 'border' => true + ], + 'required' => true, + ]); + $builder->add('prompts', '提示词', 'input', '', [ + 'required' => true, + 'props' => [ + 'type' => 'textarea', + 'autosize' => [ + 'minRows' => 4, + 'maxRows' => 20 + ] + ] + ]); + return $builder; + } + public function generateImage(Request $request) + { + $prompt = $request->post('prompt'); + if (empty($prompt)) { + return $this->fail('AI提示词不能为空'); + } + $actionBuilder = new ActionBuilder(); + $image = 'https://' . $request->host() . '/uploads/default/20251122/5b8dcf4cbec3ba58057884929194349c_69217f42205c5.jfif'; + $actionBuilder->setData([ + 'image' => $image + ]); + $actionBuilder->setDataAction('append'); + return $this->success('生成成功', $actionBuilder); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/controller/DownloadController.php b/fastmovie-admin/plugin/shortplay/app/controller/DownloadController.php new file mode 100644 index 0000000..2c0c0af --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/controller/DownloadController.php @@ -0,0 +1,20 @@ +get('uuid'); + $cacheData = Cache::get('downloadPackage_' . $uuid); + if (!$cacheData) { + return not_found(); + } + return response()->download($cacheData['filepath'] . $cacheData['filename'], $cacheData['filename']); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/functions.php b/fastmovie-admin/plugin/shortplay/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/functions.php @@ -0,0 +1,6 @@ + [ + // 设置JSON字段的类型 + 'voice' => 'json', + ] + ]; + } + public function getThreeViewImageAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setThreeViewImageAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getHeadimgAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setHeadimgAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getOriginHeadimgAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $data['origin_headimg']); + } + public function getOriginThreeViewImageAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $data['origin_three_view_image']); + } + public function user() + { + return $this->hasOne(PluginUser::class, 'id', 'uid'); + } + + public function channels() + { + return $this->hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public static function onAfterRead($model) + { + $model->status_enum = ActorStatus::get($model->status); + if ($model->status != ActorStatus::GENERATED['value']) { + $imageState = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::ACTOR_IMAGE['value']]); + $threeViewImageState = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::ACTOR_THREE_VIEW_IMAGE['value']]); + if ($imageState > 0 || $threeViewImageState > 0) { + $model->status_enum = ActorStatus::PENDING; + } + } + } + + /** + * 统一查询构造器 + * @param array $params 包含 drama_id, episode_id, storyboard_id 的数组 + * @return PluginShortplayActor + */ + public static function actorQuery($params = []) + { + $query = (new PluginShortplayActor)->alias('actor'); + // 处理参数,统一为 0 或具体 ID + $sid = !empty($params['storyboard_id']) ? (int)$params['storyboard_id'] : 0; + $eid = !empty($params['episode_id']) ? (int)$params['episode_id'] : 0; + $did = !empty($params['drama_id']) ? (int)$params['drama_id'] : 0; + + $fields = ['actor.*,actor.headimg as origin_headimg,actor.three_view_image as origin_three_view_image']; + $activeLevels = []; + + /** + * 1. 动态关联所有可能的层级 (全部使用 LEFT JOIN 保证回退链路通畅) + */ + if ($sid) { + $query->leftJoin('php_plugin_shortplay_drama_storyboard_actor s_actor', "s_actor.actor_id = actor.id AND s_actor.storyboard_id = $sid"); + $activeLevels['s'] = 's_actor'; + $fields[] = 's_actor.id as storyboard_actor_id'; + } + if ($eid) { + $query->leftJoin('php_plugin_shortplay_drama_episode_actor e_actor', "e_actor.actor_id = actor.id AND e_actor.episode_id = $eid"); + $activeLevels['e'] = 'e_actor'; + $fields[] = 'e_actor.id as episode_actor_id'; + } + if ($did) { + $query->leftJoin('php_plugin_shortplay_drama_actor d_actor', "d_actor.actor_id = actor.id AND d_actor.drama_id = $did"); + $activeLevels['d'] = 'd_actor'; + $fields[] = 'd_actor.id as drama_actor_id'; + } + + /** + * 2. 强制过滤:确保角色必须存在于你传入的最深层 ID 中 + * 对应你说的:“必须在这一集中出现的角色才显示” + */ + if ($sid) { + $query->whereNotNull('s_actor.id'); // 必须在分镜表有记录 + $fields[] = 's_actor.character_look_id'; + } elseif ($eid) { + $query->whereNotNull('e_actor.id'); // 必须在分集表有记录 + $fields[] = 'e_actor.character_look_id'; + } elseif ($did) { + $query->whereNotNull('d_actor.id'); // 必须在短剧表有记录 + $fields[] = 'd_actor.character_look_id'; + } + if ($sid || $eid || $did) { + /** + * 3. 字段组独立判定 (Image Group & Voice Group) + */ + $groupConfigs = [ + 'img' => ['check' => 'headimg', 'fields' => ['headimg', 'three_view_image']], + 'voice' => ['check' => 'voice', 'fields' => ['voice']], + ]; + + foreach ($groupConfigs as $config) { + $c = $config['check']; + + // 动态构建层级判定逻辑 (按优先级顺序遍历 activeLevels) + $winnerSql = "CASE "; + foreach ($activeLevels as $key => $alias) { + $winnerSql .= "WHEN $alias.$c IS NOT NULL AND $alias.$c != '' THEN '$key' "; + } + $winnerSql .= "ELSE 'b' END"; + + // 应用到该组所有字段 + foreach ($config['fields'] as $f) { + $caseSql = "CASE ($winnerSql) "; + foreach ($activeLevels as $key => $alias) { + $caseSql .= "WHEN '$key' THEN $alias.$f "; + } + $caseSql .= "ELSE actor.$f END AS $f"; + $fields[] = \think\facade\Db::raw($caseSql); + } + } + } + $query->field($fields); + return $query; + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayActorCharacterLook.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayActorCharacterLook.php new file mode 100644 index 0000000..8949049 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayActorCharacterLook.php @@ -0,0 +1,40 @@ +hasOne(PluginShortplayCharacterLook::class, 'id', 'character_look_id'); + } + public static function processing($where = []) + { + return self::where($where)->whereIn('status', [ActorStatus::INITIALIZING['value'], ActorStatus::PENDING['value']])->count(); + } + public static function onAfterRead($model) + { + $model->status_enum = ActorStatus::get($model->status); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayCharacterLook.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayCharacterLook.php new file mode 100644 index 0000000..1a0a8a4 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayCharacterLook.php @@ -0,0 +1,31 @@ +status_enum = ActorStatus::get($model->status); + if ($model->status != ActorStatus::GENERATED['value']) { + $imageState = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::CHARACTER_LOOK_COSTUME['value']]); + if ($imageState > 0) { + $model->status_enum = ActorStatus::PENDING; + } + } + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDrama.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDrama.php new file mode 100644 index 0000000..e59623a --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDrama.php @@ -0,0 +1,47 @@ + [ + // 设置JSON字段的类型 + 'voice' => 'json', + ] + ]; + } + public function style() + { + return $this->hasOne(PluginShortplayStyle::class, 'id', 'style_id'); + } + public function episodes() + { + return $this->hasMany(PluginShortplayDramaEpisode::class, 'drama_id', 'id'); + } + public function getCoverAttr($value,$data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setCoverAttr($value,$data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function channels() + { + return $this->hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public static function onAfterRead($model) + { + $model->cover_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::DRAMA_COVER['value']]); + $model->continue_episode_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::CREATIVE_EPISODE['value']]); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaActor.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaActor.php new file mode 100644 index 0000000..d618cab --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaActor.php @@ -0,0 +1,39 @@ + [ + // 设置JSON字段的类型 + 'voice' => 'json', + ] + ]; + } + public function actor() + { + return $this->hasOne(PluginShortplayActor::class, 'id', 'actor_id'); + } + public function getThreeViewImageAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setThreeViewImageAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getHeadimgAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setHeadimgAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisode.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisode.php new file mode 100644 index 0000000..2d77b5e --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisode.php @@ -0,0 +1,54 @@ +hasMany(PluginShortplayDramaScene::class, 'episode_id', 'id'); + } + public function storyboards() + { + return $this->hasMany(PluginShortplayDramaStoryboard::class, 'episode_id', 'id')->with(['actors', 'sceneFind', 'props', 'dialogues']); + } + public function actors() + { + return $this->hasMany(PluginShortplayDramaEpisodeActor::class, 'episode_id', 'id')->with('actor'); + } + public function getCoverAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setCoverAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getVideoPathAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setVideoPathAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function channels() + { + return $this->hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public function drama() + { + return $this->hasOne(PluginShortplayDrama::class, 'id', 'drama_id'); + } + public static function onAfterRead($model) + { + $model->init_scene_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::CREATIVE_SCENES['value']]); + $model->init_storyboard_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::CREATIVE_STORYBOARDS['value']]); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisodeActor.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisodeActor.php new file mode 100644 index 0000000..41989db --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaEpisodeActor.php @@ -0,0 +1,39 @@ + [ + // 设置JSON字段的类型 + 'voice' => 'json', + ] + ]; + } + public function actor() + { + return $this->hasOne(PluginShortplayActor::class, 'id', 'actor_id'); + } + public function getThreeViewImageAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setThreeViewImageAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getHeadimgAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setHeadimgAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaScene.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaScene.php new file mode 100644 index 0000000..61e5623 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaScene.php @@ -0,0 +1,24 @@ +image_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::SCENE_IMAGE['value']]); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboard.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboard.php new file mode 100644 index 0000000..f20fa0e --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboard.php @@ -0,0 +1,58 @@ +hasMany(PluginShortplayDramaStoryboardActor::class, 'storyboard_id', 'id')->with('actor'); + } + public function sceneFind() + { + return $this->hasOne(PluginShortplayDramaScene::class, 'id', 'scene_id'); + } + public function props() + { + return $this->hasMany(PluginShortplayDramaStoryboardProp::class, 'storyboard_id', 'id')->with('prop'); + } + public function dialogues() + { + return $this->hasMany(PluginShortplayDramaStoryboardDialogue::class, 'storyboard_id', 'id')->with('actor'); + } + public function getImageAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setImageAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getVideoAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setVideoAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getNarrationAudioAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setNarrationAudioAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public static function onAfterRead($model) + { + $model->image_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::STORYBOARD_IMAGE['value']]); + $model->video_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::STORYBOARD_VIDEO['value']]); + $model->narration_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::STORYBOARD_NARRATION_VOICE['value']]); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardActor.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardActor.php new file mode 100644 index 0000000..ba59d27 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardActor.php @@ -0,0 +1,53 @@ + [ + // 设置JSON字段的类型 + 'voice' => 'json', + ] + ]; + } + public function actor() + { + return $this->hasOne(PluginShortplayActor::class, 'id', 'actor_id'); + } + public function getThreeViewImageAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setThreeViewImageAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function getHeadimgAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setHeadimgAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public static function onAfterRead($model) + { + + $model->character_look_state = 0; + if ($model->character_look_id) { + $params['drama_id'] = $model->drama_id; + $params['episode_id'] = $model->episode_id; + $params['storyboard_id'] = $model->storyboard_id; + $params['actor_id'] = $model->actor_id; + $model->character_look_state = PluginShortplayActorCharacterLook::processing($params); + } + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardDialogue.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardDialogue.php new file mode 100644 index 0000000..b817b26 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardDialogue.php @@ -0,0 +1,43 @@ + [ + // 设置JSON字段的类型 + 'prosody_speed' => 'float', + 'prosody_volume' => 'float', + 'voice' => 'json', + ] + ]; + } + public function getAudioAttr($value, $data) + { + return Uploads::url($data['channels_uid'], $value); + } + public function setAudioAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function actor() + { + return $this->hasOne(PluginShortplayActor::class, 'id', 'actor_id'); + } + public function storyboard() + { + return $this->hasOne(PluginShortplayDramaStoryboard::class, 'id', 'storyboard_id'); + } + public static function onAfterRead($model) + { + $model->voice_state = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::DIALOGUE_VOICE['value']]); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardProp.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardProp.php new file mode 100644 index 0000000..05b23db --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayDramaStoryboardProp.php @@ -0,0 +1,13 @@ +hasOne(PluginShortplayProp::class, 'id', 'prop_id'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayProp.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayProp.php new file mode 100644 index 0000000..4e0aaae --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayProp.php @@ -0,0 +1,40 @@ +status_enum =PropStatus::get($model->status); + if ($model->status != PropStatus::GENERATED['value']) { + $imageState = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::PROP_IMAGE['value']]); + $threeViewImageState = PluginModelTask::processing(['alias_id' => $model->id, 'scene' => ModelScene::PROP_THREE_VIEW_IMAGE['value']]); + if ($imageState > 0 || $threeViewImageState > 0) { + $model->status_enum = PropStatus::PENDING; + } + } + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShare.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShare.php new file mode 100644 index 0000000..5ec0b3e --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShare.php @@ -0,0 +1,23 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public function user() + { + return $this->hasOne(PluginUser::class, 'id', 'uid'); + } + public function drama() + { + return $this->hasOne(PluginShortplayDrama::class, 'id', 'drama_id'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareEpisode.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareEpisode.php new file mode 100644 index 0000000..196fd76 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareEpisode.php @@ -0,0 +1,22 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public function drama() + { + return $this->hasOne(PluginShortplayDrama::class, 'id', 'drama_id'); + } + public function episode() + { + return $this->hasOne(PluginShortplayDramaEpisode::class, 'id', 'episode_id'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareLikes.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareLikes.php new file mode 100644 index 0000000..7e40b8e --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayShareLikes.php @@ -0,0 +1,9 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayVoice.php b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayVoice.php new file mode 100644 index 0000000..547dfcc --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/app/model/PluginShortplayVoice.php @@ -0,0 +1,23 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } +} diff --git a/fastmovie-admin/plugin/shortplay/config/app.php b/fastmovie-admin/plugin/shortplay/config/app.php new file mode 100644 index 0000000..bc3fd0f --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/config/app.php @@ -0,0 +1,9 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/shortplay/config/autoload.php b/fastmovie-admin/plugin/shortplay/config/autoload.php new file mode 100644 index 0000000..0d39f50 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/shortplay/app/functions.php', + ] +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/config/container.php b/fastmovie-admin/plugin/shortplay/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/config/container.php @@ -0,0 +1,2 @@ + support\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/shortplay/config/middleware.php b/fastmovie-admin/plugin/shortplay/config/middleware.php new file mode 100644 index 0000000..3e04c72 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/config/middleware.php @@ -0,0 +1,18 @@ + [ + app\expose\middleware\Platform::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'control' => [ + plugin\control\expose\middleware\ControlAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ] +]; diff --git a/fastmovie-admin/plugin/shortplay/config/process.php b/fastmovie-admin/plugin/shortplay/config/process.php new file mode 100644 index 0000000..cf7cb7a --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/config/process.php @@ -0,0 +1,12 @@ + [ + 'handler' => Webman\RedisQueue\Process\Consumer::class, + 'count' => 8, // 可以设置多进程同时消费 + 'constructor' => [ + // 消费者类目录 + 'consumer_dir' => base_path('plugin/shortplay/queue') + ] + ] */ +]; diff --git a/fastmovie-admin/plugin/shortplay/config/route.php b/fastmovie-admin/plugin/shortplay/config/route.php new file mode 100644 index 0000000..52128d0 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/config/route.php @@ -0,0 +1,4 @@ + 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . "/plugin/shortplay/resource/translations", +]; diff --git a/fastmovie-admin/plugin/shortplay/control.json b/fastmovie-admin/plugin/shortplay/control.json new file mode 100644 index 0000000..cb2e090 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/control.json @@ -0,0 +1,91 @@ +{ + "title": "短剧", + "icon": "VideoCamera", + "path": "/app/shortplay/control", + "sort": 2, + "children": [ + { + "title": "剧本列表", + "icon": "Tickets", + "path": "/app/shortplay/control/Drama/index", + "component": "table", + "children": [ + { + "title": "更新状态", + "show": false, + "path": "/app/shortplay/control/Drama/indexUpdateState" + } + ] + }, + { + "title": "分集列表", + "icon": "Tickets", + "path": "/app/shortplay/control/DramaEpisode/index", + "component": "table", + "children": [ + { + "title": "创建分集", + "path": "/app/shortplay/control/DramaEpisode/create", + "show": false, + "component": "form" + }, + { + "title": "编辑分集", + "show": false, + "path": "/app/shortplay/control/DramaEpisode/update", + "component": "form" + } + ] + }, + { + "title": "演员列表", + "icon": "User", + "path": "/app/shortplay/control/Actor/index", + "component": "table", + "children": [ + { + "title": "创建演员", + "path": "/app/shortplay/control/Actor/create", + "show": false, + "component": "form" + }, + { + "title": "编辑演员", + "show": false, + "path": "/app/shortplay/control/Actor/update", + "component": "form" + }, + { + "title": "更新演员状态", + "show": false, + "path": "/app/shortplay/control/Actor/indexUpdateState" + } + ] + }, + { + "title": "风格列表", + "icon": "MagicStick", + "path": "/app/shortplay/control/Style/index", + "component": "table", + "children": [ + { + "title": "创建风格", + "path": "/app/shortplay/control/Style/create", + "show": false, + "component": "form" + }, + { + "title": "编辑风格", + "show": false, + "path": "/app/shortplay/control/Style/update", + "component": "form" + }, + { + "title": "更新风格状态", + "show": false, + "path": "/app/shortplay/control/Style/indexUpdateState" + } + ] + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/resource/translations/en/admin.php b/fastmovie-admin/plugin/shortplay/resource/translations/en/admin.php new file mode 100644 index 0000000..56f91e9 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/resource/translations/en/admin.php @@ -0,0 +1,9 @@ + 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'form_basic_title'=>'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/resource/translations/en/api.php b/fastmovie-admin/plugin/shortplay/resource/translations/en/api.php new file mode 100644 index 0000000..45a7854 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/resource/translations/en/api.php @@ -0,0 +1,6 @@ + 'Success', + 'fail' => 'Fail', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/resource/translations/en/messages.php b/fastmovie-admin/plugin/shortplay/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..15ae448 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/admin.php @@ -0,0 +1,9 @@ + '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'form_basic_title'=>'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/api.php b/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/api.php new file mode 100644 index 0000000..40bd25b --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/api.php @@ -0,0 +1,6 @@ + '成功', + 'fail' => '失败', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/utils/PricingCalculator.php b/fastmovie-admin/plugin/shortplay/utils/PricingCalculator.php new file mode 100644 index 0000000..5c4b7c8 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/PricingCalculator.php @@ -0,0 +1,115 @@ +formRules = $formRules; + $this->formData = $formData; + $this->baseCost = $baseCost; + } + + /** + * 主入口 + */ + public function calculate(): int + { + foreach ($this->formRules as $rule) { + if (!in_array($rule['component'], $this->chargeableComponents, true)) { + continue; + } + + $value = $this->getSubmittedValue($rule); + if ($value === null) { + continue; + } + + // Field 级计费(switch) + $this->applyFieldPricing($rule, $value); + + // Option 级计费 + if (!empty($rule['options'])) { + $this->applyOptionPricing($rule, $value); + } + } + + return (int) ceil($this->baseCost * $this->multiplier + $this->extraPoint); + } + + protected function getSubmittedValue(array $rule) + { + $field = $rule['helper_field']; + $data = $this->formData; + if (isset($data[$field])) { + return $data[$field]; + } + return null; + } + + /** + * Field 级计费(switch) + */ + protected function applyFieldPricing(array $rule, $value): void + { + if ($rule['component'] === 'switch' && $value !== true) { + return; + } + + if (isset($rule['base_point']) && $rule['base_point'] > 0) { + $this->extraPoint += (int)$rule['base_point']; + } + + if (isset($rule['multiplier']) && $rule['multiplier'] > 0) { + $this->multiplier *= (float)$rule['multiplier']; + } + } + + /** + * Option 级计费 + */ + protected function applyOptionPricing(array $rule, $value): void + { + foreach ($rule['options'] as $option) { + if (!$this->optionMatched($rule['component'], $option, $value)) { + continue; + } + + if (isset($option['base_point'])) { + if (is_numeric($value)) { + $this->extraPoint += (int)$option['base_point'] * (int)$value; + } else { + $this->extraPoint += (int)$option['base_point']; + } + } + + if (isset($option['multiplier'])) { + $this->multiplier *= (float)$option['multiplier']; + } + } + } + + protected function optionMatched(string $component, array $option, $value): bool + { + if ($component === 'checkbox') { + return is_array($value) && in_array($option['value'], $value, true); + } + + return (string)$option['value'] === (string)$value; + } +} diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/ActorAge.php b/fastmovie-admin/plugin/shortplay/utils/enum/ActorAge.php new file mode 100644 index 0000000..5293dd0 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/ActorAge.php @@ -0,0 +1,29 @@ + '儿童', + 'value' => 'child', + ]; + const TEENAGER = [ + 'label' => '少年', + 'value' => 'teenager', + ]; + const YOUNG = [ + 'label' => '青年', + 'value' => 'young', + ]; + const MIDDLE_AGE = [ + 'label' => '中年', + 'value' => 'middle_age', + ]; + const OLD = [ + 'label' => '老年', + 'value' => 'old', + ]; +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/ActorGender.php b/fastmovie-admin/plugin/shortplay/utils/enum/ActorGender.php new file mode 100644 index 0000000..72ccd31 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/ActorGender.php @@ -0,0 +1,21 @@ + '男性', + 'value' => 'male', + ]; + const FEMALE = [ + 'label' => '女性', + 'value' => 'female', + ]; + const NONE = [ + 'label' => '无性别', + 'value' => 'none', + ]; +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/ActorSpeciesType.php b/fastmovie-admin/plugin/shortplay/utils/enum/ActorSpeciesType.php new file mode 100644 index 0000000..0f53f34 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/ActorSpeciesType.php @@ -0,0 +1,45 @@ + '人类', + 'value' => 'human', + ]; + const ANIMAL = [ + 'label' => '动物', + 'value' => 'animal', + ]; + const PLANT = [ + 'label' => '植物', + 'value' => 'plant', + ]; + const ROBOT = [ + 'label' => '机器人', + 'value' => 'robot', + ]; + const VIRTUAL_PERSON = [ + 'label' => '虚拟人', + 'value' => 'virtual_person', + ]; + const DOLL = [ + 'label' => '玩偶', + 'value' => 'doll', + ]; + const SPRITE = [ + 'label' => '精灵', + 'value' => 'sprite', + ]; + const GHOST = [ + 'label' => '鬼怪', + 'value' => 'ghost', + ]; + const OTHER = [ + 'label' => '其他', + 'value' => 'other', + ]; +} diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/ActorStatus.php b/fastmovie-admin/plugin/shortplay/utils/enum/ActorStatus.php new file mode 100644 index 0000000..b3c45f6 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/ActorStatus.php @@ -0,0 +1,44 @@ + '待初始化', + 'value' => 'initializing', + 'props' => [ + 'type' => 'primary' + ] + ]; + const PENDING = [ + 'label' => '初始化中', + 'value' => 'pending', + 'props' => [ + 'type' => 'warning' + ] + ]; + const GENERATED = [ + 'label' => '已生成', + 'value' => 'generated', + 'props' => [ + 'type' => 'success' + ] + ]; + const FAILED = [ + 'label' => '生成失败', + 'value' => 'failed', + 'props' => [ + 'type' => 'danger' + ] + ]; + const CANCELLED = [ + 'label' => '已取消', + 'value' => 'cancelled', + 'props' => [ + 'type' => 'info' + ] + ]; +} diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/PropStatus.php b/fastmovie-admin/plugin/shortplay/utils/enum/PropStatus.php new file mode 100644 index 0000000..f3ae519 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/PropStatus.php @@ -0,0 +1,44 @@ + '待初始化', + 'value' => 'initializing', + 'props' => [ + 'type' => 'primary' + ] + ]; + const PENDING = [ + 'label' => '初始化中', + 'value' => 'pending', + 'props' => [ + 'type' => 'warning' + ] + ]; + const GENERATED = [ + 'label' => '已生成', + 'value' => 'generated', + 'props' => [ + 'type' => 'success' + ] + ]; + const FAILED = [ + 'label' => '生成失败', + 'value' => 'failed', + 'props' => [ + 'type' => 'danger' + ] + ]; + const CANCELLED = [ + 'label' => '已取消', + 'value' => 'cancelled', + 'props' => [ + 'type' => 'info' + ] + ]; +} diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/StyleClassify.php b/fastmovie-admin/plugin/shortplay/utils/enum/StyleClassify.php new file mode 100644 index 0000000..50e6e70 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/StyleClassify.php @@ -0,0 +1,29 @@ + '动漫风格', + 'value' => 'anime', + ]; + const REALISTIC = [ + 'label' => '写实风格', + 'value' => 'realistic', + ]; + const PORTRAIT = [ + 'label' => '肖像风格', + 'value' => 'portrait', + ]; + const COMIC = [ + 'label' => '漫画风格', + 'value' => 'comic', + ]; + const OTHER = [ + 'label' => '其他风格', + 'value' => 'other', + ]; +} diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/VoiceEmotion.php b/fastmovie-admin/plugin/shortplay/utils/enum/VoiceEmotion.php new file mode 100644 index 0000000..0918595 --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/VoiceEmotion.php @@ -0,0 +1,37 @@ + '中性', + 'value' => 'neutral' + ]; + const FEARFUL = [ + 'label' => '恐惧', + 'value' => 'fearful' + ]; + const ANGRY = [ + 'label' => '愤怒', + 'value' => 'angry' + ]; + const SAD = [ + 'label' => '悲伤', + 'value' => 'sad' + ]; + const SURPRISED = [ + 'label' => '惊讶', + 'value' => 'surprised' + ]; + const HAPPY = [ + 'label' => '高兴', + 'value' => 'happy' + ]; + const DISGUSTED = [ + 'label' => '厌恶', + 'value' => 'disgusted' + ]; +} diff --git a/fastmovie-admin/plugin/shortplay/utils/enum/VoiceLanguage.php b/fastmovie-admin/plugin/shortplay/utils/enum/VoiceLanguage.php new file mode 100644 index 0000000..9a2a6fc --- /dev/null +++ b/fastmovie-admin/plugin/shortplay/utils/enum/VoiceLanguage.php @@ -0,0 +1,73 @@ + '中文', + 'value' => 'zh' + ]; + const EN = [ + 'label' => '英语', + 'value' => 'en' + ]; + const JA = [ + 'label' => '日语', + 'value' => 'ja' + ]; + const KO = [ + 'label' => '韩语', + 'value' => 'ko' + ]; + const FR = [ + 'label' => '法语', + 'value' => 'fr' + ]; + const DE = [ + 'label' => '德语', + 'value' => 'de' + ]; + const IT = [ + 'label' => '意大利语', + 'value' => 'it' + ]; + const RU = [ + 'label' => '俄语', + 'value' => 'ru' + ]; + const SV = [ + 'label' => '瑞典语', + 'value' => 'sv' + ]; + const DA = [ + 'label' => '丹麦语', + 'value' => 'da' + ]; + const NO = [ + 'label' => '挪威语', + 'value' => 'no' + ]; + const PT = [ + 'label' => '葡萄牙语', + 'value' => 'pt' + ]; + const TR = [ + 'label' => '土耳其语', + 'value' => 'tr' + ]; + const AR = [ + 'label' => '阿拉伯语', + 'value' => 'ar' + ]; + const ES = [ + 'label' => '西班牙语', + 'value' => 'es' + ]; + const HI = [ + 'label' => '印地语', + 'value' => 'hi' + ]; +} diff --git a/fastmovie-admin/plugin/user/admin.json b/fastmovie-admin/plugin/user/admin.json new file mode 100644 index 0000000..384380e --- /dev/null +++ b/fastmovie-admin/plugin/user/admin.json @@ -0,0 +1,54 @@ +{ + "title": "User", + "icon": "User", + "path": "/app/user/admin", + "sort": 1000, + "children": [ + { + "title": "User List", + "icon": "User", + "path": "/app/user/admin/User/index", + "component": "table", + "children": [ + { + "title": "Create User", + "path": "/app/user/admin/User/create", + "show": false, + "component": "form" + }, + { + "title": "Edit User", + "show": false, + "path": "/app/user/admin/User/update", + "component": "form" + }, + { + "title": "Update Status", + "show": false, + "path": "/app/user/admin/User/indexUpdateState", + "component": "" + } + ] + }, + + { + "title": "Settings", + "icon": "Setting", + "path": "/app/user/admin/Setting/basic", + "show": false, + "component": "form" + }, + { + "title": "余额账单", + "icon": "User", + "path": "/app/user/admin/Balance/index", + "component": "table" + }, + { + "title": "积分账单", + "icon": "User", + "path": "/app/user/admin/Points/index", + "component": "table" + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/api/Control.php b/fastmovie-admin/plugin/user/api/Control.php new file mode 100644 index 0000000..141c4f8 --- /dev/null +++ b/fastmovie-admin/plugin/user/api/Control.php @@ -0,0 +1,15 @@ +path = __DIR__; + $this->plugin = 'user'; + } +} diff --git a/fastmovie-admin/plugin/user/api/Install.php b/fastmovie-admin/plugin/user/api/Install.php new file mode 100644 index 0000000..c629205 --- /dev/null +++ b/fastmovie-admin/plugin/user/api/Install.php @@ -0,0 +1,15 @@ +plugin='user'; + $this->path = __DIR__; + } +} diff --git a/fastmovie-admin/plugin/user/api/admin/IndexController.php b/fastmovie-admin/plugin/user/api/admin/IndexController.php new file mode 100644 index 0000000..1515b9b --- /dev/null +++ b/fastmovie-admin/plugin/user/api/admin/IndexController.php @@ -0,0 +1,32 @@ +count(); + $yesterday = PluginUser::whereDay('create_time', 'yesterday')->count(); + $statistic = new Statistic; + $statistic->setLabel('今日新注册用户') + ->setUnit('人') + ->setData([ + 'today' => $today, + 'yesterday' => $yesterday, + 'growth_rate' => $yesterday ? round(($today - $yesterday) / $yesterday * 100, 2) : 0 + ]) + ->setClass('p-6'); + $builder->add($statistic, [ + 'xs' => 24, + 'sm' => 12, + 'md' => 6, + 'lg' => 4, + 'class' => 'bg-white p-4 rounded-4 shadow-lighter' + ]); + } +} diff --git a/fastmovie-admin/plugin/user/app/admin/controller/BalanceController.php b/fastmovie-admin/plugin/user/app/admin/controller/BalanceController.php new file mode 100644 index 0000000..d21e003 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/admin/controller/BalanceController.php @@ -0,0 +1,227 @@ +model = new PluginUserBill(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'placeholder' => '邮箱搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('action', '变化类型', 'select', '', [ + 'options' => MoneyAction::getOptions(), + 'props' => [ + 'placeholder' => '变化类型搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + $builder->add('num', '变化金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('before', '变化前金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('after', '变化后金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('remarks', '备注', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('action', '变化类型', [ + 'props' => [ + 'width' => '120px' + ], + 'component' => [ + 'name' => 'tag', + 'options' => MoneyAction::getOptions() + ] + ]); + $builder->add('create_time', '创建时间', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $channels_uid = $request->get('channels'); + if ($channels_uid) { + $where[] = ['ub.channels_uid', '=', $channels_uid]; + } + $username = $request->get('username'); + if ($username) { + $where[] = ['u.username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['u.mobile', 'like', "%{$mobile}%"]; + } + $email = $request->get('email'); + if ($email) { + $where[] = ['u.email', 'like', "%{$email}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['u.id', '=', $uid]; + } + $list = PluginUserBill::alias('ub') + ->with(['channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->join('plugin_user u', 'ub.uid = u.id') + ->field('ub.*,u.nickname,u.headimg,u.username,u.mobile,u.email') + ->where($where) + ->order('ub.id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/user/app/admin/controller/PointsController.php b/fastmovie-admin/plugin/user/app/admin/controller/PointsController.php new file mode 100644 index 0000000..688f312 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/admin/controller/PointsController.php @@ -0,0 +1,232 @@ +model = new PluginUserPointsBill(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $channelList = PluginChannelsUser::options(); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'placeholder' => '邮箱搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('action', '变化类型', 'select', '', [ + 'options' => MoneyAction::getOptions(), + 'props' => [ + 'placeholder' => '变化类型搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + $builder->add('num', '变化金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('before', '变化前金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('after', '变化后金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('remarks', '备注', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('action', '变化类型', [ + 'props' => [ + 'width' => '120px' + ], + 'component' => [ + 'name' => 'tag', + 'options' => MoneyAction::getOptions() + ] + ]); + $builder->add('create_time', '创建时间', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['ub.channels_uid', '=', $channels_uid]; + } + $username = $request->get('username'); + if ($username) { + $where[] = ['u.username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['u.mobile', 'like', "%{$mobile}%"]; + } + $email = $request->get('email'); + if ($email) { + $where[] = ['u.email', 'like', "%{$email}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['u.id', '=', $uid]; + } + $list = PluginUserPointsBill::alias('ub') + ->with(['channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->join('plugin_user u', 'ub.uid = u.id') + ->field('ub.*,u.nickname,u.headimg,u.username,u.mobile,u.email') + ->where($where) + ->order('ub.id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/user/app/admin/controller/SettingController.php b/fastmovie-admin/plugin/user/app/admin/controller/SettingController.php new file mode 100644 index 0000000..7d48b0d --- /dev/null +++ b/fastmovie-admin/plugin/user/app/admin/controller/SettingController.php @@ -0,0 +1,15 @@ +builder(); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/app/admin/controller/UserController.php b/fastmovie-admin/plugin/user/app/admin/controller/UserController.php new file mode 100644 index 0000000..7521cce --- /dev/null +++ b/fastmovie-admin/plugin/user/app/admin/controller/UserController.php @@ -0,0 +1,440 @@ +model = new PluginUser; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $channelList = PluginChannelsUser::options(); + // $builder->addAction('操作', [ + // 'width' => '100px', + // 'fixed' => 'right' + // ]); + // $builder->addTableAction('编辑', [ + // 'model' => Action::DIALOG['value'], + // 'path' => '/app/user/admin/User/update', + // 'props' => [ + // 'title' => '编辑《ID:{id}》用户' + // ], + // 'component' => [ + // 'name' => 'button', + // 'props' => [ + // 'type' => 'primary', + // 'size' => 'small' + // ] + // ] + // ]); + // $builder->addHeader(); + // $builder->addHeaderAction('创建用户', [ + // 'model' => Action::DIALOG['value'], + // 'path' => '/app/user/admin/User/create', + // 'props' => [ + // 'title' => '创建用户' + // ], + // 'component' => [ + // 'name' => 'button', + // 'props' => [ + // 'type' => 'success' + // ] + // ] + // ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('channels_uid', '渠道', 'select', '', [ + 'options' => $channelList, + 'props' => [ + 'placeholder' => '渠道搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'placeholder' => '邮箱搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('puid', '上级UID', 'input', '', [ + 'props' => [ + 'placeholder' => '上级UID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('channels', '渠道', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'channels.nickname', + 'avatar' => 'channels,headimg', + 'info' => 'channels.mobile', + 'tags' => [ + [ + 'field' => 'channels_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('puserinfo', '上级', [ + 'where' => [ + ['puid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'parent.nickname', + 'avatar' => 'parent,headimg', + 'info' => 'parent.mobile', + 'tags' => [ + [ + 'field' => 'puid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => State::getOptions(), + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('online_time', '活动', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'activation_time', + 'label' => '激活' + ], + [ + 'field' => 'login_time', + 'label' => '登录' + ], + [ + 'component' => 'tag', + 'field' => 'login_ip', + 'label' => '登录IP', + 'props' => [ + 'size' => 'small', + ] + ] + ] + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function query(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['nickname|username|mobile|email', 'like', "%{$query}%"]; + return $this->resData(PluginUser::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $channels_uid = $request->get('channels_uid'); + if ($channels_uid) { + $where[] = ['channels_uid', '=', $channels_uid]; + } + $username = $request->get('username'); + if ($username) { + $where[] = ['username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['mobile', 'like', "%{$mobile}%"]; + } + $email = $request->get('email'); + if ($email) { + $where[] = ['email', 'like', "%{$email}%"]; + } + $puid = $request->get('puid'); + if ($puid) { + $where[] = ['puid', '=', $puid]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['id', '=', $uid]; + } + $list = PluginUser::where($where)->with(['parent' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }, 'channels' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) { + // 三天以内创建的 + $create_time = strtotime($item->create_time); + $create_time = time() - $create_time; + if ($create_time < 3 * 24 * 60 * 60) { + $item->new_text = '新用户'; + } elseif ($create_time < 7 * 24 * 60 * 60) { + $item->week_text = '一周内'; + } elseif ($create_time < 30 * 24 * 60 * 60) { + $item->month_text = '30天内'; + } + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + HelperUser::create($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + $Component = new ComponentBuilder; + $builder->add('activation_time', '激活时间', 'date-picker', null, [ + 'prompt' => [ + $Component->add('text', ['default' => '不选择时间则不激活,由用户前台登录后自动激活'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '选择激活时间', + 'type' => 'datetime', + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'value-format' => 'YYYY-MM-DD HH:mm:ss' + ] + ]); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + HelperUser::update($D['id'], $D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('更新成功'); + } + $id = $request->get('id'); + $User = PluginUser::where(['id' => $id])->withoutField('password')->find(); + $builder = $this->getFormBuilder(); + $builder->setData($User->toArray()); + return $this->resData($builder); + } + private function getFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large', + ]); + $Component = new ComponentBuilder; + $builder->add('nickname', '昵称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true + ]); + $builder->add('headimg', '头像', 'bundle', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '支持jpg、png、jpeg格式,大小不超过2M'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'accept' => 'image/jpeg,image/png,image/jpg', + 'multiple' => 1, + 'size' => 2 + ] + ]); + $builder->add('username', '账号', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '支持字母、数字、下划线,长度不超过30个字符'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '可用于账号登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('mobile', '手机号', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '中国大陆11位手机号'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '可用于账号,短信验证码登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 11, + 'show-word-limit' => true + ] + ]); + $builder->add('email', '邮箱', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '可用于账号,邮箱验证码登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('password', '密码', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '不修改密码请留空'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '不修改密码请留空', + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + return $builder; + } +} diff --git a/fastmovie-admin/plugin/user/app/api/controller/BillController.php b/fastmovie-admin/plugin/user/app/api/controller/BillController.php new file mode 100644 index 0000000..aa4360a --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/BillController.php @@ -0,0 +1,36 @@ +get('type', 'all'); + + $uid = $request->uid; + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['uid', '=', $uid]; + if ($type != 'all') { + if ($type == 'consume') { + $where[] = ['action', '=', MoneyAction::DECREASE['value']]; + } + if ($type == 'gain') { + $where[] = ['action', '=', MoneyAction::INCREASE['value']]; + } + if ($type == 'recharge') { + $where[] = ['scene', '=', PointsBillScene::RECHARGE['value']]; + } + } + $PluginFinanceBill = PluginUserPointsBill::where($where)->order('create_time', 'desc')->paginate($limit); + + return $this->resData($PluginFinanceBill); + } +} diff --git a/fastmovie-admin/plugin/user/app/api/controller/BindThirdPartyController.php b/fastmovie-admin/plugin/user/app/api/controller/BindThirdPartyController.php new file mode 100644 index 0000000..1d9b01a --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/BindThirdPartyController.php @@ -0,0 +1,109 @@ +twofa_state) { + $time = time() - (10 * 60); + if ($request->twofa['time'] < $time) { + return $this->fail('请验证你的身份', [ + 'code' => ResponseCode::NEED_TWOFA, + 'action' => 'stop' + ]); + } + } + if ($request->isWehcatBrowser) { + } else { + try { + $res = Wechat::createQrCode(Wechat::QR_STR_SCENE, 600, [ + 'plugin\user\expose\helper\WechatOfficialAccount', + 'bind', + [ + 'uid' => $request->uid, + 'channels_uid' => $request->channels_uid, + ] + ]); + return $this->resData(['qrcode' => $res['url'], 'id' => $res['id'], 'expire' => strtotime("+" . $res['expire_seconds'] . " seconds")]); + } catch (Exception $e) { + } catch (\Throwable $th) { + return $this->exception($th); + } + try { + $res = Wechat::createQrCode(Wechat::QR_SCENE, 600, [ + 'plugin\user\expose\helper\WechatOfficialAccount', + 'scan', + [ + 'uid' => $request->uid, + 'channels_uid' => $request->channels_uid, + ] + ]); + return $this->resData(['qrcode' => $res['url'], 'id' => $res['id'], 'expire' => strtotime("+" . $res['expire_seconds'] . " seconds")]); + } catch (Exception $e) { + } catch (\Throwable $th) { + return $this->exception($th); + } + /* $auth_url=''; + return $this->code(ResponseCode::OPEN_NEW_WINDOW, '请在微信中打开', [ + 'url' => $auth_url + ]); */ + return $this->fail('无可用的微信服务,请联系客服'); + } + } + public function checkWechat(Request $request) + { + $PluginUserWechat = PluginUserWechat::where('uid', $request->uid)->find(); + if ($PluginUserWechat) { + return $this->code(ResponseCode::SUCCESS_EVENT_PUSH, '绑定成功', [ + 'event' => ResponseEvent::UPDATE_USERINFO + ]); + } + $id = $request->post('id'); + if(!$id){ + return $this->fail('参数错误'); + } + $Scene = Redis::get($id); + if (!$Scene) { + return $this->fail('二维码已过期'); + } + $res = Redis::get($id.'.bind'); + if($res){ + $res = json_decode($res,true); + if($res['status'] == 'fail'){ + return $this->fail($res['message']); + } + } + return $this->code(ResponseCode::WAIT,'等待微信扫码'); + } + public function unbindWechat(Request $request) + { + if ($request->twofa_state) { + $time = time() - (10 * 60); + if ($request->twofa['time'] < $time) { + return $this->fail('请验证你的身份', [ + 'code' => ResponseCode::NEED_TWOFA, + 'action' => 'stop' + ]); + } + } + $PluginUserWechat = PluginUserWechat::where('uid', $request->uid)->find(); + if ($PluginUserWechat) { + $PluginUserWechat->uid=null; + $PluginUserWechat->save(); + } + return $this->code(ResponseCode::SUCCESS_EVENT_PUSH, '解绑成功', [ + 'event' => ResponseEvent::UPDATE_USERINFO + ]); + } +} diff --git a/fastmovie-admin/plugin/user/app/api/controller/CaptchaController.php b/fastmovie-admin/plugin/user/app/api/controller/CaptchaController.php new file mode 100644 index 0000000..31a2da0 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/CaptchaController.php @@ -0,0 +1,34 @@ +get(); + $request->session()->set('captcha', [ + 'captcha' => strtolower($builder->getPhrase()), + 'expire' => time() + 60 * 5 + ]); + // 输出验证码二进制数据 + return response($img_content, 200, ['Content-Type' => 'image/jpeg']); + } + public function captcha_base64(Request $request) + { + return Captcha::captcha(); + } + public function captcha_json(Request $request) + { + return $this->resData(Captcha::captchaCode()); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/app/api/controller/LoginController.php b/fastmovie-admin/plugin/user/app/api/controller/LoginController.php new file mode 100644 index 0000000..5e31b81 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/LoginController.php @@ -0,0 +1,265 @@ +post(); + $where = []; + # 判断$D['username']是用户名,11位手机号,邮箱 + if (empty($D['username'])) { + return $this->fail('请输入用户名|手机号|邮箱'); + } + $username = $D['username']; + if (preg_match('/^\d{11}$/', $username)) { + // 11位手机号 + $where[] = ['mobile', '=', $username]; + } elseif (filter_var($username, FILTER_VALIDATE_EMAIL)) { + // 邮箱 + $where[] = ['email', '=', $username]; + } else { + // 用户名 + $where[] = ['username', '=', $username]; + } + $where[] = ['channels_uid', '=', $request->channels_uid]; + $user = PluginUser::where($where)->find(); + if (!$user) { + return $this->fail('用户不存在'); + } + // 验证登录密码 + if (!Password::verify((string) $D['password'], (string) $user->password)) { + return $this->fail('密码错误'); + } + if ($user->state == State::NO['value']) { + return $this->fail('用户状态异常'); + } + $captcha_state = Config::get('captcha', 'state'); + // 检验图像验证码 + if ($captcha_state) { + if (empty($D['captcha'])) { + return $this->fail('请输入验证码'); + } + $token = $D['token'] ?? null; + if (!Captcha::check($D['captcha'], $token)) { + return $this->fail('验证码错误'); + } + } + + // 更新登录信息 + $ip = $request->getRealIp(); + $user->login_ip = $ip; + $user->login_time = date('Y-m-d H:i:s'); + if ($user->save() === false) { + return $this->fail('登录失败'); + } + $info = PluginUser::getTokenInfo($user); + return $this->resData($info); + } + public function vcode(Request $request) + { + Db::startTrans(); + try { + $username = $request->post('username'); + $vcode = $request->post('vcode'); + $token = $request->post('token'); + $code = $request->header('X-Icode') ?? null; + Vcode::check($username, $vcode, VcodeScene::SIGNUP['value'], $token); + $PluginUserInvitationCode = null; + if ($code) { + $PluginUserInvitationCode = PluginUserInvitationCode::where(['code' => $code])->find(); + if (!$PluginUserInvitationCode) { + $PluginUserInvitationCode = null; + } + if ($PluginUserInvitationCode->status != 'unused') { + $PluginUserInvitationCode = null; + } + if($PluginUserInvitationCode->uid == $request->uid){ + $PluginUserInvitationCode = null; + } + if($PluginUserInvitationCode->state != State::YES['value']){ + $PluginUserInvitationCode = null; + } + } + $user = PluginUser::where('mobile', $username)->find(); + if (!$user) { + $data = [ + 'channels_uid' => $request->channels_uid, + 'mobile' => $username, + 'state' => State::YES['value'], + ]; + if ($PluginUserInvitationCode != null) { + $data['puid'] = $PluginUserInvitationCode->uid; + $data['activation_time'] = date('Y-m-d H:i:s'); + } + $user = new PluginUser(); + $user->save($data); + } + if ($user->state == State::NO['value']) { + throw new Exception('用户状态异常'); + } + // 更新登录信息 + $ip = $request->getRealIp(); + $user->login_ip = $ip; + $user->login_time = date('Y-m-d H:i:s'); + if ($PluginUserInvitationCode != null) { + $PluginUserInvitationCode->status = 'used'; + $PluginUserInvitationCode->use_uid = $user->id; + $PluginUserInvitationCode->use_time = date('Y-m-d H:i:s'); + $PluginUserInvitationCode->save(); + } + if ($user->save() === false) { + throw new Exception('登录失败'); + } + $info = PluginUser::getTokenInfo($user); + Db::commit(); + return $this->resData($info); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + } + public function qrcode(Request $request) + { + return $this->wechat($request); + } + public function wechat(Request $request) + { + if ($request->isWehcatBrowser && $request->isMobile) { + } else { + if (!empty($request->uid)) { + try { + $code = $request->header('X-Icode') ?? null; + $res = Wechat::createQrCode(Wechat::QR_STR_SCENE, 600, [ + 'plugin\user\expose\helper\WechatOfficialAccount', + 'bind', + [ + 'channels_uid' => $request->channels_uid, + 'code' => $code, + 'uid' => $request->uid, + ] + ]); + return $this->resData(['qrcode' => $res['url'], 'id' => $res['id'], 'expire' => strtotime("+" . $res['expire_seconds'] . " seconds")]); + } catch (Exception $e) { + p($e->getMessage()); + Log::info($e->getMessage(), $e->getTrace()); + } catch (\Throwable $th) { + return $this->exception($th); + } + } + try { + $code = $request->header('X-Icode') ?? null; + $res = Wechat::createQrCode(Wechat::QR_STR_SCENE, 600, [ + 'plugin\user\expose\helper\WechatOfficialAccount', + 'login', + [ + 'channels_uid' => $request->channels_uid, + 'code' => $code, + ] + ]); + return $this->resData(['qrcode' => $res['url'], 'id' => $res['id'], 'expire' => strtotime("+" . $res['expire_seconds'] . " seconds")]); + } catch (Exception $e) { + p($e->getMessage()); + Log::info($e->getMessage(), $e->getTrace()); + } catch (\Throwable $th) { + return $this->exception($th); + } + try { + $res = Wechat::createQrCode(Wechat::QR_SCENE, 600, [ + 'plugin\user\expose\helper\WechatOfficialAccount', + 'scan', + [ + 'channels_uid' => $request->channels_uid, + 'code' => $code, + ] + ]); + return $this->resData(['qrcode' => $res['url'], 'id' => $res['id'], 'expire' => strtotime("+" . $res['expire_seconds'] . " seconds")]); + } catch (Exception $e) { + Log::info($e->getMessage(), $e->getTrace()); + } catch (\Throwable $th) { + return $this->exception($th); + } + /* $auth_url=''; + return $this->code(ResponseCode::OPEN_NEW_WINDOW, '请在微信中打开', [ + 'url' => $auth_url + ]); */ + return $this->fail('无可用的微信服务,请联系客服'); + } + } + public function checkQrcode(Request $request) + { + $id = $request->post('id'); + if (!$id) { + return $this->fail('参数错误'); + } + $Scene = Redis::get($id); + if (!$Scene) { + return $this->fail('二维码已过期'); + } + $res = Redis::get($id . '.login'); + if ($res) { + $res = json_decode($res, true); + if ($res['status'] == 'success') { + $user = PluginUser::where('id', $res['uid'])->find(); + if ($user) { + // 更新登录信息 + $ip = $request->getRealIp(); + $user->login_ip = $ip; + $user->login_time = date('Y-m-d H:i:s'); + if ($user->save() === false) { + return $this->fail('登录失败'); + } + $info = PluginUser::getTokenInfo($user); + return $this->resData($info); + } else { + return $this->fail('登录失败'); + } + } else { + return $this->fail($res['message']); + } + } + return $this->code(ResponseCode::WAIT, '等待微信扫码'); + } + + public function checkBind(Request $request) + { + $id = $request->post('id'); + if (!$id) { + return $this->fail('参数错误'); + } + $Scene = Redis::get($id); + if (!$Scene) { + return $this->fail('二维码已过期'); + } + $res = Redis::get($id . '.bind'); + if ($res) { + $res = json_decode($res, true); + if ($res['status'] == 'success') { + return $this->resData(['status' => 'success']); + } else { + return $this->fail($res['message']); + } + } + return $this->code(ResponseCode::WAIT, '等待微信扫码'); + } +} diff --git a/fastmovie-admin/plugin/user/app/api/controller/SignupController.php b/fastmovie-admin/plugin/user/app/api/controller/SignupController.php new file mode 100644 index 0000000..7b8559b --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/SignupController.php @@ -0,0 +1,46 @@ +post(); + Vcode::check($D['username'],$D['vcode'],VcodeScene::SIGNUP['value'],$D['token']); + try { + $UserModel = User::create([ + 'mobile' => $D['username'], + ]); + $user =PluginUser::where('id',$UserModel->id)->find(); + if(!$user){ + return $this->fail('用户不存在'); + } + if($user->state == State::NO['value']){ + return $this->fail('用户状态异常'); + } + + // 更新登录信息 + $ip = $request->getRealIp(); + $user->login_ip = $ip; + $user->login_time = date('Y-m-d H:i:s'); + if ($user->save() === false) { + return $this->fail('登录失败'); + } + $info = PluginUser::getTokenInfo($user); + return $this->resData($info); + } catch (\Throwable $th) { + return $this->exception($th); + } + } +} diff --git a/fastmovie-admin/plugin/user/app/api/controller/UploadsController.php b/fastmovie-admin/plugin/user/app/api/controller/UploadsController.php new file mode 100644 index 0000000..24ee174 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/UploadsController.php @@ -0,0 +1,15 @@ +uid = request()->uid; + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/app/api/controller/UserBillController.php b/fastmovie-admin/plugin/user/app/api/controller/UserBillController.php new file mode 100644 index 0000000..082ca11 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/UserBillController.php @@ -0,0 +1,46 @@ +uid; + $limit=$request->get('limit',10); + $where=[ + ['uid','=',$uid] + ]; + $action=$request->get('action','all'); + if($action&&$action!='all'){ + $where[]=['action','=',$action]; + } + $scene=$request->get('scene','all'); + if($scene&&$scene!='all'){ + $where[]=['scene','=',$scene]; + } + $start_time=$request->get('start_time'); + $end_time=$request->get('end_time'); + if($start_time&&$end_time){ + $where[]=['create_time','between',[$start_time,$end_time]]; + }elseif($start_time){ + $where[]=['create_time','>=',$start_time]; + }elseif($end_time){ + $where[]=['create_time','<=',$end_time]; + } + $PluginUserBill = PluginUserBill::where($where)->order('create_time', 'desc')->paginate($limit)->each(function($item){ + $item->action_text=MoneyAction::get($item->action); + $item->scene_text=BillScene::get($item->scene); + }); + if($PluginUserBill->isEmpty()){ + return $this->fail(); + } + return $this->resData($PluginUserBill->scene('external')); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/app/api/controller/UserController.php b/fastmovie-admin/plugin/user/app/api/controller/UserController.php new file mode 100644 index 0000000..2c515db --- /dev/null +++ b/fastmovie-admin/plugin/user/app/api/controller/UserController.php @@ -0,0 +1,445 @@ + $request->uid])->find(); + if (!$user) { + return $this->fail('用户不存在'); + } + $info = PluginUser::getTokenInfo($user, $request->twofa); + $count = PluginUserInvitationCode::where(['uid' => $request->uid])->where('state', State::YES['value'])->where('status', 'unused')->count(); + $info->invitation_code_count = $count; + return $this->resData($info); + } + public function update(Request $request) + { + $D = $request->post(); + try { + if ($request->twofa_state) { + $time = time() - (10 * 60); + if ($request->twofa['time'] < $time) { + return $this->fail('请验证你的身份', [ + 'code' => ResponseCode::NEED_TWOFA, + 'action' => 'stop' + ]); + } + switch (true) { + case isset($D['username']): + $PluginUser = PluginUser::where(['id' => $request->uid])->find(); + $username_time = $PluginUser->username_time ? 30 - ceil((time() - strtotime($PluginUser->username_time)) / 86400) : 0; + if ($username_time > 0) { + return $this->fail('30天内仅支持修改一次'); + } + break; + case isset($D['mobile']): + Vcode::check($D['mobile'], $D['vcode'], VcodeScene::BIND_MOBILE['value'], $D['token']); + break; + case isset($D['email']): + Vcode::check($D['email'], $D['vcode'], VcodeScene::BIND_EMAIL['value'], $D['token']); + break; + case isset($D['password']): + if (empty($D['vpassword'])) { + return $this->fail('两次输入的密码不一致'); + } + if ($D['password'] != $D['vpassword']) { + return $this->fail('两次输入的密码不一致'); + } + Vcode::check($D['username'], $D['vcode'], VcodeScene::SET_PASSWORD['value'], $D['token'] ?? null); + break; + } + } + $D['channels_uid'] = $request->channels_uid; + User::update($request->uid, $D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->code(ResponseCode::SUCCESS_EVENT_PUSH, '更新成功', [ + 'event' => ResponseEvent::UPDATE_USERINFO + ]); + } + public function checkUsername(Request $request) + { + $username = $request->post('username'); + $user = PluginUser::where(['username' => $username])->find(); + if ($user && $user->id != $request->uid) { + return $this->fail('用户名已存在'); + } + return $this->success(); + } + public function checkMobile(Request $request) + { + $mobile = $request->post('mobile'); + $user = PluginUser::where(['mobile' => $mobile])->find(); + if ($user) { + if ($user->id == $request->uid) { + return $this->fail('和原手机号一致'); + } + return $this->fail('手机号已存在'); + } + return $this->success(); + } + public function checkEmail(Request $request) + { + $email = $request->post('email'); + $user = PluginUser::where(['email' => $email])->find(); + if ($user) { + if ($user->id == $request->uid) { + return $this->fail('和原邮箱一致'); + } + return $this->fail('邮箱已存在'); + } + return $this->success(); + } + public function checkTwofa(Request $request) + { + if (!$request->twofa_state) { + return $this->success(); + } + $time = time() - (10 * 60); + if ($request->twofa['time'] < $time) { + return $this->fail('请验证你的身份', [ + 'code' => ResponseCode::NEED_TWOFA, + 'action' => 'stop' + ]); + } + return $this->success(); + } + public function uploadHeadimg(Request $request) + { + $default_channels = config('plugin.shopwwi.filesystem.app.default'); + $UploadsClassify = UploadsClassify::where(['dir_name' => 'uploads/default', 'is_system' => 1, 'channels' => $default_channels])->find(); + if (!$UploadsClassify) { + return $this->fail('分类不存在'); + } + $date_path = date('Ymd'); + //单文件上传 + $file = $request->file('file'); + try { + $result = Storage::adapter($UploadsClassify->channels)->path($UploadsClassify->dir_name . '/' . $date_path)->upload($file); + } catch (\Throwable $th) { + return $this->exception($th); + } + try { + $Uploads = new Uploads(); + $Uploads->uid = $request->uid; + $Uploads->classify_id = $UploadsClassify->id; + $Uploads->filename = $result->origin_name; + $Uploads->path = $result->file_name; + $Uploads->ext = $result->extension; + $Uploads->mime = $result->mime_type; + $Uploads->size = $result->size; + $Uploads->channels = $UploadsClassify->channels; + $Uploads->save(); + } catch (\Throwable $th) { + return $this->exception($th); + } + $user = PluginUser::where(['id' => $request->uid])->find(); + $user->headimg = $result->file_url; + $user->save(); + return $this->code(ResponseCode::SUCCESS_EVENT_PUSH, '保存成功', [ + 'event' => ResponseEvent::UPDATE_USERINFO + ]); + } + public function outLogin(Request $request) + { + return $this->success(); + } + /** + * 获取两步验证密钥 + */ + public function getTwofaSecret(Request $request) + { + try { + $ga = new TwofaAuthenticator(); + $secret = $ga->createSecret(); + $PluginUser = PluginUser::where(['id' => $request->uid])->find(); + + // 生成 otpauth URL + $appName = "XH"; + $accountName = $PluginUser->nickname; + $qrCodeUrl = $ga->getQRCode($appName . ':' . $accountName, $secret, $appName); + } catch (\Throwable $th) { + return $this->exception($th); + } + + return $this->resData([ + 'secret' => $secret, + 'qrcode' => $qrCodeUrl + ]); + } + + /** + * 验证两步验证密钥 + */ + public function verifyTwofaSecret(Request $request) + { + $code = $request->post('code'); + $secret = $request->post('secret'); + $totp_app = $request->post('totp_app'); + $ga = new TwofaAuthenticator(); + + $isValid = $ga->verifyCode($secret, $code, 2); // 1 = 30 秒容差 + + if ($isValid) { + Db::startTrans(); + try { + $PluginUser = PluginUser::where(['id' => $request->uid])->find(); + $PluginUser->twofa_state = State::YES['value']; + $PluginUser->save(); + $PluginUserTwofaSecret = new PluginUserTwofaSecret(); + $PluginUserTwofaSecret->uid = $request->uid; + $PluginUserTwofaSecret->secret = $secret; + $PluginUserTwofaSecret->totp_app = $totp_app; + $PluginUserTwofaSecret->is_default = PluginUserTwofaSecret::where(['uid' => $request->uid])->count() == 0 ? 1 : 0; + $PluginUserTwofaSecret->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->success('验证成功'); + } else { + return $this->fail('验证码错误'); + } + } + /** + * 刷新账户两步验证时效 + */ + public function refreshTwofaSecret(Request $request) + { + $code = $request->post('code'); + $totp_app = $request->post('totp_app'); + $ga = new TwofaAuthenticator(); + $PluginUserTwofaSecret = PluginUserTwofaSecret::where(['uid' => $request->uid, 'id' => $totp_app])->find(); + if (!$PluginUserTwofaSecret) { + return $this->fail('两步验证密钥不存在'); + } + $secret = $PluginUserTwofaSecret->secret; + $isValid = $ga->verifyCode($secret, $code, 2); // 1 = 30 秒容差 + + if ($isValid) { + $user = PluginUser::where(['id' => $request->uid])->find(); + if (!$user) { + return $this->fail('用户不存在'); + } + $info = PluginUser::getTokenInfo($user, true); + return $this->resData($info); + } else { + return $this->fail('验证码错误'); + } + } + public function getTwofaList(Request $request) + { + $PluginUserTwofaSecret = PluginUserTwofaSecret::where(['uid' => $request->uid])->order('is_default desc,id desc')->select()->scene('external')->each(function ($item) { + $item->totp_app_text = TotpApp::get($item->totp_app); + }); + return $this->resData($PluginUserTwofaSecret); + } + public function setDefaultTwofa(Request $request) + { + $id = $request->post('id'); + $PluginUserTwofaSecret = PluginUserTwofaSecret::where(['uid' => $request->uid, 'id' => $id])->find(); + if (!$PluginUserTwofaSecret) { + return $this->fail('两步验证密钥不存在'); + } + Db::startTrans(); + try { + PluginUserTwofaSecret::where(['uid' => $request->uid])->update(['is_default' => 0]); + $PluginUserTwofaSecret->is_default = 1; + $PluginUserTwofaSecret->save(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->success('设置成功'); + } + public function deleteTwofa(Request $request) + { + $id = $request->post('id'); + $PluginUserTwofaSecret = PluginUserTwofaSecret::where(['uid' => $request->uid, 'id' => $id])->find(); + if (!$PluginUserTwofaSecret) { + return $this->fail('两步验证密钥不存在'); + } + Db::startTrans(); + try { + // 判断是否还有其它两步验证密钥 + $PluginUserTwofaSecretOther = PluginUserTwofaSecret::where(['uid' => $request->uid])->where('id', '<>', $id)->order('id desc')->find(); + if ($PluginUserTwofaSecretOther) { + if ($PluginUserTwofaSecret->is_default == 1) { + $PluginUserTwofaSecretOther->is_default = 1; + $PluginUserTwofaSecretOther->save(); + } + } else { + $PluginUser = PluginUser::where(['id' => $request->uid])->find(); + $PluginUser->twofa_state = State::NO['value']; + $PluginUser->save(); + } + $PluginUserTwofaSecret->delete(); + Db::commit(); + } catch (\Throwable $th) { + Db::rollback(); + return $this->exception($th); + } + return $this->success('删除成功'); + } + /** + * 获取用户未使用的邀请码 + * @author:1950781041@qq.com + * @Date:2026-01-07 + */ + public function getUnusedInvitationCode(Request $request) + { + $PluginUserInvitationCode = PluginUserInvitationCode::where(['uid' => $request->uid]) + ->with(['useUser']) + ->where('channels_uid', $request->channels_uid) + ->where('state', State::YES['value']) + ->select(); + return $this->resData($PluginUserInvitationCode); + } + /** + * 用户绑定邀请码 + * @author:1950781041@qq.com + * @Date:2026-01-15 + */ + public function bindInvitationCode(Request $request) + { + $code = $request->post('code'); + $PluginUserInvitationCode = PluginUserInvitationCode::where(['code' => $code])->find(); + if (!$PluginUserInvitationCode) { + return $this->fail('邀请码不存在'); + } + if ($PluginUserInvitationCode->uid == $request->uid) { + return $this->fail('不能邀请自己'); + } + if (!empty($PluginUserInvitationCode->use_uid)) { + return $this->fail('邀请码已使用'); + } + if ($PluginUserInvitationCode->status != 'unused') { + return $this->fail('邀请码已使用'); + } + if ($PluginUserInvitationCode->state != State::YES['value']) { + return $this->fail('邀请码已失效'); + } + $PluginUserInvitationCode->status = 'used'; + $PluginUserInvitationCode->use_uid = $request->uid; + $PluginUserInvitationCode->use_time = date('Y-m-d H:i:s'); + $PluginUserInvitationCode->save(); + $PluginUser = PluginUser::where(['id' => $request->uid])->find(); + $PluginUser->activation_time = date('Y-m-d H:i:s'); + $PluginUser->puid = $PluginUserInvitationCode->uid; + $PluginUser->save(); + return $this->success('绑定成功'); + } + /** + * 用户绑定电话 + * @author:1950781041@qq.com + * @Date:2026-01-15 + */ + public function bindMobile(Request $request) + { + $mobile = $request->post('username'); + $username = $request->post('username'); + $vcode = $request->post('vcode'); + $token = $request->post('token'); + if (empty($username) || empty($vcode) || empty($token)) { + return $this->fail('参数错误'); + } + Vcode::check($username, $vcode, VcodeScene::BIND_MOBILE['value'], $token); + $PluginUser = PluginUser::where(['mobile' => $mobile, 'channels_uid' => $request->channels_uid])->find(); + if ($PluginUser) { + PluginUser::where(['id' => $request->uid])->update(['username' => '']); + $wechat = PluginUserWechat::where(['uid' => $request->uid])->find(); + $wechat->uid = $PluginUser->id; + $wechat->save(); + return $this->resData(PluginUser::getTokenInfo($PluginUser, true)); + } + $PluginUser = PluginUser::where(['id' => $request->uid])->find(); + if ($PluginUser->mobile) { + return $this->fail('电话已绑定'); + } + $PluginUser->mobile = $mobile; + $PluginUser->save(); + return $this->resData(PluginUser::getTokenInfo($PluginUser, true)); + } + + /** + * 修改密码 + * @author:1950781041@qq.com + * @Date:2026-01-17 + */ + public function setPassword(Request $request) + { + $password = $request->post('password'); + $token = $request->post('token'); + $vcode = $request->post('vcode'); + $username = $request->post('username'); + if (empty($password) || empty($token) || empty($vcode) || empty($username)) { + return $this->fail('参数错误'); + } + Vcode::check($username, $vcode, VcodeScene::BIND_MOBILE['value'], $token); + $PluginUser = PluginUser::where(['id' => $request->uid])->find(); + $PluginUser->password = $password; + $PluginUser->save(); + return $this->success('修改成功'); + } + + public function wallet(Request $request) + { + $wallet = PluginFinanceWallet::where(['uid' => $request->uid])->find(); + if (!$wallet) { + return $this->fail('钱包不存在'); + } + return $this->resData($wallet); + } + + /** + * 验证邀请码是否被使用 + * @author:1950781041@qq.com + * @Date:2026-01-26 + */ + public function checkInvitationCode(Request $request) + { + $code = $request->post('code'); + $PluginUserInvitationCode = PluginUserInvitationCode::where(['code' => $code])->find(); + if (!$PluginUserInvitationCode) { + return $this->fail('邀请码不存在'); + } + if ($PluginUserInvitationCode->uid == $request->uid) { + return $this->fail('不能邀请自己'); + } + if (!empty($PluginUserInvitationCode->use_uid)) { + return $this->fail('邀请码已使用'); + } + if ($PluginUserInvitationCode->status != 'unused') { + return $this->fail('邀请码已使用'); + } + return $this->success(); + } +} diff --git a/fastmovie-admin/plugin/user/app/control/controller/BalanceController.php b/fastmovie-admin/plugin/user/app/control/controller/BalanceController.php new file mode 100644 index 0000000..b360372 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/control/controller/BalanceController.php @@ -0,0 +1,204 @@ +model = new PluginUserBill(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'placeholder' => '邮箱搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('action', '变化类型', 'select', '', [ + 'options' => MoneyAction::getOptions(), + 'props' => [ + 'placeholder' => '变化类型搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + $builder->add('num', '变化金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('before', '变化前金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('after', '变化后金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('remarks', '备注', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('action', '变化类型', [ + 'props' => [ + 'width' => '120px' + ], + 'component' => [ + 'name' => 'tag', + 'options' => MoneyAction::getOptions() + ] + ]); + $builder->add('scene', '触发场景', [ + 'props' => [ + 'width' => '120px' + ], + 'component' => [ + 'name' => 'tag', + 'options' => BillScene::getOptions() + ] + ]); + $builder->add('create_time', '创建时间', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['ub.channels_uid', '=', $request->channels_uid]; + $username = $request->get('username'); + if ($username) { + $where[] = ['u.username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['u.mobile', 'like', "%{$mobile}%"]; + } + $email = $request->get('email'); + if ($email) { + $where[] = ['u.email', 'like', "%{$email}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['u.id', '=', $uid]; + } + $list = PluginUserBill::alias('ub')->join('plugin_user u', 'ub.uid = u.id') + ->field('ub.*,u.nickname,u.headimg,u.username,u.mobile,u.email') + ->where($where) + ->order('ub.id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/user/app/control/controller/InvitationCodeController.php b/fastmovie-admin/plugin/user/app/control/controller/InvitationCodeController.php new file mode 100644 index 0000000..1e601bb --- /dev/null +++ b/fastmovie-admin/plugin/user/app/control/controller/InvitationCodeController.php @@ -0,0 +1,306 @@ +model = new PluginUserInvitationCode(); + } + + public function indexGetTable(Request $request) + { + $builder = new TableBuilder(); + $builder->addHeader(); + $builder->addHeaderAction('生成邀请码', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/user/control/InvitationCode/create', + 'props' => [ + 'title' => '生成邀请码' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('code', '邀请码', 'input', '', [ + 'props' => [ + 'placeholder' => '邀请码搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', '创建者', 'input', '', [ + 'props' => [ + 'placeholder' => '创建者搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('use_uid', '使用者', 'input', '', [ + 'props' => [ + 'placeholder' => '使用者搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('status', '状态', 'select', '', [ + 'options' => [ + ['label' => '未使用', 'value' => 'unused'], + ['label' => '已使用', 'value' => 'used'] + ], + 'props' => [ + 'placeholder' => '状态搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('state', '启用状态', 'select', '', [ + 'options' => State::getOptions(), + 'props' => [ + 'placeholder' => '启用状态搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + ]); + $builder->add('code', '邀请码', [ + ]); + $builder->add('userinfo', '创建者', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'user.nickname', + 'avatar' => 'user.headimg', + 'info' => 'user.username', + 'tags' => [ + [ + 'field' => 'uid', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('status', '使用状态', [ + 'component' => [ + 'name' => 'tag', + 'options' => [ + [ + 'label' => '未使用', + 'value' => 'unused', + 'props' => [ + 'type' => 'info' + ] + ], + [ + 'label' => '已使用', + 'value' => 'used', + 'props' => [ + 'type' => 'success' + ] + ] + ] + ] + ]); + $builder->add('use_userinfo', '使用用户', [ + 'props' => [ + 'width' => '300px' + ], + 'where' => [ + ['status', '=', 'used'] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'useUser.nickname', + 'avatar' => 'useUser.headimg', + 'info' => 'useUser.username', + 'tags' => [ + [ + 'field' => 'use_uid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ] + ] + ] + ]); + $builder->add('use_time', '使用时间', [ + 'where' => [ + ['status', '=', 'used'] + ], + 'props' => [ + 'width' => '180px' + ] + ]); + $builder->add('state', '启用状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/user/control/InvitationCode/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'use_time', + 'label' => '使用' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['uid', '=', $uid]; + } + $use_uid = $request->get('use_uid'); + if ($use_uid) { + $where[] = ['use_uid', '=', $use_uid]; + } + $code = $request->get('code'); + if ($code) { + $where[] = ['code', 'like', "%{$code}%"]; + } + + $status = $request->get('status'); + if ($status) { + $where[] = ['status', '=', $status]; + } + + $state = $request->get('state'); + if ($state !== null && $state !== '') { + $where[] = ['state', '=', $state]; + } + + $list = PluginUserInvitationCode::alias('ic') + ->where($where) + ->with([ + 'user' => function ($query) { + $query->field('id,nickname,headimg,username,channels_uid'); + }, + 'useUser' => function ($query) { + $query->field('id,nickname,headimg,username,channels_uid'); + } + ]) + ->order('ic.id desc') + ->paginate($limit); + + return $this->resData($list); + } + + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + $num = isset($D['num']) ? intval($D['num']) : 1; + // 如果不选择用户或为空,默认为0(平台创建) + $uid = isset($D['uid']) && $D['uid'] !== '' && $D['uid'] !== null ? intval($D['uid']) : 0; + + if ($num < 1 || $num > 100) { + return $this->fail('生成数量必须在1-100之间'); + } + + try { + PluginUserInvitationCode::addCode($uid, $num, $request->channels_uid); + return $this->success('生成成功'); + } catch (\Throwable $th) { + return $this->fail($th->getMessage()); + } + } + + $builder = new FormBuilder(null, null, [ + 'size' => 'large', + 'labelPosition' => 'right', + 'label-width' => '120px' + ]); + + $Component = new ComponentBuilder; + $builder->add('uid', '创建者', 'select', '', [ + 'remote' => [ + 'url' => '/app/user/control/User/query', + ], + 'props' => [ + 'clearable' => true, + 'filterable' => true, + 'remote' => true, + 'placeholder' => '不选择则默认为平台创建' + ], + 'prompt' => [ + $Component->add('text', ['default' => '不选择创建者则默认为平台创建(uid=0)'], ['type' => 'info', 'size' => 'small'])->builder() + ] + ]); + $builder->add('num', '生成数量', 'input-number', 1, [ + 'required' => true, + 'props' => [ + 'min' => 1, + 'max' => 100, + 'controls' => true + ], + 'prompt' => [ + $Component->add('text', ['default' => '一次最多生成100个邀请码'], ['type' => 'info', 'size' => 'small'])->builder() + ] + ]); + + return $this->resData($builder); + } + + public function indexUpdateState(Request $request) + { + $id = $request->post('id'); + $field = $request->post('field'); + $value = $request->post('value'); + $model = $this->model->where(['id' => $id])->find(); + if (!$model) { + return $this->fail('数据不存在'); + } + $model->{$field} = $value; + if ($model->save()) { + return $this->success(); + } + return $this->fail('操作失败'); + } +} diff --git a/fastmovie-admin/plugin/user/app/control/controller/PointsController.php b/fastmovie-admin/plugin/user/app/control/controller/PointsController.php new file mode 100644 index 0000000..6c78a4f --- /dev/null +++ b/fastmovie-admin/plugin/user/app/control/controller/PointsController.php @@ -0,0 +1,217 @@ +model = new PluginUserPointsBill(); + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'placeholder' => '邮箱搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('action', '变化类型', 'select', '', [ + 'options' => MoneyAction::getOptions(), + 'props' => [ + 'placeholder' => '变化类型搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('time', '时间', 'date-picker', '', [ + 'props' => [ + 'value-format' => "YYYY-MM-DD", + 'format' => "YYYY-MM-DD", + 'type' => 'daterange', + 'placeholder' => '时间范围搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'width' => '200px' + ] + ]); + + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + $builder->add('num', '变化金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('before', '变化前金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('after', '变化后金额', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('remarks', '备注', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder->add('action', '变化类型', [ + 'props' => [ + 'width' => '120px' + ], + 'component' => [ + 'name' => 'tag', + 'options' => MoneyAction::getOptions() + ] + ]); + $builder->add('scene', '触发场景', [ + 'props' => [ + 'width' => '120px' + ], + 'component' => [ + 'name' => 'tag', + 'options' => PointsBillScene::getOptions() + ] + ]); + $builder->add('create_time', '创建时间', [ + 'props' => [ + 'width' => '200px' + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['ub.channels_uid', '=', $request->channels_uid]; + $username = $request->get('username'); + if ($username) { + $where[] = ['u.username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['u.mobile', 'like', "%{$mobile}%"]; + } + $email = $request->get('email'); + if ($email) { + $where[] = ['u.email', 'like', "%{$email}%"]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['u.id', '=', $uid]; + } + $time = $request->get('time'); + if ($time) { + $where[] = ['ub.create_time', 'between', $time]; + } + $list = PluginUserPointsBill::alias('ub')->join('plugin_user u', 'ub.uid = u.id') + ->field('ub.*,u.nickname,u.headimg,u.username,u.mobile,u.email') + ->where($where) + ->order('ub.id desc') + ->paginate($limit); + return $this->resData($list); + } +} diff --git a/fastmovie-admin/plugin/user/app/control/controller/SettingController.php b/fastmovie-admin/plugin/user/app/control/controller/SettingController.php new file mode 100644 index 0000000..f989679 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/control/controller/SettingController.php @@ -0,0 +1,15 @@ +builder(); + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/app/control/controller/SystemController.php b/fastmovie-admin/plugin/user/app/control/controller/SystemController.php new file mode 100644 index 0000000..378485d --- /dev/null +++ b/fastmovie-admin/plugin/user/app/control/controller/SystemController.php @@ -0,0 +1,22 @@ +channels_uid = $request->channels_uid; + return $this->builder(); + } +} diff --git a/fastmovie-admin/plugin/user/app/control/controller/UserController.php b/fastmovie-admin/plugin/user/app/control/controller/UserController.php new file mode 100644 index 0000000..796a684 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/control/controller/UserController.php @@ -0,0 +1,409 @@ +model = new PluginUser; + } + public function indexGetTable(Request $request) + { + $builder = new TableBuilder; + $builder->addAction('操作', [ + 'width' => '100px', + 'fixed' => 'right' + ]); + $builder->addTableAction('编辑', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/user/control/User/update', + 'props' => [ + 'title' => '编辑《ID:{id}》用户' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'primary', + 'size' => 'small' + ] + ] + ]); + $builder->addHeader(); + $builder->addHeaderAction('创建用户', [ + 'model' => Action::DIALOG['value'], + 'path' => '/app/user/control/User/create', + 'props' => [ + 'title' => '创建用户' + ], + 'component' => [ + 'name' => 'button', + 'props' => [ + 'type' => 'success' + ] + ] + ]); + $formBuilder = new FormBuilder(null, null, [ + 'inline' => true + ]); + $formBuilder->add('nickname', '昵称', 'input', '', [ + 'props' => [ + 'placeholder' => '昵称搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('username', '账号', 'input', '', [ + 'props' => [ + 'placeholder' => '账号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('mobile', '手机号', 'input', '', [ + 'props' => [ + 'placeholder' => '手机号搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('email', '邮箱', 'input', '', [ + 'props' => [ + 'placeholder' => '邮箱搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('uid', 'UID', 'input', '', [ + 'props' => [ + 'placeholder' => 'UID搜索', + 'clearable' => true + ] + ]); + $formBuilder->add('puid', '上级UID', 'input', '', [ + 'props' => [ + 'placeholder' => '上级UID搜索', + 'clearable' => true + ] + ]); + $builder->addScreen($formBuilder); + $builder->add('id', 'ID', [ + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('userinfo', '用户', [ + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'nickname', + 'avatar' => 'headimg', + 'info' => 'username', + 'nicknameTags' => [ + [ + 'field' => 'new_text', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ], + [ + 'field' => 'week_text', + 'props' => [ + 'type' => 'warning', + 'size' => 'small' + ] + ], + [ + 'field' => 'month_text', + 'props' => [ + 'type' => 'info', + 'size' => 'small' + ] + ] + ] + ] + ], + 'props' => [ + 'minWidth' => '300px' + ] + ]); + $builder->add('puserinfo', '上级', [ + 'where' => [ + ['puid', '!=', null] + ], + 'component' => [ + 'name' => 'table-userinfo', + 'props' => [ + 'nickname' => 'parent.nickname', + 'avatar' => 'parent,headimg', + 'info' => 'parent.mobile', + 'tags' => [ + [ + 'field' => 'puid', + 'props' => [ + 'type' => 'success', + 'size' => 'small' + ] + ] + ], + ] + ], + 'props' => [ + 'width' => '300px' + ] + ]); + $builder->add('contact', '联系方式', [ + 'props' => [ + 'width' => '280px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'mobile', + 'label' => '手机号' + ], + [ + 'field' => 'email', + 'label' => '邮箱' + ] + ] + ] + ] + ]); + $builder->add('state', '状态', [ + 'component' => [ + 'name' => 'switch', + 'api' => '/app/user/control/User/indexUpdateState', + 'props' => [ + 'active-value' => State::YES['value'], + 'inactive-value' => State::NO['value'] + ] + ], + 'props' => [ + 'width' => '100px' + ] + ]); + $builder->add('online_time', '活动', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'activation_time', + 'label' => '激活' + ], + [ + 'field' => 'login_time', + 'label' => '登录' + ], + [ + 'component' => 'tag', + 'field' => 'login_ip', + 'label' => '登录IP', + 'props' => [ + 'size' => 'small', + ] + ] + ] + ] + ] + ]); + $builder->add('create_time', '时间', [ + 'props' => [ + 'width' => '200px' + ], + 'component' => [ + 'name' => 'table-times', + 'props' => [ + 'group' => [ + [ + 'field' => 'create_time', + 'label' => '创建' + ], + [ + 'field' => 'update_time', + 'label' => '更新' + ] + ] + ] + ] + ]); + $builder = $builder->builder(); + return $this->resData($builder); + } + public function query(Request $request) + { + $query = $request->post('query'); + if (empty($query)) { + return $this->resData([]); + } + $where = []; + $where[] = ['nickname|username|mobile|email', 'like', "%{$query}%"]; + return $this->resData(PluginUser::options($where)); + } + public function index(Request $request) + { + $limit = $request->get('limit', 10); + $where = []; + $where[] = ['channels_uid', '=', $request->channels_uid]; + $username = $request->get('username'); + if ($username) { + $where[] = ['username', 'like', "%{$username}%"]; + } + $mobile = $request->get('mobile'); + if ($mobile) { + $where[] = ['mobile', 'like', "%{$mobile}%"]; + } + $email = $request->get('email'); + if ($email) { + $where[] = ['email', 'like', "%{$email}%"]; + } + $puid = $request->get('puid'); + if ($puid) { + $where[] = ['puid', '=', $puid]; + } + $uid = $request->get('uid'); + if ($uid) { + $where[] = ['id', '=', $uid]; + } + $list = PluginUser::where($where)->with(['parent' => function ($query) { + $query->field('id,nickname,headimg,mobile'); + }]) + ->order('id desc')->paginate($limit)->each(function ($item) { + // 三天以内创建的 + $create_time = strtotime($item->create_time); + $create_time = time() - $create_time; + if ($create_time < 3 * 24 * 60 * 60) { + $item->new_text = '新用户'; + } elseif ($create_time < 7 * 24 * 60 * 60) { + $item->week_text = '一周内'; + } elseif ($create_time < 30 * 24 * 60 * 60) { + $item->month_text = '30天内'; + } + }); + return $this->resData($list); + } + public function create(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $D['channels_uid'] = $request->channels_uid; + HelperUser::create($D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('创建成功'); + } + $builder = $this->getFormBuilder(); + $Component = new ComponentBuilder; + $builder->add('activation_time', '激活时间', 'date-picker', null, [ + 'prompt' => [ + $Component->add('text', ['default' => '不选择时间则不激活,由用户前台登录后自动激活'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'placeholder' => '选择激活时间', + 'type' => 'datetime', + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'value-format' => 'YYYY-MM-DD HH:mm:ss' + ] + ]); + return $this->resData($builder); + } + public function update(Request $request) + { + if ($request->method() === 'POST') { + $D = $request->post(); + try { + $D['channels_uid'] = $request->channels_uid; + HelperUser::update($D['id'], $D); + } catch (\Throwable $th) { + return $this->exception($th); + } + return $this->success('更新成功'); + } + $id = $request->get('id'); + $User = PluginUser::where(['id' => $id])->withoutField('password')->find(); + $builder = $this->getFormBuilder(); + $builder->setData($User->toArray()); + return $this->resData($builder); + } + private function getFormBuilder() + { + $builder = new FormBuilder(null, null, [ + 'labelPosition' => 'right', + 'label-width' => "200px", + 'class' => 'w-80 mx-auto', + 'size' => 'large', + ]); + $Component = new ComponentBuilder; + $builder->add('nickname', '昵称', 'input', '', [ + 'required' => true, + 'maxlength' => 30, + 'show-word-limit' => true + ]); + $builder->add('headimg', '头像', 'bundle', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '支持jpg、png、jpeg格式,大小不超过2M'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'accept' => 'image/jpeg,image/png,image/jpg', + 'multiple' => 1, + 'size' => 2 + ] + ]); + $builder->add('username', '账号', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '支持字母、数字、下划线,长度不超过30个字符'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '可用于账号登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + $builder->add('mobile', '手机号', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '中国大陆11位手机号'], ['type' => 'info', 'size' => 'small'])->builder(), + $Component->add('text', ['default' => '可用于账号,短信验证码登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 11, + 'show-word-limit' => true + ] + ]); + $builder->add('email', '邮箱', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '可用于账号,邮箱验证码登录'], ['type' => 'success', 'size' => 'small'])->builder() + ], + 'props' => [ + 'maxlength' => 100, + 'show-word-limit' => true + ] + ]); + $builder->add('password', '密码', 'input', '', [ + 'prompt' => [ + $Component->add('text', ['default' => '不修改密码请留空'], ['type' => 'info', 'size' => 'small'])->builder() + ], + 'props' => [ + 'placeholder' => '不修改密码请留空', + 'maxlength' => 30, + 'show-word-limit' => true + ] + ]); + return $builder; + } +} diff --git a/fastmovie-admin/plugin/user/app/functions.php b/fastmovie-admin/plugin/user/app/functions.php new file mode 100644 index 0000000..ac9ec9f --- /dev/null +++ b/fastmovie-admin/plugin/user/app/functions.php @@ -0,0 +1,6 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + public function parent() + { + return $this->hasOne(PluginUser::class, 'id', 'puid'); + } + public function wallet() + { + return $this->hasOne(PluginFinanceWallet::class, 'uid', 'id'); + } + public function getHeadimgAttr($value, $data) + { + if(empty($value)){ + return ''; + } + return Uploads::url($data['channels_uid'], $value); + } + public function setHeadimgAttr($value, $data) + { + return Uploads::path($data['channels_uid'], $value); + } + public function setPasswordAttr($value) + { + return $value ? Password::encrypt($value) : $value; + } + public static function options($where = []) + { + $models = self::where($where)->field('id,nickname,mobile')->select(); + $data = []; + foreach ($models as $item) { + $data[] = [ + 'label' => $item->nickname, + 'value' => $item->id, + 'tips' => "UID:{$item->id},M:{$item->mobile}" + ]; + } + return $data; + } + public static function genUser($id) + { + $id = (int)$id; + return str_replace(['+', '/'], ['-', '_'], Rsa::encryptNumber($id)); + } + public static function getUidByUser($user) + { + return Rsa::decryptNumber(str_replace(['-', '_'], ['+', '/'], $user)); + } + public static function getTokenInfo($model, $twofa = false) + { + $request = request(); + /* 重组用户信息 */ + $User = new \stdClass; + $User->user = self::genUser($model->id); + $User->nickname = $model->nickname; + $User->headimg = $model->headimg; + $User->id = $model->id; + $User->username = $model->username; + $User->username_time = $model->username_time ? 30 - ceil((time() - strtotime($model->username_time)) / 86400) : 0; + $User->mobile = Str::mask($model->mobile); + $User->email = Str::mask($model->email); + $User->password = $model->password ? 1 : 0; + $User->create_time = $model->create_time; + $User->last_login_time = $model->login_time; + $User->last_login_ip = $model->login_ip; + $User->twofa_state = $model->twofa_state; + $User->activation_time = $model->activation_time; + $PluginUserWechat = PluginUserWechat::where(['uid' => $model->id])->field('openid,unionid,mp_openid,nickname,headimg,subscribe')->find(); + if ($PluginUserWechat) { + $User->wechat = $PluginUserWechat; + } + $User->permissions = []; + if ($model->mobile) { + $User->permissions[] = UserPermission::MOBILE['value']; + } + if ($model->email) { + $User->permissions[] = UserPermission::EMAIL['value']; + } + if ($model->twofa_state) { + $User->permissions[] = UserPermission::TWOFA['value']; + } + if ($model->username) { + $User->permissions[] = UserPermission::USERNAME['value']; + } + if ($model->password) { + $User->permissions[] = UserPermission::PASSWORD['value']; + } + if ($model->realname) { + $User->permissions[] = UserPermission::REALNAME['value']; + } + if ($model->mobile && $model->realname) { + $User->permissions[] = UserPermission::BUY['value']; + } + $pluginConfig = glob(base_path("plugin/*/api/{$request->app}/PublicController.php")); + foreach ($pluginConfig as $path) { + $plugin_name = basename(dirname(dirname(dirname($path)))); + if ($plugin_name == 'user') { + continue; + } + $class = 'plugin\\' . $plugin_name . "\\api\\{$request->app}\\PublicController"; + if (!class_exists($class)) { + continue; + } + $plugin = new $class; + if (method_exists($plugin, 'appendUserInfo')) { + $plugin->appendUserInfo($User, $model); + } + } + /* 生成token */ + $data = new \stdClass; + $data->uid = $model->id; + $data->channels_uid = $model->channels_uid; + $data->username = $model->username; + $data->mobile = $model->mobile; + $data->email = $model->email; + $data->twofa_state = $model->twofa_state; + if ($twofa) { + if ($twofa === true) { + $data->twofa = [ + 'expire' => time() + (config('oauth.expire') / 2), + 'time' => time(), + ]; + } else { + $data->twofa = $twofa; + } + } + $User->token = Auth::setPrefix('CONTROL')->encrypt($data); + if ($request->token) { + Auth::setPrefix('CONTROL')->refresh($request->token, 60); + } + return $User; + } + public static function onAfterRead($model) + { + if (empty($model->nickname)) { + if (!empty($model->mobile)) { + $model->nickname = 'FM-' . substr($model->mobile, -4); + } else { + $model->nickname = '未命名的用户'; + } + } + } + public static function onBeforeWrite($model) + { + if (empty($model->nickname)) { + if (!empty($model->mobile)) { + $model->nickname = 'FM-' . substr($model->mobile, -4); + } else { + $model->nickname = 'FM-' . Str::random(); + } + } else { + $model->nickname = iconv('UTF-8', 'UTF-8//IGNORE', $model->nickname); + } + } + public static function onAfterInsert($model) + { + $PluginFinanceWallet = new PluginFinanceWallet; + $PluginFinanceWallet->channels_uid = $model->channels_uid; + $PluginFinanceWallet->uid = $model->id; + $PluginFinanceWallet->save(); + Event::emit(EventName::USER_REGISTER['value'], $model); + } +} diff --git a/fastmovie-admin/plugin/user/app/model/PluginUserBill.php b/fastmovie-admin/plugin/user/app/model/PluginUserBill.php new file mode 100644 index 0000000..c667ebd --- /dev/null +++ b/fastmovie-admin/plugin/user/app/model/PluginUserBill.php @@ -0,0 +1,96 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } + protected function getOptions(): array + { + return [ + 'type' => [ + 'num' => 'float', + 'before' => 'float', + 'after' => 'float', + ], + ]; + } + public static function consume($order) + { + $uid = $order->uid; + $wallet = self::where('uid', $uid)->find(); + if (!$wallet) { + $wallet = new self(); + $wallet->uid = $uid; + } + if ($wallet->balance < $order->money) { + throw new \Exception('余额不足'); + } + PluginUserBill::create([ + 'uid' => $uid, + 'form_id' => $order->id, + 'num' => $order->money, + 'before' => $wallet->balance, + 'after' => $wallet->balance - $order->money, + 'remarks' => $order->title . ' 订单号:' . $order->trade_no, + 'action' => MoneyAction::DECREASE['value'], + 'scene' => $order->type, + ]); + $wallet->balance = Db::raw('balance - ' . $order->money); + $wallet->balance_used = Db::raw('balance_used + ' . $order->money); + $wallet->save(); + } + public static function expend($uid, $money, $title) + { + $wallet = self::where('uid', $uid)->find(); + if (!$wallet) { + $wallet = new self(); + $wallet->uid = $uid; + } + PluginUserBill::create([ + 'uid' => $uid, + 'num' => $money, + 'before' => $wallet->balance, + 'after' => $wallet->balance - $money, + 'remarks' => $title, + 'action' => MoneyAction::DECREASE['value'], + 'scene' => BillScene::ADMIN['value'], + ]); + $wallet->balance = Db::raw('balance - ' . $money); + $wallet->balance_sum = Db::raw('balance_sum - ' . $money); + $wallet->save(); + } + public static function income($uid, $money, $title) + { + $wallet = self::where('uid', $uid)->find(); + if (!$wallet) { + $wallet = new self(); + $wallet->uid = $uid; + } + PluginUserBill::create([ + 'uid' => $uid, + 'num' => $money, + 'before' => $wallet->balance, + 'after' => $wallet->balance + $money, + 'remarks' => $title, + 'action' => MoneyAction::INCREASE['value'], + 'scene' => BillScene::ADMIN['value'], + ]); + $wallet->balance = Db::raw('balance + ' . $money); + $wallet->balance_sum = Db::raw('balance_sum + ' . $money); + $wallet->save(); + } + protected function sceneExternal() + { + return $this->visible(['id', 'create_time', 'num', 'before', 'after', 'remarks', 'action', 'scene', 'action_text', 'scene_text']); + } +} diff --git a/fastmovie-admin/plugin/user/app/model/PluginUserInvitationCode.php b/fastmovie-admin/plugin/user/app/model/PluginUserInvitationCode.php new file mode 100644 index 0000000..2b299d9 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/model/PluginUserInvitationCode.php @@ -0,0 +1,70 @@ +hasOne(PluginUser::class, 'id', 'uid'); + } + + // 关联使用邀请码的用户 + public function useUser() + { + return $this->hasOne(PluginUser::class, 'id', 'use_uid'); + } + + public static function addCode($uid, $num = 1, $channels_uid = 0) + { + $needNum = $num; + $finalCodes = []; + while ($needNum > 0) { + $codes = []; + for ($i = 0; $i < $needNum * 2; $i++) { + $code = UtilsStr::random(6); + //转大写 + $codes[] = strtoupper($code); + } + $codes = array_unique($codes); + $exists = self::whereIn('code', $codes) + ->column('code'); + $available = array_values(array_diff($codes, $exists)); + $take = array_slice($available, 0, $needNum); + $finalCodes = array_merge($finalCodes, $take); + $needNum -= count($take); + } + $insertData = []; + foreach ($finalCodes as $code) { + $insertData[] = [ + 'uid' => $uid, + 'code' => $code, + 'channels_uid' => $channels_uid, + 'create_time' => date('Y-m-d H:i:s'), + 'update_time' => date('Y-m-d H:i:s'), + ]; + } + self::insertAll($insertData); + return $finalCodes; + } + + + public function onAfterUpdate($model) + { + if ($model->status == 'used') { + $register = new Config('register', 'user', $model->channels_uid); + if ($register->invite_reward_points) { + Account::incPoints($model->uid, $model->channels_uid, $register->invite_reward_points, PointsBillScene::INVITE['value'], 0, '邀请奖励积分'); + } + PluginUserInvitationCode::where('id', $model->id)->update(['points' => $register->invite_reward_points]); + } + } +} diff --git a/fastmovie-admin/plugin/user/app/model/PluginUserPoints.php b/fastmovie-admin/plugin/user/app/model/PluginUserPoints.php new file mode 100644 index 0000000..d950956 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/model/PluginUserPoints.php @@ -0,0 +1,9 @@ +hasOne(PluginChannelsUser::class, 'id', 'channels_uid'); + } +} diff --git a/fastmovie-admin/plugin/user/app/model/PluginUserTwofaSecret.php b/fastmovie-admin/plugin/user/app/model/PluginUserTwofaSecret.php new file mode 100644 index 0000000..65d29a6 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/model/PluginUserTwofaSecret.php @@ -0,0 +1,13 @@ +visible(['id','totp_app','is_default','create_time','totp_app_text']); + } +} diff --git a/fastmovie-admin/plugin/user/app/model/PluginUserVip.php b/fastmovie-admin/plugin/user/app/model/PluginUserVip.php new file mode 100644 index 0000000..eed9cd5 --- /dev/null +++ b/fastmovie-admin/plugin/user/app/model/PluginUserVip.php @@ -0,0 +1,10 @@ +nickname)){ + $model->nickname= iconv('UTF-8', 'UTF-8//IGNORE', $model->nickname); + } + } +} diff --git a/fastmovie-admin/plugin/user/config/app.php b/fastmovie-admin/plugin/user/config/app.php new file mode 100644 index 0000000..bc3fd0f --- /dev/null +++ b/fastmovie-admin/plugin/user/config/app.php @@ -0,0 +1,9 @@ + true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false +]; diff --git a/fastmovie-admin/plugin/user/config/autoload.php b/fastmovie-admin/plugin/user/config/autoload.php new file mode 100644 index 0000000..4c53f5c --- /dev/null +++ b/fastmovie-admin/plugin/user/config/autoload.php @@ -0,0 +1,6 @@ + [ + base_path() . '/plugin/user/app/functions.php', + ] +]; diff --git a/fastmovie-admin/plugin/user/config/container.php b/fastmovie-admin/plugin/user/config/container.php new file mode 100644 index 0000000..34d7f5c --- /dev/null +++ b/fastmovie-admin/plugin/user/config/container.php @@ -0,0 +1,2 @@ + [ + [\plugin\user\event\WECHAT_OFFICIAL_ACCOUNT::class, 'subscribe'] + ], + EventName::WECHAT_OFFICIAL_ACCOUNT_UNSUBSCRLBE['value'] => [ + [\plugin\user\event\WECHAT_OFFICIAL_ACCOUNT::class, 'unsubscribe'] + ], + EventName::USER_REGISTER['value'] => [ + [\plugin\user\event\User::class, 'code'], + [\plugin\user\event\User::class, 'points'], + ], +]; diff --git a/fastmovie-admin/plugin/user/config/exception.php b/fastmovie-admin/plugin/user/config/exception.php new file mode 100644 index 0000000..cad6520 --- /dev/null +++ b/fastmovie-admin/plugin/user/config/exception.php @@ -0,0 +1,5 @@ + app\exception\Handler::class, +]; diff --git a/fastmovie-admin/plugin/user/config/log.php b/fastmovie-admin/plugin/user/config/log.php new file mode 100644 index 0000000..8a0e0a9 --- /dev/null +++ b/fastmovie-admin/plugin/user/config/log.php @@ -0,0 +1,20 @@ + [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/user.log', + 7, + \Monolog\Level::Debug, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/fastmovie-admin/plugin/user/config/middleware.php b/fastmovie-admin/plugin/user/config/middleware.php new file mode 100644 index 0000000..3e04c72 --- /dev/null +++ b/fastmovie-admin/plugin/user/config/middleware.php @@ -0,0 +1,18 @@ + [ + app\expose\middleware\Platform::class + ], + 'admin' => [ + app\expose\middleware\AdminAuth::class + ], + 'control' => [ + plugin\control\expose\middleware\ControlAuth::class + ], + 'api' => [ + plugin\control\expose\middleware\DomainMapping::class, + plugin\user\expose\middleware\UserAuth::class, + plugin\user\expose\middleware\Twofa::class, + ] +]; diff --git a/fastmovie-admin/plugin/user/config/process.php b/fastmovie-admin/plugin/user/config/process.php new file mode 100644 index 0000000..881ab67 --- /dev/null +++ b/fastmovie-admin/plugin/user/config/process.php @@ -0,0 +1,2 @@ + '变更站点付费比例', + 'field' => 'site_change_fee_ratio', + 'value' => 50, + 'component' => 'input-number', + #配置项额外属性 + 'extra' => [ + #是否必填 + 'required' => true, + #提示 + 'prompt' => [ + $Component->add('text', ['default' => '当用户变更站点授权域名和IP时,编辑次数不足,需要支付的站点费用比例,范围0-100,默认50'], ['type' => 'info', 'size' => 'small']) + ->builder() + ], + 'props' => [ + 'min' => 0, + 'max' => 100, + 'step' => 1, + 'precision' => 0, + 'controls' => false + ] + ] + ] +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/config/settings/register.php b/fastmovie-admin/plugin/user/config/settings/register.php new file mode 100644 index 0000000..412a2fe --- /dev/null +++ b/fastmovie-admin/plugin/user/config/settings/register.php @@ -0,0 +1,52 @@ + '注册邀请码数量', + 'field' => 'register_invitation_code_num', + 'value' => 4, + 'component' => 'input-number', + 'extra' => [ + 'required' => true, + 'props' => [ + 'min' => 0, + 'step' => 1, + 'precision' => 0, + 'controls' => false + ] + ] + ], + [ + 'title' => '注册赠送积分', + 'field' => 'register_give_points', + 'value' => 100, + 'component' => 'input-number', + 'extra' => [ + 'required' => true, + 'props' => [ + 'min' => 0, + 'step' => 1, + 'precision' => 0, + 'controls' => false + ] + ] + ], + [ + 'title' => '邀请奖励积分', + 'field' => 'invite_reward_points', + 'value' => 100, + 'component' => 'input-number', + 'extra' => [ + 'required' => true, + 'props' => [ + 'min' => 0, + 'step' => 1, + 'precision' => 0, + 'controls' => false + ] + ] + ] +]; diff --git a/fastmovie-admin/plugin/user/config/translation.php b/fastmovie-admin/plugin/user/config/translation.php new file mode 100644 index 0000000..fd28022 --- /dev/null +++ b/fastmovie-admin/plugin/user/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/user/resource/translations', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/config/view.php b/fastmovie-admin/plugin/user/config/view.php new file mode 100644 index 0000000..fe78682 --- /dev/null +++ b/fastmovie-admin/plugin/user/config/view.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +// use support\view\ThinkPHP; +use support\ThinkPHP; + +return [ + 'handler' => ThinkPHP::class, +]; diff --git a/fastmovie-admin/plugin/user/control.json b/fastmovie-admin/plugin/user/control.json new file mode 100644 index 0000000..b8d285b --- /dev/null +++ b/fastmovie-admin/plugin/user/control.json @@ -0,0 +1,92 @@ +{ + "title": "User", + "icon": "User", + "path": "/app/user/control", + "sort": 1, + "children": [ + { + "title": "User List", + "icon": "User", + "path": "/app/user/control/User/index", + "component": "table", + "children": [ + { + "title": "Create User", + "path": "/app/user/control/User/create", + "show": false, + "component": "form" + }, + { + "title": "Edit User", + "show": false, + "path": "/app/user/control/User/update", + "component": "form" + }, + { + "title": "Update Status", + "show": false, + "path": "/app/user/control/User/indexUpdateState", + "component": "" + } + ] + }, + { + "title": "用户钱包", + "icon": "ShoppingCart", + "path": "/app/finance/control/Wallet/index", + "component": "table", + "children": [ + { + "title": "余额充值", + "path": "/app/finance/control/Wallet/recharge", + "component": "form", + "show": false + }, + { + "title": "积分充值", + "path": "/app/finance/control/Wallet/rechargePoints", + "component": "form", + "show": false + }, + { + "title": "临时积分充值", + "path": "/app/finance/control/Wallet/rechargeTmpPoints", + "component": "form", + "show": false + } + ] + }, + { + "title": "余额账单", + "icon": "User", + "path": "/app/user/control/Balance/index", + "component": "table" + }, + { + "title": "积分账单", + "icon": "User", + "path": "/app/user/control/Points/index", + "component": "table" + }, + { + "title": "注册设置", + "icon": "Setting", + "path": "/app/user/control/System/register", + "component": "form" + }, + { + "title": "邀请码", + "icon": "User", + "path": "/app/user/control/InvitationCode/index", + "component": "table", + "children": [ + { + "title": "创建邀请码", + "path": "/app/user/control/InvitationCode/create", + "component": "form", + "show": false + } + ] + } + ] +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/event/InvitationCode.php b/fastmovie-admin/plugin/user/event/InvitationCode.php new file mode 100644 index 0000000..1e69eb4 --- /dev/null +++ b/fastmovie-admin/plugin/user/event/InvitationCode.php @@ -0,0 +1,18 @@ +id; + $register = new Config('register', 'user', $event->channels_uid); + if ($register->register_invitation_code_num) { + PluginUserInvitationCode::addCode($uid, $register->register_invitation_code_num,$event->channels_uid ); + } + } +} diff --git a/fastmovie-admin/plugin/user/event/User.php b/fastmovie-admin/plugin/user/event/User.php new file mode 100644 index 0000000..a8cb5fb --- /dev/null +++ b/fastmovie-admin/plugin/user/event/User.php @@ -0,0 +1,28 @@ +id; + $register = new Config('register', 'user', $event->channels_uid); + if ($register->register_invitation_code_num) { + PluginUserInvitationCode::addCode($uid, $register->register_invitation_code_num, $event->channels_uid); + } + } + public function points($event) + { + $uid = $event->id; + $register = new Config('register', 'user', $event->channels_uid); + if ($register->register_give_points) { + Account::incPoints($uid, $event->channels_uid, $register->register_give_points, PointsBillScene::REGISTER['value'], 0, '注册赠送积分'); + } + } +} diff --git a/fastmovie-admin/plugin/user/event/WECHAT_OFFICIAL_ACCOUNT.php b/fastmovie-admin/plugin/user/event/WECHAT_OFFICIAL_ACCOUNT.php new file mode 100644 index 0000000..068864a --- /dev/null +++ b/fastmovie-admin/plugin/user/event/WECHAT_OFFICIAL_ACCOUNT.php @@ -0,0 +1,32 @@ +find(); + if ($UserWechat) { + $UserWechat->subscribe = State::YES['value']; + $UserWechat->save(); + } else { + $UserWechat = new PluginUserWechat(); + $UserWechat->channels_uid = $data['channels_uid']; + $UserWechat->openid = $data['FromUserName']; + $UserWechat->subscribe = State::YES['value']; + $UserWechat->save(); + } + } + public function unsubscribe(mixed $data) + { + $UserWechat = PluginUserWechat::where('openid', $data['FromUserName'])->find(); + if ($UserWechat) { + $UserWechat->subscribe = State::NO['value']; + $UserWechat->save(); + } + } +} diff --git a/fastmovie-admin/plugin/user/expose/helper/User.php b/fastmovie-admin/plugin/user/expose/helper/User.php new file mode 100644 index 0000000..956f70e --- /dev/null +++ b/fastmovie-admin/plugin/user/expose/helper/User.php @@ -0,0 +1,188 @@ + $data['username']])->find(); + if ($Find) { + return $Find; + } + $insterData['username'] = $data['username']; + } + if (!empty($data['mobile'])) { + $Find = PluginUser::where(['mobile' => $data['mobile']])->find(); + if ($Find) { + return $Find; + } + $insterData['mobile'] = $data['mobile']; + } + if (!empty($data['email'])) { + $insterData['email'] = $data['email']; + } + if (!empty($data['password'])) { + $insterData['password'] = $data['password']; + } + if (!empty($data['activation_time'])) { + $insterData['activation_time'] = $data['activation_time']; + } + if (!empty($data['nickname'])) { + $insterData['nickname'] = $data['nickname']; + } + if (!empty($data['headimg'])) { + $insterData['headimg'] = $data['headimg']; + } + $model = new PluginUser(); + $model->save($insterData); + // Event::emit(EventName::USER_REGISTER['value'], $model); + return $model; + } + /** + * 注册 + * + * @param array $data + * @param string $data['icode'] 邀请码 + * @param string $data['username'] 用户名(唯一性),可登录 + * @param string $data['mobile'] 手机号(唯一性),可登录 + * @param string $data['email'] 邮箱 + * @param string $data['password'] 密码 + * + * @return PluginUser + */ + public static function register($data) + { + $request = request(); + if ($request->icode) { + $data['icode'] = $request->icode; + } + return self::create($data); + } + /** + * 更新 + * + * @param int $uid 用户ID + * @param array $data 更新数据 + * @param string $data['username'] 用户名 + * @param string $data['mobile'] 手机号 + * @param string $data['email'] 邮箱 + * @return PluginUser + */ + public static function update($uid, $data) + { + $user = PluginUser::where(['id' => $uid, 'channels_uid' => $data['channels_uid']])->find(); + if (!$user) { + throw new \Exception('用户不存在'); + } + if (!empty($data['password'])) { + $user->password = $data['password']; + } + if (!empty($data['username'])) { + $Find = PluginUser::where(['username' => $data['username']])->find(); + if ($Find && $Find->id != $uid) { + throw new \Exception('用户名已存在'); + } + $user->username = $data['username']; + $user->username_time = date('Y-m-d H:i:s'); + } + if (!empty($data['mobile'])) { + $Find = PluginUser::where(['mobile' => $data['mobile']])->find(); + if ($Find && $Find->id != $uid) { + throw new \Exception('手机号已存在'); + } + $user->mobile = $data['mobile']; + } + if (!empty($data['email'])) { + $user->email = $data['email']; + } + if (!empty($data['nickname'])) { + $user->nickname = $data['nickname']; + } + if (!empty($data['headimg'])) { + $user->headimg = $data['headimg']; + } + $user->save(); + Event::emit(EventName::USER_UPDATE['value'], $user); + return $user; + } + /** + * 获取用户信息 + * + * @param int $uid 用户ID + * @return PluginUser + */ + public static function info($uid) + { + $Find = PluginUser::where(['id' => $uid])->find(); + if (!$Find) { + throw new \Exception('用户不存在'); + } + return $Find; + } + /** + * 获取邀请码 + * + * @param int $uid 用户ID + * @return string + */ + public static function getIcode($uid) + { + $user = PluginUser::where(['id' => $uid])->find(); + return Rsa::encryptNumber($user->id); + } + /** + * 邀请码换取用户ID + * + * @param string $icode 邀请码 + * @return int + */ + public static function getUidByIcode($icode) + { + return Rsa::decryptNumber($icode); + } +} diff --git a/fastmovie-admin/plugin/user/expose/helper/WechatOfficialAccount.php b/fastmovie-admin/plugin/user/expose/helper/WechatOfficialAccount.php new file mode 100644 index 0000000..3e5758d --- /dev/null +++ b/fastmovie-admin/plugin/user/expose/helper/WechatOfficialAccount.php @@ -0,0 +1,111 @@ +FromUserName)->find(); + if ($PluginUserWechat) { + if ($PluginUserWechat->uid && $PluginUserWechat->uid != $params['uid']) { + Redis::set($this->EventKey . '.bind', json_encode([ + 'status' => 'fail', + 'message' => '当前微信已绑定其他账号' + ]), 'EX', 60); + throw new \Exception('当前微信已绑定其他账号'); + } + } else { + $PluginUserWechat = new PluginUserWechat(); + $PluginUserWechat->openid = $this->FromUserName; + } + $PluginUserWechat->uid = $params['uid']; + $PluginUserWechat->subscribe = State::YES['value']; + $PluginUserWechat->channels_uid = $params['channels_uid']; + $PluginUserWechat->save(); + Redis::set($this->EventKey . '.bind', json_encode([ + 'status' => 'success', + 'uid' => $params['uid'] + ]), 'EX', 60); + return '绑定成功'; + } + public function login($params) + { + try { + $PluginUserInvitationCode = null; + if (!empty($params['code'])) { + $PluginUserInvitationCode = PluginUserInvitationCode::where(['code' => $params['code']])->find(); + if (!$PluginUserInvitationCode) { + $PluginUserInvitationCode = null; + } + if ($PluginUserInvitationCode->status != 'unused') { + $PluginUserInvitationCode = null; + } + if ($PluginUserInvitationCode->state != State::YES['value']) { + $PluginUserInvitationCode = null; + } + } + + $PluginUserWechat = PluginUserWechat::where('openid', $this->FromUserName)->find(); + if ($PluginUserWechat) { + if ($PluginUserWechat->uid) { + Redis::set($this->EventKey . '.login', json_encode([ + 'status' => 'success', + 'uid' => $PluginUserWechat->uid, + ]), 'EX', 60); + return '登录成功'; + } + } else { + $PluginUserWechat = new PluginUserWechat(); + $PluginUserWechat->openid = $this->FromUserName; + $PluginUserWechat->subscribe = State::YES['value']; + $PluginUserWechat->channels_uid = $params['channels_uid']; + } + $data = [ + 'channels_uid' => $params['channels_uid'], + 'username' => $this->FromUserName, + 'state' => State::YES['value'], + ]; + + if ($PluginUserInvitationCode != null) { + $data['puid'] = $PluginUserInvitationCode->uid; + $data['activation_time'] = date('Y-m-d H:i:s'); + } + $UserModel = new PluginUser(); + $UserModel->save($data); + + if ($PluginUserInvitationCode != null) { + $PluginUserInvitationCode->status = 'used'; + $PluginUserInvitationCode->use_uid = $UserModel->id; + $PluginUserInvitationCode->use_time = date('Y-m-d H:i:s'); + $PluginUserInvitationCode->save(); + } + $PluginUserWechat->uid = $UserModel->id; + $PluginUserWechat->save(); + Redis::set($this->EventKey . '.login', json_encode([ + 'status' => 'success', + 'id' => $PluginUserWechat->id, + 'uid' => $UserModel->id + ]), 'EX', 60); + return '登录成功'; + } catch (\Throwable $th) { + Redis::set($this->EventKey . '.login', json_encode([ + 'status' => 'fail', + 'message' => $th->getMessage() + ]), 'EX', 60); + return $th->getMessage(); + } + } +} diff --git a/fastmovie-admin/plugin/user/expose/middleware/Twofa.php b/fastmovie-admin/plugin/user/expose/middleware/Twofa.php new file mode 100644 index 0000000..5e1de57 --- /dev/null +++ b/fastmovie-admin/plugin/user/expose/middleware/Twofa.php @@ -0,0 +1,79 @@ +method() == 'OPTIONS') { + return response('', 204); + } + // 鉴权检测 + try { + $this->Authorization($request); + $response = $next($request); + } catch (\Throwable $th) { + if ($request->expectsJson()) { + $response = $this->exception($th); + } else { + if (config('app.debug')) { + Log::error($th->getTraceAsString()); + throw $th; + } else { + $response = response($th->getMessage(), 500); + } + } + } + return $response; + } + public function Authorization(Request $request) + { + # 无控制器地址 + if (!$request->controller) { + return true; + } + # 获取控制器鉴权信息 + $controller = new \ReflectionClass($request->controller); + $properties = $controller->getDefaultProperties(); + $notNeedLogin = $properties['notNeedLogin'] ?? []; + $notNeedTwofa = array_merge($notNeedLogin, $properties['notNeedTwofa'] ?? []); + $notNeedTwofaAll = $properties['notNeedTwofaAll'] ?? false; + if ($notNeedTwofaAll) { + $notNeedTwofa = array_merge($notNeedTwofa, [$request->action]); + } + # 是否强制登录 + if (in_array($request->action, $notNeedTwofa)) { + return true; + } + if ($request->source === 'saas') { + return true; + } + if (!$request->user) { + return true; + } + if (empty($request->user['twofa_state'])) { + return true; + } + if (empty($request->user['twofa']['expire'])) { + throw new Exception('需要两步验证', ResponseCode::NEED_TWOFA,['action'=>'logout']); + } + if (time() > $request->user['twofa']['expire']) { + throw new Exception('需要两步验证', ResponseCode::NEED_TWOFA,['action'=>'logout']); + } + $request->twofa_state = true; + $request->twofa = $request->user['twofa']; + } +} diff --git a/fastmovie-admin/plugin/user/expose/middleware/UserAuth.php b/fastmovie-admin/plugin/user/expose/middleware/UserAuth.php new file mode 100644 index 0000000..6359be7 --- /dev/null +++ b/fastmovie-admin/plugin/user/expose/middleware/UserAuth.php @@ -0,0 +1,109 @@ +method() == 'OPTIONS') { + return response('', 204); + } + // 鉴权检测 + try { + $this->Authorization($request); + $response = $next($request); + } catch (\Throwable $th) { + if ($request->expectsJson()) { + $response = $this->exception($th); + } else { + if (config('app.debug')) { + Log::error($th->getTraceAsString()); + throw $th; + } else { + $response = response($th->getMessage(), 500); + } + } + } + return $response; + } + + /** + * 业务逻辑 + * @author 贵州猿创科技有限公司 + * @Email unknown.renlong@gmail.com + * @DateTime 2023-03-11 + */ + public function Authorization(Request $request) + { + # 无控制器地址 + if (!$request->controller) { + return true; + } + # 获取控制器鉴权信息 + $controller = new \ReflectionClass($request->controller); + $properties = $controller->getDefaultProperties(); + # 无需登录方法 + $notNeedLogin = $properties['notNeedLogin'] ?? []; + # 是否整个控制器都不需要登录 + $notNeedLoginAll = $properties['notNeedLoginAll'] ?? false; + if ($notNeedLoginAll) { + $notNeedLogin = array_merge($notNeedLogin, [$request->action]); + } + # 是否强制登录 + $isForceLogin = true; + if (in_array($request->action, $notNeedLogin)) { + $isForceLogin = false; + } + try { + # 令牌验证 + $token = $request->header('authorization'); + if (!$token) { + throw new Exception('请先登录', ResponseCode::NEED_LOGIN); + } + $user = Auth::setPrefix('CONTROL')->decrypt($token); + if (!$user) { + throw new TokenExpireException(); + } + $request->token = $token; + $request->user = $user; + $request->channels_uid = $user['channels_uid']; + $request->uid = $user['uid']; + } catch (TokenExpireException $e) { + if ($isForceLogin) { + throw new Exception('登录已过期,请重新登录', ResponseCode::NEED_LOGIN); + } + } catch (LockException $e) { + if ($isForceLogin) { + throw new Exception($e->getMessage(), ResponseCode::LOCK); + } + } catch (\Throwable $th) { + if ($isForceLogin) { + throw new \Exception($th->getMessage(), ResponseCode::NEED_LOGIN); + } + } + if ($request->uid) { + $User = PluginUser::where('id', $request->uid)->find(); + if ($User->state != State::YES['value']) { + throw new Exception("您的账号已被禁用", ResponseCode::NEED_LOGIN); + } + } + } +} diff --git a/fastmovie-admin/plugin/user/package.json b/fastmovie-admin/plugin/user/package.json new file mode 100644 index 0000000..c496739 --- /dev/null +++ b/fastmovie-admin/plugin/user/package.json @@ -0,0 +1,10 @@ +{ + "name": "interface-example", + "title": "应用示例项目", + "version": "0.0.1", + "description": "应用市场", + "author": { + "name": "余心", + "email": "unknown.renloong@gmail.com" + } +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/en/admin.php b/fastmovie-admin/plugin/user/resource/translations/en/admin.php new file mode 100644 index 0000000..56f91e9 --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/en/admin.php @@ -0,0 +1,9 @@ + 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'form_basic_title'=>'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/en/api.php b/fastmovie-admin/plugin/user/resource/translations/en/api.php new file mode 100644 index 0000000..45a7854 --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/en/api.php @@ -0,0 +1,6 @@ + 'Success', + 'fail' => 'Fail', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/en/control.php b/fastmovie-admin/plugin/user/resource/translations/en/control.php new file mode 100644 index 0000000..56f91e9 --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/en/control.php @@ -0,0 +1,9 @@ + 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'form_basic_title'=>'Basic', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/en/messages.php b/fastmovie-admin/plugin/user/resource/translations/en/messages.php new file mode 100644 index 0000000..b787574 --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/zh_CN/admin.php b/fastmovie-admin/plugin/user/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..15ae448 --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/zh_CN/admin.php @@ -0,0 +1,9 @@ + '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'form_basic_title'=>'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/zh_CN/api.php b/fastmovie-admin/plugin/user/resource/translations/zh_CN/api.php new file mode 100644 index 0000000..40bd25b --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/zh_CN/api.php @@ -0,0 +1,6 @@ + '成功', + 'fail' => '失败', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/zh_CN/control.php b/fastmovie-admin/plugin/user/resource/translations/zh_CN/control.php new file mode 100644 index 0000000..15ae448 --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/zh_CN/control.php @@ -0,0 +1,9 @@ + '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'form_basic_title'=>'基础信息', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/resource/translations/zh_CN/messages.php b/fastmovie-admin/plugin/user/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..99cb14f --- /dev/null +++ b/fastmovie-admin/plugin/user/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功', +]; \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/utils/enum/CertificateType.php b/fastmovie-admin/plugin/user/utils/enum/CertificateType.php new file mode 100644 index 0000000..141b75c --- /dev/null +++ b/fastmovie-admin/plugin/user/utils/enum/CertificateType.php @@ -0,0 +1,33 @@ + '身份证', + 'value' => 'idcard', + ]; + const PASSPORT = [ + 'label' => '护照', + 'value' => 'passport', + ]; + const HK_MC_PASSPORT = [ + 'label' => '港澳通行证', + 'value' => 'hk_mc_passport', + ]; + const TW_PASSPORT = [ + 'label' => '台湾通行证', + 'value' => 'tw_passport', + ]; + const TW_RETURN_PASSPORT = [ + 'label' => '台胞证', + 'value' => 'tw_return_passport', + ]; + const HK_MC_RETURN_PASSPORT = [ + 'label' => '返乡证', + 'value' => 'hk_mc_return_passport', + ]; +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/utils/enum/MoneyAction.php b/fastmovie-admin/plugin/user/utils/enum/MoneyAction.php new file mode 100644 index 0000000..254f2f1 --- /dev/null +++ b/fastmovie-admin/plugin/user/utils/enum/MoneyAction.php @@ -0,0 +1,23 @@ + '增加', + 'value' => 'increase', + 'props'=>[ + 'type'=>'danger' + ] + ]; + const DECREASE = [ + 'label' => '减少', + 'value' => 'decrease', + 'props'=>[ + 'type'=>'success' + ] + ]; +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/utils/enum/PropsType.php b/fastmovie-admin/plugin/user/utils/enum/PropsType.php new file mode 100644 index 0000000..b1276aa --- /dev/null +++ b/fastmovie-admin/plugin/user/utils/enum/PropsType.php @@ -0,0 +1,51 @@ + '无', + 'value' => 'normal', + 'props'=>[ + 'type'=>'default' + ] + ]; + const PRIMARY = [ + 'label' => '主要', + 'value' => 'primary', + 'props'=>[ + 'type'=>'primary' + ] + ]; + const SUCCESS = [ + 'label' => '成功', + 'value' => 'success', + 'props'=>[ + 'type'=>'success' + ] + ]; + const INFO = [ + 'label' => '信息', + 'value' => 'info', + 'props'=>[ + 'type'=>'info' + ] + ]; + const WARNING = [ + 'label' => '警告', + 'value' => 'warning', + 'props'=>[ + 'type'=>'warning' + ] + ]; + const DANGER = [ + 'label' => '危险', + 'value' => 'danger', + 'props'=>[ + 'type'=>'danger' + ] + ]; +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/utils/enum/TotpApp.php b/fastmovie-admin/plugin/user/utils/enum/TotpApp.php new file mode 100644 index 0000000..69c2a36 --- /dev/null +++ b/fastmovie-admin/plugin/user/utils/enum/TotpApp.php @@ -0,0 +1,37 @@ + '1Password', + 'value' => '1password' + ]; + const GOOGLE = [ + 'label' => 'Google Authenticator', + 'value' => 'google' + ]; + const MICROSOFT = [ + 'label' => 'Microsoft Authenticator', + 'value' => 'microsoft' + ]; + const AUTHY = [ + 'label' => 'Authy', + 'value' => 'authy' + ]; + const DUO = [ + 'label' => 'Duo Mobile', + 'value' => 'duo' + ]; + const LASTPASS = [ + 'label' => 'LastPass Authenticator', + 'value' => 'lastpass' + ]; + const BITWARDEN = [ + 'label' => 'Bitwarden Authenticator', + 'value' => 'bitwarden' + ]; +} diff --git a/fastmovie-admin/plugin/user/utils/enum/UserPermission.php b/fastmovie-admin/plugin/user/utils/enum/UserPermission.php new file mode 100644 index 0000000..11bc972 --- /dev/null +++ b/fastmovie-admin/plugin/user/utils/enum/UserPermission.php @@ -0,0 +1,41 @@ + '已绑定手机号', + 'value' => 'mobile' + ]; + const EMAIL = [ + 'label' => '已绑定邮箱', + 'value' => 'email' + ]; + const TWOFA = [ + 'label' => '已开启两步验证', + 'value' => 'twofa' + ]; + const WECHAT = [ + 'label' => '已绑定微信', + 'value' => 'wechat' + ]; + const USERNAME = [ + 'label' => '已设置用户名', + 'value' => 'username' + ]; + const PASSWORD = [ + 'label' => '已设置密码', + 'value' => 'password' + ]; + const REALNAME = [ + 'label' => '已实名认证', + 'value' => 'realname' + ]; + const BUY = [ + 'label' => '可以购买', + 'value' => 'buy' + ]; +} \ No newline at end of file diff --git a/fastmovie-admin/plugin/user/utils/enum/VcodeScene.php b/fastmovie-admin/plugin/user/utils/enum/VcodeScene.php new file mode 100644 index 0000000..0fc7079 --- /dev/null +++ b/fastmovie-admin/plugin/user/utils/enum/VcodeScene.php @@ -0,0 +1,34 @@ + '登录', + 'value' => 'login', + 'variable' => ['code' => '验证码', 'expire' => '过期时间'] + ]; + const SIGNUP = [ + 'label' => '注册', + 'value' => 'signup', + 'variable' => ['code' => '验证码', 'expire' => '过期时间'] + ]; + const BIND_MOBILE = [ + 'label' => '绑定手机号', + 'value' => 'bind_mobile', + 'variable' => ['username' => '用户昵称', 'code' => '验证码', 'expire' => '过期时间'] + ]; + const BIND_EMAIL = [ + 'label' => '绑定邮箱', + 'value' => 'bind_email', + 'variable' => ['username' => '用户昵称', 'code' => '验证码', 'expire' => '过期时间'] + ]; + const SET_PASSWORD = [ + 'label' => '设置密码', + 'value' => 'set_password', + 'variable' => ['username' => '用户昵称', 'code' => '验证码', 'expire' => '过期时间','password' => '密码'] + ]; +} diff --git a/fastmovie-admin/process/Monitor.php b/fastmovie-admin/process/Monitor.php new file mode 100644 index 0000000..92b3716 --- /dev/null +++ b/fastmovie-admin/process/Monitor.php @@ -0,0 +1,243 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace process; + +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; +use Workerman\Timer; +use Workerman\Worker; + +/** + * Class FileMonitor + * @package process + */ +class Monitor +{ + /** + * @var array + */ + protected $paths = []; + + /** + * @var array + */ + protected $extensions = []; + + /** + * @var string + */ + public static $lockFile = __DIR__ . '/../runtime/monitor.lock'; + + /** + * Pause monitor + * @return void + */ + public static function pause() + { + file_put_contents(static::$lockFile, time()); + } + + /** + * Resume monitor + * @return void + */ + public static function resume(): void + { + clearstatcache(); + if (is_file(static::$lockFile)) { + unlink(static::$lockFile); + } + } + + /** + * Whether monitor is paused + * @return bool + */ + public static function isPaused(): bool + { + clearstatcache(); + return file_exists(static::$lockFile); + } + + /** + * FileMonitor constructor. + * @param $monitorDir + * @param $monitorExtensions + * @param array $options + */ + public function __construct($monitorDir, $monitorExtensions, array $options = []) + { + static::resume(); + $this->paths = (array)$monitorDir; + $this->extensions = $monitorExtensions; + if (!Worker::getAllWorkers()) { + return; + } + $disableFunctions = explode(',', ini_get('disable_functions')); + if (in_array('exec', $disableFunctions, true)) { + echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n"; + } else { + if ($options['enable_file_monitor'] ?? true) { + Timer::add(1, function () { + $this->checkAllFilesChange(); + }); + } + } + + $memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null); + if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) { + Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]); + } + } + + /** + * @param $monitorDir + * @return bool + */ + public function checkFilesChange($monitorDir): bool + { + static $lastMtime, $tooManyFilesCheck; + if (!$lastMtime) { + $lastMtime = time(); + } + clearstatcache(); + if (!is_dir($monitorDir)) { + if (!is_file($monitorDir)) { + return false; + } + $iterator = [new SplFileInfo($monitorDir)]; + } else { + // recursive traversal directory + $dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new RecursiveIteratorIterator($dirIterator); + } + $count = 0; + foreach ($iterator as $file) { + $count ++; + /** var SplFileInfo $file */ + if (is_dir($file->getRealPath())) { + continue; + } + // check mtime + if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) { + $var = 0; + exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var); + $lastMtime = $file->getMTime(); + if ($var) { + continue; + } + echo $file . " update and reload\n"; + // send SIGUSR1 signal to master process for reload + if (DIRECTORY_SEPARATOR === '/') { + posix_kill(posix_getppid(), SIGUSR1); + } else { + return true; + } + break; + } + } + if (!$tooManyFilesCheck && $count > 1000) { + echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n"; + $tooManyFilesCheck = 1; + } + return false; + } + + /** + * @return bool + */ + public function checkAllFilesChange(): bool + { + if (static::isPaused()) { + return false; + } + foreach ($this->paths as $path) { + if ($this->checkFilesChange($path)) { + return true; + } + } + return false; + } + + /** + * @param $memoryLimit + * @return void + */ + public function checkMemory($memoryLimit) + { + if (static::isPaused() || $memoryLimit <= 0) { + return; + } + $ppid = posix_getppid(); + $childrenFile = "/proc/$ppid/task/$ppid/children"; + if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) { + return; + } + foreach (explode(' ', $children) as $pid) { + $pid = (int)$pid; + $statusFile = "/proc/$pid/status"; + if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) { + continue; + } + $mem = 0; + if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) { + $mem = $match[1]; + } + $mem = (int)($mem / 1024); + if ($mem >= $memoryLimit) { + posix_kill($pid, SIGINT); + } + } + } + + /** + * Get memory limit + * @return float + */ + protected function getMemoryLimit($memoryLimit) + { + if ($memoryLimit === 0) { + return 0; + } + $usePhpIni = false; + if (!$memoryLimit) { + $memoryLimit = ini_get('memory_limit'); + $usePhpIni = true; + } + + if ($memoryLimit == -1) { + return 0; + } + $unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]); + if ($unit === 'g') { + $memoryLimit = 1024 * (int)$memoryLimit; + } else if ($unit === 'm') { + $memoryLimit = (int)$memoryLimit; + } else if ($unit === 'k') { + $memoryLimit = ((int)$memoryLimit / 1024); + } else { + $memoryLimit = ((int)$memoryLimit / (1024 * 1024)); + } + if ($memoryLimit < 30) { + $memoryLimit = 30; + } + if ($usePhpIni) { + $memoryLimit = (int)(0.8 * $memoryLimit); + } + return $memoryLimit; + } +} diff --git a/fastmovie-admin/public/404.html b/fastmovie-admin/public/404.html new file mode 100644 index 0000000..2bde119 --- /dev/null +++ b/fastmovie-admin/public/404.html @@ -0,0 +1,12 @@ + + + 404 Not Found - webman + + +
+

404 Not Found

+
+
+
webman
+ + diff --git a/fastmovie-admin/public/article/.gitignore b/fastmovie-admin/public/article/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/fastmovie-admin/public/article/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/fastmovie-admin/public/favicon.ico b/fastmovie-admin/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b9f722e0eeed8dc7f3639bd3295a7b0376b98eca GIT binary patch literal 4286 zcmbuDc}!GC9LI-{lvLYdn`%sIykgTPHU44S)cR&9&1g0})ukRu+b zsI4iH+Tum=wxm?+g|1x7X%O&eYs0dFEXuJv?|~SRU%$V%yN_jIWzi)cX5Q?Z`F`g& z^PBfT2tWLcA20Z~I66XzQ9_8f0h+{zfZMeXzI$!B14qNqe+Ms>0t^d|k|#hpcn^q~ z;CVg3dWG2j%ifLf3af($0x zFqHZ@&|>{}K^tIw<{IEO5%LUi;PHV0JOSi}L_G^kF%-24L#d6mnNZLTT>4y=fJs32 z>T3n#v;Jb>u1$s#{kl4$sCDYsB{mVVOo|!b%Y>Or`)Ip-WMT~hhU&qo|1rvkx`)Dc z3eN*q@6-It4HR~%KIfv@I@xr$`J)wSwu_-_>;xY!T~{fgJ^dN=dHt(3eaw6QQ5(&Q zwbSe!_bDQ$p3rC#TC=DQ1t2=B$w95sS3oCG8?O+V{O^KwP2A z#JKe59cj_##AAmi*eL7j1FC3lq3dmSsf7^;4_WBvlMm=;#>pmNa_ zbN6mz6D^FlYx?YA-dafqFMD`^pBqx8l17ELWJ#L6Tv|SjloLN+GW0x%FxdnZY!{J* zO>bu3zE@n@Xrm(g1kiF6>)}|NZnR~9*Ma-cgVuwiY@)K+DvIix zgRsv#uD3~jt+NXh3XhXo&T-yFZlPoKYP(QS z*TQ4y`W)ZW)fHq&oMLL^ISVq=dQNX$&xlL$K1x# z*7(XDl{RE3ywB*nWQl#}H3ly4Cosk+Zyun@=e8?%w36eyYL}hLt={+9v0{aOIj?AY z&fVhxb&f+O2n7A&0BwGLmwI}bwM^2!bF$w)ca6_E*FOh3kc$sBYV94a`E&Ugq}JFv zUI2YO@bePdQ$U|M@LuPAo?E48`R&s6T0+lcHq#kXA^lZmqVmQj`0L|ALEl9{RJ+9? zBwEvF5AT3{ps$4w`&^&nANhHd5c>@!tuxTZHQ}@=WhEJJ-SqVEFZ8Fd2lo#&S)Z8* z_5ps5X+Ag(Ntt}s_vQln>$*J0l6s4#{qxQkI&ok-*hafkmQvcTMCbFO_kQ%<4_@^2 zzeWJ+2eNu`#%p8DK354;Lol7p6fud@%wulH<1{44HRY0Ks{v&TtXTdEpTWA|O34LUvWxqcXMUTb6W9?1XuHtqZ&nG)l^rKAmuHEs4) zB@AIt%@K#v30S=-d0QdDYxZT9iy zD*2z!VqR{nZf6e)`*;iRdk3Gh&V8v;dak0+*M06>|6-+l@VPb}Ttsel z@|n4OhceLL37RLIYdu32dP)cEJ5k+^SlHD-nud)91PZ{o+ zzq%#)Txl0+$CLm(L*M6p4y`kYcOBka9)liSH{d#+x;9dMR}dLv$B}VbAQ?vokWn}= z3x8s4pi9FKr63Hx5d?v8ATSXag$OVU;co-o_?H7Z`k|xHI-r3NX+lrJ=!7o`Ul1Dq E2mbpSrT_o{ literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/icon.svg b/fastmovie-admin/public/icon.svg new file mode 100644 index 0000000..a588c13 --- /dev/null +++ b/fastmovie-admin/public/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fastmovie-admin/public/index.html b/fastmovie-admin/public/index.html new file mode 100644 index 0000000..dee1c58 --- /dev/null +++ b/fastmovie-admin/public/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + Loading... + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/fastmovie-admin/public/index.php b/fastmovie-admin/public/index.php new file mode 100644 index 0000000..7237959 --- /dev/null +++ b/fastmovie-admin/public/index.php @@ -0,0 +1,22 @@ +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 KMNUMnLSTY4dZgk2 literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/static/apps/platform/mini_alipay.png b/fastmovie-admin/public/static/apps/platform/mini_alipay.png new file mode 100644 index 0000000000000000000000000000000000000000..5860371040f8fce3284d1656e275bad55b11f72c GIT binary patch literal 4350 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 literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/static/apps/platform/mini_douyin.png b/fastmovie-admin/public/static/apps/platform/mini_douyin.png new file mode 100644 index 0000000000000000000000000000000000000000..56d3ad0501eff0f9cbc5e61867e9e8c238270222 GIT binary patch literal 5897 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 literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/static/apps/platform/mini_jd.png b/fastmovie-admin/public/static/apps/platform/mini_jd.png new file mode 100644 index 0000000000000000000000000000000000000000..e535e71eb41c148670ed6834c3fd290800237c48 GIT binary patch literal 5468 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 literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/static/apps/platform/other.png b/fastmovie-admin/public/static/apps/platform/other.png new file mode 100644 index 0000000000000000000000000000000000000000..f8e0644874aa9ae1cf2f1ec9e959b5134714c2c7 GIT binary patch literal 3242 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 literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/static/apps/platform/pc.png b/fastmovie-admin/public/static/apps/platform/pc.png new file mode 100644 index 0000000000000000000000000000000000000000..d42e5c22fd8de26bfb36d125bc0a72d739e53abe GIT binary patch literal 5129 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 literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/static/bg.jpg b/fastmovie-admin/public/static/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..67b7c892967757c4755f455084469b949a16abb1 GIT binary patch literal 53034 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 { + /** + * 设置储存数据 + * @param key 键 + * @param data 值 + * @param expire 过期时间(秒) + * @returns Promise + */ + const set = (key, data, expire) => { + return new Promise((resolve, reject) => { + const obj = { + expire: 0, + data: data, + } + if (expire !== undefined) { + obj.expire = Date.now() + expire * 1000 + } + globalThis.localStorage.setItem(key, JSON.stringify(obj)); + if (get(key) === null) { + reject(); + } else { + resolve(true); + } + }) + } + /** + * 获取储存数据 + * @param key 键 + * @returns StorageInterface + */ + const get = (key) => { + const data = globalThis.localStorage.getItem(key); + + if (data === null) { + return null; + } + const ret = JSON.parse(data); + + if (ret?.expire > 0 && ret?.expire < Date.now()) { + return null; + } + return ret?.data; + } + return { set, get }; +} +function icodeRun(){ + // 获取地址栏参数 + const url = new URL(globalThis.location.href); + const icode = url.searchParams.get('icode'); + const storage = useStorage(); + // 保存到本地存储 + if (icode&&!storage.get('CONTROL.ICODE')) { + storage.set('CONTROL.ICODE', icode); + console.log('icode.js loaded') + } +} +icodeRun() \ No newline at end of file diff --git a/fastmovie-admin/public/static/image/alipay.png b/fastmovie-admin/public/static/image/alipay.png new file mode 100644 index 0000000000000000000000000000000000000000..fd3bfef71d951322e1d92066d39a8d86ec6aae1c GIT binary patch literal 6212 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 lL79Ov3(5?XSx{!6{1U2oqhby5IW_D`Km5+C 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 JPDHLkV1n{j?3Mrk literal 0 HcmV?d00001 diff --git a/fastmovie-admin/public/static/version.json b/fastmovie-admin/public/static/version.json new file mode 100644 index 0000000..3026882 --- /dev/null +++ b/fastmovie-admin/public/static/version.json @@ -0,0 +1,3 @@ +{ + "version": "0.1.3" +} \ No newline at end of file diff --git a/fastmovie-admin/public/template/fastmovie.html b/fastmovie-admin/public/template/fastmovie.html new file mode 100644 index 0000000..1d8dc6c --- /dev/null +++ b/fastmovie-admin/public/template/fastmovie.html @@ -0,0 +1,18 @@ + + + + + + + loading... + + + + + + + + +
+ + diff --git a/fastmovie-admin/resource/translations/en/admin.php b/fastmovie-admin/resource/translations/en/admin.php new file mode 100644 index 0000000..a2a633c --- /dev/null +++ b/fastmovie-admin/resource/translations/en/admin.php @@ -0,0 +1,68 @@ + 'Dashboard', + 'Console' => 'Console', + 'User' => 'User', + 'User List' => 'User List', + 'Create User' => 'Create User', + 'Edit User' => 'Edit User', + 'Update Status' => 'Update Status', + 'Functional' => 'Functional', + 'Administrator' => 'Administrator', + 'Staff List' => 'Staff List', + 'Create Administrator' => 'Create Administrator', + 'Edit Administrator' => 'Edit Administrator', + 'Delete Administrator' => 'Delete Administrator', + 'Move Role' => 'Move Role', + 'Role Management' => 'Role Management', + 'Role List' => 'Role List', + 'Create Role' => 'Create Role', + 'Edit Role' => 'Edit Role', + 'Delete Role' => 'Delete Role', + 'Systems' => 'Systems', + 'Basic Settings' => 'Basic Settings', + 'Control Settings' => 'Control Settings', + 'Upload Settings' => 'Upload Settings', + 'SMS Settings' => 'SMS Settings', + 'Payment Management' => 'Payment Management', + 'Payment Settings' => 'Payment Settings', + 'Payment Template' => 'Payment Template', + 'Create Payment Template' => 'Create Payment Template', + 'Edit Payment Template' => 'Edit Payment Template', + 'Delete Payment Template' => 'Delete Payment Template', + 'Platform Management' => 'Platform Management', + 'WeChat Mini Program' => 'WeChat Mini Program', + 'WeChat Official Account' => 'WeChat Official Account', + 'Alipay Mini Program' => 'Alipay Mini Program', + 'WeChat Open Platform' => 'WeChat Open Platform', + 'App Platform' => 'App Platform', + 'H5 Platform' => 'H5 Platform', + 'PC Platform' => 'PC Platform', + 'Public Permissions' => 'Public Permissions', + 'Edit Profile' => 'Edit Profile', + 'Get Profile' => 'Get Profile', + 'Logout' => 'Logout', + 'Get Upload Categories' => 'Get Upload Categories', + 'Edit Upload Category' => 'Edit Upload Category', + 'Delete Upload Category' => 'Delete Upload Category', + 'Upload File' => 'Upload File', + 'Delete Upload File' => 'Delete Upload File', + 'Edit Upload File' => 'Edit Upload File', + 'Get Upload File List' => 'Get Upload File List', + 'Login Title'=>'Admin Login', + 'toolbar Lock'=>'Lock', + 'toolbar Search'=>'Search', + 'toolbar Notification'=>'Notification', + 'toolbar FullScreen'=>'FullScreen', + 'toolbar House'=>'Home', + 'toolbar Control'=>'Control', + 'userDropdownMenu updateSelf'=>'Admin Info', + 'Logout Success'=>'Logout Success', + 'PIN Code Cannot Be Empty'=>'PIN Code Cannot Be Empty', + 'Please Enter 6 Digits PIN Code'=>'Please Enter 6 Digits PIN Code', + 'Lock Success'=>'Lock Success', + 'Unlock Success'=>'Unlock Success', + 'form_basic_title'=>'Basic', + 'Settings_basic_web_name'=>'Apps Name' +]; \ No newline at end of file diff --git a/fastmovie-admin/resource/translations/en/messages.php b/fastmovie-admin/resource/translations/en/messages.php new file mode 100644 index 0000000..80fa9ee --- /dev/null +++ b/fastmovie-admin/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'Success' +]; \ No newline at end of file diff --git a/fastmovie-admin/resource/translations/zh_CN/admin.php b/fastmovie-admin/resource/translations/zh_CN/admin.php new file mode 100644 index 0000000..fd67c8e --- /dev/null +++ b/fastmovie-admin/resource/translations/zh_CN/admin.php @@ -0,0 +1,68 @@ + '首页', + 'Console' => '控制台', + 'User' => '用户', + 'User List' => '用户列表', + 'Create User' => '创建用户', + 'Edit User' => '编辑用户', + 'Update Status' => '更新状态', + 'Functional' => '职能', + 'Administrator' => '管理员', + 'Staff List' => '人员列表', + 'Create Administrator' => '创建管理员', + 'Edit Administrator' => '编辑管理员', + 'Delete Administrator' => '删除管理员', + 'Move Role' => '移动角色', + 'Role Management' => '角色', + 'Role List' => '角色管理', + 'Create Role' => '创建角色', + 'Edit Role' => '编辑角色', + 'Delete Role' => '删除角色', + 'Systems' => '系统', + 'Basic Settings' => '基本配置', + 'Control Settings' => '控制台配置', + 'Upload Settings' => '上传配置', + 'SMS Settings' => '短信配置', + 'Payment Management' => '支付管理', + 'Payment Settings' => '支付配置', + 'Payment Template' => '支付模板', + 'Create Payment Template' => '新增支付模板', + 'Edit Payment Template' => '编辑支付模板', + 'Delete Payment Template' => '删除支付模板', + 'Platform Management' => '平台管理', + 'WeChat Mini Program' => '微信小程序', + 'WeChat Official Account' => '微信公众号', + 'Alipay Mini Program' => '支付宝小程序', + 'WeChat Open Platform' => '微信开放平台', + 'App Platform' => 'APP', + 'H5 Platform' => 'H5', + 'PC Platform' => 'PC', + 'Public Permissions' => '公共权限', + 'Edit Profile' => '修改自己的信息', + 'Get Profile' => '获取自己的信息', + 'Logout' => '退出登录', + 'Get Upload Categories' => '获取上传分类', + 'Edit Upload Category' => '编辑上传分类', + 'Delete Upload Category' => '删除上传分类', + 'Upload File' => '上传文件', + 'Delete Upload File' => '删除上传文件', + 'Edit Upload File' => '编辑上传文件', + 'Get Upload File List' => '获取上传文件列表', + 'Login Title'=>'管理员登录', + 'toolbar Lock'=>'锁屏', + 'toolbar Search'=>'搜索菜单', + 'toolbar Notification'=>'查看通知', + 'toolbar FullScreen'=>'全屏/退出全屏', + 'toolbar House'=>'网站首页', + 'toolbar Control'=>'网站前台控制台', + 'userDropdownMenu updateSelf'=>'管理员信息', + 'Logout Success'=>'退出成功', + 'PIN Code Cannot Be Empty'=>'PIN码不能为空', + 'Please Enter 6 Digits PIN Code'=>'请输入6位PIN码', + 'Lock Success'=>'锁定成功', + 'Unlock Success'=>'解锁成功', + 'form_basic_title'=>'基础信息', + 'Settings_basic_web_name'=>'应用名称' +]; \ No newline at end of file diff --git a/fastmovie-admin/resource/translations/zh_CN/messages.php b/fastmovie-admin/resource/translations/zh_CN/messages.php new file mode 100644 index 0000000..c409f63 --- /dev/null +++ b/fastmovie-admin/resource/translations/zh_CN/messages.php @@ -0,0 +1,5 @@ + '成功' +]; \ No newline at end of file diff --git a/fastmovie-admin/start.php b/fastmovie-admin/start.php new file mode 100644 index 0000000..41ad7ef --- /dev/null +++ b/fastmovie-admin/start.php @@ -0,0 +1,5 @@ +#!/usr/bin/env php +name == 'plugin.webman.push.server') { + echo "Worker {$worker->name} 启动成功\n"; + Online::clear(); + echo "Worker {$worker->name} 清除历史在线用户成功\n"; + } + } +} diff --git a/fastmovie-admin/support/Request.php b/fastmovie-admin/support/Request.php new file mode 100644 index 0000000..e3f6ac3 --- /dev/null +++ b/fastmovie-admin/support/Request.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Request + * @package support + */ +class Request extends \Webman\Http\Request +{ + +} \ No newline at end of file diff --git a/fastmovie-admin/support/Response.php b/fastmovie-admin/support/Response.php new file mode 100644 index 0000000..9bc4e1e --- /dev/null +++ b/fastmovie-admin/support/Response.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Response + * @package support + */ +class Response extends \Webman\Http\Response +{ + +} \ No newline at end of file diff --git a/fastmovie-admin/support/ThinkPHP.php b/fastmovie-admin/support/ThinkPHP.php new file mode 100644 index 0000000..258f779 --- /dev/null +++ b/fastmovie-admin/support/ThinkPHP.php @@ -0,0 +1,75 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use think\Template; +use Webman\View; +use function app_path; +use function array_merge; +use function base_path; +use function config; +use function is_array; +use function ob_get_clean; +use function ob_start; +use function request; +use function runtime_path; + +/** + * Class Blade + * @package support\view + */ +class ThinkPHP extends \support\view\ThinkPHP implements View +{ + /** + * Assign. + * @param string|array $name + * @param mixed $value + */ + public static function assign(string|array $name, mixed $value = null): void + { + $request = request(); + $request->_view_vars = array_merge((array) $request->_view_vars, is_array($name) ? $name : [$name => $value]); + } + + /** + * Render. + * @param string $template + * @param array $vars + * @param string|null $app + * @param string|null $plugin + * @return false|string + */ + public static function render(string $template, array $vars, string $app = null, string $plugin = null): string + { + $request = request(); + $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; + $app = $app === null ? $request->app : $app; + $configPrefix = $plugin ? "plugin.$plugin." : ''; + $viewSuffix = config("{$configPrefix}view.options.view_suffix", 'html'); + $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); + $viewPath = $app === '' ? "$baseViewPath/view/" : "$baseViewPath/$app/view/"; + $defaultOptions = [ + 'view_path' => $viewPath, + 'cache_path' => runtime_path() . '/views/', + 'view_suffix' => $viewSuffix + ]; + $options = array_merge($defaultOptions, config("{$configPrefix}view.options", [])); + $views = new Template($options); + ob_start(); + $vars = array_merge((array) $request->_view_vars, $vars); + $views->fetch($template, $vars); + return ob_get_clean(); + } +} diff --git a/fastmovie-admin/support/bootstrap.php b/fastmovie-admin/support/bootstrap.php new file mode 100644 index 0000000..d913def --- /dev/null +++ b/fastmovie-admin/support/bootstrap.php @@ -0,0 +1,139 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Dotenv\Dotenv; +use support\Log; +use Webman\Bootstrap; +use Webman\Config; +use Webman\Middleware; +use Webman\Route; +use Webman\Util; +use Workerman\Events\Select; +use Workerman\Worker; + +$worker = $worker ?? null; + +if (empty(Worker::$eventLoopClass)) { + Worker::$eventLoopClass = Select::class; +} + +set_error_handler(function ($level, $message, $file = '', $line = 0) { + if (error_reporting() & $level) { + throw new ErrorException($message, 0, $level, $file, $line); + } +}); + +if ($worker) { + register_shutdown_function(function ($startTime) { + if (time() - $startTime <= 0.1) { + sleep(1); + } + }, time()); +} + +if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) { + if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) { + Dotenv::createUnsafeMutable(base_path(false))->load(); + } else { + Dotenv::createMutable(base_path(false))->load(); + } +} + +Config::clear(); +support\App::loadAllConfig(['route']); +if ($timezone = config('app.default_timezone')) { + date_default_timezone_set($timezone); +} + +foreach (config('autoload.files', []) as $file) { + include_once $file; +} +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['autoload']['files'] ?? [] as $file) { + include_once $file; + } + } + foreach ($projects['autoload']['files'] ?? [] as $file) { + include_once $file; + } +} + +Middleware::load(config('middleware', [])); +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project) || $name === 'static') { + continue; + } + Middleware::load($project['middleware'] ?? []); + } + Middleware::load($projects['middleware'] ?? [], $firm); + if ($staticMiddlewares = config("plugin.$firm.static.middleware")) { + Middleware::load(['__static__' => $staticMiddlewares], $firm); + } +} +Middleware::load(['__static__' => config('static.middleware', [])]); + +foreach (config('bootstrap', []) as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['bootstrap'] ?? [] as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } + } + foreach ($projects['bootstrap'] ?? [] as $className) { + /** @var string $className */ + if (!class_exists($className)) { + $log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } +} + +$directory = base_path() . '/plugin'; +$paths = [config_path()]; +foreach (Util::scanDir($directory) as $path) { + if (is_dir($path = "$path/config")) { + $paths[] = $path; + } +} +Route::load($paths); + diff --git a/fastmovie-admin/support/helpers.php b/fastmovie-admin/support/helpers.php new file mode 100644 index 0000000..5ad4b10 --- /dev/null +++ b/fastmovie-admin/support/helpers.php @@ -0,0 +1,2 @@ +setName('webman cli'); +$cli->installInternalCommands(); +if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) { + $cli->installCommands($command_path); +} + +foreach (config('plugin', []) as $firm => $projects) { + if (isset($projects['app'])) { + foreach (['', '/app'] as $app) { + if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) { + $command_path = base_path() . "/plugin/$firm{$app}/$command_str"; + $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str"); + } + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['command'] ?? [] as $class_name) { + $reflection = new \ReflectionClass($class_name); + if ($reflection->isAbstract()) { + continue; + } + $properties = $reflection->getStaticProperties(); + $name = $properties['defaultName']; + if (!$name) { + throw new RuntimeException("Command {$class_name} has no defaultName"); + } + $description = $properties['defaultDescription'] ?? ''; + $command = Container::get($class_name); + $command->setName($name)->setDescription($description); + $cli->add($command); + } + } +} + +$cli->run(); diff --git a/fastmovie-admin/windows.php b/fastmovie-admin/windows.php new file mode 100644 index 0000000..f37a72c --- /dev/null +++ b/fastmovie-admin/windows.php @@ -0,0 +1,136 @@ +load(); + } else { + Dotenv::createMutable(base_path())->load(); + } +} + +App::loadAllConfig(['route']); + +$errorReporting = config('app.error_reporting'); +if (isset($errorReporting)) { + error_reporting($errorReporting); +} + +$runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows'; +$paths = [ + $runtimeProcessPath, + runtime_path('logs'), + runtime_path('views') +]; +foreach ($paths as $path) { + if (!is_dir($path)) { + mkdir($path, 0777, true); + } +} + +$processFiles = []; +if (config('server.listen')) { + $processFiles[] = __DIR__ . DIRECTORY_SEPARATOR . 'start.php'; +} +foreach (config('process', []) as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, ''); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name"); + } + } + foreach ($projects['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm); + } +} + +function write_process_file($runtimeProcessPath, $processName, $firm): string +{ + $processParam = $firm ? "plugin.$firm.$processName" : $processName; + $configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']"; + $fileContent = << true]); + if (!$resource) { + exit("Can not execute $cmd\r\n"); + } + return $resource; +} + +$resource = popen_processes($processFiles); +echo "\r\n"; +while (1) { + sleep(1); + if (!empty($monitor) && $monitor->checkAllFilesChange()) { + $status = proc_get_status($resource); + $pid = $status['pid']; + shell_exec("taskkill /F /T /PID $pid"); + proc_close($resource); + $resource = popen_processes($processFiles); + } +} diff --git a/fastmovie-vue/.env.development b/fastmovie-vue/.env.development new file mode 100644 index 0000000..ff6b1b4 --- /dev/null +++ b/fastmovie-vue/.env.development @@ -0,0 +1 @@ +VITE_REQUEST_BASE_URL = http://localhost:36300 diff --git a/fastmovie-vue/.env.production b/fastmovie-vue/.env.production new file mode 100644 index 0000000..63808a1 --- /dev/null +++ b/fastmovie-vue/.env.production @@ -0,0 +1 @@ +# VITE_REQUEST_BASE_URL = https://fastmovie.admin.xhadmin.com.cn \ No newline at end of file diff --git a/fastmovie-vue/.eslintrc-auto-import.json b/fastmovie-vue/.eslintrc-auto-import.json new file mode 100644 index 0000000..acea81e --- /dev/null +++ b/fastmovie-vue/.eslintrc-auto-import.json @@ -0,0 +1,81 @@ +{ + "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, + "DirectiveBinding": true, + "EffectScope": true, + "ElMessage": true, + "ElMessageBox": true, + "ExtractDefaultPropTypes": true, + "ExtractPropTypes": true, + "ExtractPublicPropTypes": true, + "InjectionKey": true, + "MaybeRef": true, + "MaybeRefOrGetter": true, + "PropType": true, + "Ref": true, + "ShallowRef": true, + "Slot": true, + "Slots": true, + "VNode": true, + "WritableComputedRef": true, + "computed": true, + "createApp": true, + "customRef": true, + "defineAsyncComponent": true, + "defineComponent": true, + "effectScope": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "getCurrentWatcher": true, + "h": true, + "inject": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "isShallow": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onUnmounted": true, + "onUpdated": true, + "onWatcherCleanup": true, + "provide": true, + "reactive": true, + "readonly": true, + "ref": true, + "resolveComponent": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "toRaw": true, + "toRef": true, + "toRefs": true, + "toValue": true, + "triggerRef": true, + "unref": true, + "useAttrs": true, + "useCssModule": true, + "useCssVars": true, + "useId": true, + "useModel": true, + "useSlots": true, + "useTemplateRef": true, + "watch": true, + "watchEffect": true, + "watchPostEffect": true, + "watchSyncEffect": true + } +} diff --git a/fastmovie-vue/.gitignore b/fastmovie-vue/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/fastmovie-vue/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/fastmovie-vue/.htaccess b/fastmovie-vue/.htaccess new file mode 100644 index 0000000..0c54fb9 --- /dev/null +++ b/fastmovie-vue/.htaccess @@ -0,0 +1 @@ +# 请将伪静态规则或自定义Apache配置填写到此处 diff --git a/fastmovie-vue/README.md b/fastmovie-vue/README.md new file mode 100644 index 0000000..1511959 --- /dev/null +++ b/fastmovie-vue/README.md @@ -0,0 +1,5 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` + + diff --git a/fastmovie-vue/package-lock.json b/fastmovie-vue/package-lock.json new file mode 100644 index 0000000..abe9b2a --- /dev/null +++ b/fastmovie-vue/package-lock.json @@ -0,0 +1,6431 @@ +{ + "name": "short-play", + "version": "0.0.55", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "short-play", + "version": "0.0.55", + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@webav/av-canvas": "^1.2.7", + "@webav/av-cliper": "^1.2.7", + "axios": "^1.13.2", + "element-plus": "^2.11.7", + "highlight.js": "^11.11.1", + "isomorphic-dompurify": "^2.31.0", + "marked": "^16.4.1", + "marked-highlight": "^2.2.2", + "mp4box": "^2.3.0", + "pinia": "^3.0.3", + "qrcode": "^1.5.4", + "vue": "^3.5.22", + "vue-i18n": "^11.1.12", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/node": "^24.10.0", + "@types/qrcode": "^1.5.6", + "@typescript-eslint/eslint-plugin": "^8.46.3", + "@typescript-eslint/parser": "^8.46.3", + "@vitejs/plugin-vue": "^6.0.1", + "eslint": "^9.39.1", + "eslint-plugin-vue": "^10.5.1", + "sass-embedded": "^1.93.3", + "typescript": "^5.9.3", + "unplugin-auto-import": "^20.2.0", + "unplugin-icons": "^22.5.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.1.7", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-inspect": "^11.3.3", + "vue-tsc": "^3.1.3" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.23", + "resolved": "https://registry.npmmirror.com/@acemir/cssom/-/cssom-0.9.23.tgz", + "integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==", + "license": "MIT" + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.1" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.4", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", + "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.10.0.tgz", + "integrity": "sha512-fdRs9PSrBF7QUntpZpq6BTw58fhgGJojgg39m9oFOJGZT+nip9b0so5cYY1oWl5pvemDLr0cPPsH46vwThEbpQ==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.15", + "resolved": "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz", + "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.1", + "globals": "^15.15.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "mlly": "^1.7.4" + } + }, + "node_modules/@intlify/core-base": { + "version": "11.1.12", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.12.tgz", + "integrity": "sha512-whh0trqRsSqVLNEUCwU59pyJZYpU8AmSWl8M3Jz2Mv5ESPP6kFh4juas2NpZ1iCvy7GlNRffUD1xr84gceimjg==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "11.1.12", + "@intlify/shared": "11.1.12" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.1.12", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.12.tgz", + "integrity": "sha512-Fv9iQSJoJaXl4ZGkOCN1LDM3trzze0AS2zRz2EHLiwenwL6t0Ki9KySYlyr27yVOj5aVz0e55JePO+kELIvfdQ==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.1.12", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.1.12", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.12.tgz", + "integrity": "sha512-Om86EjuQtA69hdNj3GQec9ZC0L0vPSAnXzB3gP/gyJ7+mA7t06d9aOAiqMZ+xEOsumGP4eEBlfl8zF2LOTzf2A==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.29", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", + "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmmirror.com/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", + "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/type-utils": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.3", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.46.3.tgz", + "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", + "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.3", + "@typescript-eslint/types": "^8.46.3", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", + "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", + "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", + "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.46.3.tgz", + "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", + "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.3", + "@typescript-eslint/tsconfig-utils": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.46.3.tgz", + "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.3", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", + "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.3", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", + "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.29" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.22.tgz", + "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/shared": "3.5.22", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", + "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", + "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/compiler-core": "3.5.22", + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.19", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", + "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.1.3.tgz", + "integrity": "sha512-KpR1F/eGAG9D1RZ0/T6zWJs6dh/pRLfY5WupecyYKJ1fjVmDMgTPw9wXmKv2rBjo4zCJiOSiyB8BDP1OUwpMEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.22.tgz", + "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.22.tgz", + "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", + "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/runtime-core": "3.5.22", + "@vue/shared": "3.5.22", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.22.tgz", + "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "vue": "3.5.22" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.22.tgz", + "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@webav/av-canvas": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@webav/av-canvas/-/av-canvas-1.2.7.tgz", + "integrity": "sha512-UFpZzjRTNCdrRmVkfQsANdRjpPOl+ejBB/lKbitHoi7f2APAq2GlJx/X4Wnwl0RGVDhUa4UgGdChpuP8RiYvqw==", + "dependencies": { + "@webav/av-cliper": "1.2.7", + "@webav/internal-utils": "1.2.7" + } + }, + "node_modules/@webav/av-cliper": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@webav/av-cliper/-/av-cliper-1.2.7.tgz", + "integrity": "sha512-zn+h5gJVyOupTraykCz1/wiowSfV1/jqt7Kz3FHArhp1NgB74/3H9chL/Bz+uwiVYCusU9C8Eu/l3F9WvTbsig==", + "dependencies": { + "@webav/internal-utils": "1.2.7", + "@webav/mp4box.js": "^0.5.7", + "opfs-tools": "^0.7.2", + "wave-resampler": "^1.0.0" + } + }, + "node_modules/@webav/internal-utils": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@webav/internal-utils/-/internal-utils-1.2.7.tgz", + "integrity": "sha512-99hajvWwrVb/j0O3vu6zK5XXB+aAI5S4MLkPcJdw2GmGn/JYKZU8I7y7iyIf5ipuPGLucmD9O7zzb6SSeXcYkg==", + "dependencies": { + "@webav/mp4box.js": "^0.5.7" + } + }, + "node_modules/@webav/mp4box.js": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/@webav/mp4box.js/-/mp4box.js-0.5.7.tgz", + "integrity": "sha512-KgScEtDpiLnWvj1Rj8Ri9fDWxTjfdNr7cmY/PT88EmttHh6cLxMAeyD/W8Vydes4z7qV/c5VDAaP4cTlaO0E9g==", + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.0.6.tgz", + "integrity": "sha512-gCs0YqC1mkYGC6IRXsSrA62ShOSv1FlVN5tRp/Cs2vRWLK/BAeluWIdfsl253pFQPznKEvRmHhfep7crWfyfWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/birpc": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.7.0.tgz", + "integrity": "sha512-tub/wFGH49vNCm0xraykcY3TcRgX/3JsALYq/Lwrtti+bTyFHkCUAWF5wgYoie8P41wYwig2mIKiqoocr1EkEQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true, + "license": "MIT/X11" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-5.3.3.tgz", + "integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/element-plus": { + "version": "2.11.7", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.7.tgz", + "integrity": "sha512-Bh47wuzsqaNBNDkbtlOlZER1cGcOB8GsXp/+C9b95MOrk0wvoHUV4NKKK7xMkfYNFYdYysQ752oMhnExgAL6+g==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.18", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "10.5.1", + "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-10.5.1.tgz", + "integrity": "sha512-SbR9ZBUFKgvWAbq3RrdCtWaW0IKm6wwUiApxf3BVTNfqUIo4IQQmreMg2iHFJJ6C/0wss3LXURBJ1OwS/MhFcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" + }, + "peerDependenciesMeta": { + "@stylistic/eslint-plugin": { + "optional": true + }, + "@typescript-eslint/parser": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isomorphic-dompurify": { + "version": "2.31.0", + "resolved": "https://registry.npmmirror.com/isomorphic-dompurify/-/isomorphic-dompurify-2.31.0.tgz", + "integrity": "sha512-/XPACpfVJeEiy28UgkBWUWdhgKN8xwFYkoVFsqrcSJJ5pXZ3HStuF3ih/Hr8PwhCXHqFAys+b4tcgw0pbUT4rw==", + "license": "MIT", + "dependencies": { + "dompurify": "^3.3.0", + "jsdom": "^27.1.0" + }, + "engines": { + "node": ">=20.19.5" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "27.1.0", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-27.1.0.tgz", + "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.19", + "@asamuzakjp/dom-selector": "^6.7.3", + "cssstyle": "^5.3.2", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "16.4.1", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.1.tgz", + "integrity": "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/marked-highlight": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/marked-highlight/-/marked-highlight-2.2.2.tgz", + "integrity": "sha512-KlHOP31DatbtPPXPaI8nx1KTrG3EW0Z5zewCwpUj65swbtKOTStteK3sNAjBqV75Pgo3fNEVNHeptg18mDuWgw==", + "license": "MIT", + "peerDependencies": { + "marked": ">=4 <17" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mp4box": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/mp4box/-/mp4box-2.3.0.tgz", + "integrity": "sha512-nnABYbdh4UguEYyV+uRwQBi1tbb8kXka2Fx9yKzmDKAeh8gkvRKYxoK1XDd8GQIjSfN4rvsXrW1CBo4yRQJZDA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.8.1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opfs-tools": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/opfs-tools/-/opfs-tools-0.7.4.tgz", + "integrity": "sha512-DJgQnXyPcCj3q8pKa8SjP4HfNBPffj8kO6W1nc2zQydTqu5G/AxOuIjmWnRxZ84nE1EOpzZzhj8MYCZP1jRR9A==" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-manager-detector": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.93.3.tgz", + "integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.93.3.tgz", + "integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.93.3", + "sass-embedded-android-arm": "1.93.3", + "sass-embedded-android-arm64": "1.93.3", + "sass-embedded-android-riscv64": "1.93.3", + "sass-embedded-android-x64": "1.93.3", + "sass-embedded-darwin-arm64": "1.93.3", + "sass-embedded-darwin-x64": "1.93.3", + "sass-embedded-linux-arm": "1.93.3", + "sass-embedded-linux-arm64": "1.93.3", + "sass-embedded-linux-musl-arm": "1.93.3", + "sass-embedded-linux-musl-arm64": "1.93.3", + "sass-embedded-linux-musl-riscv64": "1.93.3", + "sass-embedded-linux-musl-x64": "1.93.3", + "sass-embedded-linux-riscv64": "1.93.3", + "sass-embedded-linux-x64": "1.93.3", + "sass-embedded-unknown-all": "1.93.3", + "sass-embedded-win32-arm64": "1.93.3", + "sass-embedded-win32-x64": "1.93.3" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.3.tgz", + "integrity": "sha512-3okGgnE41eg+CPLtAPletu6nQ4N0ij7AeW+Sl5Km4j29XcmqZQeFwYjHe1AlKTEgLi/UAONk1O8i8/lupeKMbw==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "sass": "1.93.3" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.3.tgz", + "integrity": "sha512-8xOw9bywfOD6Wv24BgCmgjkk6tMrsOTTHcb28KDxeJtFtoxiUyMbxo0vChpPAfp2Hyg2tFFKS60s0s4JYk+Raw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.3.tgz", + "integrity": "sha512-uqUl3Kt1IqdGVAcAdbmC+NwuUJy8tM+2ZnB7/zrt6WxWVShVCRdFnWR9LT8HJr7eJN7AU8kSXxaVX/gedanPsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.3.tgz", + "integrity": "sha512-2jNJDmo+3qLocjWqYbXiBDnfgwrUeZgZFHJIwAefU7Fn66Ot7rsXl+XPwlokaCbTpj7eMFIqsRAZ/uDueXNCJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.3.tgz", + "integrity": "sha512-y0RoAU6ZenQFcjM9PjQd3cRqRTjqwSbtWLL/p68y2oFyh0QGN0+LQ826fc0ZvU/AbqCsAizkqjzOn6cRZJxTTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.3.tgz", + "integrity": "sha512-7zb/hpdMOdKteK17BOyyypemglVURd1Hdz6QGsggy60aUFfptTLQftLRg8r/xh1RbQAUKWFbYTNaM47J9yPxYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.3.tgz", + "integrity": "sha512-Ek1Vp8ZDQEe327Lz0b7h3hjvWH3u9XjJiQzveq74RPpJQ2q6d9LfWpjiRRohM4qK6o4XOHw1X10OMWPXJtdtWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.3.tgz", + "integrity": "sha512-yeiv2y+dp8B4wNpd3+JsHYD0mvpXSfov7IGyQ1tMIR40qv+ROkRqYiqQvAOXf76Qwh4Y9OaYZtLpnsPjfeq6mA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.3.tgz", + "integrity": "sha512-RBrHWgfd8Dd8w4fbmdRVXRrhh8oBAPyeWDTKAWw8ZEmuXfVl4ytjDuyxaVilh6rR1xTRTNpbaA/YWApBlLrrNw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.3.tgz", + "integrity": "sha512-fU0fwAwbp7sBE3h5DVU5UPzvaLg7a4yONfFWkkcCp6ZrOiPuGRHXXYriWQ0TUnWy4wE+svsVuWhwWgvlb/tkKg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.3.tgz", + "integrity": "sha512-PS829l+eUng+9W4PFclXGb4uA2+965NHV3/Sa5U7qTywjeeUUYTZg70dJHSqvhrBEfCc2XJABeW3adLJbyQYkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.3.tgz", + "integrity": "sha512-cK1oBY+FWQquaIGEeQ5H74KTO8cWsSWwXb/WaildOO9U6wmUypTgUYKQ0o5o/29nZbWWlM1PHuwVYTSnT23Jjg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.3.tgz", + "integrity": "sha512-A7wkrsHu2/I4Zpa0NMuPGkWDVV7QGGytxGyUq3opSXgAexHo/vBPlGoDXoRlSdex0cV+aTMRPjoGIfdmNlHwyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.3.tgz", + "integrity": "sha512-vWkW1+HTF5qcaHa6hO80gx/QfB6GGjJUP0xLbnAoY4pwEnw5ulGv6RM8qYr8IDhWfVt/KH+lhJ2ZFxnJareisQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.3.tgz", + "integrity": "sha512-k6uFxs+e5jSuk1Y0niCwuq42F9ZC5UEP7P+RIOurIm8w/5QFa0+YqeW+BPWEW5M1FqVOsNZH3qGn4ahqvAEjPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.3.tgz", + "integrity": "sha512-o5wj2rLpXH0C+GJKt/VpWp6AnMsCCbfFmnMAttcrsa+U3yrs/guhZ3x55KAqqUsE8F47e3frbsDL+1OuQM5DAA==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.93.3" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.3.tgz", + "integrity": "sha512-0dOfT9moy9YmBolodwYYXtLwNr4jL4HQC9rBfv6mVrD7ud8ue2kDbn+GVzj1hEJxvEexVSmDCf7MHUTLcGs9xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.93.3", + "resolved": "https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.3.tgz", + "integrity": "sha512-wHFVfxiS9hU/sNk7KReD+lJWRp3R0SLQEX4zfOnRP2zlvI2X4IQR5aZr9GNcuMP6TmNpX0nQPZTegS8+h9RrEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/superjson": { + "version": "2.2.5", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.5.tgz", + "integrity": "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "7.0.17", + "resolved": "https://registry.npmmirror.com/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.17" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.17", + "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.5.0.tgz", + "integrity": "sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "pkg-types": "^2.3.0", + "scule": "^1.3.0", + "strip-literal": "^3.1.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.10", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.10", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.10.tgz", + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "20.2.0", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-20.2.0.tgz", + "integrity": "sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "local-pkg": "^1.1.2", + "magic-string": "^0.30.19", + "picomatch": "^4.0.3", + "unimport": "^5.4.0", + "unplugin": "^2.3.10", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^4.0.0", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-icons": { + "version": "22.5.0", + "resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-22.5.0.tgz", + "integrity": "sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/utils": "^3.0.2", + "debug": "^4.4.3", + "local-pkg": "^1.1.2", + "unplugin": "^2.3.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@svgx/core": "^1.0.1", + "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0", + "vue-template-compiler": "^2.6.12", + "vue-template-es2015-compiler": "^1.9.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@svgx/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + }, + "vue-template-es2015-compiler": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "30.0.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-30.0.0.tgz", + "integrity": "sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "debug": "^4.4.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.10", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2 || ^4.0.0", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.12", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-dev-rpc": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", + "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^2.4.0", + "vite-hot-client": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" + } + }, + "node_modules/vite-hot-client": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/vite-hot-client/-/vite-hot-client-2.1.0.tgz", + "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.3.3", + "resolved": "https://registry.npmmirror.com/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", + "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.1.0", + "debug": "^4.4.1", + "error-stack-parser-es": "^1.0.5", + "ohash": "^2.0.11", + "open": "^10.2.0", + "perfect-debounce": "^2.0.0", + "sirv": "^3.0.1", + "unplugin-utils": "^0.3.0", + "vite-dev-rpc": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.22.tgz", + "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-sfc": "3.5.22", + "@vue/runtime-dom": "3.5.22", + "@vue/server-renderer": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-i18n": { + "version": "11.1.12", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.12.tgz", + "integrity": "sha512-BnstPj3KLHLrsqbVU2UOrPmr0+Mv11bsUZG0PyCOzsawCivk8W00GMXHeVUWIDOgNaScCuZah47CZFE+Wnl8mw==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.1.12", + "@intlify/shared": "11.1.12", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.1.3.tgz", + "integrity": "sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.23", + "@vue/language-core": "3.1.3" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/wave-resampler": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/wave-resampler/-/wave-resampler-1.0.0.tgz", + "integrity": "sha512-bE3rbpZXuKAV52Cd8/BeJvy82ZqEHK8pPWHrZ9JioaVVTBlmWbDC+u4p9blhFcf0Skepb4hlOAHc25XfqLC48g==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/fastmovie-vue/package.json b/fastmovie-vue/package.json new file mode 100644 index 0000000..10f40fc --- /dev/null +++ b/fastmovie-vue/package.json @@ -0,0 +1,46 @@ +{ + "name": "short-play", + "private": true, + "version": "0.0.55", + "type": "module", + "scripts": { + "dev": "vite --mode development --port 36310 --host", + "build": "node build-version.mjs && vue-tsc --noEmit && vite build --mode production", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@webav/av-canvas": "^1.2.7", + "@webav/av-cliper": "^1.2.7", + "axios": "^1.13.2", + "element-plus": "^2.11.7", + "highlight.js": "^11.11.1", + "isomorphic-dompurify": "^2.31.0", + "marked": "^16.4.1", + "marked-highlight": "^2.2.2", + "mp4box": "^2.3.0", + "pinia": "^3.0.3", + "qrcode": "^1.5.4", + "vue": "^3.5.22", + "vue-i18n": "^11.1.12", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/node": "^24.10.0", + "@types/qrcode": "^1.5.6", + "@typescript-eslint/eslint-plugin": "^8.46.3", + "@typescript-eslint/parser": "^8.46.3", + "@vitejs/plugin-vue": "^6.0.1", + "eslint": "^9.39.1", + "eslint-plugin-vue": "^10.5.1", + "sass-embedded": "^1.93.3", + "typescript": "^5.9.3", + "unplugin-auto-import": "^20.2.0", + "unplugin-icons": "^22.5.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.1.7", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-inspect": "^11.3.3", + "vue-tsc": "^3.1.3" + } +} \ No newline at end of file diff --git a/fastmovie-vue/public/fastmovie/static/image/login-image.png b/fastmovie-vue/public/fastmovie/static/image/login-image.png new file mode 100644 index 0000000000000000000000000000000000000000..574f847272ec40aa2ee7a8b1eba75ba6ebeadca4 GIT binary patch literal 197907 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
WnKiJn%GRY?>e?B@mI@c7l3nE3+rIRBFL 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 EkCJs z#6}lKi7;!?tl6lC&l(kZkU20J0{tb(9{3W_9Lg5tmm=}O)N+0@d_Yu9vXI@2a48d#85 za3VK3N_ly{sTVgk^w#UI>F@l5|61St{&zLqoT*HzYAvdza$giJxakkrRp*a?-F^FA zEpFXy-v{zx^Oy%wolHfw%HLoy?SA`bKhwYbm;X}#_TT+G{pgQ=te?O0wz9*2?Tt5d z14m-@;p!(|LkBFIQC^8vMs$}u1}OST9ogvnU4+|2!`0vHZ^E1q(!UbDq-|a zvC4mL#=?NcV3Qsb13eYFsoJ5z=~&89c603Q&qdH5+p>Y2%*S9`c8~Q;lUN7IC&y9o z$r`6Qqi*P_^q1ngR{GfnGr#L-#TX+Ni&kjDBv}_Y%#JlP<{^RQN}1jc%2ZC00z{Zz zKIkZ!gB%{|>PpELNBtzw#(wmZ8i36RJJxn+L#(XiSPhSQ2VDmu)cgE(`8pq+^H|Rh zueG5~x?IEI9v>V?`Lnl=_H_UAFZ3rk_K$Dh(g8PH9C{Br!{SnqT0%i?UY0FlE3_zq z?$gQYg2Sqs{|Lz~@ z#W!D*cp4NMTbVc!=sFGP_)dDH;|CA)@QW|>@Uzb{e9Aw=zsvDa{u%yt`ZJ*=9cSmq zhevwvop*Kn{zL6AIvvS+ba<>!KL1jmv7^7t$K%&-zND)-`9fcsLTj^Gskf-q0mUi>en72-Hq>UrcB5Q0)|15|f^Dv6W5<{% zn&jE1`I)n>4W`(zkb+OTLqCY)&ck|{brz@2NBlgb7w}!vRjjtkNb-_}jlq1{)xl@8 z(TRu6TD`3R7qVr0Kxq{5uQYbCZmC~8sS~s@b{lUq?leSdR63!r*<47LeF_r9KCN@X zeu|vB&Z+;oCT*YA-v;M2`9v;yYiMf$c9)7q|7yJoa-B3AzVK!=`ar(BTH78@sJ)yY(wv^mEY zwq>0S%q8_{1)&odI}N4C@C7=e)~aB_n@Ui5ASPXERjB}7+p^tP2C?`d9UD%5K-#t< zE43;%YAsMuNRJruCIcvP9dx#uqN^(r#M&uuWeBN!~orS&67o{wYJNYCdd zJ?25PX4`1OH%fl1k2H}gTZ*hcMK&=!WEFdzTt`vUi18<3Wx2y=bVF)M@m3Q0^!R*Q zPq1;WaJj_p_1cR!R5v&E5zg>m|JhG;`}k0YtiKbSMF+$pw5z7FCLn+3B@Vn=>i-5z z2NgL`Aq#9|MN`UdsA{Xu_2QLldU<18uaucyM)vI&U(~C}c4iy;FaFx^>Bj4?w2!+> ziz9@IFo0t|*5c?;9Y--I4<70OXPg`2gf?dAjyNva!}*_ZgmdFt-%z)`p<`arrd!+k z^5H`rA6sQvXm@FC+2(`mW$K%i2`?*4eSY_$-u>hg?P70>TSY|)*11td7|9Bwje;f+whA<`t&!R$%aSZuNoEL2LoHBXC$9(1 zQ9|koZ5Z;^sN|FhToCo;Y^#v2U_}*~>0gf$9f@=8u0e6kU^(n^LW4MAC725fKIx9} z2t+zMOXj2f4ILcW5}X_#X%VM>%-q5sGrtSy3G>)dN5^*$nxh`QlhnCBXb7!Z_|%uO zuzfMwAO1Y-lsGH3E!zkK$W!8Z4ckN6wL-{BF-`8rJY#IqLFAznQip8G7#-37j8Uho zwGpfp>_pG*>#%!@+GDZ9PK0)^WA`+&UnOjxnx_M4N9N*Gc$Qn`qsua(vx$@-oh}4t zOOR@L(OE;*A;`F`z}i_3NIgr8buHboDeG8{!Rf9YLE?bq(Uw*QzBMj&x5{Dq*d?F5 zF23O+P+td@S}5x{bctTymX5kk(OX=r6Wb_^;^(3kKf@e!DL<4lv`qP!>iJyrtCu;3 zS?Z9Fhv+9Z&^3jLvSA8_vRG>|4q`xw;}Pi&4II3sS_YtBk#2IPpJR|4JWQ^0?tXP^ zLodI4LuF=d-MFGRe)Bi9ySJ+oZvGj%m2zhC5J=qk$NPKQfAm00+SkD^cXzemIO%AA zUrUZ!uHSrFuYTt(9FcT_)2N&~A8;&lf@3Lo!AV!z@6TV|-q8mA&0rs?(;@AAgrm9j z@PY2|jp2}q=%!U_4!+Zgr95mp+Owpelg$lXx_V6$+DNxlq!2wTV5aHWmi=@T#?~$h z9pO@Bua%Qqm1$QOw!!Mq2?WJ}ge_T0(gUwC4J~94)dPW^9I|%S7f`R+tw{1E(l8z4 zzGVKEjV{$_cINEZ7trk3GuI2|J9(pBO853;*@1d0YZZ!RyCrSI3s_RnTEIPWYDvGw zKy%L47D3;v);imA<}$FG?xKwcZ>Nl#do2xFEx#oq2ztrm*Qez@uKfgwpCEru@e|ZC z6FvGXlY{2KxAj8u7mb6Nj(}@-w@~HSR@*8dPIn@+69!~XP?|s^ zH*%5+YIyW5Ddl!>>f6}g!LeS&LB7b_!u-;vPB?;j`M18S!;Pv(2ahzJO>x3SX0lFj z>Puc(7vPv9nC^g6^SyoDefNFse)OqMID0?Xd!!{FK`*~{Q{CoVyLax%=wG>VRp0xa z-`1s9-ay?RP@sjeu}$d!LbNT@4%;fUt27=>(l)Z76qO1>fg31Co*LAX zlns#C$&a1DDyIAz?Bqw@Q>}$=Sb(7;l0EW0Wy!Ep=>{w~0}Jx-T%M6Fl=JkX9GQXq zj(5@vzKz7Ww;P_ZvE8?ke%4wWZ})9;wN#|OlOg6J8UTja(dHEf+4B;r!L*->*s>k+ zMA+_apkZ)6mSOxYx2$Dz?h95L$)T*svn~59v=;2Mc_uG?hdeXd(LSqYKQ23~@zqml z&pMXOPp~fgMfqjc7o(Q)t!1pA@E!6c^@MhzgHGe|D~*@``ZDMuvJQnh>Qwm$mt zPxSV``=NgRAAh6`j)H#YcYjZ>zV^B*$1$GHb?4C|eZdZWk3m0@czG&1qP|1gdw>Vt zMY45!`ZyynR;@&1|>8BmrNEUB36q6lVAumB55(Ewq?dk zP!Sx!+eTy++GO`U0wOZfrIZyOO$rVXof|X#6xloQF zdeiQW(A)!O(btaxOpKw0Lmq8~J_eN0I=#|<60LlblRhnjxTQym2I=U-(G(iUdWA{l zfHc>$5gBDhn^+?Dve@a7G^N({;G12tdYu$oOCaoqzIN!2zzW4^-I{F7dlF5VCm~Cr ziI?7aD^uK(rl>xJL>o-Tju4ciep~1W{RRE1Tbu14QG~1$w?vX9b}}9%?SoN(!A^DOZq0r$VbM<6l*v zBV0&>P~#|8z_x)@WHge}Bh zI%fa?5CBO;K~&qCaC}yA;B_*EpQ+;5OPR>7D*V`%J)%&m$}TGzg41ZJIl4%~=bj4x zb%M4$-Q44q+E#uFv=%cbQvPm!frsJl$T%)V>y^?0i5A1JK>5ap8tnkBCJRj(=nRq| zb1kN(Amz@}$Xa>yQ!;@4>M@1}79=;a|RobZUGO*-ghF^H}Ru)Pf!%690jwFHGk z@~FIHwrLQ8q#;A>C^xW;bWQi^*QU63vCqy{iP0s_bB`VEt1sWw_uqI^8=Z4o(Q&g7 zK`AX=q$V?05kdSAkuZJiu2B*`ORj~w? zw0B9{J7D^szeu^U$+1=P7&Fh$%0B9Kc${5h+`FyS^n}%A7`Y@L4Qx{(`q+mTa>GN36e+EAmeTLfC zl0A;mIl3hM7_xTOaZaaW?RCf+SY)AM2t%_LW3P)2!VW!VNR5UMF6dCpk39J!k_M;4 zg!9F2>|v0YN>NCJrMhqpXtw;=y$DfyOrE|3)Zc}kL+-U;yRfv4p$}b2c2@po*lb5zdboP;2iyK`^Qw5Uwyfqp&#J*53-a0 z^b0= zq@_hUZm@JDWhp24tGsPo<>4{mM(jnF=~IK@&tcS&`aC@&pe4J=fG(^SfoRwHqBhzq zq)gHy4d0`n(@;nUq-`@lJ!HxD3Wa$x2tAKVzg?xucxW1 z*@_&?Eo+@Nd20hwY$ZLG!D7=NFUdQD^>PEhjE@3m^ai_;jrpU^a(H;C1F%GiRioX* zeI2I-EVe4EXbe_i{hXpBw^QNT^jKUKlpr$7PjjOd^SySW4bIxj&Zf3rx~4ez*^SE* zA9I&DSngn{7HaL(90N~j(IL~O{fCcqz(>?eS2^##d`0 zK8D`Cs}uU+Y1Q$>IuMgc>*S(EXAXJ_PF&jO0h7NQ6Ng^eF$(bHhBx0 z!B27U)9GB($xM?es54cZf2|XhQdL}4T~y&~DJsPcE=3x;U}T23xgfNmME#1w6HTwp zU2e7pR(0QuG1IcGLi>x;pCTmcvgVawiG2Ne1nxHlYjuyUUK4%oY^#!r8vQEPQy>`2 zU?aoL2-1xt`i!3AI_kpqIZwKxFzQ~Y(os#1CuBb^#$<#kg{KJ%k~#%~C&sKLDs4r> zUxf4d<%A=U2@Y)7DaxW?CFSL*sSsZbd)j;M1Ou-g6 zq-_H_(t+0UyMmQLp1eS$Z9_&>_|i~HRdIojtVfek zM*+1K6==rQE>fwot282MoPK5O5wDHrNj)=`(_^=0Va_82`DmlP7ys+ zpAv1$KE9p(M)g#8#k_Me(kchc6N{*YWKk-P1*e|ZRIA(htT7~?wvE>104CX6y@k=Siwe38*vt9|D)U=P zRZyWG(k?|osg=2yYKG&E1K*5eXMJsKZYqv`zOkk0Y(o?BgF2b1)Tvx2phz{aRILt{ zrl=IFkT$fzmdLPaW2q`7)bf}gYU|k{I>ECwqY=#@d=Q?gfn_!lTLMPwAp1;AsX#J6 zZC=^6AM-}J&-z}lX4QhZ8jT);y9k!Z@fqmX5uVn?uUNm)4`Oiu3s*at><&gr(Y38bqzV?SVvTX z)EN}pTClkGIdfm|0qmt$UeVPnS2aOyGvvVL=7EZ23WqK51*WX1Ran#$(i4Q!BF&-K zUc9dF{?=d8q*gun>XFy*gw+#W(S=!{0X$T(Zg&3g+UPVSpSs6# zE+R){fs9bsXf_@!H1&WyBQm@!OKwq^jQL|=eVxjdTAtBcwzA7JI=!v+YHeBy$Z7h` z9%b@7{|CHH9v&WO$$b!cYq#6=RCle;*zLHC?STE2$FwW;ok!%q2-4v7a!EfA*2|3i z*rqYvF^&veGq;khjo;H`DRd?u|Iy|w6dTEhZId(VMb6KS*>i2$^kE-nGMe7R-(I_T{Z;)jN61SNOH&=gi zPc-9Zor4(;az2?V|53*E*I(6?9q{7*Bi;QEKi0vI{!I1rdpLRaqFJ4IO2Z1DPkX7B zVJueN+R!Y=FYKVVID*;8**A{<-X>8*L*V5HqL{sxK4Pj&F`xpPpXb+UmW}b zhjEC}10Gz5IQcmC4%^w~XD6TPmsZ=scd|O#zKv7AdgBF6aZ;vlWUB{PqFU&ZRGs-h3_8>FP@0qZaxGG~6 z+CRcBk}TVb1I~QcQdazgahow0`OiXTIPd9f*50~ijNycJ5P4{wOq8;dI%y7mLOOYo z#ctX?!Y(pMx|T{Gi)`u#M&6@Or>vd!^*XRhupRP?A;Z6h#&XkgTMYJ^1pmQ$MSkYy zYUx4k0%Eb&khkU;hM1f5;e!Y5Z4{$OL{)tqJx4u*q-CyMPPL-hV=eoaV#j_xuaQxI zMW0eII_ud>LHV8r1(GrJWjt`G@$ho#DksCBO$@ky-w!Zz!>)Pmwra0ay z3$Ymx)2clNVLuL@30YR05)XimJFByF$Ux+Om^!H3 za64O?UB9AkYo-ppv|TE)EvpY`^~`XMyd$?mM*}*9kqN3bnOeFpXj{Y;dglg(<6nbulejBOp2hO3KNs7OU||0~ZA#!9WzF>jg= zcOUBHlhldrtu7Rz3|ic!Au)__(s{l^^}5%3`WvqKX;@U19?_G<9;eZ1G`KMw@KH= zqxG@L`gF#ot-g*@fZM0H37>L3I?4-Row8}?MbPE+2>JF`u8(%X80o$n8R^9P!8gFnh2@WO#U+L>wkJKxppH@>Ndckk-+|L`Ln zz4wXg?tvDM_H-0Sf5J^jBlAs@KB&~H;)#xN*n4*$>Cx?ny7M89{oq79o=H3uvO}+1 zb9EaN-Qv~uv)w)2<^$F3qXRwS<^iE|Tqrv@x*F91K{`M_zH(l{v}|yfV(UPBg)xPgoxX^!Za2 zS5TT=d$0wyM?QIZNPs2_?Pbnb1tu-pHUJ{qdU{Cyh^K2lH8%?;Kxw>ry1|hw7u;Sf zs@zy3y7h^p%oro@D7T(YnLgdO^N0=3E^9cuuc1n*&(djd^A9bZF=%KhE~9d&WV=Dn z3h1GTmchl*Chr{5#&uj&ay|d+;xlfm;|P34?_lv@!`OENy{J9UE9)5L;6nYuJ|m(F zk#VA6@*t1tjIz+@f!$kb{bCS_t71b_()pFFT)uu?S8m);{$5JEb(T_WynyVtzWp1z zjC0=RCf(-d+`@qeJ7ub!L3V|YnmKy;>wn{K=m$UefnK`#vR-)kW&Pl{e_P-D_FGEW zf3&N8oURT}IN$Fyd;O*^ed}8~#zF7>^nD$@^RYUdw%BdQ`xf39v}Ez(=#AGg@`vp5 zzj&7&|3|m9c~q%?Lyx|=r-yeQXo^#r&!?(&sk`?c=pl}MpWZ&;!S(T-JMGU4Ot!aG zco?p7qypn9gvAYREzb5N%?`*cxHNBNog z>6g|pi*1p4v?_%SL~v}6NJEgVN87S{G`ZWgU2L!Ao$~hiwk5B~HjcXITiXjV>aL~W z*sBWEpw=QG*FbHzvNn`_pFw1x$ zy5{ByM!oP)MP{jElE6wYvcZ6TS~n_9y3*sJz>K#TEt@N*a=#pLT-xi?uk;7lMK<>a z87mv>Jl1VD9lNK9eyM0%7*xWAb*|SBAH8+Z9sDk0_*7EfeK$pcV;`999f6=_j~LfYQb?!XBNDdy8nO& z2|J^lz3*wsam+&Z_o)+|86Wow?eAnQU4(TlQjR{)_}I6}$J^P)OvMxBp^|p%D+Qx{ z9*A0uJWOa1UZQ#>g!VA9AX3R9w2Nd9jHJdT9rP((6uNa`E3_^c_%*qGnh0xKc8^ke z$5Kj>;rnz^Vf+enty)DwrB+Z@I>lbuFjbC#)yNyJVbmp^{AZypY?MX{OLW6C?mu+C zTIAN@7U@`~fcqnL<|b;u)S0m+omN0}wkut;p(g~h=sH&B$>Nv|(XN(!^cONUBfUk2 zNY4|}p7VH~a_4nYYj9rv=b6sfGmw9tP56p+X9R<zb!d{=2=;zfhb!u1?d=!tHTPQ*^0J_0pBA`Zl}o z8!x@2VB^vyZSf&;2l>l4Ur|-jlH-={c%d0PW8<)o?>yAQ_dn782OrBg`UwCiLD#-c zpaZFwDmCGQsvhpEd$2F#v=y3l)stet?v8C?hNS}T3 znGV_I9B{*((6z#Ox}mwbd12Nu&VKLcNI7%wW)lU+ockOX>4Y0+#%}#GA8Y6H`7n5r zX}`FN5L<=IIx>dOhGm~c!AM~Daw(LClp=%9me6SwKD0qn#we4<^N-s<=?+<^&Savy zgsk$BMGy08(AnS!E!kH430*@Li&!{lK>D^R4tzj?)><@StS3D5EAv=L7y3|al~QGg z7Qx8KZ6hyQ@jgwL((}dS2c#W}&PYYD&9#rY!5bKuMw#$ay5!zxYzA3h;({yrQm+@f zuF-4_w%cw9-6m@kZD`XUnv|xU!mLyo0S5}P5*sY`w4-T4xPo3lt zGrJHX*$PnAa%dxB2^-OB2pW=#*X{)tKw-5j4cL_$S5)ZAATK~i(o7_(q89uO-61rIWV@xs?XFlTGWS>>ABOZZIx0^LAshW zG}dgcbRcERop;~YXMg&0z59nh)`vg+nI3+5OZkUFpMUtFJ^__gGNr(jYG=?nbQ@>= z>I*OG`py+CI3LXK=n4;Wz+#NUzOuce6V42eA3Rd!Au&5zsysT<@vVF6I7V51c~?g` zoJqz=eA&6Mw9TOQ_5j#@53cv?2LG4>D}Bqzfbh?TnJd(jb_l4aYyOST(CuuplC zXQN35kpEm`8~O`+yxu$yWp&s43r3JDDub1Lu>ETD8vip9h#qPe2?mpnWoO+;vyILz*Yga5nEh{Zn8puT}GK!!FJP;SE9z)mhHwr@5Od2W7D1sAY_4U z71|Owv=)_G8(fMU8MF)zUY2Y(a8V293Tz{K90eQA4*3Nb^%hygjdJO5d}t zwdCPPLm1Pc5jmIVK$rCjMpfIg&qC_c8UopB>mSDUAU#WLpV6^N7w$u-ZP}!kCkq|0 z%Rb?Z`(W=tpMUwK{>{Jt_xhXY?`;QK^SXdu_ zdP_g~leaaWZfJ`)i+QQq!MSg;gP)SF)2SxYi7=sjW0>$z%2CYb&bBH$`8XBHO6VAv zAUpddg4~0UCq~<{gYzmza0*nKUV*XlBiZJ{$U$u@dCjSZXR^WF@={eAiYjy+c`yd07IaR5F(o(`U>CW;t)I4KpMyx# z4v5HVkR_Xo0x9zmXb2sWVA&}7WOg`DNbg7oy^Be-F3wq(#@>3Gs?H?Ggl(15kAYfy z`!1`c! z>~fKva5kNP&AH@b<&?pl;INmxD!N+J%{fOSn>f@NXY!pEpWo8%`yXqHBi_c@FL30$ z?|-O=9Kjso$d@?q4##}L1K@<+_mV1K{jKlm(n~Mu{@r_8l&WnU@`5*pZl7Zq>Jty2 zJ=(Z`aH#Cqju#7F1P*lv=l=QrLJu%RIMt8-;Ac9f?i-x_PZ`k}oxOBvQ(cvo z6KgRkT24vKCfdNMZ|>}9!XOwuF<5vUi8cjEj!3$bu*fC(kSE2l%sOr5Dc2zk^1xWf zXghgAhgp^wi_S$IR8Uz&E}|WbOPm=3*;alKf3-+Cfxb>`I>jwR3J4V`RAd&Bj3gAt zmGKG+<5qa!Hy&D9Sfw()=tRFz#-`i|4Pi{L(XbpgORO_mLvcpMfKl)Nr70tZskFCi zG+K{iiGBlPviVE3Mfs% z_4?qE2%Bk311)UQlx;|#f?{W5fx`lSjQrqAEmacy9Ej{8G=n-e?&DmmWkK>wS zKALW@(>~#q^!BHp=@z^CLmn#oobm7R@$^1#8TUDp-{nK?E+1WwT=i)GSRZ}M1cSX!XE$@Q^-I9wYDw;Vpi+p}7i{XpJEYYUIgSu}@=c z%XY7Fjz-!r%6l!XWXMfhN+}A=czWbVT8nHqa8dH@AwAkl8s6^Zw$*e61xbovn|dtU zY9VCcb3==Cqb>97C1V>&#wg<(B>i+)wwsFG)0P-6c4UKPc#EGhAn5^7w~+_QhYDR0 zxLqVnbU1>>AZrm>qidI5x1(gILykTlvgye`Zn9%r$f4?k4ZniO1MZM2tN>4*EirZIJs1 zD7;mW1+^6Jg-W`}h7X#oPmeN7vMoz0_gFM_Yg@-#8+z~F9X-PFa>~&jWO-txQF#JQ zxM?dzx;WMMKhnqli8J~4K58Fd@zdJ-_zOLJ_hTJ#376oWZ_GG2-B&yvJlzW#=Nm&{3I>Owk`^`$UvIAzkJ6BiYt^$eJ3lE?@E7SXt1cPUtF6 zXKgh(%PH&itX|7mUD5>Gve%Jz_9%t+vf(gm*gn;XL(c=Lfp&$)Xrt7db_SGC#?}re z*;amWVUNwulFgT~+a6fRH3ZuOcb3?e?X$vPA{9#`ejzhy%d>#tj?LyeGA@XJC2_qu z7gdn{j`FOFU`*R?;|pmbrd882)pyu1zXIK0LtB`$lnI(ryO?Va%L5M~+LnDDEdx1} zX!rTBEGrgIDsGAXQkKQK{Q7J9qc1<#J)B};L26hyMyLH+maJJ8D}|RFw=CJEU*Eo@ z3A^S8pMR+d>Gyu}Q$6BnCBLS4!p=I5Je!kc%76vimE$5k;ZAOPib967}!ix~qqG?uzkfA8Q}*kT{^;Lmn*0IQs>=?E{_=4|y={@xeBB z&BN&k2Y#RP$@f10RAqZp4;M$ecetk$43%0mn@^ShquI zJUHVr!0zR%w9W<_8=y&{HtCkg#avh*3P#?tt=Kbl*nL_AIIexP`#H4T3of{xy{Xjf za$QlahuFcC&1GE%u_WyW%>n%`_4cQ3#qYO+I^B6WspZ5 z1C7X%ZBW!Kp})_O=EFyiv|vq>L9`O=o`(Nb!gj0EKBZr^KKrz+eKG$mo9E~AS^7`Q zH<`BA`i0>ZV1@BBk;LhGMtF_Jdlq@tfb# zYrp;5I=KBvw}1AY=6lE5n$Bg;!cTD6cX93y@*Uv#P{%m*UF;sPD_{6TrB*%QxaASA zJIlJ1tCjQgL(*~XC)D}Lo!k190F^lI6 z(1@J{H&`B6BwHMHTH-5QQf+Wik;q%~7&n_#BGk5A%-j@}0^Gy4#$a3a09k!BBq-1x zQc>UeB4fw4>>eVx!Rmh$ZIvP4#d7K&V3QsoC`J1SZ?EO`QjKHQ=pZ{wD1*Q@*FJ`` zt+8Gken79$Uo$RtpBIVkkcKli1CsT0=2ocM77;Km^s|t#2brL)&sC)W7r7vFQ6*hj zR})YRa$6!#P_jcBWQLtJ#2^sUf;j!9vZj_e>`2h)TNgdJKK40F-f;K#_w@(|f5KUO z@=OJ`W%npU{dS*@&VNqGx=DXR1NB1_(F(YW=1iYNSBe)h88nk z!fhTRFWSQo{+JVvlV#Pl>6SLIP?(P*!y|~*;2ben=f-2(pKrNZ0p~B@Q(KRAa+u!Itpi@cX5go5WzThv7k8=B5|fY3j!14ckq_4h2vFrx3IeE-ekc zFlJ6c@^VvlSyRCvi#+9Si<<`fasPZwiPN&LW!z(U&zik5>K|xOcIittHR)>y&k(4`5}N$3+2E4cteC6Ky#$tw*Ae%{E<5-I zn<}z~*bVg8Lt2}tYMqk&rD8uu%g1bV4A6+jGS>y@WgUGLP-NYLY^xNXp_27n0kv#s z9~9|7efpu^dvs4Hl|)Cza~VKss6TBWwTi9(t98@$_e*ooE0xAXul(rF7wRTcozRh! zvSgpjpwiPcqX9!=@#TZSo3Fj5X`SeU4?a-c-qfq#cwG-}-O?`S<7TmSRFWOLT(qFv zWHQqXM?aa*bv&PF#u3o#zwuqw>0Ed2+|gZj{~LVV+~BBWKOc&YPPD(fr(KR=4r#^- z&zeWb7Yxb?c(A*#yX@rm7^g?9;afZe?y$h`bA0n?ai~2nbXcX6_G^!pc^lc@+0o9W z9d_*#6{p`6q?dVWn)c>h%T1oV=SQqOFIHB6fqGIl#nQ%7O21PqRizf04wcAA*IN6$ z(9-jz7OlhRk0{}7m4Xh)y0tCaO0XZNwp+dpjS|Qh7;1Z!N3z8%u;hn|H8s+f$tCFk zb}gnvpS0aG?b`FUSM{XrwpV?yAJ4PN2X>z)f^Dv+(H_!JJLA!hU#-wmSvQdtnY{BT zHa*ibT2&I*?Tt2}n1GG3m#k3CLKB5oL`+ZOb0O+*R6^tnA$PcXzeK z!N(3JsfPkUM{+G@&N{hegbH3Q*zWm`WN=F0o*c_Q#aOz6(14DGvBZ36hu`bOs*yPY zxrPJY-q_SfAAP7@KI&Y1=|ydBZ|VMLUuek=Iy(u`h{Epp1V_KXIg5uzna$){bj*4B z^|!vMjT_f>d~l>&AAZVt`o1>#qI`_<7kpGbb`hcDN9`+8ZbX25+(&>7Rt;uge(_ks$_WC5~=xrtA*FuQHy-42p~{=5jr&9LbUgY3z25W)K6(?Wb@B zIGN(AQqCc=0J%Y7%nU+Hv5LzIF7UwaE!&nrYvMq|uaIn+AJp0vN+v_jxWH$@>NIrY z7>@Kbi(#avYx1nP0CTKCJ%-46wy!MMv;#ztehA)PlfftN43ODpb?iQETm2Xn#a7TE z3nfE=o$<0t#&HdXSD{>kCBp||g|&v$Gy0nG7@>rgr?vzsp|-}F3>%?JfN7BnNpj?2 zz)ix`U0|An-`~}O`;?JI6LS~U*>awISyCqGYkRWZSItS2pN7`=rx-mmZcows<+B)6 z1%<0+doVfD=Np-{w=dW!ziBvMblN)Uw81@+UyID|z?)Uxow>(5nStmbc;+xy_if3Q z8%L3bwv7Y!GOiSB;zp4P|C<(TLgqYppS zEq3Y^XZiB$uV{he{rtW6w8(F;a12tAiyg%Y6?8O8?9j!T(-Ax9`3u+e3cL2n)|PI4 zaZ4Y+!|s1+ZC$>sFYn#c=eKWj*1o6Pj~?j($3(YyV0`-Up>7`@=pj4td&TeSQ9^Q5Y9{Ih{mzjW!6<~&>`wa_dpb7fm)goLag)X4OV zVDbwXV>d7zH%0g-VNiQ+(zRI{tWhb=rEH`=w+?sX+{aR3HI_Xh14O;W93UFGfZ>1*l1-SX3_v_abk-2Yc#&Q!O`hQ~V2a zF)eu#$ohICgHizTx$Ii`VQe!m4N`l^!FS}TL91XJK9FoT=&{f#^E_wbe}mJ5-?@BM zFRN$+aS<2fC{PNXs{`1Htg9Pa+9*>MoSI3Sb~K7kiiWHx37-Q=N=b1IDmdeYJW1fY zP{3+=_!J@l01yC4L_t*4=6t5BIN;m2ZtEehe`05T?%bsev{#LVaJ=CQ}51iy}oY68}OnQ@@{3(k;(|Xcb5%%^WMBBXV&=Zg@D^ak}(# zklRQt*;a`x6xePuyQig44%n1!=;dUq4`Z5qxfgw1R~9qNMGEBtxzKWlQZQz&^P?=( zwrneXvU}d{sm=xaak=eQHsv)0yVtWVB|>e>UPsH)ddRtP?w_D-%*-Q2X1*PoB zFK=$?jaOdLocprImIzRyY|A8bX*B$R4qutJwqK+T@VB?OwTXkzj=i8yR6y@mH2jEhK#agPI)+<| zByB$?w|klGHRUI>wtE{^qY$kJiyw3#O>B>8tAp1XY@Bky7!+VzYSFUKgBdP^OwbYM z^vbA%JjQHv0qROdk#@M_a$PU|aSXJR|*U zYuZyQ${b|Q*2g&oSfvbOa+7*Kbh4ISYM+^X_x`Rv=DXx8JD2rxoomt+q@X)*tJ5)a z_SxOr663eSsfVLOgVF|#fjr4XtFd}KM86c794y2DVCl7vOmLL>-Sfr4u};`Q(;B_; zjW=}Z@+EEvYs#YC<)dg8b$*lO1c!W5t?aB1uya(a^84ubPmVObc11T{e?zzK+}Ho| zzx*qG`Q;ZnrrIy|_VklaKhu5X+yw}s57f&4P-qrws9i2(Kc>LfFYjB8HJYr{B$rO zo5qZZf!c0zul2Lg&%lK|ChAZK@__Ed`ao~VWF9O}^zO$WYVYVsVtyNBk;PTYST&x85ouEgFsRee zbod@AvjB_}HJWB&R9DR=GZl{BkNLQnMe^pGZ|dgFm-WF1@9UVI!4Zym_rU{oRKBou zMB^SD?Q55F_>)?6piX!B@cL+RtXKGuTG+M!kN@NUS0B*9J@oHM`Vr^%|Kamb^%?0; zNqRjQQ~TM9ZOgs@ zsM)<71qGyJX$7_XBIl>^CKKp1AeQZhQ>jkVw2y`sa3z$kSF^5{Nxr z5SlH?c7q5GNFz{S8)B7ESpv4z96a^4Uz3D#_asYpbo9#wOxSL=Z2xk$hSIU}RWKBL zDrw_98hxtfSIvBWqe~|Iut>Kl@-g2gEBEGiu3py-?#T)MIBkTq>0w2qOv_a+O>w6q~jym%Hqvl_v z)>4!+_zyq$KnFPT{4Yl*tmJ*3HV=;u+RlBShCMt!)GoVr?QH3#Z+}bw>mU74|L#XW zBwsq1Zs_5BQ=fBO^Tm9o+q3M{t9EPEel0ox`|uBV= z6PhwX_#kzy3_gYN`8))+=_3#r_>%Dfp(AXo1jy}ET^ko zW8x8y>69~N$9h_wtsV4)&MtAN3);G5v$TYEq&xB>Z*6aVmOM}m!FJ=K8zxEH=z-94 z=twu9Otq4W3Xsv4l)AHvhf%5;?UMEXkIyv%thr(CGIr~DR`DhC9i7ZXP9AorReQ*; zY|Qk{%^h9utSQIX;xz+tmIY|D$W&Q*Ff8E4Iy09Y>uF5)6s=a6no@K?@_Ofvd_qN& zu4og-zBQdIA6Pj6YR50HT)(0B-+o6A??2Et-gra#69Hd-@ue1&JEG%<1CFeAY;s7m zBHQEa{mQGa=okn7KmD73)6Snd9PpBlvd6XPXgbjW#{@^E>X@4;vIZ7sUT02l;0qRM zoV&?avEtZ`ykKK8n`(}8pUvhf$)`Puwz9VMyrCZtCh>U-T%gy+7eg1(3)C+ZS*D9+ z1=~u<)wb-5Q7R56V-u`&Qc|r<@(bhRHN*i_#=W2)*w~g7KCs=&+SrCCYZdK2ZzZ^Z zVJMn(q^w#sG$|}J3ox>b*>YU8gFJaloMI6EBB0vhs*aXAz1V#_?Vify4SefkvFuZj zD|1;#A%8V?@M#Ast2m}Z(~qHNbc^*wpVIz-d$eUmC+%fFBt{lJwK|o6{g4s%dP|U! zG}?BqeVU|#4w-<}hmrOv+LDS}BYMeSq^oV3zK-%2sd>L#Tj(2|)1nsbalHJ=;hyf^ zyRFxEw)NKb69vbk30wR)T)>M!a8i>6IspL*=d4C@TgFH=O>u}@Inpdp($&77zE zWYPZh=uJKXya?`bgmUG|Wxaay6@Bo|`|Ve+@?<)}k)Pn) z?pyk|fB2vD*_U6+oHw1|&=03Y2UF{qUM*SW3(kgkcxj3L!h&`HN?up3{Oj3{GNPkHaI*3%;mjExabS7v-rph)Y_(`kq20JLdckK!#x#^^OW zP4E>iDf?`s?sY7cc?-^$P1R%nLz51=jB&s^XlW=bRL8V>4?p*i-TUWz2co0;?H6Cs zcQ$skgY)B#YckZ@nu*OW;{cX`R$-1-;1d<43qWfz%XQE%`D5~PR>TbDom#c!q8W~T zdLnIms&BpWik2L=EZK#>^76|%-aFJCJ`PmjJSay$#KTA3WP%f(D7*Lk`r-qQWUjpa zn)d0;zy0A4g*h!*eTNhC1hWo!iXB(8)0a+))D_csfKEqRg^mQ3Jajwk2nQeMK4r%q z6y&B{p-gd6qgeUcBhAW#wE^R_OoD!0@k#c~_>TRmt3IZmMW(-}aNm@W0Tw@$v3n}$ z8N`tXgWT{1n}KcY0c2gVx}PX#6-Ar;0*Z*~ z?B_M}ob)=Fh4ye*w6(fyK5(~MUrgqD_s)GS?%vn$U4L2Mo9*bzqG+}>-)h`U#9l`^ zW{q^;0;X1UNhqn8uZ>t=40&=oEwjctE|Jt?ZPbZ2=-K3CsSR$VH(t1*Db^l6xZi#! z{l>NH`sCw}bb|ADDOAKVg)Re*S3-|i)CcU^bIkI}x4x-=^&kJEZa;je1v(2J9?M!3 zBpv-F^$J-B?a;|@!3f&y544h3OHrl#1XLa*mHMKiUntOP+S!I76m$b(j@;Fx{r}ke z4nRqYa_e(??(UK#NHCKzXGIZ2F@l%{1Ox*rVn9@K5F|)85mW>bjF=zh@XY?^oU@`L zk~0!^@AQApS6w|lJ>ky1dv{rU^wy~_)>pBsdunEPEzgX`$X3odo~i4p)XSU@R2_nn znEoUkHI@O*tBpaVH{V3)n~F*K<~UZo#~^ML$qtj{t`=`>)LLt?No(C?*)-0q?W1z$ zo9ODTqExxA;CK;NOB(Wz@m8Or527BDLIP>Qo(Z{Jl9nR(y{FR+5e(UxYBhmX){-;}U%9WQO(>Af zD;7AsIb<@Bi4OPbY02crdK7xLNjZSF72ZI~-HI z0p7-DbmdVQIOEH2q5Nk<-U915Lq+KfTC&a<4J-nzzk4!m(P$mCfQpd zKyoOgjM7wI$fpUZlqi=}KM2SL?Te)JJ%9iUWnyShc>}VdeHFI1l^R4Zxag^Q*+Z^2 zUGrV(A(#)?7SMK7E#$5iv;V?~f8F5?+cY+Bec@!() zOy#8{(nX3>lEDcu4_y^CiBOfnZSmO4BT_lI;6vY+DF{iB34OLP0-5~D;dD&VFp&kQ zL1_a_@$5tu3O0(s{UlDbvwg`^u_dle^vx(>LFOn{K!P zi*(j~r@Pz<|M>Ssxa#JcakB=0ai@cuiUPNYPA}-hG0U}T^p$!d-MGq1xNLecetyAU zaf{xvmrQlA#C`?zKmfX;ON7OOX{kn(;=R~SA=wKY(vbvJ57qFXs#~)qX-sm@Tkyi} zVMJM#Nj}-GTR)9uX_@i|UuWf+= zy{JgnDLM#rrqI!SViZLOq)Hh@5j;RddFkAzkO{3hqLdDT^`w-ju8K7womcvbL?1NP zgBCd`SlGobk2JImOHnbwv8CO&K8*3U-x%{qlUqP|j-V~A)SG!3)W0yiTu0P@otnna zuDJ|XYLmauTI*uN1*>9}5?CnsZ>NfA&n)A5**bV=4SiX~BKZmIqjQX6`1z{>`U zw5V3r=H6L6jVZk;t+(18u*!l3xaR6>Fs+GSXWex%wbDZTcegp9$&j?wYYF8U{X_yyEt(icRSFG588@jIiy4rwCmo=KU7T=XT z3JMJ^f4IpcMX5z(t*D9Wk<#1*(A9W@m*@RU76tk$S=Xmj`S z?85&f`PDk2`FE#_3;uHruD2*REKOU zrZhl{ijIzAy6D`z7^^Q>ggdRhHm*@c{o|L$Fz^?$VRyn*{~ zkzTc^gDVzvaIL=NyyXt7Ve!hV=q0`hll-l>zaEQ9Stn1~tT!axCX?H4 z&OUuace;e0bC5aLnYq11>yWwj1pLyBj4tK=HYp$bX@(#53gKCiX%2>ae zRTnj+?o0N|u%TF_4S1mz%|bmMR@UqEdaJIE)fTRV>#n~Zx9DhP)iqbc!c|wog;!jz zW0($ZUTGyMg3wy3%i(GH4^ zp!=fG{XtRGAS@dw2q+b@)gY~ssQ@Xyf&?W3KtR2X(J_@l{ym!=NJ^amN+UUZ#r7rH zNPF?+&X#rz*Jm>MJp=FR40u8(&Lr@mF1UJ+8d|Ms3>K#1|5m ztvrP*R#|{6ys0mm!nJz4zfMOn*DvV6e+%m-z1jPp`Sk{Wi{i!`d#6BG8-Ho41?vS} z7P|Rl*XmJQ2ygf$)L<A<8 z4^Uu*)PN|Kc!loq(3nU)F1aOMkZzYT&bMuIxqu9PRZ9qc(8?&1Q(APIH`zUgJzup( zMs3@ATkZ;_kZFl&b<}MPe+BE>w?gQ|2h~m;R6dSxk}>RvB*sh*Djs7Oqb8x0A>x>C zpsRLdy^75`xolLV_(7vWf=np1&O4nBOp7AXDai};sdh^DNJqRuc0d-OP^e5G_$g3w zf@11^AQ|l>2{I#84Ox1Zvpt!@RAH+Q5LHNRN=l0cz` z1K!Bt%MDb!OcV-ofhrZezy-xha)H?Fsj_V-PVUGgRPIAlv+PiZrjl;7LA6V5lFj8s zh9;6A6NWnUDJdyKbzn3Y8$qY?PsAmG$=Zbxm;2H!sZBVc6rVckwx|ZnY8M66pzj4h zbtgcVBc<`_itU!AZYwCRX@+EZYUw4teFWbSQV%3n8ZvX0qD>Tos*|G8YVqMx(Gvax z<)Ljbm0S^$AX!NWp0sQ@vH@MCY=E`NT*+E1MbSa0+Pn*`=}xD>l&{VkCSd{P{X5aX7^i~0y*Qhnv^a)Can(vzyba3sWDO@G-D(%a+D8>R#VL{oEx0LdgqqN>1J1DhYK=HCVnTO(( z@+(%7#y^2vb-_YFAC0H9Af&G*mwCk%U7GzY41#hvdlBRN%n|-om&2W%zTxF|xk{}aoA$xqjNry&uV_aaK zq1qKudDRZG07-aWI=)vjIz`gy9_ncGp3>21vL`*6Vibksil=#jnrD(2b&-U9NrnsA zPeb{CFeB4A^mToRs?WYm1+Pp)`=RcKc2%X?W^_dj^SW6evF#c%IcOuMVNI;L z(yF-jvMaForkk<;h8yDU8*G3pFTV`_{~0&fXhTe|v;fx>(^#y<2rWjSP*4U)f=nny zYiJ>sMBLQ5fvGzx)=ctgifx``9iJo$qY%xIO!oVbJsFK_Sp8)31%bGE6xS4_lKTmb zYgq%n2-!FsXzAN>#O?XWjQVl|8!$^5NrI%flt_lGJQR!^2&zmec#B~k(Jh! zwcvfw$YpHSKu8Quw3qyrKB(2W#o|pClJK8ismVDWrdprd=pC<^gK2c|{Gw3Et8Z=IrrOs06GG#T`uY=jeV*eDI{uI zF{BJm0mAFRK;v?~87>w)3G)yw{4bNOu>|PcxEL!hS_$i{vo0>V=wkf+FMmT(bg*F6 zMfk0K{8eh-Q!lSOKCy zfgT7bVJR+>AbXfN6al)CqNpy*luUADOtRlbCb8&tfU0aFZKBtuDx3MA#4P~?rR zp(6>@EDWOn01yC4L_t*R$>c*0rx<#bJ#F8O)>Ah*vOl&T@&y4xo3TMtO9o&&WhD?f zNss~(3iB$1pwbmo8Byt?Z0HLBl}`O9%?r{&7b#v43)(J#w1|`k3zC5(p^wK3aFMBf zD`YcRDM%vsnHsZ7JsBaBpGgyGxM8!^$!T;>QoaO~0)?0iOny;?sXipCyoxIV zk`ah~cB!n-b(z{NpuHA?__Vi*k#Dj78f#&dg$v>T-f5kUHo&^~+zeMQz6CxPU30y4 z@biEF8-Km-dfcpUZT%OLN*&2in?ALus0sop78Ea`YKL#7VUT%-V!}zBdPrVW}3L?8lybcrUuEru_UP}C<3Hx09X)31rjKf1q$|+g)iHz7jk@n zKtMSQ#3dD_qh$LTB*T<4^Y#_|tCdolyk18pTta%3%>#l{H*H{&Iz3--2 z|Gt~yk|j6eSJz*QE4BIGDEMcMr2@Mmf>76#6OgVAQhMkEkBE77xllZ z(E}BJexN`~ilqjy0kIP&LVwgq;4@I>O zNWzy1DPB`2N!Cf@Wy-NnyX;FM?3&4-{VgB&rScq>T9owYl<#@eEG$= zbm0R0XQh?nXOGiU1*Qva*gB)~CU3aBH*Fo)>unzi1Z-1ll$fA~(;xw*en=7eL`$fA z1re0~L*;>J1@)$-8710tG$kwP@-$V`Ev;!O>rc~Rvkg^@^bDkB3}P^1S5N`x1p(J9h~Z@vw(SB@Oc z7t%wQtPz(KCxBAUBKZx+?xtgg&n^`-$6DponzVvZAyWu3ICSBuH8fh?q5kFRTDEPst=90+9ghQUI1w zU8QsoGIfFeHBLBIp+GDOkO@*EgOrc-5KXWTk>hka9S{J?J|JHjhT{58iM8*&3D(_o z1FX900!;tsYW)5$f5Z=T=Kin3zqwoqH!he0)R1`VFSNM|&_}yK&*}VoJ zTOjPL?L|u_dp0uhV2an#!u29%L6C&`Cdi~GiRKj>QXYt=VF+I|%0{^%K7%R;H@xP$ zYMXd9f$WV{vy7}Ul!Go36e%j0cdH~K43LyJ zK@}R-N9zo^Mv`0IUMqaJna%|yK_<*po=kpZj}9vn?Q@?DIgkY?fD-DKhnE!rk{ZJV z=<>Xi1X)2ku|Q$QP_MN&#hxP?dfSB16|#$WnSy{p6@zshqR*CAttAAaLKi+Z+uF^UC5^e0I8d*jlfY7BZD$Z1@7*felrI|Oi4D01H*F+NJaDa1- zI;`A?U?bGSBFv}(5Up>wmvM00QW!tR|~`E?K4UC zhHOxC992|0Qe|RKrPUL`J4MrxOl8SLT{lUPgfbMX-+3MU>(76|&%XX`Tz28Vur>^;l|qz0-!P_Jz~TwNhT@v+yGxxmLWDL zLVsGZvu$EZ!0jgqQk@lrE|P$fc3{lM6_XR2ZWXN*od-B%vyJVLss&JANQz`&=*2>I zL%x9cpooi@?KM|nmDN_k+Uu={>!%mvdg{$YA1lz&$)Xxo$`KwDw6xiKM1$H0n06=4ysyl&uwW zVLT9i5(>c%jgc*x4t1JWCCP|K67feATEh}RN@K`_f*yY|F@VNJ+L{zSF3F?w(S5YV zVr$8~)5el%=sZm&86et=XIX}ao^*k(4 zq5T`1Zn_EXbHDrI+5-6L<(K0Mof+M*a0*L0Y77{EpoWmz>;-N8`br6b2+GLQ%cJaN zCBquYE<1NPN`7xj6po*fs_xIdBGqBPam5!#@+Ao}p?0ohGU>@dT1cmYb&*LJ*V~7# z+O76@I9y(mn&n1XQvYP@^PfcS^XZ~n3f+X#^<~&k`qD68Bh@|IoMaRsn>6uZ7?s)I z7-g^YBoTi^ttxagkTe@036jC*tT}cX*sKoPv<>6KNttaT(5Ywu8n1*hzFI0k>8Tum z>LPUv24%jKs5iQ6Zw~v;UTFp=TN>0ngS~WsC`^^RFxf1)3DoQy`oDD%V z*?Ob}Yge%`sD%c)X@?AUZ36RrX(T-SX=oJN6JdyzPGhCX(yYyzPsJ47jDm@adV)%) zB-vr5k&xF&pR$NPy%}$O3@6FSYsT#?^KMU#5onkx(%*GB^b0 z>rzvip)7h5-JZEZ9aK_j_i-rnSxfq6y|LT(DUuLwYLwfgpi)Xu-b+bNxje+UjQz#R zEww|8jLMTT4PlI@)S^~rB%^$tWFrZZmI5S0W=JU`89Ga3;z>eE%=L16MvY78-O&-j zNk-Ko4QkzRG{caU57<=KJf*LZeMKQXL`hjIVAv&_{c1FPXx^D&Uhu|4Qtm_{TCfO8 zCLJY1{5X)=m(YwifL1JmvJOo)2qZ`Q3@tc;QZX?DNJd*d1|(sB^9I?Hkz`0uQiK3{ zkP0LN@D+>A%q*c8AV9JQ^e%@bldb5ivj#4_{(4-tcnPjsxCqlz3o*?O;@2^HA{0y$ z3lIpPb!9z4>J6FIREqjzmy}8zfD{N|^yP*IC?CbPf5;Lip@?z--4B6Ylo);Gn90M1 z6E~~eLB**o9baVs5o0cl|1|9l3iX$2_iX?k5 z%IsO`!(S@J$v$F3YY#(Kq!CTIWrHNh2Bah#vUsVgBt^mnJXea z60DFL3g0V*bP~u+(>_pyjH-leBRGt?s$s7^+H!4ZFr@3simv9Q2MO6QD@u9K2#rOI zNA{KM{e+}?4ww%+#RH*3b49CJ9|6K~miC)Z84J9@axG^p+rE zR{a)HLv0JlAW^yWDs0~M&I&5$dFUW5<;|Kvl2K?8|am{)!;#gXS_K zST2*^vTWfe+BGn(=fgCn#oMcNY_&xZQL5f1tdycK3{0kr?|z>$Qf~WwP{glF=q| z1ns#7N6OFrZ9{qm0<)g-Ga7QAUHTX|MfH5wpcG1OdlORirEKd{HU+PX_Lx{_AJsw7 zTrAMyf}3u{>4gB-000mGNklfj*_YQ`GbZ5WR7i`Kr-}XMTb>M$Onilc%qQM zK}vzq7ok7$A$xSRch9mkTwZ%i6JvRoB{e>(lnsd0=s90{#F!|IsYoO$I?3Rj71yt_ zGXAq@5pJHEO8>NgjAYNjH%%SD`T_XXa;OHPxH6@Nc97M5=J?tloCfV#qc|i%&H-R= z<57J%X6M5Y1MD9Dz}%qMl7O{kjOf|@q#%f6vg zM=ZxiuY#e|h99(+Q`>VesiUu^SvLtK8E#~v7D$4uP*gO_6;%`M8h*jdkh$$m7t7Tw zsNJ55)Su9+6K&coI~QKjRgdx^v0c|BF-g{LB-M8arUXEcGXS#t4O1^xg(y9?rZbi6 zv!lL9#cQB8%#rap7`ZsfVDmhMu!3h~YV$xdN9hL~CsU9F$)V20RSTwYqc-+Z4jw!o zsc7lRIi&c&)B6w%SPR7}GqR`Uil-b%<-qc>8eR;Jv|OI)&AUbbM& zbD><&d=fpXLxj*nIjW1I@sYTqB6T0k_aSwqQM%8%f(iu5Xd6lKsDb9irjU5*ehaR$ z6)xXYpfZH+*2&2JHdIq|>on#zQY)Fpj3h{+SW+ZL62!t3Oa#^CfsONA>7haa*<#bT z?1G~9D-lkzRyQ9_333o}g0vu&D+VblSZ_#zVSXG)c{MQ3H*8eihE(3sN`1jt!@HaH z0@V_c*dY8?VOY-kC@X)LHE)|?zf$LerOu{NKPtOb9MOhz>GBm^wuz>#&Bxod%Qf1r z3Yx`(cU!#~+rN!c9uZg6wz7Idm#9#-B2m{9sAM^mL+e#d;WtQCBodVyB0yvQNJ<<; zQRxflkJgoP_@Qlm@E{0X@`J#F(CRY_0w4+hLF1zMK7 zKa8ZRljp+%Nhq0ZfUuGjg<(-ta@mANpk#pz$Oc^`#S(=mU|u67o+Rqatl)MSwLTca zu7+(*gc+W9t#7UUBtzfLlz}YQWhzCMEt>Z_G|XG41&C7XQlUd!k+W#08kpOzB_U}r z+1|vUN77`UkO+-ELG&&%NhCog6#f#W&_^;OEdUiDz=8-{r+`)f(t?4g0D;P!Dyp?< zNwC5Yi%iXr205AE=m@j7P;F~srU)f^J#6}BebAOm=$g)%m!Bsqi!;&L_|6n-z^=AL z*Gz3}ufn!$pD}P4_uF)g8%T}5(C1@WAp32F)+b4rcB(J>VpA`)r5j9?<_-ChDF(HU z$;88#c#|e5Aw%r4Nv_vuMbGtS+A@z3HNw^0kc{yRXYoO+jHI|W0O}8X*-kM59bt;i zDO6b)LDdQ^pGjtXM1Ub`M;oHPT~&<$^W0hX{E3o;?+@Ptq&x)e+-Z6UKr#{~NFpv# z>IQ+tz5AP<#as8q_JC*M9De#PG>9gxg3)=3cZKJVM>_8Y`02tO34H zNQxl}QQs>8iGW>XOEefXulAsZtRTo}IxkON4Iv1pvA|MZswWn44imD3CY6|zmVIW2 zQ=C)Jg z(E(Tp0GRY7#Wvu6LvjWI*ax~y1p<^)tS}1lGI&?dmqBhfmq~T6olL=@OG8 zcQuzFrHQp9jaWz~i9%2kg;Z=o8x^E8SO7r@U#Sd13Q~wDhh(D=B>)4uOq-CZPEGwN zzII-&jOV90Uv+o-T9MjJ88aXr%kYYae>1)t+LTA|6S|EkJ)znQA!EWKvvZ@|4UrLP zzSSh*H+SDF1?KC@eaLja;?6@HFM6X6rIlm3gcvDzD4}tY1j*146g5Ozi6o-D^rSK+Q$`sJfbFC-6fcVED#Vk7QjN+q3sS1T z`fQ_5Ib|QLZy~R##+a2AKZ^>7yRxSy*c5vFC{B}-+mUF$qk5q}+9~U$srs5Vrt*k+ zH=hylhTJ^rcEk+31h2M6ULs|sqsfXgyfXrYmfxoK2J4%gcy_s7&S zJ--x}A=Zf`=5KSy?bomo(9$2KzSVTT>Bd!_7ftpHS%T8IVzaV2W)mbqa>f`W!%sxC zXn~LdfGBzw`()MzR(eW9Iu{ekd`2j4Ga0FNE9C|^*ifiJH6LJAb$jCa=!XK<@PDmfS5g)sUH%U&dl*_i%^D27s1Nm*oR8QW4O4Mlk zWxaVL%b9ldreWA-WWA~7CajwY_HX-GXIO7Kt@cjqt;J)kX2$m!<_*jC8wJ~a?9z9p zu7PjLqsorbJ7!mHa@(4)zh%G4_|3-hSZJZy-^hMai7n&J&Gkqan$!g#Im5i)o(pj` zWHXPUmm?e5Xx^jQh5x;ojIcVmpm5L>BvnEARS1nTIZz}gvMFV4gU0yj?N95e47d%d zLZl2^sbNgWrOHujH#>;tvSRC`C=tsFg*NE}$>>i(kC=D}=B0;t1TgfX7FP}Vsigck z9P*W6#^gR^C-X*@YpN=&&+vLQvc0U|s*@)Bnc8n0QiF{rXSxiv<*S<)u1TSc3z zw$<=bNKvY^NF?z|iKHZa(L&ZsK|`{s*riKu;xLSeN)jYTSjVZWGA4pU^dNkiE+_to1dDqL0xbu7^-Z5AQPmi zc{1t9(bWDKWb(6IIQzc_bst9VcY4xlDJW%KrnN0AM6)hasrpji#@=VS^@Mg?+j6ul zFYFM~RvANLeWe>9O&g(cMSV#Oxu(^^BMGtrh*v36v;nCB$;tr8248eG1kuvalY|l? zVo855)|b*K)Hj zX;q}$dVQRzBUsJ|rKhC1KE#LJpI!{TP=%ke*|kaqW=K&_u5TD^m7meS9ivr#Hlq7V zIv-z(B*=vEp2Mu1?9APBgz9SYm1@N&L<|zDi)6^kE6p+{9<(Nwadpk`x!R^~6hGjb z7xbtC19}2XNw%%h4C{^OxAD7U3V3|ceBU|d^OQ6x35o`X?*I4W~LM#A%ODQtP6Oc5ALg9m3|Wc*C-heAGt^ zP1sp-wu_Bq+2w7{2*Zesnpeu!1f&~>t`?_JX1~&NW~iNNKk6$-trf$WsyuSMntFj7 zIZl&e^d&bn5r#%L_SRuk)uL2g_8T26Sqf+&l;REZP$dasWJ42`pM4F;1+tT7WSlUO zEq}{3h6f?3st71yo-dN%z?V-{ElYz!L|(IVQcTZbAAIvFN>3*z zB{f%CWD+#1NrEKm%X|frVIvcTH*^6S77aH7RLLeJ_NS(?%_mPv?T3XyN=WL+Nk$t& zP)1VuU?QbA22FF&cm!uxOk0djIA&kYwUF%VqjmFwq0JepoR{im3PSe!87~%Uim7%x z73B<&Ia1Rv6s;`VTj~KZ`)M(IL06y_wE<*%o1*U`2{J(p5&#ew4?AGsB76G6Pt7Kv zh%}fuR9EB@vpvgg>So2;w)|ySaZZA1sBcSP>IFxS)W8wmpBdIw@10fe6rDNrE zI~h9yd7d<6WBr=O(wk$A9HHs4g7Y&rc}9m>bkGaYm_?D>h-=}en4gh`H*+Iz@Y?0| zk|IqxelW`!>@$yr2e(35$i3o`XervQV7`>c(xk2yem@Yu$?mEjqcE<%cA2GZpUXF6 zF8%n9ZoOkCbi+wQ5VW0nCcG~EG1$B@;G-RgB*=sTI~b~7Zmx+i*O^wH=4HAdV&EwzG>x^9$^yJD{7=lK z-O&c4E~$TNw{;rF)D`tb$5||NZ`9D+rhM!#QYY81W~$GMA*1UlH9*7QLZzI`%HESz zFb2|nbicdk%BL&ej8eS)4fBS1Baf;1ChTKmoq3}tchzn|-PKF9#-VHhk_~DtpoXSV z<;og4o~h$EVI9;r>!L5}hpBrUHp!*>S_bXK9F)U*1AMv|Cqb?u@qH z@+iG+lfpw)OzJ#rf|q8(2~Jw|3g_p3dpt5$xCYL#LGD*yQQcVsx;}?_5d4PJ+QjpQ z-j43JWLwa9!;1Gd?fcr!97|ih{kjoTI=xw!8L~U3?V+1v|9a&W-!Tmbo(*(!x7|sC z-Vh2=!T$ghNtha>^dM3aMemCK?WawxTOo96ze-2mSFRQm zpyuboI64$R!4XJ;W?CQ#vI&G-XcH%?4ZI^1=oCgrv=g#7b%Sh_MUTf!m%8Y&=(TXB z0xi4Bw>1Af;(v4BG)r1q%10#ky$!!7^|^-wwRS3K;h}+2{2=6x9>mTfDOx;sOT_P{ zRIOh~(|aek05y)sE^q znslV|1cuUY-F~}PAKUJwbZzK0P^m<>Dr5V!VswRR>C79JORWj*@#sV7O6d$#XSb$Z z@fq%2YK`M3PPsN#RP$#KBaemo0T8OK2NxaifiC%h1*oH!LU&@K-rNnMBbzt&u#sSq z;CLA%(Y)Mlqp36R`c0cCHFl|$N!yl_wv{VT>G_EsCflgDn2VzR0u3#Xx>QZ>E69mdU&;wKfW|cfoO{Bt2P3CQ(>Lau^DnN1{ zO3Zi_sd(~pKzOkT{zAj$15EZh?~^U`gH%)jNcvK=AAnTNIY51>ZjvBbtNIwJZOVY; z+Af|{Rx4HzMr|Sql1tT~Ql&>hriqf)Zsk|T!mflMWPkg}e%Vg0-*l-lBvAWCYu1r% zrm=ymBZ)6{6AIekyVCWb4O^Q=SF5ZHKzRb(t)b{Osy1s3f)|B0*HYAQjPMSlTH2t4 zdKr~m#-QRraRLQIWb~aVU2Nv2^afw3N`{>(Q%Db8WTg|8I8pK@XGfAID+FR*G)K8H zn!cFJEH6u1B7avisuqvaIE*ScTu%~Y57~?C>Bz*}Mkb!DC@6J5NP_G^4t@7@e4$!e zOJ`~ETQ-tLxkp{(_H>&VfuW7e8)2GZ<<$h2y15MRd}U*v%^SmljnWVE9nh(cQXBDr zzEZeZj-TJqO%3a6=+&yJtBTgdeipoWn6_biM%z})x@mOh)M6i_dxfOTg<#r){m7n7 zj^kb4X!=MlI*Qu?yx|*^4s_@$4|Zrq5+rL|yPuY8z$duf^?ljzrsEax`idbuMpLvNy-BThn3E2W{?h^5PNVjRfh5 zR@QXBhPNG%vS5v!0eN0Z5zRi^vj)Y zl_NP(dt1e7QKi8`c^i~&21UOv-!OnK+VwQULRO&9*UB z^`$p<@h(1}o4P^{Pp)XVC24IplGK-%T90mHpYm1^NXDnQqN1KsG%vNYPt*gg@P;1H z?3WcFno(lJC4)PzcffuJ>-97*n^48cH@TLj9w72d(m*qly2@Ryjcy~h`cQd_D5ibI z{hv*x#=wg)Z`S6GG$19kHuO?bCb7!%n3-l&re#iYeVI;D;cR@z#UlsTURNjZ{#2fO5DCX%RsZF6!M8X%vnJ!P1 z+RXb=#_>z(#To-SyVj7zux>b>fw^36i}n>Y3<1fMoWI@+on$Qn1eBEn{p#x8%9umP z8twKy+g1E=DAmRq(VkNEXCVE~-OVs^Z1ed)g2=iyRR}1aQ`rw-C^{Sjc}EWT^0;SyoNOPXtNp|G1MD)4|d7$`t9h; z*pAy!bn1r;S-_V+t&#-UMj>4SKrHL4IfsBwIjW6o;(C{T6Lp_>Hymt{w(LRWd}uTV za}6^vo>d^dxp(!}-t}`MVc;pF?x^Aj5VR^;5f4G5bevFHwmzeBjRA`|Suujz#~5K= z?&?X_QuH;(ry4J{GISNqzl)B0M*)2!ieyn<2p+nu7papX#@H#Vbu%$)irn3TK~Ilu zvb$A-_N=jA=I4Vtp3#mqsm|kKxV$Op+)_V0Uj3S$|iF6s4Pe4nS2vA83Nw5%@fQc`)Ukk)^c zWs1T?nz`*p)|J{(O6@0E&ga)InjsfAH$kC?`GjxNR`D(yvDH>B_F!w8q+XtVDq4y* z(R1u^w)dopQ{C39xP~Q!0h;Wk_SGJ&t0MFf>(e+VwdG+5tE!7Wi+W5#Hzw2Ot_ElV zyPAYDm+Ygx(u&y=D$ZrBAAoAt_oj$h!(>T=+fvbz-FN6R^@A;-{^##4 z>Tysx&^M)wBv|pJc>)zB8TCnnd^xs}2F|J-$ZeYp<9HDqdjCXi|DQjXqODsVH7M0X|u zi1m%?vR@NcH|5M*--fF<+ImDw2y#4hUIOY901B!I!ym0{Sp8)3RgN0cHH(t6RA}nw zo%i4x69#sFCUgxq`)7n=1V7SB;}ux(FVA$lm0Dt+3!99#c?LPpWu{}@ z6+io#j`f|ZQGIc2RW;WL1La7990Je301gysD3X!mof`g$B~?O55-ojWo5p=_uGf3NACswnFJOA<@=qfMU|JZk`%C>Na(~#!S`Nr-_;ThuNFuhw<$) zZJ*jcPh;cwj%k`<*~qe~UZIpcD_Ykh>JC2_oB@tvDAG_*LVt8)k%<(XR z42s5`=DM2*n?KtJs%i+33}7CvTwf05K3NxaXXO}b|7Ua+Aaw`HETXQ#sb0sT(od!Y*SBFGs1D1r&CUDaIhR7)e^1 zoY0@8zOmhw9<;}4!_%#7l{T8u<2`xH9ixeM$!wdg-sRe5vTWN>xK9E1QJt(h9KP0D z&OsWK?2R?ahDC8C#YU!0H~vqVq6BpZF*a-&om50gGlm2fpaE8YP+et3TST#46Qxcd z$nlWtJSDfum>}GQZjR(JGo4SwJT~T#Ok*^kbu?owi#{~tg8;RT3@pV(5+nho^ixO; z(m>-id3gZGsM<+_000mGNkl%eTyZqJ8@mLj|?mtryfx)vwz@ zjH(~XLw@6s%Juib?U)p*UG?_LOThBIT(`c)^_Su!p;oG+xlfYRL=BoJP~5PTedn7t z>v9XSJW!7ewg(z*Z~DolFV(jCZxecK61vqte-J(@Wh&B_tZSmrXu(U*bhdCgA?`b6 zBtZ@V*o!PyE9|?>+8ltPjlmrC`c(;;pR0_8*_lw+q$S! zAVUl1P!3-Nq^>~G1XLF(8G@-HzB1iTGyw(b|RI-C#1S^J2G{U%*#Xjr*ahtUDZ4L z%!KgIfYG9dT<$1D}Jwf#P3tb zS7U3g%KT`!1GZ*remFd(q&Dr?tSKi$q)H?~vXW*d8Zk66u7t-*BcO5cH)(FG#>;nm z9vnxK+I_ZP9m@(s3dac8w}OZ)jbvZN34KK;ZDWoyTmM{cdC=7VG(zs)q;*C)jg6@$ zK><`+a^|f!vOJ91J_$Vbdc{!Fw3eEKIhr0DHr9VlkMX$y5w++nvL9kQ^y|eX|1h>`Gh1nHARuZNhP(*w6-JKbJwss#BdK>9Ea4!9E$@$d@cx0%Ll5 z35;%c8cW1?)z*Z*;yJu1`v4TGs|`wLSZ^w^FO`x?6k3=JNOY1bN%&8Q<^=>GB1Q)R|n;2USwGE*jk^c9Svzb*4p3^u&%2+S8GA0NKz?D7gEZyBGaVp<&N1v z)R}!vd!C~?`(-q|B9>&5AQL1;BB^O=Ws7jg_8S@xmhD&GeGkCPj4xE6f%ned|(Clc)Dred?ts0cdSu16g zEM@&Ls#=tKof`0|;tV4@?eJ7=@yLe`GtH=5b=6g{!?T`(2j2hQGx`p5SZD3E@Z`sD zJPnHIW2qOPkeA62WdP%BiQ zW$UxfbXh{PrTHSDgdrD6kQ`!JW~$+VH8vg>-Fda$vKK+nTD$z1T(4;p%dZ8uotiv8 zejS-*i{m!b95<(ZmLXd1j@VMwqSlz|s>MNOMB%&Kc|Gj$f}Qc;2i9DVR>;f?by zcUl+5o!46z8)$QX(XKn-#k=hU%NgsfeMfi$_aZV@UTG0NeCFHmk+V+0N8WvEqGz5G zI_r(|&UzaR$4u3iSZ&o+vEM6S8rc6;FU0|S?}-CMYhLnt>*~Rz0$nYJCp>0LeCqx0 z!k*9HS(|njuDjHRZFHx*IB@TmW8YW21kZiuQ?dJw&lH}H7wr5@J>1rkZ@g-VdH}S3 z6)z2na;Y3k)!d#&jey4}7SSk&Wuy1J7?VaP8E^1@BOe7a-81+vzI4)&7P$v+IJEFJ z0P>2P!uNyyq>5>_UIry`GTPRJ40i3qFuT@G6|c{AMnScziV_=bxIUiv_(#`xj^4Um z_=fBMgD2??x`~H9DmmkLTet zA37`W+4IiATaGytzIJ~0Q(-ydqwhI2#$7vXP~SxN(gW@0n{UC_zWqIXRWNPhy6dio zeHN=M+lI-xxwd8gHbu3l+;-nzZg=om zC#nUfB&&GaB*dD{)Vd}dV;q{k=IyJ^9dX*`cbZYi8?WAzLW`!Y1K^NvsNi?zu{8(;&yAzy#P z4QS#wzx@Ng`~4sH>1^Lb8~LUiZ_pQGo2?&(175oqe4LUEpZ(%jv6YTi?zG-IQkA&V zdTZmM54u0T^ZlP-@#4kO^(XJ$Btf!aU@eQQ_2!MNc=F@6#{KVePkiX(pT;e>+=AVb@y@f~pYWa!;GGTp=x4u-a^8&nmOZUE z?SEf-8Mad$?>Of}VcTx!J+R$#_kiR4^0&XowmZKFH~i;DoOZ$+;J5h?pZ{6xeejVn zeqB8vrggsVPig0zdLsM)``>SV2WP(j!+7sUK8cU~&u4Lm)mFvDmtG$69;UH>m_EsE zq8EXvtj^qtB#d0fGN*MOjI!I)vK#@gOJiBqF;i?(cUT5ElY5`Nx1##gwJ?*tcAN#{ zm`zM@ETbi@5_!?jqORK2lhj0QsF!NP?L@P)5_3kOO*;0?#)fFq8_ z{=)g6|6e_WOTBoPqIzs=JTJNIa$I};_4xY#`yRG`(l*%ZMY{^mt04Y`3HP|`UGS~% z{t(_+yYX|wX~pql9@y!b+v7W5_;}#|eg0!;;>Ei?H~KuSH|-^YH+l1>YDCyP&%sn# zsRze)`s8)Ldu@h&4}T+$ee2utA8q#2u2&C-1)V8;;2m$lBOmr)ELk#*`)sxej(`0D zIR5YhaNO(nhmVn-NU&3@<^po&@Epxo)aS} zZqH{m0vbr&VKWx-D$MY`;PIXfq5|E+E}JpI=I&x@p>B}g)LS{ii= zL<^#1Q(-=oDZ4>$tmEKKA$bnwk9y?8;<&{}E^WL*$1uj0@);}%c*Fn7*Z&`O(Wint zuC+$ETzc*AjT#YO+V1pb2dIXa^G#jk#g-~I8=VV@-1?g@{9&%F0|-VR9EQFKS0 zRj(4oN#ckD_STv5+PLwio8r9vgd+~biElUvZ#(w&ShR2<{I+WOVw{Z{f;;mhaPjX>?if%&|^-*eup26z2)=Ii~b$uruF7sYLk}` zvE89ur>8Zb`g{bl{Vp%YXTSV)l-lg~e(4Kv@IEicf~l$K^GjZH5d3N4KR4cpZJxO+ z9{=p!uS*RsI!4KO z#?zh*AMb1_UB)Ilj(PcO4#ocRZ`c3+^FA4>m%LzSZ64^u%XF5#nvMw0dhdr-t8DUg zUos>PP1`;3v3OBz>}gYf@$+`nmzz7nv0Px6XFoN5A#<~iVk|dtcfDaR(HpLhWNLj? zq1Hm(zJFeL5sp6ibvS6By|mAkI^MY#e)Ic3;m1Gw6?~N9*nU9$@Mph(v1ED))>-F{ z@TNYkw`U)*Jm5a}iU*j_$NfnnVP)wOR#|1`cpzE7h2FADZR({q_x07r$LqU_O?5Qp zHa=d*TaVM3`&w(Pja(TC)7w zlIt^#@tdH-TDn@(@>VnZ_#tKc?3KR;T!rOqS)`o`FWPZ%?ZX0RoqvNl6WWz#*5)Nas@9B$7f0^0DcXV!^apO%lt8|I3I*_5U ziP-0T{4;p|OJ7^VM>xOx!=G!}HgDjg#>-!zp7*g&V_R*=+id+v#ViB+wc4m`0X>pb-B<*`qK8Nzx)k8|G(d`h~MZ<)#v3t zmbrQHE%^SAeilbE2fpEiIF8xxh~ss@wJYuYC{0S^ARFqrIJu zj}CbC%k(AZ6Yz|uJTcz>@3q;+c;Z|z{fJvI``f|Uw)o& zgf>=vVmLuK=(5!M000mGNkleC`bM%1mfANRG!&GG=$7`My%C@0y;#j!g)1r0$}Aw`G7K3!Rdp;SMIqn<`jUq2Mc^VLM5 z_+wKkt0!`4pb$fok4Uc9F^Y}mZ>V_7bHy3&{vh7@?hlN_<(FSMGW1~w?u~Qb_2!ZG zNemy>1LDtry#V`b1G~rF?t*pJz9SCZ@0EJ9yb|xMzBHU9PWHJ`M=c(sHnRO2-}wO! zJn}@`()xndo1@Rh{bl3f`|quzn^(i<&VGwM@}SpZ!}af$L&S>t!H<6)M@wvOu^6h*oD;n?qM`-(798ULV(9cO5o<$X3|!L0e*j z2X28aw|y2K`q=Go_xo)gxX1k;5f2{+@O*ny{@ABKk0-dOT^z7vjk~ zq$8D||N6Ia#NspduYc-8c+8_V$36!iiMPG;J!*iWXmNIm4*W^rv5$Hrp7ZqWu&zE) zoTxT_`};q_6Sm$Gzt98hjB`JPb3XKO+^jE=w|(5B@hE*-ROwkKRj1ny0qA1QNti_< z8Qvs8GWc5~^PCyCM%}l8I+wDEp4a(=o70SEc|3~o=4EU3rtpU2-iA+p`inZ7ep|d< zEp3fYeEg%UpFWxh+~Q$PmpD1C$x8@YBYKkbWxc*n^{$M4!LKj{AV!IwYre*EwMd;qK8VKuyP zmmToc^FN3Wopnl7q`e)bHg(ToSDUdvG?QjlaJNQbvN2!8|!t2U;n-sOLWxZhsZ@2U!rwz32xCwe6x<4F1+Ys_@|oR(I>Je@9=zl=8Ip6U%))~ zSx=32Ip&V~688c3yBFU2jrC6sPcLa8Q&eQOrcfA#_dhu>J zT@U^@zU7@Dsiu?_^sG?3N+CKoER(;yhFN;L@L|MO-qVXCr4hs?yLd9`$jPb8FD=~7 zuCrF4vHfrP-YzcN5%63zF|4+JyKbtMla6JyWTp394}}WI?E7{Mbd$(BELZWC#r4Tk zmAP>~_IT&nAB5i=?K=wXHe|o7&+-AU-3tjX7ahc%@3d~X=5m>?tvBrTwYmGt(5y4P zQE$$F{_Ee-M4bOBitWSTpWdCJKDjQ(JoiJN&=+}sSBYL~m+M&)XM5r_USc$2$|p-2 zT72@eU&ixau^)DS`M!bOUjDk+?7d<7NanXX9&$|AW4h%d9u_~_^qaorAN=I!xYy>7 z$2~WH9Q;Oo>Y3+avxjek$3A`MXhf-vxoc>H_a?7hQuB;@BkxK-)#>2on{SrM5z(WH z5cT@}EjG3S8{GYF+SD(NjZfxC+VbH4hV9BLufhRG9ES__#k6%t9kMTuJ^TP{vf&2d zckods;FNcrBYAy%>ErLiOLyA|C+O|{*tfnTex2mM)by+0{Sj`@Pk!+m#HSoLAWBb` zshN{Gj`LzN#%N4hzsod{LX6ByO}^1~u+C`f2jn5;-CkqB_jbj+kAUaMXa}~g4|4Dl z>VpJLpYAi?Ye4mds@`HS402MC@ktTmwjNGPbP=0?Hv*v)Qq-pZ3^hX?WgXdiLLZD-qB`spL=hHZS)0S*Z}y;#uHCD15Nzuw|^YdV_=)d zY=M`*WH;QRH&1_|=PyM4rm1kf)SLzJx%~}?U`yE&6ZwRhqm}WuOy79uzPSIr?-_q< z)4zp3@`O|3F9B&pQ>-ff3wPZC|G4m9vi(^45)Z!reeqQtg?#N(=fxM5{>9GMKYboP z{O;3aS82^-S7+^ka*4*H8+yM<8;^Y0L-b|pD){y9{s3?EUUA7I+yL#C{&fy@hP|M( z01tiO{qg6&T_80HO=M!w=X8&wkprc=Oxeji2e*1|%N-kO$%HQ%}Tw z?zI_?Jn?OK@oNu>bNYAay#B{O|5bcvaOV3yfL~vMo@7IsIH`| zgdhrE*(sTbKT?w78i08uog7Cdl9jDjSEX1Eq|`JB>V2~^uQ9{AXq7X=3j3NjLSFie zB*^81rEl#ria^CRL?%sX=9;RAA9sB7 zJKx7HFL@Py_|u1V$8LnyT28$bN%FR=CY&y92Yr#|U%_=?`%{Yl_O`y7OG&ii=8y6c_qgdHydO}y((M`Oc#+%5DoWu)2=fZ7It&=r{|AQLRnIWhA!iO^R*%35M;(Z>PCWq+xbMAi`kC*=s}DR97higr-i9y2UfST#)famH z#mU=_KU`uNE_>9_a9JcSPqIJg~aX|dF|IdB+-T2&j zJ$(KnXGdM(G$Q+xzSw=?tMD3!fJXVE@-$>-VLoDY57 z-tjZU%dWUmM`l06&wuqB{90d5?)Z%DvEQp+hPBpMqmI+nnY(|x@8hb^e&oIQ@Y(Oc zwbx#Ur|t40ee(Gi-t&PE$HxB2_nxUY=fm)*%^!*l^w9AqCI2I5uia}8{NrC2!C!)s zTJi!6wQc1^E8zt@KL_W(?+iTbK@Y$Q`V@7>IUj__JutNZFM8gN`1a>NhPR)5G*()9 zB^+?fi8%hWcj4wGi?PS9JLx=s8h_WPlE3Op;scI6Nn@b7e*1~IlO7l(Rm=P*hb5_Q z2zm&C^Iyl(M$Gro(#C0*Ckb*i+Wzw`ld10cj=Q|$=`PjImhxeB=lKgNhxUG79m}Ku z!}GLlx?mfMr^*NI){jCvhUHPdbJ2hDGhf6wcmrz2-EjT8c3%{ z?O*unH}Qq9BwTUT)m0TEMp5b|(_OqpUo`G_`0=>srW@gjkJ+j!BO<%<_}ivuf8Zmy z_PQJJ{O9g~{a(4J#^Sel{4;mOhd=Qd-Ka(Uvjwj|_GCPFk5|CoOPqGnF)=n?Sf`$F z6u$eV^Ksna2PwvNc;PEwhbKI1XZ-!27vf^Qy`ORJ2k_XZ?u6}keGz`3x9mN4-xcqA z^Kp9fF7Uwn-8=qKv$yC|g&)*DsxhdBk;HxPwJG+|xc^PDUhvBO@S%@?3R6>4vAHi? zxDX2$EySn3@D-eV`k8pbbDoda9e$j~^jduJytDDCbKi-FJm5Y!>9n(O^)=TjBJlm6 z`~tgcjtp%iB-(svgo+4EBB=&W~l zT0a5~Q^Dgt!s~YRZE6Hcow;iPcoB?-wtOwDvW2v0l(+Sg+hdT+CVh!+Io$_oWa^HA zOpO$0>{^AU5Vgs_V?XI_XT;mL-@r%VjVGV3H_4B=NZ^8hT!_71b12UJVEXtTN@d^v z?ho<$W8RGCzVKDp?l~{S(|36(c74e{4ZJ3P{qmQ;{R6Ms?}*T!_wi5T+H0@tyVZQp zC|o3OCa0hEemwJeFRR|{FUP<1CE@93y$`3q`~7%(1@otgKke-I!$|V`aH>8LoO-6{ zS?|Ru;xD}D;)q*n%s>6buj073zEcw-l`54=W8Br-wNc_5I!^Jw9rK}2d|EoW$;X>% zsn(vWPZei>;G^zb?KMKhP$hq~STKwg}nkF)k$- z8c~gHsc)0Y7)z;Bl2ID9CK%azG|GEYlBaw$+?M4MLZzRU&WO1YUq&raqujbG)W&p6 zmt>aHpzduL`fdc05me<>m)j$vvH*b|)Sf;W=-GhaG2$W#l8UJy@^Kx8*uF$E{K(<7 z&v^!c&b>J^Srxq;0f`tp< zH|~z!x^KAQMqKdEi?p{F@-Ofg9hZ;=9E$*qU;O5G@sE8Kig)Q{SHR`inL;rNs;lU9 zeX@-w7WT&BO`)ml@$<*!dR*77g?4Y+C^CUs` za9i)sZLR$Uw5Z0xi_~z(OlOAIZ@-tz8d~SEQDkaeM%J}yvJclL+lrYtTRc$cG$q zYEXN!Z`9sf@RZ(`J9?A$8~N0N1z5OXk>1b~7A{yQUb;mKV{`YHpRxHCDlaHl9Nm<@ z`6Vl|>QOG)egg=-?Aok33i2W4GX?;G3S^H@IxO|&5i4Ud(tXeim@TF~?Zljm8I1y^ z7l&li8j`gTocF=@TG5JA>nCE|yQK;+Q7yA_T1L%DJ)M2-nV(hr=jiNikAuEY9pCt_ z7Elj>_KUIbB2tl9Uzyfz&x%YdLn@{A!OcEvNwJe zKnAHga=9UtD&yFrKoUwLQABzB5cwi0{{%6iCX*}@a$lw~)8uHBJW)MMrPlCC-&0S_ z<#`xZz!7VxqU_s8-J)z0KQxZ)!}NW+x*};=L1bB^Nn*N>+0VR@<)x<0zRrT5WEf}D zHj-L;j3R{ogsi0`&A4V2vQLy8WH8)BMQ?XnmLs5vDZMEh=C!iAhBot3@P^$D+SuT= zsb^i<)Tgx{bpzJ>1{p-J6J%OoMW=(J=wQm5y0CEJ0xSsVtz8@Z)Pe=t(5Fyzug*S1b=oBbSS-5nd&;}k*EG3W%B#_j;E`RwJ6-b6%xhMp2SvT+0@mo+^X*BB6 z2vuW;G%OHh?E5>se(D9q+(R^i+s2+GO%d8nqN!QVD zr|V-wXFfwQn|0M*YR6E0?XYx=V-G`)nr&I0H=6rB4*@4>BQ^FJbE&dkeVV7alpk6q zVo@eT#3|O7GuaWY0!73%P5j4Rm?!o!(9`Zve9@`5{m7O=yw)2coHEy3XanmAMI}Q@k{}a6mrJ1hdLT#v zl#!7`G6;}t0RWX+dg}(@6+I~wGad-pU$UBraj5%=e5uetBPb2!V2I3=2HdP@!$GVw z?T4q!u(phQ%-sBe?hRQ5q@{}gKSYS=H=WmGfn|1)s;LRpJKM@;JahMAX`{aqZ3?>i zOQEUn>J8Q?XIN~u&jiO+E!u>XD_-`uz7JaAw$-|Zuk|%*hl;7n)%I4ouH<1*u`0l% zWPbhVT}UBGF9b25Gob{@fHEf`$$pC#7qT3KmSWV!!SR(+NqPwKq8yb7q9~NR4ge$p z7?7iq3RRL6kc9#GqO%I-jVvk-$)0beHz_K8vM#&ab$hnBThQ#E`I&04kJnL#S5dnRsr#Ub zxO1cvwXfe)z;DihBF>fz z-uAI^Jy;oI9s7+g5*lyr!Q7sW8Uc^hY%mbVg$I<&QZYk?UW#Kt-DgF!d45`CH1%!X zK5ZQo3=>>#vY$f61n$emMK^={P(yD+Zcm2ZJ!e-h0UwEG__%a-0cP**It*yHqc&5h z4UQ5UtTx%A0!efVZQg=6^aTqR#9t~}xNu?6jyC-$BR?|v7M%`qc^|ukUFE5I%tO@m zk*QM8DRfavlF)GKMN*@wah4iZnL?&8hM>#^4YAFnl2Kk+Q!U;B#y}O31-Q&WkVdY9 z8j2oB*#PMZ<*Z=dkX%+2;xjaf9;O;P0jt-gm5MSfbt{{FZ8tT{8|Dr3%N-gAcTZh| zk?VJRj4Y3$_8EtExwh|2_O|~hd0d>fZCS`ZT+K-LjTQ&G0EW@Q8_BC}#&(ee$+3Pj znET4cx*TjvpU>^W_z{TN@Stnc)#SjNF-fr%v;kALyg3)04vLO2mC#u*1#k3=7A?X; zVQRrbSXXGXFFJ*M8*rRL`OTXCDNJP%<_p^2YOk2L(nB>wFu z+azWBeP-FP{=#X)XoaxoMczcb@u_UqS(X@I7G|Ak*4t)SHxBk02Dc$&n0>kFyaH4i zsTp+=^~MfSXd^bYoR4h^ZPXn@n{`Lu665tEqMcs321u#4Ni#C#UmMMK9p`{#Y74nMX`wMl5TmA~y2Z>}%HwHj zXfRRwtX-J5N|s|F_o0d9SoLkDD!%|X_axs#eT|o*IfetgzVj^l3M?bR4NZ4?uL8AA z_i%fg?b&bVMvnkxGDPY`QhN)L-_%L*MS((_a$!0IWb)}01-!urbX?n`z400(V-pS?h>qk=!KBj$?M>R7b)gO3 zZ|de3#2dWc-fj0rE`)w+A*MPD6r+Pq(TNzO>Qr3lDuM?@Kp;V5qlr(?cE4#_7W&4@ zk9l{f34MW4>cFGXN0{1?q8`Fp9(JM@e1m(l2iPgRrYS#)$j_^cl&?|PiA}d-{mlTkCv)* zHgFl29fhvy^>WTg-POcseL(1OmN3EP*11;G1XgjHh26Id&p|+KVH;{t(JZ(u`P5v* z_^ZvRf!pq-y#~<_%>K&bF{_f4w;?i@fK$}T*N0hM%XiJi1F(R>izLW|rj;bT$&;0>qAUt+`W=`WMaYG8MIDDsWeZc=O$E$wc^nP9mCCB& z1xjsJ608#^i*n0NXv23|kH2X~I8TyMP8nkBWSs@mHmq-f(4QfFC5P)<#b*@VFFX0> z3_cA?Z1AO?d>WGA?Xx1o&GuXw=H1p(jh_@QHwupRe+eAYSuM9d?XYnz`aEtIVgzEK z#CcH~Vpq6C5@ZFnj+}WaNdf{%kpyqvWa3#6MMoRC(CKu7&$RF+-T+yZ8A3f~85nZK z`1(fIYkj?miPx^BB#mtjkjpmtteb(7seR!_)cqwbpzk7C+xrY-1TvKwU!KoJdIA!|HjN+4mkU7?BE>wq-B=YW>uEV>yDLX1ko7AnfnOyGE=AXsuHH9Zsh!g zR(pJl*Y#R&U9DJVjB63p7PlwgNXx9cc?j&W$xzEDju=Zq3q&gh2__Uv_;OA%WN+wV zG$%wUT@#^i>5|=165K8$pCl%2$|=$W>?mDds^+7V(eo1U8+;|(H+0!g)H);U&C4C} z3Cm>#%b}k`rY_H+)Wx+?#sB~i07*naRLn(kdvjl1BJn=90`go#eaz>p(GT}W2%W|- z&R}OrUDqh&c8#(_jVSf-X~JzS)y}$%c*_~JenKXfo19JIAYsRhz1#0;eEqS?%ZdL|H^kW_yH8hmQ$@ff5)KG`ph|qcFM) z8g%Vg%2AjaayDTK^7n>J8LRN^6XMEJNISlo!{jBBr_(eO^e;WOM{8(shr~e5^pMUt0k)D zHl!MvNNRm>q#d!K`j-Q(1JOL2CkfKJevs0`Kz&&vo!6={49D17rWkx}d7Ar{Wwhw< zO^o)O(L(pr{Sw5^=-+f0w@$tOv#Q6BV_PH&gRR2A{#%$WHRW5fec$F8Umr!ZME4Ns~@td%rAZfb7K;2kj+A`1( z4^nN_s{T=C#yPCgiU09rjS($l^hR*f_g9z)LA{v zi~6XCl&%5m3fak|2Z*>N2c1Jk5+qYe5t1R3i6%oYCzGBWL%~$Qqyn=pS>~QMhQHKC zrU|KIl`%)P*vF7+L4`iUJPIV|G1}}yx%;y(wG2zlF|>!#m{sw{x|mftIrqP+N#W$h z)ErRD)=z?Fxk>zo`M5uZpXQU+y>P(-Joo|k9Tsj@x}Bc6z2e-juZ-JZxIJxHZ>+WE z8rbDIPlKuDW`LcZ`4rsuUYj;meZ2d+(FXUxi+9@*cU)@?tg-qXP;?5vu+?bpArHLo zy>P!F@d~Y+jJ^tn37#lJw*L0^Zsby%j3F1Lo?gO0Qq{*LSM{M5Z}q@bkG@s z@GUw>)MY4se0?4>zrB>v2XR4BU)E>T%B5x0BvM&3jzm&fQ#XVk)t0RH@s)a^=d5yd zF0Zp#07K#}D^+gKO!3vkXqsty5UJhLbhTzI=Q#eZBge|RuI8!<$7rJ8f7mxRszLRX zBLL~)pWB*3Z~8*FLMdu=-BwlPdPO3YYFDW;2~A6w$7gc)Te(pxPTQFz$OPM{%n(s$ zl&idQ^mJ0FEp#lg))#UpdXoP|n#(a`txdB)OD+x7`~>v2cm zQJX)s_C4VZ2jSEcjvR`YK7W_mZ{?L%!n$kUG31?|^;A6MfxXTBIZxXb`|R~XyheEK z%U*~k?tYg$MH#nu*BzdQm%dvmA z9@Xl$PiT8HBC?RO5zI1}!pRkR|iZQl1nzS_a#3>t?W1uJ&ebU`q zjzV|=pZx3>@spqZ63==1c9jNr;Qj7{)mB{vfBNeMeb{v44YBct_o)3I{@@4T?Qc4! zmbczwa~$%zSK!%CdosL1Z?^GW28h?7piW}9pn8+?8M z9K6rVu=|eB4Ewljo2?#+!_@ZY=~G10j}i=#I)$u^R90)-%%DwEp*854ftDBp&_xnH zjtOX!Cnbv(0r^QLM^pQe$zJ3Qorw6P7~J-+{&lSP$G-cdgx_f0OW*uuH;u97+tC|= z{xK|LEjD#h^qeQ+9HadSA?eCIKi*j)GU-C!%Pv9=0F;bY|9Mg9oZWciO(s}*<(2Tazh8*Mj(ZD!^z&cgmc@%hfA7sULGxh{%2q}aFWTif zcu0D%DJ?;;7Zhxv;DD_78_$vQeO^V2VA?#^ic zw#6kzVaaqCKl{~h0^j(~_i^3zH{f3P+(esf+7unjWe-1KA3Wi)Tg6~mHyri^zWu!) z;<&fG10Va;=kXAo)&I)*S`^e z)@J{y&wUv;-gKij`+sAxHs+7K5m zoDvUg{^0xLxjLhNoZi4U);au{Z$1WFZ}IT3jmt_IH`!=|_*C)lOD@GCeTvw6i_N3I zw)KGsz28+-tUqbP)2@d;p#LHjKnWE{f+UoI^&g9pq+er=J76=l?GCH11~TaiZ}JsS zCY^}vE@u#4oCMOp9ny6VF!m~2H%9HOqVXmyd)8&&OtXHzX^x+1#8-RL45Zwfx|zwL4&EXwp^(vAnwovZVeZg7=@d)fu3^axiH>&&IhrkHj*!W z?OXWwrI$s7T+Tw1w`@G(VGqKyb!6f%F%Nm&tK!SdtsnIW9J=o-1MBPP#Aond`PTo# zTi^8_y-oiTJ}P;{Lmuejxa{&Pb%e43PCMx+eC*tJVCyX(fv0Wv1e|yFskql>8)MBi zR>w!rd3)fI4}UOr+2QH? zUHto!%aGsd!=aIp1W70hZ2P!J!`CryeJB2-PXgQNoIl%Yst3va?zI_?J>+$G-)Sf6 zL3IGuU1x0^bI@yW=stUajXyqmI%@w{<6OOUzwMYq@tp0SgsG_xdWogI+?Xb26K~7SABs00 zeW>16x1UY1p}u?SC}Q1p*2a53{E4CVVakq2%;@oDZM>d2P6l!ll~GfBlC)RnJ|D5})|Y7xB^aKih|oo&Pyptc}^nH0GQ5sBGtd z{(qx6zyAH7aK87ucU;!# z6nMPe_W!Blp`ZQcclg%#e}v8TUBY_n+!3({R%`iw_u3S1*Y^TjJ>sFb@ZXoHzbnBT z@+0JXAARDa$ij)fm$zx(rF_35I(L3{6sV-9=`NCi{6>z&udL-on#=>7MJ zU+-LY<&{C-c+hLH#X}zi>d%!|UyEz5yAHmtyy|LwV!0e$ecB>zg*<>ld{OA;8vx_D zha@17ME>J4q$Cy~iHR=V?0KEG{YUBBe)B1-Ia)u?=QHZYAi%4h6+fG<>_0_{~rp%kH<9_42KZwolE8qTJ6xmjB zNQRCiUi;D)qN4}aOZGhsuh#=*pVuD?`}z}x3wYBs4u9P%<3YC5p0B~l?|3&}bNI3G zpz#;V54!KYV?!qCBKYl}{*0F&bR-Tt{;fFp*f)pn$vOffiU0Y+SMa&7d^2?BPdxoB zTzmcXA(O1zMGo=+0fA&yA0Re>xn7<=W3J`rv7B$f2)BZ?C))CVDND->$P~THQ_@7w zGCQu0QTp*=N!xf3SDIpZ9T=8OGb}F+mTGg)jc{8t+?dP{qe-WaA1JY;y97Ryc9|tQ zyS`p;%#LY!Hh84o5|7ZCy32S|edD3~#?gso^tJ8zw~zrziqmV#%+PtKSOh zw|#&A@Hib4g+keCtF40j-+MEBR}Zwm{^LSv02ah~`qbg^hc4>vdE*V&$35H zbIsLt?6My2dgnXg2l`T4F7dYRH*NFAm%sU)bbqkGPk#AZecy3Q{nJw08CHN4CqNgO zc#@J*A{jnO0@M@~{!CpdSkY00?Dz0mY9p6z>n&^I^^&Evsfn>X3OPp8KkHn^ddtC! z$y98_SEi&@<5bmU+oVvjsA3cy7sqWevX6QTpR21G@b+Uw+{RFbtOA*e?MtCofI6;t zPg@;Aa&3!}w)8;wj|}qDz>Mu%)MeKhAaf+w8)X#Rd?<4!o$J#8q>Vt1_A#RUlAE-@ zVHzoK*ctWcNguolSM@laSyLlb)>YLBN!CobJ$9S}NR43wzCVk095Oq#EyHDZA%9n4 z+NGL01JosTzr$!T$6Ss*kQGI@I z$D&i{?0j>4=LbKL*lUm9{`~-wvM*7K(~3X;^#Z(FZ?GA^{LSySHjUc`tb46o6mR;Cb|3x=rQ`z)#vQbddd^=nwP!+uXy3E zxc-L!;6Cd6z4eKRB=TAJzc0NUq*`6!rkifoC!Q-oRc72(XZ>Qa+jE|dPrm!Kz$e~) zD&Bd*kygXsYnWk_fR0(z8>$bq7WFTDk_3%&nc3M>#nhC+pz71%4KheZJdz+ez=vW~ z<|eg}ZoFOr%Wd?m73UZg+ayuWJs-GXQ-PGLgc$pZ8=FX`KC)2M(SSy$9~>K3)hnLo zsNtr%R2dB4(`a2y9hu@gPIik8Q9=PmYtXt}g0f|}0Z~R{<-fG!SWAOebrN$lu}TyQ z!Bjh}IH)xVXk50BhGOQ8-84F(!nuIh6kC3>3rc#qM46dcAxBMHOIiGUoIr#1my^&foJ9q>A-*5gu9Clnf4)AZ$Y`}oI1&=>8xWBk+qI2+2DBJdNkJ0)2 zdieNfz91>3_10didi%dh3Qw4b9#IHn**fzj{Fj#=t}k8vK-f~}+unfvW$FV|m%XO- zV1p(m0df8KXTJ)%g}!(_`-2~gpG1aYhRhrL9adX4ytY?gw|~N8VE9el%ExW_NE6(k z&ECwKtKY$-MyE3se={}|S}1-fm_Pfxk7I{D_X+H%hhy0=ELwY?tmyw9&K=Vp_Wy2pe>qb z`;0_Y!1js_V%b2w?g@xzz+3`myk_gbkp*nY80j>D!TlP57EjhWMCMI3x>nV&t_d5m zYZ-h|Ii!oMseJ5;YAn4 z7oXOp3DQc}B>v`if5e~u@;8Mq@yzX?gg3wOu;7*9xFZh4UVH3{yXnmSnrp7Z32!?C zH|lNKwkG_L@$rbiSbgtD{wGYkKKp5LO!JBZj=;&MzdLm8%;)OX?f&Wmu-)_atnt*{ z_rlfJUWaYB*~)W`tJJ>>)u(NARO7evLW{xhgJ99Zg$@z;lg7ed1pAHuV!h=X`PWAe zywAPhGy8BHk+GrIyGYu@r0WzN<)hO~i@7L>CP;$p2bT6q)0w7rSgeNKeH-Rt9hJof zE~@1b@!Y>|R{>(vERlUo?Y1IeRNt(3KcYkGn0aH8wwH1;M3#9X39^D}BMXoew+vG? zuW7++q$KCDF-_3Og+9~d=T;?qk~44pFp!M?ki)5e&F0T{!^$pKohQ5x(fM8{H4JlV zk2h1F#s5*8?k7I;#l9HN)wy@x6o2!FKZUK+>A*)g7hiHIPI=ckq5JmteuRT{bo7Gf zJ_mpQ=fCjrv)_(?UwSF_KkE3fJ9FEQeaq>3Q2ZL_oc<>4qc4Zgc+UsmH*tT7d*B<6 z!!}!OfhRn6OZ?-)f8j^f8+w#gU%eP!{Jb4;oW3i#@~W$`_8r%X-?n>#cYfCPc*0}0 zj4yWmAaj`}R$FxyJn~@=!teg{*Z3t$)8+@n#=F=!)MezP@XF^u)pckvGM@*;b%sv;8dY=H8)e(F%k}B}aw-3) zL!aEV@|0AQdD%)Kk6%#9xqOZlRBdYNt(z3Zq>jtV&*yh-XcSbP20|T6yu?{1Z-{5U zkHJK@Wrov3(u3EKNa}juzAW8 z9~&F9W&aw-zJ~kYxW<#Wd2}2Xef8Vl1F7vn$@+}1e*62uK3Q+?X1=M1)&57Fh~NMD zuXxD)?;FQduY1Gs>63)YMhMaOee8UkrK6!OH-8A;{Dy&7qY5_*vr2)86bI<1*v z1pLEQ%c??(2_yyGNZTsaL^n>n<$VcyVDZYk;wzJ~6D9IKBiOrJap!S}>0 z+()>&VNHy1i-pj%x#31rHxK+q8{7kjX~XrePxe0ONWIZs7XP-4e?9Wivros@KlLGa zt^YuquK&R>Z|d7Wahv$P`6~}N9C_1!@ovw>htE1KHrSW!dkB1_an6T7fkTdc6ZY6; zM{M!12gh6KH9GqEk~ZcSylOuja?DBa7prJpepsA-_6P8+=f50pJL6pVy9R$h;5YT_ zZ@2-+>8R$&H=Q0|f*yL@TjK5d2=(CteOj>Z!i5X)(RZJMPo8r+Hra6f*qmL(CqMUP zZ1wbAah|@Ew%o=qfA!viBs z>-gY(ANv%Zwa4DrRS%pUUa}8%-fLfcPh*DaI!8xGk9qd<@n;=dRjMFQ-Tmd*=kQ}u zYE5Lk`HXY0?Jh6DGhg&d{OuqAl%FPEb~$NtFfWyp*sKdyx*S97a!=lPYPh|v7UZ^N z>bBd>ZCA(Soj5VK$rOE8hwA>h7bChf)K~s}WhMW&rWh8DmHX#Dd%ue@ks`_Vkf!pH zdT0t?S>&m_m#jB>n(p3LpK{=lg!?@dR~Voc~$8PcTX6YRSHo}R8D6Xzxono}x0-CYHK_ZM(W>9=Y^l7rw^g;=^fzzR#ub{$ z1E6^nt2|Au+f0D^2o(T9675Sxbf>qqJ()!TqoJD)U+Wx4!>l_zO>oeaZG{yO};qzwo)Qd_!Y&4NiRhe)!^t&cYYZI}5w% z%-z3>|LCW`0Fpz|A7xN--A%YZO?fB}&r1VpyOOjno#2|X&b&dUHYSg3HMMmd?9)WQ z@6a}uYTpP%Y1yg?R&yNN+EgYu+r>Dx2z+GZ)got!_HRS#G0Aae1;uLD5VA5i&5+Ar z(z*_%{DL;KtTS!En~A}u?*x4A&jhZ?Mf0DS3G=E!y?Wf}3CmV)x$G!fxoIh+xmT_! zRvrUvYBANDW60UcTvTGuUMt1a{^Dno^rb5MZf1ahmf&!3jLp+($j1xA7#c@*MH!b| zG$Wcb&UuK<=XXuSkh$7X=-W=hyFY3)Mk<>OvCU*vsfISuv}*<>c59T{(7O#-mT|^Z z&ge0-gWtkm^qNDkzdq%>`$Hdxk4yI4|8VSkr1#B2HI=Sjcj<&2U{vg5AJ=Tv8EscT;XU?_i%hEtN=j)X%M9}OTlLvcKM-o73!$b zQuo9d#~}rK9>I|WDOmElm=tq&mI&tvUtK7*=HQIlx}M-*!UMvkB!KP-c* zP+OKk*cC5iJ>VMM0e&8uI}o!`&(QFBc8405tAd_CG*y0rCy;(9(z+aEU!oNhL)!KDXp0B5w(6UaQT4N zx5rSVik+gVmc-Z)TzCbS$I#_fD0IP#Yg4HvNc}F$>Y>qJ%dISVfX89Jn@8Oor1A>$ z9lsrqQm87B;*=!TiL`>U)v7r$q+@VI+5gYpcL2yzRcW8EXEBQiilB%}6huW)FrXwA zNg~J+o{}Uh8725YMUsJ_h$KN&1Tm9b%mG$ce=%cRT}50Flr;eod|m(dom;o+R&`Z% zn4aN*H215M&pkKP?V6ea@(n8=Yx8Ldz1P&*5yLyLdPz#)hl0jlMZ7VGXMezB?q(pa zN$jC2M=8?iH7*5U0QkVbNbZMn?YU3*8kSm~+v4d$yX5)8zr^7_%4EhxZl=<9WN9$_ zI!Z5egq9{cDyR!`rrkG?yYoVw%cR^z+Cd4Z3@&b>G;!nhoEOP;1Us*J3>Sz3Bckk5d5Q6*rkT4J4tMU(jNPVau5I45{z_eC z<>+&EQ`8HI?-mPMbI^t`1G5B(w;`XnU~M#pE4L$Gc=xBVpyCUJTbbL0n!yIZfD(fq ziN-rkWM`!e*~RsF;qR#}EB*sRwRKL{oIGsC0(zu9(}&da z&STcMz9Sw9i$iM424@rPmCJ3d%n#4QX+@w4?OTv`AZ%Zpx)z)FE zI{s@*oK-GE^EGV9aP{f*OFm!Z*>a(o46`>!m0InuLMoCKOks*l*b`Bj9_{h&nO?U0 zX{Ipw+(=}!a7{>uPB*`SOTW}Qx4aHRfY%(;>J11v>-)vW$lJ_srS|BvM5}TJ@lrIILG4(}h;HrnUL#DjsEI2|TBw{!*VXg7cJxanUSX<Cx3m|KtX4ig%Af9kNUM5`}> z%AAlZx0ZAf#h!DI{KA|$H#X^UtC@f6vIkM`ppu3LDs{t&3r{rF;`|k>GNoYR41Dj% z*h)GI01V&{4d6RhQZv}3AJw#oeMG3oVY##bU8u*iTmw*`ZkJri$9hy(1VuAVQVvcF z=R9kLzR6=glmxo#u{;-n?3AL?U(@LJ96U$(d2)>E#Bj(V`yVj zt`v%qqedVqD2HeP1q_T$5fO#w_>5}xarD_kR=|v7l%CYISCY(LUltS|p7CyquthZV z6XcH6q%F>?tm0GKxV-A=g3qz`cn*IJ#=?u3To3B>UR~=6>IzV=9EHDR7MaR*o@Q?q zJ?Bw!U%zM)n5RcC^-Gpr@YE#ZJ~^eiXws+XbA6($HBv4nF+T2_XtXhFk)w<8_$h2D zcHY%Z&BmKJXeQ~|w-3W3B|GiLh0tUc;3Cepi#SqIM`uPrjl1gFaaMuT!Dh$WGM29U+)gUeOM^7=Rk+Z^;MZbrHmm4sCV~F+igtI7hCpQ>G1m zVMi4{ekYVhhEW*5RdO?Y9GUJ@X-$19ajDPehhr|ruK^P<$n`!1j*5*#2@5)6ImVxl@=_i}wW6n^JtaNH^vPUq2dE}~G5z1LYzVwh^2YSIK zXMq8WTT@vqG7eI7Sc!3B+vu?PaqX(93;Y3%ug$Uo91gqS_o~0y73Iwo#~D{pPIWLU zHua}(1V-{!)HPUKPgd_4Ju^3;zjyS!G^igEZ{h_PM!BdT82EdVD)hS(p=TJd1znH& zI!a&VG~?A8Kddp1tI?h->6Xv2krEtpM(D|1P>x8&*O8}Tgrby<)Yl+dhSLyb%rO~; z_t5RZihb<$I*KSa@S^5pBU7tZ^HTUP28`DJz0&Cb}riM1k`d1oo9eHAm1?v|Ojd*-;n* z6E=aIf}CH+B#IZt4CC=@jWB*RIFbn(pjcsoV21ddkTx}qlD7u;zOGFkr7q1A;!4~7 zlhY`hMy9zE!r9T}alg<3Tsou0wGBTXoMxZ^57+d7=Z4C?I7iGwGZ}BGi9R&CW{@g3 z9lUwG`?=_ss!s7zi!xF~Vh4)DUB14#nK978sGwJ3ded*vqe-KiR^9aY4aC9cp$ZTA znqs$m<(%7U=GM_SSOjVGQY-4vr`E$7$pI=^A8RQ{O0y3V`Na9Os-JsLw*8 zz!tGIx;T|iX!0*~+Aj>y6BD10 zyywD3{o=}NT+;-H>T_0%*JmR1Q0U|oRExzmtxNo{qU0mg{iGX!_JzPP57=tDzCVR? z_tf-FarM6aE=}U1ovLy@YIP_k`ZZCVx=niOss{-btxZpu{-Zxb#8=Q7HAe>4Fc+EA z1)7Wev(2gcJO>X<(L$Ec2u)N(qr;pjfly?)j@M1BxK1lsWaOcwL%yP~37R9JroLZ_ znu{=2a-5%x2T?ju9U^^RXyVMJmbu_#FQRjie|~ainlHk8BV`M$UnjdLry?tq)1=&7 zx$>ar`nism=&QICJ-rWRtS_jje;eozvHkC1`7Pv@GNi&Wv0mEi8$vg#-UB)4(4^673p56F6G>W)}a3RdO9oanf1-5kCyMtCPB~D;q8qKYi=w@cJ73 z8&w)R`k%7p)b+f$jj^H>$8L(7EynJY0Ym%L zI5{_cBC|!D!#>~5FDj5xXWS#kJwj!1Kk2@Tx!KJ?lWjm*T{iY*j_3#0AcXnXUW}HU za!=?_D)WCW#4?w8f-tYV`}z8)Zy}E=h;&^j91}+V5_ut%6yznFQ*IXL=@xC$(_KL&T;Z1d z;sMkudUE8+W<`eeuH6#Gz3Y+F9N1+qr!>2c?#9`3f`k})a8}O~?Xy`Al@<1#V ztfI`C=aP;l8`v_|{!xtT6yzlu(ImT0UxE>N-7%)1bab9oiiS-^Wm8lALTanaw0R!s zbGXd>AvZ@tJ+Hq1W$Uv>nZ*mikBO2N4);Vc-z?{^C|E@{gWpKx2s*GuPa^56)nr5ku&VBw;DxO2*sJ!#&I2|CHOq<2o-HCn`ni zoa)7gyrqY9DWWCSHSy7}i8lF1U6cQyj$5HNrWoHw2XnM*n4Mq7yrzA$xVQC}>g%C; zB1!F$ulp@-GN*&{!X3gr8~89svCgQAvZ%%;DpYtLxQv#10P+*q(|~O}1$$so$~7B= zIyh+7Jjm;(SiN~}w%7YD>ZiMGg*;0J0MFI{rE1D8ogn*2;M~DMZ&F@E9|o}{DEbeb z2`!B!2fzThqEd(Ef?xXj;+I}ORaxS)?;U_s)(=gn)GMUe`!_s>bkfeH%tOoX?P!@Y z&8JFZ5R~1i^n`dp`g;Q`!8jEG*8I^7HCg=0-Cxc(S*N7a4cE6`Z>ADc z7h-&>f9OLVVz1e5YkTUdE18}UbojJ^C8VLY+R9I<$P(6H?>YAJjbAKwPVnzrzRcEL z`{|Y3Yd`I&wqTnr3f*?}S%2MU7xK7_=4p*pRaJ?yL<4e}OQY@Nu zLEyIMAzYv>j9++Q-*>g9} zVeBz#NICZz@6)Cn(y{vxCqF)Z2G;-p5CBO;K~#YX=^f%z$XSp^Zun% zh0;}!e33{mKS8`8z6r7g#J$7pwZ4PB+#~)4#*;hJro3%BWr?WzNEYOkHn5`)d%azL(Ybca?$+-ryXyXoPBa5)_KO7rZ0`%wtuDjZ@m6`cG$kV zIzQQ^+`PIE6Uo!*29n-|f1!(ydodb#eO)R`L8I7!PFVEMzAe}` z9E!&ry1!j~_PYzLx#}v~PmMTizdh|0n{HytzhIlK>_IdCX8cjWk+DAYAE8bo)_KNS zcH#R@v9;G))BbH{#*RPg5WA8G_7xYOYnNVlw!PymheubZ%Pjq9``G!X+hMQY!?xUX zWBbcr|7HgqajaeSrLVg^y@Vw*e%G6E(sD{ZZ?bRn!u4$7A^X`*+isa_&HduPH}GQq z1hDYHeeB6AJl+OBVePit@}+jdkq6o)Jcu9q(1$oLd8s8IY0r7)(_CJ#?dB{YJf(d4 zsxR6fZuz4<;^7aqPh9bN`_*r*x22bQl=C;;{6|}JwUzi&#|w+@RHx4!?@;#0JiKzD zU00zYf5xA_GU_?)TmlrJ%CbHxFujZoBepEzjXLE>VFMpk)~B|LEbFt+MRNk>Hoiqo zAzc?(Ajj6PKsq7IGS->V)&WkW_HgtUuQ;>#20GN?vP?)oraco@yJ?cI^*-Y*&8% z%l2y?Jgf87z?ZN7hJOiw5h)(V*D3El{%Bk9s#n;#ANr^r@}{@hI%}`xPt#4WOBn9$ zNl$!&ow)EYJNMM%?N`6O-gZ6UaNB90Lma#9f4KejcfYs0?!Md3y5M5>kC$8ibgOOn zidWl9w%x^U`Qxqj>)-y){_EQB+uirvW2>(GWXEcxkK`C1yu=c=`btl>)p9&|i6vqh zQcbGaZaZ#kTfAgL+k597c-TIS`m$-9tFH7U+nhfoEWOlHZrFgYNVeN@Q}(nNakEbn)6mD1(llfqP-Qw? zs1*&Mz!vzjkV2M!4%hJj^@B+|@kF}HOx4)}IajA7)X7ToIyo9Q$0|gVC->l}pa}P#@{o!@?=95nKTxWgYB0l+k%~s+s0%^@mR_GHys^_h#|5X~b z(nOaqaTA4l&}`#9iEYJK8YjQ)O)ebD1AVuhUS)UlN%;+LKG7EJeUKmU?|avAw&D8E z^F!Mm^NZf8pMKI??455t(w5!1H@dmMa}ZNKM%_VLR;Yd8GwO?K?z2im)jUud7Y;0$}*W0$eL4_Rpc^_}nM zJ(fhTV!r#8%k0`OUuKVAZdo74wO_u}4&Cpy?)33X|HJk@>}dPJbwBmI`@Z2A`_Lty zc3b_j>BcW|)$ymEWq17JPTO>&^#l!vQ{7K|@jvxHw=eFEqA8Q-F;JHOg{RFTIzf>N(%+j`KQQubK)4%wcnO?yc z&U47b4Lyfl+%HlCnCQTm&ER#oHO05BS@F7uuTuI1pHP~@hw9q;9U3?b2TrfgFA(OL zq$Xp;?5Mo@w{|le@9DC1+Z?PS=+0y#_ zmhJcgWFO7#m=jOsd+N9Hsq#VYpm*-q{@^;>^PnUBN%=0m;`s5;e!;`$Np|xe|Ktai zUdIY6uJ{Dgm%=)0Kiw|=)Mfsje8H=>wo~4=u!6T9aUc)r$Jn87KEZzbyBlo5wp&^G z1fSXOZ@kg&zUMAmWA&9?|4+U{)R)GmuDHs+boDpwb$q(jFH|!QFha%t>=(bXlizoq zefVRak!Zi+>l?{m`^L9j))$4w@|VB<-Traso%X#S{lu=n;eXwhUS{}ye`9`e-EiX{ zcp(4OuI54g+-E(*^b6(a7y1p7c-=%ShriH$B7dTJi0PM|dOvP@ETY>A%P(uI@~~co zzhI4c)RK>&rEH($PeX5f+q-zMFEYKdIq>Kc?bHA9MXy!-MW4RHZvFFL>^Il{KI{R{ z&D&c0!uc}SIGmoyG=>PJD)>B6lp~Gf%FUr3))D3V64lx@A-}I%hvIX=`%LpCC$0qN zfmdw=DQb{^+Rs3K#$`N!2Oxa|VtVCO4Nh}qpKH-)E*8Y48t5ta3WOC0X1=Z}rwOW8 z@H*hvVHbdY1vpd%P{q^S1a)J{C!M22U1b?0zZ4T4?gLq?ikie;wTcC3_JJ4=<0b6v zZ$84F|J-NWVXxcE*5GRaTE}XSc=*F@`&VvZ=bUz;zlu??-j)A@Pp-n;w3r-O38!;i67?fyDD<*f5U@} zk1VUxm;AIi&(iHPdzBgWdm8V!2bSUe9 z(xI$a;f_20VM{-HDZ7NPbM`#+DBI%Id)OzhyvmL{@=)9JH9N?KIajh{k2t8nDaRef z_uLPrE#sm0a9gn5R{obcx9}&6cfR!qe|JtJ>t8Xv8e)EZk?9wpPhD+gnJU;U{~P$v ze(`Irzn1gxcfRl6@P+HS+;-6u|AsGE*UewK=3BP(QjfCr*L$|hqdd89@sbT}`>kGX z@Bhdr?1r0eHr;SqaNRbWz0|I{=p5VXr5oCh_*0a3;QK~D(GA~Y z?w?>7)4@hNiAGClO8dHXUTph46UEvxMj2+A58B zpC}1+u+LPb|5Fzbl#1&F&k#M~%SSo-e0r`U+O#$LbP6;)1_0H5X!a&`Ghl#V1}i_H zB#^jLd;#c>w=Hi z(TDACA3OiO{)q)_MhLfb)WqVx4m-nm)a?BJIbEE z=IWmBUHrYk_FHf6ws_GmIX8Izy3BW{ZA1Tq_S)6<+iii5Z;e$~_Ai#`TcHqkW{}oNlb0xn_UU$^7 zrq?e~cj@Q8Vuv03PCMg-h4zd!R`chEOZim4|H2dOH3uAN$Gqdc(RHz;y#FkPS?x}g zm^z5J`|1ZAoiI1`1Mv0N!IJ_&=vOp{DgdY_dcB@?LB4`~L`Q*M!!l{;GC17$7p}`%`NWb;zT$+vU;Xd)g|C0Z^f$)x{eR#8v8})EI(GZ*cM!MRJ@?%0uh0Ja zx4*l5yMG~Od@9$tG=j{2`9Ig&iANn`CmnULo@r>5-}CT@0lI-J^njQgG~G9wp5Cwg!dLuNi=J@B3;HV-9eS&;yppB3=3C$KU-C7LR`0&` ziYCR4H{WbG-h7LHQ79&}Vmne4Pr~*wzLq)t{U5YVci7E7%vV2m-*vYgwD<0I+Hni* z?30hP-4|?Umwe_*d;95UTl}crpZ?;%?W_5_a^o`GeCw_DPyU#>`GURdyo)~WzZAZW zhpN7;e);R)+J&FI%r@S3XTD1Mqo;|lWESlA27Af&y9eyByS;p;J-z0aZ}cMfYr6dC zr@ydIT=BVzX~$P?ZoBQcjbp!EcXFFv-R!s9POjUXwwG=20v@WFBiy2N(^RCON{we1 z4`Fh-Z4qC=QMyky-Y{CC#jbV$24L!S0Wi!aE8~p-dIlbb4kZ+Nyd(g5L5H6X&^8#r zRzin$E9e_v_W&?cBL!eU2@2-5#TrRX07KvxF$9~-W=6HK<|br})i^>9I(iJhD9GwI zu-XTJ0jOyw7j?k*iW_snJW8S`av@C-^}4ZB{ao^oS^Clbr;Nk;(CeUKK9ksQ;upZh zpT69GIjZ;azvH`p{dLYp>p$Q0H|j^c?Va}a)6cQAe?7%7JR$sje!b6|m{0G`>-xL- z6Ul9SvX=f44}X|F?>W!1pZ@aKcGE54u$NxY)A$#!{-*!p__@zqODZmRb!(k)`Z;#< zpKcAd{JO`XN81kj9BKz0eIj4&++p9p?q~M3Z~njhFJMlv?e{*|-oe*O?#^`j-G%<7 z@(7M2;@#(6=;jMQdAS`)z2n5w>`0>Nm$L3W;RS{wQv0qD=77;tGlx6}nWvKy$y_vL zbUT6tAj1}<@(S<}qNus-^C5%mkgW130hCF_)nT*)AZdZM=(}P%Y^n^%bH>~}lo3Ij zeChxd=@$b4p5r|OVBq0x1OTIA0Px&BGtvs=FC8fWhL*4~KU$v@^>FrHSB)pn=i%K{ znf{gd4f#vM4GH~4ivES7LSAx7e{%iqkA7?^p0oB^CLj6MbLt(v-p!}@>_z9B5PvyZ zk6GdI%bR}Ds(1NE9I&r_=!}!?nkznTC%)+od=LH)d*>PF+U7g%VW0o%HCF%VJ@*a# z5~%A(l{edD1OM~QFY60NxZvh{6ou#isk|F=q;3`h84NaavQISBU_8K7c`LFauyZ zd<-Qo*#RoeWHJDzCt?7B7q#&|K!^nrIwXbqoSEsF&?6PlV|+xKtA$Uy02o_`Q5Qj5 zj|X)Z0GJWc2k7z4?iq5bFM$*={lgG5aw%Z?FqHu)5Oh^py4j$Y@pR63Lzn1&E}^^i zlXvj5>3kZoPg-#Wd&}VmIhK9wGNvcwx4!Wp6Ld(G^1;+qvF7Tl4wRrjL)0ss6j$(- z&lR8lvgvQq6~HABDR2;ffqLGlZ@0G{aftt`|9X-==R+6Uf_)FRZFbw2zihnB?&7;^ zKRk7S5=HPh41zO|GZgjYzuwEYE8fvl6&jZ@<6`~rCqK9KHs9WkJMB!@pMUWu?FC!x zVBi1oPx-1u73qwYGtDHLuaBJZPTPIQZSATrUv2vw@fQDyKu`C(9ek8s&EJje#rO1Y z-1oKC>&@5-%P(iEKIKUPE0N|{{&A1xyZGOlezMSi@*rSETXm%sP5+|P6P90=9XGyt z4Tbm|v7nsliJ1YcnK709s`u>yvNO17*?=EC5?$fg!l=&_8RxUbbt-DD>QMOFcX-j> zVda4Y2O!!bAAxYt=m4b+q%<0pgqgwo1Hf5O5==57ngRet&}*;(VCr_z?6Cj>HTlLm zd7!d3)fZv%tLZQ!zdD$Ye(c=bE+2shyK;1bJ^(EG0I=A9g@Ss4{Xj7Tfc2;gdCUxE z0b-QPjEb_VSwma;^_nc$f?{kP9|DLn?OV~yBcD|m` zp}*@vN7}9jme}QhBQ4-4QsKzLE`P--Cy!eaRWl4c)KxH%ek&*pfJ=F}fxQamG&ut_pok4&*`Va2>Uz05v%*r$C~!_770{0nL1j z1{nZ65K92m){s$7p_+^bY)01z=`#9>RzN@bS`XGn0T|+;On(TVPB794$wHLt)NN(} zbSV%->db=8%qb9bHo<4AhkN=&KgSO_6AX2Dhz|Sozx@&;0GPgQ_2+{6my7Pa^G-wZ z&+l*i17G`G$6s*XVzc>t^E==3U!FGQ`0>wv$v%G4WSUIJ1&W8Z=(oQ2LqFKXU&mkY z-o@87;w#7xR{a+s|N8g8^C_CgYRtoPGxg>1Fi=-v=NF3e%BOhJ^a;6%S2D$j)}gfs zIyjME#wT(>4PD@<|R9C}~^F;;QFa zJiF4AAOVm0@$$u3MR8+_j0+&nzvb&<`A5lf_Fs$unv)*qqaF1r0TcsQ17Ivc2?8$w zTu0F<5M&@w11$sq1Bf6RS}4_1Z9v|WXb!M<0PLp(00R%zo>TEe?jcD`N7mEEGjyQIH^y6-1o~vw=bo#> zxNm4Inqu2fIHe4SQ}Iu2IUnu0I6$I3(gBBgQUKaeg83~IIP;Hq00zK#@mcc~q?|<< zKRItkKZ4WaD`3A?qpqlsg9l(h6@{G)P_+S7nJ%3T7}KAHGQv`Hc0eNl)6)UKC_&MrR{H=58Xk&) z30G-T1a0*i{|5*Kjb`OakmiZ}v^S0pyV!jfP5la<^Q)MQ2fAP;1;H^$E|FR0x!H-iZ4)~H_GlK$}Tj|QKndFQD31IkI4jf9rhQ0TF6F(&R6$UFuV1dsLXtS zWPD-|psCt8z!>LL{V50a3@^S9Jcv5?k=+%%Q0IPRCux~-2}6T3&PjT|spFwR^XdQ# z|EN=Oq5?pP5HA2h2C7uHsRP{^-Rzfczj3T{y)w{YpUj!hrxtp4TJ$O@E!{Q_jDCBn_&9D4E)FR0WXAX01ODAw$gy0 z2Ld&N&E}(MFb9B+G?0e@Fk`dI2Zmz2JQC$ov5zkR$_X7$<%0MMT` znP?R)k%hV!H*?fLjBb_!Qgng04SmxaAmzg&QCIu}wt1NWU!(Ggu->N*p15uUHjkA@ z-IB;M)6c&oT4>B)(AwrJ^o(!glO8KRNto>bl?zqn`P}_d<5BHExJaU_)DdlDoE_k$|;2_d0KHz zeC=|1zKHf|y)XYg2bI1zRBL5GuAntE#S4<+e(K3Z-wP3vQ^g95sRJNY1!%|* zz<>@IZ8?n8@()10KpnZ_nwy=Z`pCOM7x3bM0iaNA;Vc60??IbQ=>s724JIUSqJ9yR zg7#YT{iHob@X4Ks z;=p>xTxovsQ9m(dXUa1Ud`cM?`sBV8!!e5po=Z;D^@-er8U{(OK`|*jo<310Ir8IB z#R=oY1YcIn{uLV$^o!FK2B0j6JdnzzX|6bc{jn*(2!L8mvXlUV9e^2|wR}KhM2pK< z>U0fLepnLs)vncE5FHkPL5CiA~WCobSBTM@8$TE&U@bde&7HA5CBO;K~%y5&mVAUXOpS|kD0@Z*T)*i>msdm zyvX_U2D=i>bQP@S1HjNjVFh47iNa7Yw9#KNZ7`Stx|kV>E; zzSn!m^M<`$9BW0+!D5um76JBB-^;rS)MOzc)Mxc9j&oE@MXh1kn zVlh3+g7Kj7Mj3!sz{g-Cd7q<;R+%}(XM9J<-9WQ+B9!93SBF{9@oKGy zJD5(8uN>Q;LOdrOBW?4YSkK%XHOEGVop5}`zhuLeoH*^Iff{pLET?+F)TA45iVBWT zEJrFbGC)r)W`tyc>fud&Ml?*QY7vGe;ti42I#ry;FQjYO#D@~-Nk(&AK~k62U$!)d zbS|q%#(No}ETFJacAuOo2hEAa7ORb}jhxp>Iy_dVLOV%dXz^j=2slWRXbm!=NE#+&Fb8PsWWl42oBW!H=&)3I%t`ka&79R3i!CPGfX_ zWo+MnLE5(pr9Skf7~@6{u;df%b?PpR*VqbBS@)AnwKwszLD7%;^^D=-qm(WTBPbW+ zYpA!+;gki(y;q-~afdMwUv^QXs{5oeH%;N!#(4cTN7r!;LN4w`_Dhjttn>7An`krC zZ^#qul2b%m)YWC_Gu?-*F=G*7&u85Gndw!d=308eZMko%=hukRl#sK6oib@@Y3HJE zr0t&fl7$N~mpya3wFH$e=D5pBUlP<`8)P39T0;(oyO|M5Lg^E+$%O&4YucD8yie}I zl=iiWb6xB$wTpK>eVn#ksF&{mQoZ;bB=giX%k$LLTy>)%q?n6-()@LPl>TB&gRe~C z1}Kg39v3aO?VR>iyS3S(m?~HW_px+}5Y6;}LtP=0cfBZ|+%w)Ee%O=e;jVJ4P@tee zW(pbt`KIWKkss69xKLaPuZ&`#_5Nt+Z0^tFsXFRGNlD}@nnJo1ZVRPwHPk4uu?q=~ zipasI@!~zl2p9PZWbYq5?dzi68cE%K@!Ay;TR zVqTx{Tt8cqFm~zvcrYYlGnC6AG$zFhtRQat!HZ&@HZQNf1 zBdOqP88KDph7pvI{vpAXuAbAAvWPLL;#$ExaY7;;T5d!48Ncl+-~=hpK)?0==okAH zrI}4!4%&S+B307aBClV+O5!)Pzs+mil({d9;zG0IHkl^1`go-i+Vrwjp$>8lS)T>c zuKwfHq;7czC!ufSVQyh6sZD*gU&9j%kA5Z#{_L4NT}N&)t{#x~H5)$7tMwA%`iZ9Y zc0N|n<`M0aQ01&wrOKp}O^A8LPXRGqqys6{w`!Okz&i%~1rhinFQP}CAiYs<^Y{t- zuFw75=l%}+*0-)-wC6QBfo6O{0JL!$pOt7p2;Jahm6r zz72}+g5sL&sa||5VhyD7DRQw&=SijS1Y_N|fp&#+c01EfWYjO!1O>Am-f`ll3XN3> zBNaam6qjP0*mhseCdZrRt@81JY4@W?WBO{IGQQC{QMSd+M&GoaWj>CeHV3hs(x6y?&@m5-8JWDu=sgU(7BVVgU|xl=6Hs z>03}vy5~rB_;5ug2=Q=+at(ELv3dbTLRS6SlG3c2#@v*kP2c3pEmcvgHVz=@ zTnKF2)m?OH0)y(cfKOKdXi-y;XaE2M0)l4rD}d3RXX#lV(|GAL<`m;acYCLKbr*!y z6<4)HeRsCS&8jgp)uek>W0XxaLb5}Fa^l(Ga??dVP5L$+{fm7-zBM)bW%FAqP*&$m zOe~o^b;`%rGE#IvzC%EBr77_^NQ2|p3Q8t3zI@2Ii5lP&!NDXW9&qI7{Kp;r_|k>e zTxo&i+s3B&!p3jXD6=3&@ya?2dkddCayT5U3!`1+-IaTkEcN&pBfWT0LA)S7f}@kF z3KbQ=3*Y~cPdhjo7BTLHaOnWDE^xn?@-1)Gl#YMs<&nWbxj zY=Uf}a|linx3Di;q}uO}TDNhIYwDq5TU^x7hN|#-e5D|c&&N_wG8$;%l+Q8Mg{GJqb;%L;zWSD3vc@Dtxu>VT;Q)Vm)eLr@cQ+fNi7Js9SaU$% zd28*z&u+6JU@{v8nxLj!OyAw|AWdq~4_C;| zjm|@YeMn$maTFS5g!Z$8iNP@j$O_l+GEr0g&4Jw9e3xzl9r~=TPu~5>vrNkgS^3h5 zBq+)7;ZoI6JEYN<8QKrXA>rNTI5}-p(XxrQU`TzM5H1~u(0whk56M^f<&5XoOh;Kg zr84u$CpR|}`qT{>e=#Cmj1?UQ`3tF^6`dY5_7vokjS0+t7P z(6LwAsbuT(>zzV0G*Nmf|6&adybI$N-<3yLo&3r)u;Fr zLle~;6w5u!?Z6(=+Q>)O;1ldoH{Vn>q+r3COo#Ms^>o?VV#d;E^cF~(8UO=e`st@c z&vihsp_fz22Ai7?7nv*Q*Vw^;tTk^Mi*!whi6X7}Xzm4e%5`F6e#u?v^Kk1zQ*7+M z>_>eG9AVKX56gt7m0>EFByW-g&y(v4B7-C3Ec7)-^+gpuy_<}ObkwEPeOXiNPqNUb zPBb9YUFytwp4g+jBIc}8mnumZk;tklB(LgI9g}vn7{^TBS%+(bNg)mDii$ljIaP8V z1$6Cf^&E9)*{M^%Lg<`|qy;@K`CKL@UyW2Ai)Y_4cZ{pcQD3L>YeU_4WSRAh>^qX9 z5=Jn9^QQw``;F4bEsnDupvUR76gW=B=WeKwui*;|1khYyBLwIHj(hI_gKMK|U2QUm7nP zXM$a*3KI4kWbqRsrb@!qj}TSj3$Ug>zofkD+(9+wEVms!=@Vtq)b91NPd55ek}uIG zGXB)xQ$Q!^*kzY_7`ozpU}beIQbK2h`)H5?1&*rQUsg^NyBh5(|1NkI4qE*S(O0|> zWp>zJ8;6OdC$R;q1sWPq?S2u~1O^|(Ja1a0ts6vM`HKm@1 zH9!q`XcGV|{yz)=>W#<`dgBKGuVw&RpxYZ({cwC7eA_kkjXtjPsnNxy&WrcRIpl=; ze2QugBiKFlOu=-{lP#Kbg)qo?O?<-5hQRYGSB}_sJ7r0%Iojj?i4_3wIhG7v=&NzX z_wcz$i>&;8A^TOXq7GWTQ7@frJ{AT#PwIBR$jO=F0z<(Ef7MGcS&(Z_?)28`le9N= z<3%t@yVw295(cfAYsZ`nSsl+Vu`&oTi+CZ*DV4qpsjqnL1JUVVcouEv6uOxzXiCAG zM4LUD#P|7hR8rmqi`iZVVBw%Q(wx^JF3_g?8h`^B6cQS~qT1A70 z*2uy2(Ozpp@Em^iJo#Q|;s-N~H%L`2=zs`R*A#<2NE62-z6tzVu!w<*IG%wRck&BM zVRrgte0b59Q|q5X#w7H$Oy81ueAM5cY13v%b(4<0o&$gZf{|)r^tV3(dFtaIB8+n! z)%%FD)jnFzXltWsh(h2$!7R0xLXOUuNi+X3;*UK3Ak zZQj!O%pX{IGUPse&PzY%J(_%vDHknZBsI*=V_yDansRf z97~FNR&H%pH~bpo+><$@9H_=7PP7TKB|yUGtZK~Jkx0c~^$f#EStoZ=J;FE{!oKR6TXB!l2M-gm7OeARy!YZ36E>zaPa5l9^mFDOfej%nF3;M$Z5qGm3UO+5*i; zqLojd^vV)1jim_;3Qgg&0Jo0uL6^OTboHz3b=@#oQxuU5WgrZktyEc&*|Fxguyfr3 z6k=om7@O&T+>x%6WT6kuX=dDAU`!LJW=ky;=cU~nea^?g#u}G4?*v5w;KF14NNy}X zTJf%A$2nizU?!bSme(fx=w=rx(X5M=xJpz9x_{~MPF1aX4I}7D-JOP96f?xd&E6O1 zV4xni4!dwIxF#77_Dq!S0K!~?f|{rxJ_oG&Pp+OdVb>NhNir}ff-GB@oyy*!wySC0 zQ*dgz?--G%B6pu_aZW_NcJJh>|0fYdMM0i^Ny1aS%hlSA<{%$S8?aLRpK%oafeSmxgKyr_Io9YLMc`^IE4f9zX z%*;-6m?ANh+ZtDfVab*Ln&XJpxSC(|pM{0o4OD@W2c_Far \ No newline at end of file diff --git a/fastmovie-vue/scss/_variables.scss b/fastmovie-vue/scss/_variables.scss new file mode 100644 index 0000000..a099aba --- /dev/null +++ b/fastmovie-vue/scss/_variables.scss @@ -0,0 +1,62 @@ +// 方向 +$direction: top, right, bottom, left; +// 边距 +$spacing: 3px; +// 圆角 +$rounded-size: 2px; +// 边框粗细 +$border-size: 1px; +// 背景颜色 +$color-list: ( + "white":var(--el-bg-color), + "primary":var(--el-color-primary), + "primary-light-3":var(--el-color-primary-light-3), + "primary-light-9":var(--el-color-primary-light-9), + "success":var(--el-color-success), + "success-light-3":var(--el-color-success-light-3), + "warning":var(--el-color-warning), + "warning-light-3":var(--el-color-warning-light-3), + "danger":var(--el-color-danger), + "danger-light-3":var(--el-color-danger-light-3), + "danger-dark-2":var(--el-color-danger-dark-2), + "error":var(--el-color-error), + "hover":var(--el-bg-color-page), + "overlay":var(--el-bg-color-overlay), + "mask":var(--el-mask-color), + "mask-light":var(--el-mask-color-extra-light), + "dark":var(--el-color-black), + "page":var(--el-bg-color-page), + "info":var(--el-color-info), + "gray":#272727 +); +// 文字颜色 +$text-color-list: ( + "primary":var(--el-color-primary), + "primary-light-3":var(--el-color-primary-light-3), + "success":var(--el-color-success), + "danger":var(--el-color-danger), + "warning":var(--el-color-warning), + "error":var(--el-color-error), + "white":var(--el-bg-color), + "grey":var(--el-color-info), + "info":var(--el-color-info), + "text-primary":var(--el-text-color-primary), + "regular":var(--el-text-color-regular), + "secondary":var(--el-text-color-secondary), + "placeholder":var(--el-text-color-placeholder), + "disable":var(--el-text-color-disabled), + "dark":var(--el-text-color-primary), + "bg":var(--el-bg-color), + "price":#f56c6c +); +// 自适应断点 +$breakpoint: ( + "m":375px, + "p":480px, + "xs": 768px, + "sm": 960px, + "md": 1280px, + "lg": 1440px, + "xl": 1920px, + "xxl": 2560px +); \ No newline at end of file diff --git a/fastmovie-vue/scss/common.scss b/fastmovie-vue/scss/common.scss new file mode 100644 index 0000000..bf10f73 --- /dev/null +++ b/fastmovie-vue/scss/common.scss @@ -0,0 +1,576 @@ +@import "./variables"; + +.pb-safe-area { + padding-bottom: constant(safe-area-inset-bottom) !important; + padding-bottom: env(safe-area-inset-bottom) !important; +} + +.pt-safe-area { + padding-top: constant(safe-area-inset-top) !important; + padding-top: env(safe-area-inset-top) !important; +} + +.mx-auto, +.ml-auto { + margin-left: auto !important; +} + +.mx-auto, +.mr-auto { + margin-right: auto !important; +} + +@for $i from 0 through 10 { + + .p-#{$i}, + .pl-#{$i}, + .px-#{$i} { + padding-left: $spacing * $i !important; + } + + .p-#{$i}, + .pr-#{$i}, + .px-#{$i} { + padding-right: $spacing * $i !important; + } + + .p-#{$i}, + .pt-#{$i}, + .py-#{$i} { + padding-top: $spacing * $i !important; + } + + .p-#{$i}, + .pb-#{$i}, + .py-#{$i} { + padding-bottom: $spacing * $i !important; + } + + .m-#{$i}, + .ml-#{$i}, + .mx-#{$i} { + margin-left: $spacing * $i !important; + } + + .m-#{$i}, + .mr-#{$i}, + .mx-#{$i} { + margin-right: $spacing * $i !important; + } + + .m-#{$i}, + .mt-#{$i}, + .my-#{$i} { + margin-top: $spacing * $i !important; + } + + .m-#{$i}, + .mb-#{$i}, + .my-#{$i} { + margin-bottom: $spacing * $i !important; + } + + .top-#{$i} { + top: $spacing * $i !important; + } + + .left-#{$i} { + left: $spacing * $i !important; + } + + .right-#{$i} { + right: $spacing * $i !important; + } + + .bottom-#{$i} { + bottom: $spacing * $i !important; + } + + .top--#{$i} { + top: -$spacing * $i !important; + } + + .left--#{$i} { + left: -$spacing * $i !important; + } + + .right--#{$i} { + right: -$spacing * $i !important; + } + + .bottom--#{$i} { + bottom: -$spacing * $i !important; + } +} + +.inline-flex { + display: inline-flex; +} + +.flex { + display: flex; + + &-shrink-0 { + flex-shrink: 0; + } + + @each $var in wrap, + nowrap { + &-#{$var} { + flex-wrap: $var; + } + } + + @each $var in column, + column-reverse, + row, + row-reverse { + &-#{$var} { + flex-direction: $var; + } + } + + @each $var in center, + baseline, + flex-start, + flex-end { + &-y-#{$var} { + align-items: $var; + } + } + + @each $var in center, + flex-start, + flex-end, + space-around, + space-between, + space-evenly { + &-x-#{$var} { + justify-content: $var; + } + } + + @for $i from 1 through 12 { + &-#{$i} { + flex: $i; + min-width: 0; + } + } + + &-center { + align-items: center; + justify-content: center; + } +} + +[class*="grid-columns-"] { + display: grid; + align-content: flex-start; +} + +.grid { + display: grid; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + + @for $i from 1 through 23 { + &-columns-#{$i} { + grid-template-columns: repeat($i, minmax(0px, 1fr)); + } + } + + @for $i from 1 through 10 { + + &-gap-#{$i}, + &-gap-x-#{$i} { + column-gap: $spacing * $i !important; + } + + &-gap-#{$i}, + &-gap-y-#{$i} { + row-gap: $spacing * $i !important; + } + } + + @for $i from 1 through 24 { + &-column-#{$i} { + grid-column: span $i / span $i; + } + + &-row-#{$i} { + grid-row: span $i / span $i; + } + } + + @each $key, $value in $breakpoint { + @media only screen and (min-width: $value) { + @for $i from 1 through 24 { + &-columns-#{$key}-#{$i} { + grid-template-columns: repeat($i, minmax(0px, 1fr)); + } + + &-column-#{$key}-#{$i} { + grid-column: span $i / span $i; + } + + &-row-#{$key}-#{$i} { + grid-row: span $i / span $i; + } + } + } + } +} + +@each $var in relative, +absolute, +fixed, +sticky { + .position-#{$var} { + position: $var; + } +} + +@for $i from 1 through 20 { + .vh-#{$i * 5} { + height: $i * 5vh; + } + + .vw-#{$i * 5} { + width: $i * 5vw; + } + + .h-#{$i * 5} { + height: $i * 5%; + } + + .w-#{$i * 5} { + width: $i * 5%; + } + + .min-h-#{$i * 5} { + min-height: $i * 5%; + } + + .min-w-#{$i * 5} { + min-width: $i * 5%; + } + + .min-vh-#{$i * 5} { + min-height: $i * 5vh; + } + + .min-vw-#{$i * 5} { + min-width: $i * 5vw; + } + + .max-h-#{$i * 5} { + max-height: $i * 5%; + } + + .max-w-#{$i * 5} { + max-width: $i * 5%; + } + + .max-vh-#{$i * 5} { + max-height: $i * 5vh; + } + + .max-vw-#{$i * 5} { + max-width: $i * 5vw; + } +} + +.bg { + background-color: var(--el-bg-color); +} + +.bg-filter { + backdrop-filter: blur(6px); +} + +.bg-mosaic, +.el-avatar.bg-mosaic, +#app .el-avatar { + --el-avatar-bg-color:#1E1E1E; + /* background: linear-gradient(45deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%), + linear-gradient(-45deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.05) 75%), + linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.05) 75%); + background-size: 20px 20px; + background-position: 0 0, 0 10px, 10px -10px, -10px 0px; */ +} + +@each $key, +$color in $color-list { + + .bg-#{$key}, + .hover-bg-#{$key}:hover { + background-color: $color; + } +} + +@for $i from 1 through 20 { + .bg-#{$i * 5} { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), $i * 0.05); + } +} + + +.text { + + @each $key, + $color in $text-color-list { + &-#{$key} { + color: $color; + } + } + + @each $var in nowrap, + wrap { + &-#{$var} { + white-space: $var; + } + } + + @each $var in break-all, + break-word, + keep-all { + &-#{$var} { + word-break: $var; + } + } + + @for $i from 1 through 10 { + &-ellipsis-#{$i} { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: $i; + -webkit-line-clamp: $i; + -webkit-box-orient: vertical; + } + } + + @for $i from 1 through 5 { + &-spacing-#{$i} { + word-spacing: $i * 0.53333vw; + } + } + + @for $i from 1 through 5 { + &-letter-spacing-#{$i} { + letter-spacing: $i * 0.53333vw; + } + } + + @each $var in left, + center, + right, + initial { + &-#{$var} { + text-align: $var; + } + } + + @each $var in capitalize, + uppercase, + lowercase { + &-#{$var} { + text-transform: $var; + } + } + + &-s { + text-decoration: line-through; + } +} + +@each $i in $direction { + + .border, + .border-#{$i} { + border-#{$i}-width: $border-size; + border-#{$i}-style: solid; + border-#{$i}-color: var(--el-border-color); + } + + @each $key, + $var in $color-list { + + .border-#{$key}, + .border-#{$i}-#{$key} { + border-#{$i}-color: $var; + } + + .border-hover-#{$key}:hover, + .border-hover-#{$i}-#{$key}:hover { + border-#{$i}-color: $var; + } + } +} + +@for $i from 0 through 6 { + @each $n in $direction { + + .border-#{$i}, + .border-#{$n}-#{$i} { + border-#{$n}-width: $i * $border-size; + } + } +} + +@each $i in solid, +dotted, +dashed, +double, +groove, +ridge, +inset, +outset { + @each $n in $direction { + + .border-#{$i}, + .border-#{$n}-#{$i} { + border-#{$n}-style: $i; + } + } +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-round { + border-radius: 100px !important; +} + +@for $i from 0 through 6 { + @if $i ==1 { + + .rounded, + .rounded-left, + .rounded-bottom, + .rounded-bottom-left { + border-bottom-left-radius: $i * $rounded-size; + } + + .rounded, + .rounded-left, + .rounded-top, + .rounded-top-left { + border-top-left-radius: $i * $rounded-size; + } + + .rounded, + .rounded-right, + .rounded-bottom, + .rounded-bottom-right { + border-bottom-right-radius: $i * $rounded-size; + } + + .rounded, + .rounded-right, + .rounded-top, + .rounded-top-right { + border-top-right-radius: $i * $rounded-size; + } + } + + @else { + + .rounded-#{$i}, + .rounded-left-#{$i}, + .rounded-bottom-#{$i}, + .rounded-bottom-left-#{$i} { + border-bottom-left-radius: $i * $rounded-size !important; + } + + .rounded-#{$i}, + .rounded-left-#{$i}, + .rounded-top-#{$i}, + .rounded-top-left-#{$i} { + border-top-left-radius: $i * $rounded-size !important; + } + + .rounded-#{$i}, + .rounded-right-#{$i}, + .rounded-bottom-#{$i}, + .rounded-bottom-right-#{$i} { + border-bottom-right-radius: $i * $rounded-size !important; + } + + .rounded-#{$i}, + .rounded-right-#{$i}, + .rounded-top-#{$i}, + .rounded-top-right-#{$i} { + border-top-right-radius: $i * $rounded-size !important; + } + } +} + +.font-weight { + &-bold { + font-weight: bold; + } + + &-normal { + font-weight: normal; + } + + @for $i from 1 through 9 { + &-#{$i * 100} { + font-weight: $i * 100; + } + } +} + +@for $i from 1 through 10 { + .h#{$i} { + font-size: 32px - ($i * 2px); + } +} + +.line-height { + line-height: 1; + + @for $i from 1 through 10 { + &-#{$i} { + line-height: 20px + ($i * 2px); + } + } +} + +@each $i in hidden, +visible, +auto, +scroll { + + .overflow-#{$i}, + .overflow-x-#{$i} { + overflow-x: $i; + } + + .overflow-#{$i}, + .overflow-y-#{$i} { + overflow-y: $i; + } +} + +img { + vertical-align: middle; +} + +.shadow { + box-shadow: var(--el-box-shadow); + + @each $i in light, + lighter, + dark { + &-#{$i} { + box-shadow:var(--el-box-shadow-#{$i}); + } + } +} + +.pointer { + cursor: pointer; +} \ No newline at end of file diff --git a/fastmovie-vue/scss/css/common.css b/fastmovie-vue/scss/css/common.css new file mode 100644 index 0000000..241101e --- /dev/null +++ b/fastmovie-vue/scss/css/common.css @@ -0,0 +1,6418 @@ +.pb-safe-area { + padding-bottom: constant(safe-area-inset-bottom) !important; + padding-bottom: env(safe-area-inset-bottom) !important; +} + +.pt-safe-area { + padding-top: constant(safe-area-inset-top) !important; + padding-top: env(safe-area-inset-top) !important; +} + +.mx-auto, +.ml-auto { + margin-left: auto !important; +} + +.mx-auto, +.mr-auto { + margin-right: auto !important; +} + +.p-0, +.pl-0, +.px-0 { + padding-left: 0px !important; +} + +.p-0, +.pr-0, +.px-0 { + padding-right: 0px !important; +} + +.p-0, +.pt-0, +.py-0 { + padding-top: 0px !important; +} + +.p-0, +.pb-0, +.py-0 { + padding-bottom: 0px !important; +} + +.m-0, +.ml-0, +.mx-0 { + margin-left: 0px !important; +} + +.m-0, +.mr-0, +.mx-0 { + margin-right: 0px !important; +} + +.m-0, +.mt-0, +.my-0 { + margin-top: 0px !important; +} + +.m-0, +.mb-0, +.my-0 { + margin-bottom: 0px !important; +} + +.top-0 { + top: 0px !important; +} + +.left-0 { + left: 0px !important; +} + +.right-0 { + right: 0px !important; +} + +.bottom-0 { + bottom: 0px !important; +} + +.top--0 { + top: 0px !important; +} + +.left--0 { + left: 0px !important; +} + +.right--0 { + right: 0px !important; +} + +.bottom--0 { + bottom: 0px !important; +} + +.p-1, +.pl-1, +.px-1 { + padding-left: 3px !important; +} + +.p-1, +.pr-1, +.px-1 { + padding-right: 3px !important; +} + +.p-1, +.pt-1, +.py-1 { + padding-top: 3px !important; +} + +.p-1, +.pb-1, +.py-1 { + padding-bottom: 3px !important; +} + +.m-1, +.ml-1, +.mx-1 { + margin-left: 3px !important; +} + +.m-1, +.mr-1, +.mx-1 { + margin-right: 3px !important; +} + +.m-1, +.mt-1, +.my-1 { + margin-top: 3px !important; +} + +.m-1, +.mb-1, +.my-1 { + margin-bottom: 3px !important; +} + +.top-1 { + top: 3px !important; +} + +.left-1 { + left: 3px !important; +} + +.right-1 { + right: 3px !important; +} + +.bottom-1 { + bottom: 3px !important; +} + +.top--1 { + top: -3px !important; +} + +.left--1 { + left: -3px !important; +} + +.right--1 { + right: -3px !important; +} + +.bottom--1 { + bottom: -3px !important; +} + +.p-2, +.pl-2, +.px-2 { + padding-left: 6px !important; +} + +.p-2, +.pr-2, +.px-2 { + padding-right: 6px !important; +} + +.p-2, +.pt-2, +.py-2 { + padding-top: 6px !important; +} + +.p-2, +.pb-2, +.py-2 { + padding-bottom: 6px !important; +} + +.m-2, +.ml-2, +.mx-2 { + margin-left: 6px !important; +} + +.m-2, +.mr-2, +.mx-2 { + margin-right: 6px !important; +} + +.m-2, +.mt-2, +.my-2 { + margin-top: 6px !important; +} + +.m-2, +.mb-2, +.my-2 { + margin-bottom: 6px !important; +} + +.top-2 { + top: 6px !important; +} + +.left-2 { + left: 6px !important; +} + +.right-2 { + right: 6px !important; +} + +.bottom-2 { + bottom: 6px !important; +} + +.top--2 { + top: -6px !important; +} + +.left--2 { + left: -6px !important; +} + +.right--2 { + right: -6px !important; +} + +.bottom--2 { + bottom: -6px !important; +} + +.p-3, +.pl-3, +.px-3 { + padding-left: 9px !important; +} + +.p-3, +.pr-3, +.px-3 { + padding-right: 9px !important; +} + +.p-3, +.pt-3, +.py-3 { + padding-top: 9px !important; +} + +.p-3, +.pb-3, +.py-3 { + padding-bottom: 9px !important; +} + +.m-3, +.ml-3, +.mx-3 { + margin-left: 9px !important; +} + +.m-3, +.mr-3, +.mx-3 { + margin-right: 9px !important; +} + +.m-3, +.mt-3, +.my-3 { + margin-top: 9px !important; +} + +.m-3, +.mb-3, +.my-3 { + margin-bottom: 9px !important; +} + +.top-3 { + top: 9px !important; +} + +.left-3 { + left: 9px !important; +} + +.right-3 { + right: 9px !important; +} + +.bottom-3 { + bottom: 9px !important; +} + +.top--3 { + top: -9px !important; +} + +.left--3 { + left: -9px !important; +} + +.right--3 { + right: -9px !important; +} + +.bottom--3 { + bottom: -9px !important; +} + +.p-4, +.pl-4, +.px-4 { + padding-left: 12px !important; +} + +.p-4, +.pr-4, +.px-4 { + padding-right: 12px !important; +} + +.p-4, +.pt-4, +.py-4 { + padding-top: 12px !important; +} + +.p-4, +.pb-4, +.py-4 { + padding-bottom: 12px !important; +} + +.m-4, +.ml-4, +.mx-4 { + margin-left: 12px !important; +} + +.m-4, +.mr-4, +.mx-4 { + margin-right: 12px !important; +} + +.m-4, +.mt-4, +.my-4 { + margin-top: 12px !important; +} + +.m-4, +.mb-4, +.my-4 { + margin-bottom: 12px !important; +} + +.top-4 { + top: 12px !important; +} + +.left-4 { + left: 12px !important; +} + +.right-4 { + right: 12px !important; +} + +.bottom-4 { + bottom: 12px !important; +} + +.top--4 { + top: -12px !important; +} + +.left--4 { + left: -12px !important; +} + +.right--4 { + right: -12px !important; +} + +.bottom--4 { + bottom: -12px !important; +} + +.p-5, +.pl-5, +.px-5 { + padding-left: 15px !important; +} + +.p-5, +.pr-5, +.px-5 { + padding-right: 15px !important; +} + +.p-5, +.pt-5, +.py-5 { + padding-top: 15px !important; +} + +.p-5, +.pb-5, +.py-5 { + padding-bottom: 15px !important; +} + +.m-5, +.ml-5, +.mx-5 { + margin-left: 15px !important; +} + +.m-5, +.mr-5, +.mx-5 { + margin-right: 15px !important; +} + +.m-5, +.mt-5, +.my-5 { + margin-top: 15px !important; +} + +.m-5, +.mb-5, +.my-5 { + margin-bottom: 15px !important; +} + +.top-5 { + top: 15px !important; +} + +.left-5 { + left: 15px !important; +} + +.right-5 { + right: 15px !important; +} + +.bottom-5 { + bottom: 15px !important; +} + +.top--5 { + top: -15px !important; +} + +.left--5 { + left: -15px !important; +} + +.right--5 { + right: -15px !important; +} + +.bottom--5 { + bottom: -15px !important; +} + +.p-6, +.pl-6, +.px-6 { + padding-left: 18px !important; +} + +.p-6, +.pr-6, +.px-6 { + padding-right: 18px !important; +} + +.p-6, +.pt-6, +.py-6 { + padding-top: 18px !important; +} + +.p-6, +.pb-6, +.py-6 { + padding-bottom: 18px !important; +} + +.m-6, +.ml-6, +.mx-6 { + margin-left: 18px !important; +} + +.m-6, +.mr-6, +.mx-6 { + margin-right: 18px !important; +} + +.m-6, +.mt-6, +.my-6 { + margin-top: 18px !important; +} + +.m-6, +.mb-6, +.my-6 { + margin-bottom: 18px !important; +} + +.top-6 { + top: 18px !important; +} + +.left-6 { + left: 18px !important; +} + +.right-6 { + right: 18px !important; +} + +.bottom-6 { + bottom: 18px !important; +} + +.top--6 { + top: -18px !important; +} + +.left--6 { + left: -18px !important; +} + +.right--6 { + right: -18px !important; +} + +.bottom--6 { + bottom: -18px !important; +} + +.p-7, +.pl-7, +.px-7 { + padding-left: 21px !important; +} + +.p-7, +.pr-7, +.px-7 { + padding-right: 21px !important; +} + +.p-7, +.pt-7, +.py-7 { + padding-top: 21px !important; +} + +.p-7, +.pb-7, +.py-7 { + padding-bottom: 21px !important; +} + +.m-7, +.ml-7, +.mx-7 { + margin-left: 21px !important; +} + +.m-7, +.mr-7, +.mx-7 { + margin-right: 21px !important; +} + +.m-7, +.mt-7, +.my-7 { + margin-top: 21px !important; +} + +.m-7, +.mb-7, +.my-7 { + margin-bottom: 21px !important; +} + +.top-7 { + top: 21px !important; +} + +.left-7 { + left: 21px !important; +} + +.right-7 { + right: 21px !important; +} + +.bottom-7 { + bottom: 21px !important; +} + +.top--7 { + top: -21px !important; +} + +.left--7 { + left: -21px !important; +} + +.right--7 { + right: -21px !important; +} + +.bottom--7 { + bottom: -21px !important; +} + +.p-8, +.pl-8, +.px-8 { + padding-left: 24px !important; +} + +.p-8, +.pr-8, +.px-8 { + padding-right: 24px !important; +} + +.p-8, +.pt-8, +.py-8 { + padding-top: 24px !important; +} + +.p-8, +.pb-8, +.py-8 { + padding-bottom: 24px !important; +} + +.m-8, +.ml-8, +.mx-8 { + margin-left: 24px !important; +} + +.m-8, +.mr-8, +.mx-8 { + margin-right: 24px !important; +} + +.m-8, +.mt-8, +.my-8 { + margin-top: 24px !important; +} + +.m-8, +.mb-8, +.my-8 { + margin-bottom: 24px !important; +} + +.top-8 { + top: 24px !important; +} + +.left-8 { + left: 24px !important; +} + +.right-8 { + right: 24px !important; +} + +.bottom-8 { + bottom: 24px !important; +} + +.top--8 { + top: -24px !important; +} + +.left--8 { + left: -24px !important; +} + +.right--8 { + right: -24px !important; +} + +.bottom--8 { + bottom: -24px !important; +} + +.p-9, +.pl-9, +.px-9 { + padding-left: 27px !important; +} + +.p-9, +.pr-9, +.px-9 { + padding-right: 27px !important; +} + +.p-9, +.pt-9, +.py-9 { + padding-top: 27px !important; +} + +.p-9, +.pb-9, +.py-9 { + padding-bottom: 27px !important; +} + +.m-9, +.ml-9, +.mx-9 { + margin-left: 27px !important; +} + +.m-9, +.mr-9, +.mx-9 { + margin-right: 27px !important; +} + +.m-9, +.mt-9, +.my-9 { + margin-top: 27px !important; +} + +.m-9, +.mb-9, +.my-9 { + margin-bottom: 27px !important; +} + +.top-9 { + top: 27px !important; +} + +.left-9 { + left: 27px !important; +} + +.right-9 { + right: 27px !important; +} + +.bottom-9 { + bottom: 27px !important; +} + +.top--9 { + top: -27px !important; +} + +.left--9 { + left: -27px !important; +} + +.right--9 { + right: -27px !important; +} + +.bottom--9 { + bottom: -27px !important; +} + +.p-10, +.pl-10, +.px-10 { + padding-left: 30px !important; +} + +.p-10, +.pr-10, +.px-10 { + padding-right: 30px !important; +} + +.p-10, +.pt-10, +.py-10 { + padding-top: 30px !important; +} + +.p-10, +.pb-10, +.py-10 { + padding-bottom: 30px !important; +} + +.m-10, +.ml-10, +.mx-10 { + margin-left: 30px !important; +} + +.m-10, +.mr-10, +.mx-10 { + margin-right: 30px !important; +} + +.m-10, +.mt-10, +.my-10 { + margin-top: 30px !important; +} + +.m-10, +.mb-10, +.my-10 { + margin-bottom: 30px !important; +} + +.top-10 { + top: 30px !important; +} + +.left-10 { + left: 30px !important; +} + +.right-10 { + right: 30px !important; +} + +.bottom-10 { + bottom: 30px !important; +} + +.top--10 { + top: -30px !important; +} + +.left--10 { + left: -30px !important; +} + +.right--10 { + right: -30px !important; +} + +.bottom--10 { + bottom: -30px !important; +} + +.inline-flex { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; +} + +.flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +.flex-shrink-0 { + -ms-flex-negative: 0; + flex-shrink: 0; +} + +.flex-wrap { + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; +} + +.flex-column { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.flex-column-reverse { + -webkit-box-orient: vertical; + -webkit-box-direction: reverse; + -ms-flex-direction: column-reverse; + flex-direction: column-reverse; +} + +.flex-row { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; +} + +.flex-row-reverse { + -webkit-box-orient: horizontal; + -webkit-box-direction: reverse; + -ms-flex-direction: row-reverse; + flex-direction: row-reverse; +} + +.flex-y-center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.flex-y-baseline { + -webkit-box-align: baseline; + -ms-flex-align: baseline; + align-items: baseline; +} + +.flex-y-flex-start { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.flex-y-flex-end { + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; +} + +.flex-x-center { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.flex-x-flex-start { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.flex-x-flex-end { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; +} + +.flex-x-space-around { + -ms-flex-pack: distribute; + justify-content: space-around; +} + +.flex-x-space-between { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.flex-x-space-evenly { + -webkit-box-pack: space-evenly; + -ms-flex-pack: space-evenly; + justify-content: space-evenly; +} + +.flex-1 { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + min-width: 0; +} + +.flex-2 { + -webkit-box-flex: 2; + -ms-flex: 2; + flex: 2; + min-width: 0; +} + +.flex-3 { + -webkit-box-flex: 3; + -ms-flex: 3; + flex: 3; + min-width: 0; +} + +.flex-4 { + -webkit-box-flex: 4; + -ms-flex: 4; + flex: 4; + min-width: 0; +} + +.flex-5 { + -webkit-box-flex: 5; + -ms-flex: 5; + flex: 5; + min-width: 0; +} + +.flex-6 { + -webkit-box-flex: 6; + -ms-flex: 6; + flex: 6; + min-width: 0; +} + +.flex-7 { + -webkit-box-flex: 7; + -ms-flex: 7; + flex: 7; + min-width: 0; +} + +.flex-8 { + -webkit-box-flex: 8; + -ms-flex: 8; + flex: 8; + min-width: 0; +} + +.flex-9 { + -webkit-box-flex: 9; + -ms-flex: 9; + flex: 9; + min-width: 0; +} + +.flex-10 { + -webkit-box-flex: 10; + -ms-flex: 10; + flex: 10; + min-width: 0; +} + +.flex-11 { + -webkit-box-flex: 11; + -ms-flex: 11; + flex: 11; + min-width: 0; +} + +.flex-12 { + -webkit-box-flex: 12; + -ms-flex: 12; + flex: 12; + min-width: 0; +} + +.flex-center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +[class*="grid-columns-"] { + display: -ms-grid; + display: grid; + -ms-flex-line-pack: start; + align-content: flex-start; +} + +.grid { + display: -ms-grid; + display: grid; + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); +} + +.grid-columns-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); +} + +.grid-columns-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); +} + +.grid-columns-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); +} + +.grid-columns-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); +} + +.grid-columns-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); +} + +.grid-columns-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); +} + +.grid-columns-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); +} + +.grid-columns-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); +} + +.grid-columns-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); +} + +.grid-columns-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); +} + +.grid-columns-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); +} + +.grid-columns-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); +} + +.grid-columns-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); +} + +.grid-columns-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); +} + +.grid-columns-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); +} + +.grid-columns-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); +} + +.grid-columns-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); +} + +.grid-columns-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); +} + +.grid-columns-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); +} + +.grid-columns-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); +} + +.grid-columns-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); +} + +.grid-columns-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); +} + +.grid-columns-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); +} + +.grid-gap-1, .grid-gap-x-1 { + -webkit-column-gap: 3px !important; + column-gap: 3px !important; +} + +.grid-gap-1, .grid-gap-y-1 { + row-gap: 3px !important; +} + +.grid-gap-2, .grid-gap-x-2 { + -webkit-column-gap: 6px !important; + column-gap: 6px !important; +} + +.grid-gap-2, .grid-gap-y-2 { + row-gap: 6px !important; +} + +.grid-gap-3, .grid-gap-x-3 { + -webkit-column-gap: 9px !important; + column-gap: 9px !important; +} + +.grid-gap-3, .grid-gap-y-3 { + row-gap: 9px !important; +} + +.grid-gap-4, .grid-gap-x-4 { + -webkit-column-gap: 12px !important; + column-gap: 12px !important; +} + +.grid-gap-4, .grid-gap-y-4 { + row-gap: 12px !important; +} + +.grid-gap-5, .grid-gap-x-5 { + -webkit-column-gap: 15px !important; + column-gap: 15px !important; +} + +.grid-gap-5, .grid-gap-y-5 { + row-gap: 15px !important; +} + +.grid-gap-6, .grid-gap-x-6 { + -webkit-column-gap: 18px !important; + column-gap: 18px !important; +} + +.grid-gap-6, .grid-gap-y-6 { + row-gap: 18px !important; +} + +.grid-gap-7, .grid-gap-x-7 { + -webkit-column-gap: 21px !important; + column-gap: 21px !important; +} + +.grid-gap-7, .grid-gap-y-7 { + row-gap: 21px !important; +} + +.grid-gap-8, .grid-gap-x-8 { + -webkit-column-gap: 24px !important; + column-gap: 24px !important; +} + +.grid-gap-8, .grid-gap-y-8 { + row-gap: 24px !important; +} + +.grid-gap-9, .grid-gap-x-9 { + -webkit-column-gap: 27px !important; + column-gap: 27px !important; +} + +.grid-gap-9, .grid-gap-y-9 { + row-gap: 27px !important; +} + +.grid-gap-10, .grid-gap-x-10 { + -webkit-column-gap: 30px !important; + column-gap: 30px !important; +} + +.grid-gap-10, .grid-gap-y-10 { + row-gap: 30px !important; +} + +.grid-column-1 { + grid-column: span 1/span 1; +} + +.grid-row-1 { + grid-row: span 1/span 1; +} + +.grid-column-2 { + grid-column: span 2/span 2; +} + +.grid-row-2 { + grid-row: span 2/span 2; +} + +.grid-column-3 { + grid-column: span 3/span 3; +} + +.grid-row-3 { + grid-row: span 3/span 3; +} + +.grid-column-4 { + grid-column: span 4/span 4; +} + +.grid-row-4 { + grid-row: span 4/span 4; +} + +.grid-column-5 { + grid-column: span 5/span 5; +} + +.grid-row-5 { + grid-row: span 5/span 5; +} + +.grid-column-6 { + grid-column: span 6/span 6; +} + +.grid-row-6 { + grid-row: span 6/span 6; +} + +.grid-column-7 { + grid-column: span 7/span 7; +} + +.grid-row-7 { + grid-row: span 7/span 7; +} + +.grid-column-8 { + grid-column: span 8/span 8; +} + +.grid-row-8 { + grid-row: span 8/span 8; +} + +.grid-column-9 { + grid-column: span 9/span 9; +} + +.grid-row-9 { + grid-row: span 9/span 9; +} + +.grid-column-10 { + grid-column: span 10/span 10; +} + +.grid-row-10 { + grid-row: span 10/span 10; +} + +.grid-column-11 { + grid-column: span 11/span 11; +} + +.grid-row-11 { + grid-row: span 11/span 11; +} + +.grid-column-12 { + grid-column: span 12/span 12; +} + +.grid-row-12 { + grid-row: span 12/span 12; +} + +.grid-column-13 { + grid-column: span 13/span 13; +} + +.grid-row-13 { + grid-row: span 13/span 13; +} + +.grid-column-14 { + grid-column: span 14/span 14; +} + +.grid-row-14 { + grid-row: span 14/span 14; +} + +.grid-column-15 { + grid-column: span 15/span 15; +} + +.grid-row-15 { + grid-row: span 15/span 15; +} + +.grid-column-16 { + grid-column: span 16/span 16; +} + +.grid-row-16 { + grid-row: span 16/span 16; +} + +.grid-column-17 { + grid-column: span 17/span 17; +} + +.grid-row-17 { + grid-row: span 17/span 17; +} + +.grid-column-18 { + grid-column: span 18/span 18; +} + +.grid-row-18 { + grid-row: span 18/span 18; +} + +.grid-column-19 { + grid-column: span 19/span 19; +} + +.grid-row-19 { + grid-row: span 19/span 19; +} + +.grid-column-20 { + grid-column: span 20/span 20; +} + +.grid-row-20 { + grid-row: span 20/span 20; +} + +.grid-column-21 { + grid-column: span 21/span 21; +} + +.grid-row-21 { + grid-row: span 21/span 21; +} + +.grid-column-22 { + grid-column: span 22/span 22; +} + +.grid-row-22 { + grid-row: span 22/span 22; +} + +.grid-column-23 { + grid-column: span 23/span 23; +} + +.grid-row-23 { + grid-row: span 23/span 23; +} + +.grid-column-24 { + grid-column: span 24/span 24; +} + +.grid-row-24 { + grid-row: span 24/span 24; +} + +@media only screen and (min-width: 375px) { + .grid-columns-m-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-m-1 { + grid-column: span 1/span 1; + } + .grid-row-m-1 { + grid-row: span 1/span 1; + } + .grid-columns-m-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-m-2 { + grid-column: span 2/span 2; + } + .grid-row-m-2 { + grid-row: span 2/span 2; + } + .grid-columns-m-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-m-3 { + grid-column: span 3/span 3; + } + .grid-row-m-3 { + grid-row: span 3/span 3; + } + .grid-columns-m-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-m-4 { + grid-column: span 4/span 4; + } + .grid-row-m-4 { + grid-row: span 4/span 4; + } + .grid-columns-m-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-m-5 { + grid-column: span 5/span 5; + } + .grid-row-m-5 { + grid-row: span 5/span 5; + } + .grid-columns-m-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-m-6 { + grid-column: span 6/span 6; + } + .grid-row-m-6 { + grid-row: span 6/span 6; + } + .grid-columns-m-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-m-7 { + grid-column: span 7/span 7; + } + .grid-row-m-7 { + grid-row: span 7/span 7; + } + .grid-columns-m-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-m-8 { + grid-column: span 8/span 8; + } + .grid-row-m-8 { + grid-row: span 8/span 8; + } + .grid-columns-m-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-m-9 { + grid-column: span 9/span 9; + } + .grid-row-m-9 { + grid-row: span 9/span 9; + } + .grid-columns-m-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-m-10 { + grid-column: span 10/span 10; + } + .grid-row-m-10 { + grid-row: span 10/span 10; + } + .grid-columns-m-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-m-11 { + grid-column: span 11/span 11; + } + .grid-row-m-11 { + grid-row: span 11/span 11; + } + .grid-columns-m-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-m-12 { + grid-column: span 12/span 12; + } + .grid-row-m-12 { + grid-row: span 12/span 12; + } + .grid-columns-m-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-m-13 { + grid-column: span 13/span 13; + } + .grid-row-m-13 { + grid-row: span 13/span 13; + } + .grid-columns-m-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-m-14 { + grid-column: span 14/span 14; + } + .grid-row-m-14 { + grid-row: span 14/span 14; + } + .grid-columns-m-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-m-15 { + grid-column: span 15/span 15; + } + .grid-row-m-15 { + grid-row: span 15/span 15; + } + .grid-columns-m-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-m-16 { + grid-column: span 16/span 16; + } + .grid-row-m-16 { + grid-row: span 16/span 16; + } + .grid-columns-m-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-m-17 { + grid-column: span 17/span 17; + } + .grid-row-m-17 { + grid-row: span 17/span 17; + } + .grid-columns-m-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-m-18 { + grid-column: span 18/span 18; + } + .grid-row-m-18 { + grid-row: span 18/span 18; + } + .grid-columns-m-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-m-19 { + grid-column: span 19/span 19; + } + .grid-row-m-19 { + grid-row: span 19/span 19; + } + .grid-columns-m-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-m-20 { + grid-column: span 20/span 20; + } + .grid-row-m-20 { + grid-row: span 20/span 20; + } + .grid-columns-m-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-m-21 { + grid-column: span 21/span 21; + } + .grid-row-m-21 { + grid-row: span 21/span 21; + } + .grid-columns-m-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-m-22 { + grid-column: span 22/span 22; + } + .grid-row-m-22 { + grid-row: span 22/span 22; + } + .grid-columns-m-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-m-23 { + grid-column: span 23/span 23; + } + .grid-row-m-23 { + grid-row: span 23/span 23; + } + .grid-columns-m-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-m-24 { + grid-column: span 24/span 24; + } + .grid-row-m-24 { + grid-row: span 24/span 24; + } +} + +@media only screen and (min-width: 480px) { + .grid-columns-p-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-p-1 { + grid-column: span 1/span 1; + } + .grid-row-p-1 { + grid-row: span 1/span 1; + } + .grid-columns-p-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-p-2 { + grid-column: span 2/span 2; + } + .grid-row-p-2 { + grid-row: span 2/span 2; + } + .grid-columns-p-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-p-3 { + grid-column: span 3/span 3; + } + .grid-row-p-3 { + grid-row: span 3/span 3; + } + .grid-columns-p-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-p-4 { + grid-column: span 4/span 4; + } + .grid-row-p-4 { + grid-row: span 4/span 4; + } + .grid-columns-p-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-p-5 { + grid-column: span 5/span 5; + } + .grid-row-p-5 { + grid-row: span 5/span 5; + } + .grid-columns-p-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-p-6 { + grid-column: span 6/span 6; + } + .grid-row-p-6 { + grid-row: span 6/span 6; + } + .grid-columns-p-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-p-7 { + grid-column: span 7/span 7; + } + .grid-row-p-7 { + grid-row: span 7/span 7; + } + .grid-columns-p-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-p-8 { + grid-column: span 8/span 8; + } + .grid-row-p-8 { + grid-row: span 8/span 8; + } + .grid-columns-p-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-p-9 { + grid-column: span 9/span 9; + } + .grid-row-p-9 { + grid-row: span 9/span 9; + } + .grid-columns-p-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-p-10 { + grid-column: span 10/span 10; + } + .grid-row-p-10 { + grid-row: span 10/span 10; + } + .grid-columns-p-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-p-11 { + grid-column: span 11/span 11; + } + .grid-row-p-11 { + grid-row: span 11/span 11; + } + .grid-columns-p-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-p-12 { + grid-column: span 12/span 12; + } + .grid-row-p-12 { + grid-row: span 12/span 12; + } + .grid-columns-p-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-p-13 { + grid-column: span 13/span 13; + } + .grid-row-p-13 { + grid-row: span 13/span 13; + } + .grid-columns-p-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-p-14 { + grid-column: span 14/span 14; + } + .grid-row-p-14 { + grid-row: span 14/span 14; + } + .grid-columns-p-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-p-15 { + grid-column: span 15/span 15; + } + .grid-row-p-15 { + grid-row: span 15/span 15; + } + .grid-columns-p-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-p-16 { + grid-column: span 16/span 16; + } + .grid-row-p-16 { + grid-row: span 16/span 16; + } + .grid-columns-p-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-p-17 { + grid-column: span 17/span 17; + } + .grid-row-p-17 { + grid-row: span 17/span 17; + } + .grid-columns-p-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-p-18 { + grid-column: span 18/span 18; + } + .grid-row-p-18 { + grid-row: span 18/span 18; + } + .grid-columns-p-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-p-19 { + grid-column: span 19/span 19; + } + .grid-row-p-19 { + grid-row: span 19/span 19; + } + .grid-columns-p-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-p-20 { + grid-column: span 20/span 20; + } + .grid-row-p-20 { + grid-row: span 20/span 20; + } + .grid-columns-p-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-p-21 { + grid-column: span 21/span 21; + } + .grid-row-p-21 { + grid-row: span 21/span 21; + } + .grid-columns-p-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-p-22 { + grid-column: span 22/span 22; + } + .grid-row-p-22 { + grid-row: span 22/span 22; + } + .grid-columns-p-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-p-23 { + grid-column: span 23/span 23; + } + .grid-row-p-23 { + grid-row: span 23/span 23; + } + .grid-columns-p-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-p-24 { + grid-column: span 24/span 24; + } + .grid-row-p-24 { + grid-row: span 24/span 24; + } +} + +@media only screen and (min-width: 768px) { + .grid-columns-xs-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-xs-1 { + grid-column: span 1/span 1; + } + .grid-row-xs-1 { + grid-row: span 1/span 1; + } + .grid-columns-xs-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-xs-2 { + grid-column: span 2/span 2; + } + .grid-row-xs-2 { + grid-row: span 2/span 2; + } + .grid-columns-xs-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-xs-3 { + grid-column: span 3/span 3; + } + .grid-row-xs-3 { + grid-row: span 3/span 3; + } + .grid-columns-xs-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-xs-4 { + grid-column: span 4/span 4; + } + .grid-row-xs-4 { + grid-row: span 4/span 4; + } + .grid-columns-xs-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-xs-5 { + grid-column: span 5/span 5; + } + .grid-row-xs-5 { + grid-row: span 5/span 5; + } + .grid-columns-xs-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-xs-6 { + grid-column: span 6/span 6; + } + .grid-row-xs-6 { + grid-row: span 6/span 6; + } + .grid-columns-xs-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-xs-7 { + grid-column: span 7/span 7; + } + .grid-row-xs-7 { + grid-row: span 7/span 7; + } + .grid-columns-xs-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-xs-8 { + grid-column: span 8/span 8; + } + .grid-row-xs-8 { + grid-row: span 8/span 8; + } + .grid-columns-xs-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-xs-9 { + grid-column: span 9/span 9; + } + .grid-row-xs-9 { + grid-row: span 9/span 9; + } + .grid-columns-xs-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-xs-10 { + grid-column: span 10/span 10; + } + .grid-row-xs-10 { + grid-row: span 10/span 10; + } + .grid-columns-xs-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-xs-11 { + grid-column: span 11/span 11; + } + .grid-row-xs-11 { + grid-row: span 11/span 11; + } + .grid-columns-xs-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-xs-12 { + grid-column: span 12/span 12; + } + .grid-row-xs-12 { + grid-row: span 12/span 12; + } + .grid-columns-xs-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-xs-13 { + grid-column: span 13/span 13; + } + .grid-row-xs-13 { + grid-row: span 13/span 13; + } + .grid-columns-xs-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-xs-14 { + grid-column: span 14/span 14; + } + .grid-row-xs-14 { + grid-row: span 14/span 14; + } + .grid-columns-xs-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-xs-15 { + grid-column: span 15/span 15; + } + .grid-row-xs-15 { + grid-row: span 15/span 15; + } + .grid-columns-xs-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-xs-16 { + grid-column: span 16/span 16; + } + .grid-row-xs-16 { + grid-row: span 16/span 16; + } + .grid-columns-xs-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-xs-17 { + grid-column: span 17/span 17; + } + .grid-row-xs-17 { + grid-row: span 17/span 17; + } + .grid-columns-xs-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-xs-18 { + grid-column: span 18/span 18; + } + .grid-row-xs-18 { + grid-row: span 18/span 18; + } + .grid-columns-xs-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-xs-19 { + grid-column: span 19/span 19; + } + .grid-row-xs-19 { + grid-row: span 19/span 19; + } + .grid-columns-xs-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-xs-20 { + grid-column: span 20/span 20; + } + .grid-row-xs-20 { + grid-row: span 20/span 20; + } + .grid-columns-xs-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-xs-21 { + grid-column: span 21/span 21; + } + .grid-row-xs-21 { + grid-row: span 21/span 21; + } + .grid-columns-xs-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-xs-22 { + grid-column: span 22/span 22; + } + .grid-row-xs-22 { + grid-row: span 22/span 22; + } + .grid-columns-xs-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-xs-23 { + grid-column: span 23/span 23; + } + .grid-row-xs-23 { + grid-row: span 23/span 23; + } + .grid-columns-xs-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-xs-24 { + grid-column: span 24/span 24; + } + .grid-row-xs-24 { + grid-row: span 24/span 24; + } +} + +@media only screen and (min-width: 960px) { + .grid-columns-sm-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-sm-1 { + grid-column: span 1/span 1; + } + .grid-row-sm-1 { + grid-row: span 1/span 1; + } + .grid-columns-sm-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-sm-2 { + grid-column: span 2/span 2; + } + .grid-row-sm-2 { + grid-row: span 2/span 2; + } + .grid-columns-sm-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-sm-3 { + grid-column: span 3/span 3; + } + .grid-row-sm-3 { + grid-row: span 3/span 3; + } + .grid-columns-sm-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-sm-4 { + grid-column: span 4/span 4; + } + .grid-row-sm-4 { + grid-row: span 4/span 4; + } + .grid-columns-sm-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-sm-5 { + grid-column: span 5/span 5; + } + .grid-row-sm-5 { + grid-row: span 5/span 5; + } + .grid-columns-sm-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-sm-6 { + grid-column: span 6/span 6; + } + .grid-row-sm-6 { + grid-row: span 6/span 6; + } + .grid-columns-sm-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-sm-7 { + grid-column: span 7/span 7; + } + .grid-row-sm-7 { + grid-row: span 7/span 7; + } + .grid-columns-sm-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-sm-8 { + grid-column: span 8/span 8; + } + .grid-row-sm-8 { + grid-row: span 8/span 8; + } + .grid-columns-sm-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-sm-9 { + grid-column: span 9/span 9; + } + .grid-row-sm-9 { + grid-row: span 9/span 9; + } + .grid-columns-sm-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-sm-10 { + grid-column: span 10/span 10; + } + .grid-row-sm-10 { + grid-row: span 10/span 10; + } + .grid-columns-sm-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-sm-11 { + grid-column: span 11/span 11; + } + .grid-row-sm-11 { + grid-row: span 11/span 11; + } + .grid-columns-sm-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-sm-12 { + grid-column: span 12/span 12; + } + .grid-row-sm-12 { + grid-row: span 12/span 12; + } + .grid-columns-sm-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-sm-13 { + grid-column: span 13/span 13; + } + .grid-row-sm-13 { + grid-row: span 13/span 13; + } + .grid-columns-sm-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-sm-14 { + grid-column: span 14/span 14; + } + .grid-row-sm-14 { + grid-row: span 14/span 14; + } + .grid-columns-sm-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-sm-15 { + grid-column: span 15/span 15; + } + .grid-row-sm-15 { + grid-row: span 15/span 15; + } + .grid-columns-sm-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-sm-16 { + grid-column: span 16/span 16; + } + .grid-row-sm-16 { + grid-row: span 16/span 16; + } + .grid-columns-sm-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-sm-17 { + grid-column: span 17/span 17; + } + .grid-row-sm-17 { + grid-row: span 17/span 17; + } + .grid-columns-sm-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-sm-18 { + grid-column: span 18/span 18; + } + .grid-row-sm-18 { + grid-row: span 18/span 18; + } + .grid-columns-sm-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-sm-19 { + grid-column: span 19/span 19; + } + .grid-row-sm-19 { + grid-row: span 19/span 19; + } + .grid-columns-sm-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-sm-20 { + grid-column: span 20/span 20; + } + .grid-row-sm-20 { + grid-row: span 20/span 20; + } + .grid-columns-sm-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-sm-21 { + grid-column: span 21/span 21; + } + .grid-row-sm-21 { + grid-row: span 21/span 21; + } + .grid-columns-sm-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-sm-22 { + grid-column: span 22/span 22; + } + .grid-row-sm-22 { + grid-row: span 22/span 22; + } + .grid-columns-sm-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-sm-23 { + grid-column: span 23/span 23; + } + .grid-row-sm-23 { + grid-row: span 23/span 23; + } + .grid-columns-sm-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-sm-24 { + grid-column: span 24/span 24; + } + .grid-row-sm-24 { + grid-row: span 24/span 24; + } +} + +@media only screen and (min-width: 1280px) { + .grid-columns-md-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-md-1 { + grid-column: span 1/span 1; + } + .grid-row-md-1 { + grid-row: span 1/span 1; + } + .grid-columns-md-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-md-2 { + grid-column: span 2/span 2; + } + .grid-row-md-2 { + grid-row: span 2/span 2; + } + .grid-columns-md-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-md-3 { + grid-column: span 3/span 3; + } + .grid-row-md-3 { + grid-row: span 3/span 3; + } + .grid-columns-md-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-md-4 { + grid-column: span 4/span 4; + } + .grid-row-md-4 { + grid-row: span 4/span 4; + } + .grid-columns-md-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-md-5 { + grid-column: span 5/span 5; + } + .grid-row-md-5 { + grid-row: span 5/span 5; + } + .grid-columns-md-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-md-6 { + grid-column: span 6/span 6; + } + .grid-row-md-6 { + grid-row: span 6/span 6; + } + .grid-columns-md-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-md-7 { + grid-column: span 7/span 7; + } + .grid-row-md-7 { + grid-row: span 7/span 7; + } + .grid-columns-md-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-md-8 { + grid-column: span 8/span 8; + } + .grid-row-md-8 { + grid-row: span 8/span 8; + } + .grid-columns-md-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-md-9 { + grid-column: span 9/span 9; + } + .grid-row-md-9 { + grid-row: span 9/span 9; + } + .grid-columns-md-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-md-10 { + grid-column: span 10/span 10; + } + .grid-row-md-10 { + grid-row: span 10/span 10; + } + .grid-columns-md-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-md-11 { + grid-column: span 11/span 11; + } + .grid-row-md-11 { + grid-row: span 11/span 11; + } + .grid-columns-md-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-md-12 { + grid-column: span 12/span 12; + } + .grid-row-md-12 { + grid-row: span 12/span 12; + } + .grid-columns-md-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-md-13 { + grid-column: span 13/span 13; + } + .grid-row-md-13 { + grid-row: span 13/span 13; + } + .grid-columns-md-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-md-14 { + grid-column: span 14/span 14; + } + .grid-row-md-14 { + grid-row: span 14/span 14; + } + .grid-columns-md-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-md-15 { + grid-column: span 15/span 15; + } + .grid-row-md-15 { + grid-row: span 15/span 15; + } + .grid-columns-md-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-md-16 { + grid-column: span 16/span 16; + } + .grid-row-md-16 { + grid-row: span 16/span 16; + } + .grid-columns-md-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-md-17 { + grid-column: span 17/span 17; + } + .grid-row-md-17 { + grid-row: span 17/span 17; + } + .grid-columns-md-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-md-18 { + grid-column: span 18/span 18; + } + .grid-row-md-18 { + grid-row: span 18/span 18; + } + .grid-columns-md-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-md-19 { + grid-column: span 19/span 19; + } + .grid-row-md-19 { + grid-row: span 19/span 19; + } + .grid-columns-md-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-md-20 { + grid-column: span 20/span 20; + } + .grid-row-md-20 { + grid-row: span 20/span 20; + } + .grid-columns-md-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-md-21 { + grid-column: span 21/span 21; + } + .grid-row-md-21 { + grid-row: span 21/span 21; + } + .grid-columns-md-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-md-22 { + grid-column: span 22/span 22; + } + .grid-row-md-22 { + grid-row: span 22/span 22; + } + .grid-columns-md-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-md-23 { + grid-column: span 23/span 23; + } + .grid-row-md-23 { + grid-row: span 23/span 23; + } + .grid-columns-md-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-md-24 { + grid-column: span 24/span 24; + } + .grid-row-md-24 { + grid-row: span 24/span 24; + } +} + +@media only screen and (min-width: 1440px) { + .grid-columns-lg-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-lg-1 { + grid-column: span 1/span 1; + } + .grid-row-lg-1 { + grid-row: span 1/span 1; + } + .grid-columns-lg-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-lg-2 { + grid-column: span 2/span 2; + } + .grid-row-lg-2 { + grid-row: span 2/span 2; + } + .grid-columns-lg-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-lg-3 { + grid-column: span 3/span 3; + } + .grid-row-lg-3 { + grid-row: span 3/span 3; + } + .grid-columns-lg-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-lg-4 { + grid-column: span 4/span 4; + } + .grid-row-lg-4 { + grid-row: span 4/span 4; + } + .grid-columns-lg-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-lg-5 { + grid-column: span 5/span 5; + } + .grid-row-lg-5 { + grid-row: span 5/span 5; + } + .grid-columns-lg-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-lg-6 { + grid-column: span 6/span 6; + } + .grid-row-lg-6 { + grid-row: span 6/span 6; + } + .grid-columns-lg-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-lg-7 { + grid-column: span 7/span 7; + } + .grid-row-lg-7 { + grid-row: span 7/span 7; + } + .grid-columns-lg-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-lg-8 { + grid-column: span 8/span 8; + } + .grid-row-lg-8 { + grid-row: span 8/span 8; + } + .grid-columns-lg-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-lg-9 { + grid-column: span 9/span 9; + } + .grid-row-lg-9 { + grid-row: span 9/span 9; + } + .grid-columns-lg-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-lg-10 { + grid-column: span 10/span 10; + } + .grid-row-lg-10 { + grid-row: span 10/span 10; + } + .grid-columns-lg-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-lg-11 { + grid-column: span 11/span 11; + } + .grid-row-lg-11 { + grid-row: span 11/span 11; + } + .grid-columns-lg-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-lg-12 { + grid-column: span 12/span 12; + } + .grid-row-lg-12 { + grid-row: span 12/span 12; + } + .grid-columns-lg-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-lg-13 { + grid-column: span 13/span 13; + } + .grid-row-lg-13 { + grid-row: span 13/span 13; + } + .grid-columns-lg-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-lg-14 { + grid-column: span 14/span 14; + } + .grid-row-lg-14 { + grid-row: span 14/span 14; + } + .grid-columns-lg-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-lg-15 { + grid-column: span 15/span 15; + } + .grid-row-lg-15 { + grid-row: span 15/span 15; + } + .grid-columns-lg-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-lg-16 { + grid-column: span 16/span 16; + } + .grid-row-lg-16 { + grid-row: span 16/span 16; + } + .grid-columns-lg-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-lg-17 { + grid-column: span 17/span 17; + } + .grid-row-lg-17 { + grid-row: span 17/span 17; + } + .grid-columns-lg-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-lg-18 { + grid-column: span 18/span 18; + } + .grid-row-lg-18 { + grid-row: span 18/span 18; + } + .grid-columns-lg-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-lg-19 { + grid-column: span 19/span 19; + } + .grid-row-lg-19 { + grid-row: span 19/span 19; + } + .grid-columns-lg-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-lg-20 { + grid-column: span 20/span 20; + } + .grid-row-lg-20 { + grid-row: span 20/span 20; + } + .grid-columns-lg-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-lg-21 { + grid-column: span 21/span 21; + } + .grid-row-lg-21 { + grid-row: span 21/span 21; + } + .grid-columns-lg-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-lg-22 { + grid-column: span 22/span 22; + } + .grid-row-lg-22 { + grid-row: span 22/span 22; + } + .grid-columns-lg-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-lg-23 { + grid-column: span 23/span 23; + } + .grid-row-lg-23 { + grid-row: span 23/span 23; + } + .grid-columns-lg-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-lg-24 { + grid-column: span 24/span 24; + } + .grid-row-lg-24 { + grid-row: span 24/span 24; + } +} + +@media only screen and (min-width: 1920px) { + .grid-columns-xl-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-xl-1 { + grid-column: span 1/span 1; + } + .grid-row-xl-1 { + grid-row: span 1/span 1; + } + .grid-columns-xl-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-xl-2 { + grid-column: span 2/span 2; + } + .grid-row-xl-2 { + grid-row: span 2/span 2; + } + .grid-columns-xl-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-xl-3 { + grid-column: span 3/span 3; + } + .grid-row-xl-3 { + grid-row: span 3/span 3; + } + .grid-columns-xl-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-xl-4 { + grid-column: span 4/span 4; + } + .grid-row-xl-4 { + grid-row: span 4/span 4; + } + .grid-columns-xl-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-xl-5 { + grid-column: span 5/span 5; + } + .grid-row-xl-5 { + grid-row: span 5/span 5; + } + .grid-columns-xl-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-xl-6 { + grid-column: span 6/span 6; + } + .grid-row-xl-6 { + grid-row: span 6/span 6; + } + .grid-columns-xl-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-xl-7 { + grid-column: span 7/span 7; + } + .grid-row-xl-7 { + grid-row: span 7/span 7; + } + .grid-columns-xl-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-xl-8 { + grid-column: span 8/span 8; + } + .grid-row-xl-8 { + grid-row: span 8/span 8; + } + .grid-columns-xl-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-xl-9 { + grid-column: span 9/span 9; + } + .grid-row-xl-9 { + grid-row: span 9/span 9; + } + .grid-columns-xl-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-xl-10 { + grid-column: span 10/span 10; + } + .grid-row-xl-10 { + grid-row: span 10/span 10; + } + .grid-columns-xl-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-xl-11 { + grid-column: span 11/span 11; + } + .grid-row-xl-11 { + grid-row: span 11/span 11; + } + .grid-columns-xl-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-xl-12 { + grid-column: span 12/span 12; + } + .grid-row-xl-12 { + grid-row: span 12/span 12; + } + .grid-columns-xl-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-xl-13 { + grid-column: span 13/span 13; + } + .grid-row-xl-13 { + grid-row: span 13/span 13; + } + .grid-columns-xl-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-xl-14 { + grid-column: span 14/span 14; + } + .grid-row-xl-14 { + grid-row: span 14/span 14; + } + .grid-columns-xl-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-xl-15 { + grid-column: span 15/span 15; + } + .grid-row-xl-15 { + grid-row: span 15/span 15; + } + .grid-columns-xl-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-xl-16 { + grid-column: span 16/span 16; + } + .grid-row-xl-16 { + grid-row: span 16/span 16; + } + .grid-columns-xl-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-xl-17 { + grid-column: span 17/span 17; + } + .grid-row-xl-17 { + grid-row: span 17/span 17; + } + .grid-columns-xl-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-xl-18 { + grid-column: span 18/span 18; + } + .grid-row-xl-18 { + grid-row: span 18/span 18; + } + .grid-columns-xl-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-xl-19 { + grid-column: span 19/span 19; + } + .grid-row-xl-19 { + grid-row: span 19/span 19; + } + .grid-columns-xl-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-xl-20 { + grid-column: span 20/span 20; + } + .grid-row-xl-20 { + grid-row: span 20/span 20; + } + .grid-columns-xl-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-xl-21 { + grid-column: span 21/span 21; + } + .grid-row-xl-21 { + grid-row: span 21/span 21; + } + .grid-columns-xl-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-xl-22 { + grid-column: span 22/span 22; + } + .grid-row-xl-22 { + grid-row: span 22/span 22; + } + .grid-columns-xl-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-xl-23 { + grid-column: span 23/span 23; + } + .grid-row-xl-23 { + grid-row: span 23/span 23; + } + .grid-columns-xl-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-xl-24 { + grid-column: span 24/span 24; + } + .grid-row-xl-24 { + grid-row: span 24/span 24; + } +} + +@media only screen and (min-width: 2560px) { + .grid-columns-xxl-1 { + -ms-grid-columns: (minmax(0px, 1fr))[1]; + grid-template-columns: repeat(1, minmax(0px, 1fr)); + } + .grid-column-xxl-1 { + grid-column: span 1/span 1; + } + .grid-row-xxl-1 { + grid-row: span 1/span 1; + } + .grid-columns-xxl-2 { + -ms-grid-columns: (minmax(0px, 1fr))[2]; + grid-template-columns: repeat(2, minmax(0px, 1fr)); + } + .grid-column-xxl-2 { + grid-column: span 2/span 2; + } + .grid-row-xxl-2 { + grid-row: span 2/span 2; + } + .grid-columns-xxl-3 { + -ms-grid-columns: (minmax(0px, 1fr))[3]; + grid-template-columns: repeat(3, minmax(0px, 1fr)); + } + .grid-column-xxl-3 { + grid-column: span 3/span 3; + } + .grid-row-xxl-3 { + grid-row: span 3/span 3; + } + .grid-columns-xxl-4 { + -ms-grid-columns: (minmax(0px, 1fr))[4]; + grid-template-columns: repeat(4, minmax(0px, 1fr)); + } + .grid-column-xxl-4 { + grid-column: span 4/span 4; + } + .grid-row-xxl-4 { + grid-row: span 4/span 4; + } + .grid-columns-xxl-5 { + -ms-grid-columns: (minmax(0px, 1fr))[5]; + grid-template-columns: repeat(5, minmax(0px, 1fr)); + } + .grid-column-xxl-5 { + grid-column: span 5/span 5; + } + .grid-row-xxl-5 { + grid-row: span 5/span 5; + } + .grid-columns-xxl-6 { + -ms-grid-columns: (minmax(0px, 1fr))[6]; + grid-template-columns: repeat(6, minmax(0px, 1fr)); + } + .grid-column-xxl-6 { + grid-column: span 6/span 6; + } + .grid-row-xxl-6 { + grid-row: span 6/span 6; + } + .grid-columns-xxl-7 { + -ms-grid-columns: (minmax(0px, 1fr))[7]; + grid-template-columns: repeat(7, minmax(0px, 1fr)); + } + .grid-column-xxl-7 { + grid-column: span 7/span 7; + } + .grid-row-xxl-7 { + grid-row: span 7/span 7; + } + .grid-columns-xxl-8 { + -ms-grid-columns: (minmax(0px, 1fr))[8]; + grid-template-columns: repeat(8, minmax(0px, 1fr)); + } + .grid-column-xxl-8 { + grid-column: span 8/span 8; + } + .grid-row-xxl-8 { + grid-row: span 8/span 8; + } + .grid-columns-xxl-9 { + -ms-grid-columns: (minmax(0px, 1fr))[9]; + grid-template-columns: repeat(9, minmax(0px, 1fr)); + } + .grid-column-xxl-9 { + grid-column: span 9/span 9; + } + .grid-row-xxl-9 { + grid-row: span 9/span 9; + } + .grid-columns-xxl-10 { + -ms-grid-columns: (minmax(0px, 1fr))[10]; + grid-template-columns: repeat(10, minmax(0px, 1fr)); + } + .grid-column-xxl-10 { + grid-column: span 10/span 10; + } + .grid-row-xxl-10 { + grid-row: span 10/span 10; + } + .grid-columns-xxl-11 { + -ms-grid-columns: (minmax(0px, 1fr))[11]; + grid-template-columns: repeat(11, minmax(0px, 1fr)); + } + .grid-column-xxl-11 { + grid-column: span 11/span 11; + } + .grid-row-xxl-11 { + grid-row: span 11/span 11; + } + .grid-columns-xxl-12 { + -ms-grid-columns: (minmax(0px, 1fr))[12]; + grid-template-columns: repeat(12, minmax(0px, 1fr)); + } + .grid-column-xxl-12 { + grid-column: span 12/span 12; + } + .grid-row-xxl-12 { + grid-row: span 12/span 12; + } + .grid-columns-xxl-13 { + -ms-grid-columns: (minmax(0px, 1fr))[13]; + grid-template-columns: repeat(13, minmax(0px, 1fr)); + } + .grid-column-xxl-13 { + grid-column: span 13/span 13; + } + .grid-row-xxl-13 { + grid-row: span 13/span 13; + } + .grid-columns-xxl-14 { + -ms-grid-columns: (minmax(0px, 1fr))[14]; + grid-template-columns: repeat(14, minmax(0px, 1fr)); + } + .grid-column-xxl-14 { + grid-column: span 14/span 14; + } + .grid-row-xxl-14 { + grid-row: span 14/span 14; + } + .grid-columns-xxl-15 { + -ms-grid-columns: (minmax(0px, 1fr))[15]; + grid-template-columns: repeat(15, minmax(0px, 1fr)); + } + .grid-column-xxl-15 { + grid-column: span 15/span 15; + } + .grid-row-xxl-15 { + grid-row: span 15/span 15; + } + .grid-columns-xxl-16 { + -ms-grid-columns: (minmax(0px, 1fr))[16]; + grid-template-columns: repeat(16, minmax(0px, 1fr)); + } + .grid-column-xxl-16 { + grid-column: span 16/span 16; + } + .grid-row-xxl-16 { + grid-row: span 16/span 16; + } + .grid-columns-xxl-17 { + -ms-grid-columns: (minmax(0px, 1fr))[17]; + grid-template-columns: repeat(17, minmax(0px, 1fr)); + } + .grid-column-xxl-17 { + grid-column: span 17/span 17; + } + .grid-row-xxl-17 { + grid-row: span 17/span 17; + } + .grid-columns-xxl-18 { + -ms-grid-columns: (minmax(0px, 1fr))[18]; + grid-template-columns: repeat(18, minmax(0px, 1fr)); + } + .grid-column-xxl-18 { + grid-column: span 18/span 18; + } + .grid-row-xxl-18 { + grid-row: span 18/span 18; + } + .grid-columns-xxl-19 { + -ms-grid-columns: (minmax(0px, 1fr))[19]; + grid-template-columns: repeat(19, minmax(0px, 1fr)); + } + .grid-column-xxl-19 { + grid-column: span 19/span 19; + } + .grid-row-xxl-19 { + grid-row: span 19/span 19; + } + .grid-columns-xxl-20 { + -ms-grid-columns: (minmax(0px, 1fr))[20]; + grid-template-columns: repeat(20, minmax(0px, 1fr)); + } + .grid-column-xxl-20 { + grid-column: span 20/span 20; + } + .grid-row-xxl-20 { + grid-row: span 20/span 20; + } + .grid-columns-xxl-21 { + -ms-grid-columns: (minmax(0px, 1fr))[21]; + grid-template-columns: repeat(21, minmax(0px, 1fr)); + } + .grid-column-xxl-21 { + grid-column: span 21/span 21; + } + .grid-row-xxl-21 { + grid-row: span 21/span 21; + } + .grid-columns-xxl-22 { + -ms-grid-columns: (minmax(0px, 1fr))[22]; + grid-template-columns: repeat(22, minmax(0px, 1fr)); + } + .grid-column-xxl-22 { + grid-column: span 22/span 22; + } + .grid-row-xxl-22 { + grid-row: span 22/span 22; + } + .grid-columns-xxl-23 { + -ms-grid-columns: (minmax(0px, 1fr))[23]; + grid-template-columns: repeat(23, minmax(0px, 1fr)); + } + .grid-column-xxl-23 { + grid-column: span 23/span 23; + } + .grid-row-xxl-23 { + grid-row: span 23/span 23; + } + .grid-columns-xxl-24 { + -ms-grid-columns: (minmax(0px, 1fr))[24]; + grid-template-columns: repeat(24, minmax(0px, 1fr)); + } + .grid-column-xxl-24 { + grid-column: span 24/span 24; + } + .grid-row-xxl-24 { + grid-row: span 24/span 24; + } +} + +.position-relative { + position: relative; +} + +.position-absolute { + position: absolute; +} + +.position-fixed { + position: fixed; +} + +.position-sticky { + position: -webkit-sticky; + position: sticky; +} + +.vh-5 { + height: 5vh; +} + +.vw-5 { + width: 5vw; +} + +.h-5 { + height: 5%; +} + +.w-5 { + width: 5%; +} + +.min-h-5 { + min-height: 5%; +} + +.min-w-5 { + min-width: 5%; +} + +.min-vh-5 { + min-height: 5vh; +} + +.min-vw-5 { + min-width: 5vw; +} + +.max-h-5 { + max-height: 5%; +} + +.max-w-5 { + max-width: 5%; +} + +.max-vh-5 { + max-height: 5vh; +} + +.max-vw-5 { + max-width: 5vw; +} + +.vh-10 { + height: 10vh; +} + +.vw-10 { + width: 10vw; +} + +.h-10 { + height: 10%; +} + +.w-10 { + width: 10%; +} + +.min-h-10 { + min-height: 10%; +} + +.min-w-10 { + min-width: 10%; +} + +.min-vh-10 { + min-height: 10vh; +} + +.min-vw-10 { + min-width: 10vw; +} + +.max-h-10 { + max-height: 10%; +} + +.max-w-10 { + max-width: 10%; +} + +.max-vh-10 { + max-height: 10vh; +} + +.max-vw-10 { + max-width: 10vw; +} + +.vh-15 { + height: 15vh; +} + +.vw-15 { + width: 15vw; +} + +.h-15 { + height: 15%; +} + +.w-15 { + width: 15%; +} + +.min-h-15 { + min-height: 15%; +} + +.min-w-15 { + min-width: 15%; +} + +.min-vh-15 { + min-height: 15vh; +} + +.min-vw-15 { + min-width: 15vw; +} + +.max-h-15 { + max-height: 15%; +} + +.max-w-15 { + max-width: 15%; +} + +.max-vh-15 { + max-height: 15vh; +} + +.max-vw-15 { + max-width: 15vw; +} + +.vh-20 { + height: 20vh; +} + +.vw-20 { + width: 20vw; +} + +.h-20 { + height: 20%; +} + +.w-20 { + width: 20%; +} + +.min-h-20 { + min-height: 20%; +} + +.min-w-20 { + min-width: 20%; +} + +.min-vh-20 { + min-height: 20vh; +} + +.min-vw-20 { + min-width: 20vw; +} + +.max-h-20 { + max-height: 20%; +} + +.max-w-20 { + max-width: 20%; +} + +.max-vh-20 { + max-height: 20vh; +} + +.max-vw-20 { + max-width: 20vw; +} + +.vh-25 { + height: 25vh; +} + +.vw-25 { + width: 25vw; +} + +.h-25 { + height: 25%; +} + +.w-25 { + width: 25%; +} + +.min-h-25 { + min-height: 25%; +} + +.min-w-25 { + min-width: 25%; +} + +.min-vh-25 { + min-height: 25vh; +} + +.min-vw-25 { + min-width: 25vw; +} + +.max-h-25 { + max-height: 25%; +} + +.max-w-25 { + max-width: 25%; +} + +.max-vh-25 { + max-height: 25vh; +} + +.max-vw-25 { + max-width: 25vw; +} + +.vh-30 { + height: 30vh; +} + +.vw-30 { + width: 30vw; +} + +.h-30 { + height: 30%; +} + +.w-30 { + width: 30%; +} + +.min-h-30 { + min-height: 30%; +} + +.min-w-30 { + min-width: 30%; +} + +.min-vh-30 { + min-height: 30vh; +} + +.min-vw-30 { + min-width: 30vw; +} + +.max-h-30 { + max-height: 30%; +} + +.max-w-30 { + max-width: 30%; +} + +.max-vh-30 { + max-height: 30vh; +} + +.max-vw-30 { + max-width: 30vw; +} + +.vh-35 { + height: 35vh; +} + +.vw-35 { + width: 35vw; +} + +.h-35 { + height: 35%; +} + +.w-35 { + width: 35%; +} + +.min-h-35 { + min-height: 35%; +} + +.min-w-35 { + min-width: 35%; +} + +.min-vh-35 { + min-height: 35vh; +} + +.min-vw-35 { + min-width: 35vw; +} + +.max-h-35 { + max-height: 35%; +} + +.max-w-35 { + max-width: 35%; +} + +.max-vh-35 { + max-height: 35vh; +} + +.max-vw-35 { + max-width: 35vw; +} + +.vh-40 { + height: 40vh; +} + +.vw-40 { + width: 40vw; +} + +.h-40 { + height: 40%; +} + +.w-40 { + width: 40%; +} + +.min-h-40 { + min-height: 40%; +} + +.min-w-40 { + min-width: 40%; +} + +.min-vh-40 { + min-height: 40vh; +} + +.min-vw-40 { + min-width: 40vw; +} + +.max-h-40 { + max-height: 40%; +} + +.max-w-40 { + max-width: 40%; +} + +.max-vh-40 { + max-height: 40vh; +} + +.max-vw-40 { + max-width: 40vw; +} + +.vh-45 { + height: 45vh; +} + +.vw-45 { + width: 45vw; +} + +.h-45 { + height: 45%; +} + +.w-45 { + width: 45%; +} + +.min-h-45 { + min-height: 45%; +} + +.min-w-45 { + min-width: 45%; +} + +.min-vh-45 { + min-height: 45vh; +} + +.min-vw-45 { + min-width: 45vw; +} + +.max-h-45 { + max-height: 45%; +} + +.max-w-45 { + max-width: 45%; +} + +.max-vh-45 { + max-height: 45vh; +} + +.max-vw-45 { + max-width: 45vw; +} + +.vh-50 { + height: 50vh; +} + +.vw-50 { + width: 50vw; +} + +.h-50 { + height: 50%; +} + +.w-50 { + width: 50%; +} + +.min-h-50 { + min-height: 50%; +} + +.min-w-50 { + min-width: 50%; +} + +.min-vh-50 { + min-height: 50vh; +} + +.min-vw-50 { + min-width: 50vw; +} + +.max-h-50 { + max-height: 50%; +} + +.max-w-50 { + max-width: 50%; +} + +.max-vh-50 { + max-height: 50vh; +} + +.max-vw-50 { + max-width: 50vw; +} + +.vh-55 { + height: 55vh; +} + +.vw-55 { + width: 55vw; +} + +.h-55 { + height: 55%; +} + +.w-55 { + width: 55%; +} + +.min-h-55 { + min-height: 55%; +} + +.min-w-55 { + min-width: 55%; +} + +.min-vh-55 { + min-height: 55vh; +} + +.min-vw-55 { + min-width: 55vw; +} + +.max-h-55 { + max-height: 55%; +} + +.max-w-55 { + max-width: 55%; +} + +.max-vh-55 { + max-height: 55vh; +} + +.max-vw-55 { + max-width: 55vw; +} + +.vh-60 { + height: 60vh; +} + +.vw-60 { + width: 60vw; +} + +.h-60 { + height: 60%; +} + +.w-60 { + width: 60%; +} + +.min-h-60 { + min-height: 60%; +} + +.min-w-60 { + min-width: 60%; +} + +.min-vh-60 { + min-height: 60vh; +} + +.min-vw-60 { + min-width: 60vw; +} + +.max-h-60 { + max-height: 60%; +} + +.max-w-60 { + max-width: 60%; +} + +.max-vh-60 { + max-height: 60vh; +} + +.max-vw-60 { + max-width: 60vw; +} + +.vh-65 { + height: 65vh; +} + +.vw-65 { + width: 65vw; +} + +.h-65 { + height: 65%; +} + +.w-65 { + width: 65%; +} + +.min-h-65 { + min-height: 65%; +} + +.min-w-65 { + min-width: 65%; +} + +.min-vh-65 { + min-height: 65vh; +} + +.min-vw-65 { + min-width: 65vw; +} + +.max-h-65 { + max-height: 65%; +} + +.max-w-65 { + max-width: 65%; +} + +.max-vh-65 { + max-height: 65vh; +} + +.max-vw-65 { + max-width: 65vw; +} + +.vh-70 { + height: 70vh; +} + +.vw-70 { + width: 70vw; +} + +.h-70 { + height: 70%; +} + +.w-70 { + width: 70%; +} + +.min-h-70 { + min-height: 70%; +} + +.min-w-70 { + min-width: 70%; +} + +.min-vh-70 { + min-height: 70vh; +} + +.min-vw-70 { + min-width: 70vw; +} + +.max-h-70 { + max-height: 70%; +} + +.max-w-70 { + max-width: 70%; +} + +.max-vh-70 { + max-height: 70vh; +} + +.max-vw-70 { + max-width: 70vw; +} + +.vh-75 { + height: 75vh; +} + +.vw-75 { + width: 75vw; +} + +.h-75 { + height: 75%; +} + +.w-75 { + width: 75%; +} + +.min-h-75 { + min-height: 75%; +} + +.min-w-75 { + min-width: 75%; +} + +.min-vh-75 { + min-height: 75vh; +} + +.min-vw-75 { + min-width: 75vw; +} + +.max-h-75 { + max-height: 75%; +} + +.max-w-75 { + max-width: 75%; +} + +.max-vh-75 { + max-height: 75vh; +} + +.max-vw-75 { + max-width: 75vw; +} + +.vh-80 { + height: 80vh; +} + +.vw-80 { + width: 80vw; +} + +.h-80 { + height: 80%; +} + +.w-80 { + width: 80%; +} + +.min-h-80 { + min-height: 80%; +} + +.min-w-80 { + min-width: 80%; +} + +.min-vh-80 { + min-height: 80vh; +} + +.min-vw-80 { + min-width: 80vw; +} + +.max-h-80 { + max-height: 80%; +} + +.max-w-80 { + max-width: 80%; +} + +.max-vh-80 { + max-height: 80vh; +} + +.max-vw-80 { + max-width: 80vw; +} + +.vh-85 { + height: 85vh; +} + +.vw-85 { + width: 85vw; +} + +.h-85 { + height: 85%; +} + +.w-85 { + width: 85%; +} + +.min-h-85 { + min-height: 85%; +} + +.min-w-85 { + min-width: 85%; +} + +.min-vh-85 { + min-height: 85vh; +} + +.min-vw-85 { + min-width: 85vw; +} + +.max-h-85 { + max-height: 85%; +} + +.max-w-85 { + max-width: 85%; +} + +.max-vh-85 { + max-height: 85vh; +} + +.max-vw-85 { + max-width: 85vw; +} + +.vh-90 { + height: 90vh; +} + +.vw-90 { + width: 90vw; +} + +.h-90 { + height: 90%; +} + +.w-90 { + width: 90%; +} + +.min-h-90 { + min-height: 90%; +} + +.min-w-90 { + min-width: 90%; +} + +.min-vh-90 { + min-height: 90vh; +} + +.min-vw-90 { + min-width: 90vw; +} + +.max-h-90 { + max-height: 90%; +} + +.max-w-90 { + max-width: 90%; +} + +.max-vh-90 { + max-height: 90vh; +} + +.max-vw-90 { + max-width: 90vw; +} + +.vh-95 { + height: 95vh; +} + +.vw-95 { + width: 95vw; +} + +.h-95 { + height: 95%; +} + +.w-95 { + width: 95%; +} + +.min-h-95 { + min-height: 95%; +} + +.min-w-95 { + min-width: 95%; +} + +.min-vh-95 { + min-height: 95vh; +} + +.min-vw-95 { + min-width: 95vw; +} + +.max-h-95 { + max-height: 95%; +} + +.max-w-95 { + max-width: 95%; +} + +.max-vh-95 { + max-height: 95vh; +} + +.max-vw-95 { + max-width: 95vw; +} + +.vh-100 { + height: 100vh; +} + +.vw-100 { + width: 100vw; +} + +.h-100 { + height: 100%; +} + +.w-100 { + width: 100%; +} + +.min-h-100 { + min-height: 100%; +} + +.min-w-100 { + min-width: 100%; +} + +.min-vh-100 { + min-height: 100vh; +} + +.min-vw-100 { + min-width: 100vw; +} + +.max-h-100 { + max-height: 100%; +} + +.max-w-100 { + max-width: 100%; +} + +.max-vh-100 { + max-height: 100vh; +} + +.max-vw-100 { + max-width: 100vw; +} + +.bg { + background-color: var(--el-bg-color); +} + +.bg-filter { + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); +} + +.bg-mosaic, +.el-avatar.bg-mosaic, +#app .el-avatar { + --el-avatar-bg-color:#1E1E1E; + /* background: linear-gradient(45deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%), + linear-gradient(-45deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.05) 75%), + linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.05) 75%); + background-size: 20px 20px; + background-position: 0 0, 0 10px, 10px -10px, -10px 0px; */ +} + +.bg-white, +.hover-bg-white:hover { + background-color: var(--el-bg-color); +} + +.bg-primary, +.hover-bg-primary:hover { + background-color: var(--el-color-primary); +} + +.bg-primary-light-3, +.hover-bg-primary-light-3:hover { + background-color: var(--el-color-primary-light-3); +} + +.bg-primary-light-9, +.hover-bg-primary-light-9:hover { + background-color: var(--el-color-primary-light-9); +} + +.bg-success, +.hover-bg-success:hover { + background-color: var(--el-color-success); +} + +.bg-success-light-3, +.hover-bg-success-light-3:hover { + background-color: var(--el-color-success-light-3); +} + +.bg-warning, +.hover-bg-warning:hover { + background-color: var(--el-color-warning); +} + +.bg-warning-light-3, +.hover-bg-warning-light-3:hover { + background-color: var(--el-color-warning-light-3); +} + +.bg-danger, +.hover-bg-danger:hover { + background-color: var(--el-color-danger); +} + +.bg-danger-light-3, +.hover-bg-danger-light-3:hover { + background-color: var(--el-color-danger-light-3); +} + +.bg-danger-dark-2, +.hover-bg-danger-dark-2:hover { + background-color: var(--el-color-danger-dark-2); +} + +.bg-error, +.hover-bg-error:hover { + background-color: var(--el-color-error); +} + +.bg-hover, +.hover-bg-hover:hover { + background-color: var(--el-bg-color-page); +} + +.bg-overlay, +.hover-bg-overlay:hover { + background-color: var(--el-bg-color-overlay); +} + +.bg-mask, +.hover-bg-mask:hover { + background-color: var(--el-mask-color); +} + +.bg-mask-light, +.hover-bg-mask-light:hover { + background-color: var(--el-mask-color-extra-light); +} + +.bg-dark, +.hover-bg-dark:hover { + background-color: var(--el-color-black); +} + +.bg-page, +.hover-bg-page:hover { + background-color: var(--el-bg-color-page); +} + +.bg-info, +.hover-bg-info:hover { + background-color: var(--el-color-info); +} + +.bg-gray, +.hover-bg-gray:hover { + background-color: #272727; +} + +.bg-5 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.05); +} + +.bg-10 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.1); +} + +.bg-15 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.15); +} + +.bg-20 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.2); +} + +.bg-25 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.25); +} + +.bg-30 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.3); +} + +.bg-35 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.35); +} + +.bg-40 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.4); +} + +.bg-45 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.45); +} + +.bg-50 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.5); +} + +.bg-55 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.55); +} + +.bg-60 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.6); +} + +.bg-65 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.65); +} + +.bg-70 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.7); +} + +.bg-75 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.75); +} + +.bg-80 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.8); +} + +.bg-85 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.85); +} + +.bg-90 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.9); +} + +.bg-95 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.95); +} + +.bg-100 { + background-color: rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 1); +} + +.text-primary { + color: var(--el-color-primary); +} + +.text-primary-light-3 { + color: var(--el-color-primary-light-3); +} + +.text-success { + color: var(--el-color-success); +} + +.text-danger { + color: var(--el-color-danger); +} + +.text-warning { + color: var(--el-color-warning); +} + +.text-error { + color: var(--el-color-error); +} + +.text-white { + color: var(--el-bg-color); +} + +.text-grey { + color: var(--el-color-info); +} + +.text-info { + color: var(--el-color-info); +} + +.text-text-primary { + color: var(--el-text-color-primary); +} + +.text-regular { + color: var(--el-text-color-regular); +} + +.text-secondary { + color: var(--el-text-color-secondary); +} + +.text-placeholder { + color: var(--el-text-color-placeholder); +} + +.text-disable { + color: var(--el-text-color-disabled); +} + +.text-dark { + color: var(--el-text-color-primary); +} + +.text-bg { + color: var(--el-bg-color); +} + +.text-price { + color: #f56c6c; +} + +.text-nowrap { + white-space: nowrap; +} + +.text-wrap { + white-space: wrap; +} + +.text-break-all { + word-break: break-all; +} + +.text-break-word { + word-break: break-word; +} + +.text-keep-all { + word-break: keep-all; +} + +.text-ellipsis-1 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 1; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +} + +.text-ellipsis-2 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.text-ellipsis-3 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 3; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} + +.text-ellipsis-4 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; +} + +.text-ellipsis-5 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 5; + -webkit-line-clamp: 5; + -webkit-box-orient: vertical; +} + +.text-ellipsis-6 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 6; + -webkit-line-clamp: 6; + -webkit-box-orient: vertical; +} + +.text-ellipsis-7 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 7; + -webkit-line-clamp: 7; + -webkit-box-orient: vertical; +} + +.text-ellipsis-8 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 8; + -webkit-line-clamp: 8; + -webkit-box-orient: vertical; +} + +.text-ellipsis-9 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 9; + -webkit-line-clamp: 9; + -webkit-box-orient: vertical; +} + +.text-ellipsis-10 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + line-clamp: 10; + -webkit-line-clamp: 10; + -webkit-box-orient: vertical; +} + +.text-spacing-1 { + word-spacing: 0.53333vw; +} + +.text-spacing-2 { + word-spacing: 1.06666vw; +} + +.text-spacing-3 { + word-spacing: 1.59999vw; +} + +.text-spacing-4 { + word-spacing: 2.13332vw; +} + +.text-spacing-5 { + word-spacing: 2.66665vw; +} + +.text-letter-spacing-1 { + letter-spacing: 0.53333vw; +} + +.text-letter-spacing-2 { + letter-spacing: 1.06666vw; +} + +.text-letter-spacing-3 { + letter-spacing: 1.59999vw; +} + +.text-letter-spacing-4 { + letter-spacing: 2.13332vw; +} + +.text-letter-spacing-5 { + letter-spacing: 2.66665vw; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-initial { + text-align: initial; +} + +.text-capitalize { + text-transform: capitalize; +} + +.text-uppercase { + text-transform: uppercase; +} + +.text-lowercase { + text-transform: lowercase; +} + +.text-s { + text-decoration: line-through; +} + +.border, +.border-top { + border-top-width: 1px; + border-top-style: solid; + border-top-color: var(--el-border-color); +} + +.border-white, +.border-top-white { + border-top-color: var(--el-bg-color); +} + +.border-hover-white:hover, +.border-hover-top-white:hover { + border-top-color: var(--el-bg-color); +} + +.border-primary, +.border-top-primary { + border-top-color: var(--el-color-primary); +} + +.border-hover-primary:hover, +.border-hover-top-primary:hover { + border-top-color: var(--el-color-primary); +} + +.border-primary-light-3, +.border-top-primary-light-3 { + border-top-color: var(--el-color-primary-light-3); +} + +.border-hover-primary-light-3:hover, +.border-hover-top-primary-light-3:hover { + border-top-color: var(--el-color-primary-light-3); +} + +.border-primary-light-9, +.border-top-primary-light-9 { + border-top-color: var(--el-color-primary-light-9); +} + +.border-hover-primary-light-9:hover, +.border-hover-top-primary-light-9:hover { + border-top-color: var(--el-color-primary-light-9); +} + +.border-success, +.border-top-success { + border-top-color: var(--el-color-success); +} + +.border-hover-success:hover, +.border-hover-top-success:hover { + border-top-color: var(--el-color-success); +} + +.border-success-light-3, +.border-top-success-light-3 { + border-top-color: var(--el-color-success-light-3); +} + +.border-hover-success-light-3:hover, +.border-hover-top-success-light-3:hover { + border-top-color: var(--el-color-success-light-3); +} + +.border-warning, +.border-top-warning { + border-top-color: var(--el-color-warning); +} + +.border-hover-warning:hover, +.border-hover-top-warning:hover { + border-top-color: var(--el-color-warning); +} + +.border-warning-light-3, +.border-top-warning-light-3 { + border-top-color: var(--el-color-warning-light-3); +} + +.border-hover-warning-light-3:hover, +.border-hover-top-warning-light-3:hover { + border-top-color: var(--el-color-warning-light-3); +} + +.border-danger, +.border-top-danger { + border-top-color: var(--el-color-danger); +} + +.border-hover-danger:hover, +.border-hover-top-danger:hover { + border-top-color: var(--el-color-danger); +} + +.border-danger-light-3, +.border-top-danger-light-3 { + border-top-color: var(--el-color-danger-light-3); +} + +.border-hover-danger-light-3:hover, +.border-hover-top-danger-light-3:hover { + border-top-color: var(--el-color-danger-light-3); +} + +.border-danger-dark-2, +.border-top-danger-dark-2 { + border-top-color: var(--el-color-danger-dark-2); +} + +.border-hover-danger-dark-2:hover, +.border-hover-top-danger-dark-2:hover { + border-top-color: var(--el-color-danger-dark-2); +} + +.border-error, +.border-top-error { + border-top-color: var(--el-color-error); +} + +.border-hover-error:hover, +.border-hover-top-error:hover { + border-top-color: var(--el-color-error); +} + +.border-hover, +.border-top-hover { + border-top-color: var(--el-bg-color-page); +} + +.border-hover-hover:hover, +.border-hover-top-hover:hover { + border-top-color: var(--el-bg-color-page); +} + +.border-overlay, +.border-top-overlay { + border-top-color: var(--el-bg-color-overlay); +} + +.border-hover-overlay:hover, +.border-hover-top-overlay:hover { + border-top-color: var(--el-bg-color-overlay); +} + +.border-mask, +.border-top-mask { + border-top-color: var(--el-mask-color); +} + +.border-hover-mask:hover, +.border-hover-top-mask:hover { + border-top-color: var(--el-mask-color); +} + +.border-mask-light, +.border-top-mask-light { + border-top-color: var(--el-mask-color-extra-light); +} + +.border-hover-mask-light:hover, +.border-hover-top-mask-light:hover { + border-top-color: var(--el-mask-color-extra-light); +} + +.border-dark, +.border-top-dark { + border-top-color: var(--el-color-black); +} + +.border-hover-dark:hover, +.border-hover-top-dark:hover { + border-top-color: var(--el-color-black); +} + +.border-page, +.border-top-page { + border-top-color: var(--el-bg-color-page); +} + +.border-hover-page:hover, +.border-hover-top-page:hover { + border-top-color: var(--el-bg-color-page); +} + +.border-info, +.border-top-info { + border-top-color: var(--el-color-info); +} + +.border-hover-info:hover, +.border-hover-top-info:hover { + border-top-color: var(--el-color-info); +} + +.border-gray, +.border-top-gray { + border-top-color: #272727; +} + +.border-hover-gray:hover, +.border-hover-top-gray:hover { + border-top-color: #272727; +} + +.border, +.border-right { + border-right-width: 1px; + border-right-style: solid; + border-right-color: var(--el-border-color); +} + +.border-white, +.border-right-white { + border-right-color: var(--el-bg-color); +} + +.border-hover-white:hover, +.border-hover-right-white:hover { + border-right-color: var(--el-bg-color); +} + +.border-primary, +.border-right-primary { + border-right-color: var(--el-color-primary); +} + +.border-hover-primary:hover, +.border-hover-right-primary:hover { + border-right-color: var(--el-color-primary); +} + +.border-primary-light-3, +.border-right-primary-light-3 { + border-right-color: var(--el-color-primary-light-3); +} + +.border-hover-primary-light-3:hover, +.border-hover-right-primary-light-3:hover { + border-right-color: var(--el-color-primary-light-3); +} + +.border-primary-light-9, +.border-right-primary-light-9 { + border-right-color: var(--el-color-primary-light-9); +} + +.border-hover-primary-light-9:hover, +.border-hover-right-primary-light-9:hover { + border-right-color: var(--el-color-primary-light-9); +} + +.border-success, +.border-right-success { + border-right-color: var(--el-color-success); +} + +.border-hover-success:hover, +.border-hover-right-success:hover { + border-right-color: var(--el-color-success); +} + +.border-success-light-3, +.border-right-success-light-3 { + border-right-color: var(--el-color-success-light-3); +} + +.border-hover-success-light-3:hover, +.border-hover-right-success-light-3:hover { + border-right-color: var(--el-color-success-light-3); +} + +.border-warning, +.border-right-warning { + border-right-color: var(--el-color-warning); +} + +.border-hover-warning:hover, +.border-hover-right-warning:hover { + border-right-color: var(--el-color-warning); +} + +.border-warning-light-3, +.border-right-warning-light-3 { + border-right-color: var(--el-color-warning-light-3); +} + +.border-hover-warning-light-3:hover, +.border-hover-right-warning-light-3:hover { + border-right-color: var(--el-color-warning-light-3); +} + +.border-danger, +.border-right-danger { + border-right-color: var(--el-color-danger); +} + +.border-hover-danger:hover, +.border-hover-right-danger:hover { + border-right-color: var(--el-color-danger); +} + +.border-danger-light-3, +.border-right-danger-light-3 { + border-right-color: var(--el-color-danger-light-3); +} + +.border-hover-danger-light-3:hover, +.border-hover-right-danger-light-3:hover { + border-right-color: var(--el-color-danger-light-3); +} + +.border-danger-dark-2, +.border-right-danger-dark-2 { + border-right-color: var(--el-color-danger-dark-2); +} + +.border-hover-danger-dark-2:hover, +.border-hover-right-danger-dark-2:hover { + border-right-color: var(--el-color-danger-dark-2); +} + +.border-error, +.border-right-error { + border-right-color: var(--el-color-error); +} + +.border-hover-error:hover, +.border-hover-right-error:hover { + border-right-color: var(--el-color-error); +} + +.border-hover, +.border-right-hover { + border-right-color: var(--el-bg-color-page); +} + +.border-hover-hover:hover, +.border-hover-right-hover:hover { + border-right-color: var(--el-bg-color-page); +} + +.border-overlay, +.border-right-overlay { + border-right-color: var(--el-bg-color-overlay); +} + +.border-hover-overlay:hover, +.border-hover-right-overlay:hover { + border-right-color: var(--el-bg-color-overlay); +} + +.border-mask, +.border-right-mask { + border-right-color: var(--el-mask-color); +} + +.border-hover-mask:hover, +.border-hover-right-mask:hover { + border-right-color: var(--el-mask-color); +} + +.border-mask-light, +.border-right-mask-light { + border-right-color: var(--el-mask-color-extra-light); +} + +.border-hover-mask-light:hover, +.border-hover-right-mask-light:hover { + border-right-color: var(--el-mask-color-extra-light); +} + +.border-dark, +.border-right-dark { + border-right-color: var(--el-color-black); +} + +.border-hover-dark:hover, +.border-hover-right-dark:hover { + border-right-color: var(--el-color-black); +} + +.border-page, +.border-right-page { + border-right-color: var(--el-bg-color-page); +} + +.border-hover-page:hover, +.border-hover-right-page:hover { + border-right-color: var(--el-bg-color-page); +} + +.border-info, +.border-right-info { + border-right-color: var(--el-color-info); +} + +.border-hover-info:hover, +.border-hover-right-info:hover { + border-right-color: var(--el-color-info); +} + +.border-gray, +.border-right-gray { + border-right-color: #272727; +} + +.border-hover-gray:hover, +.border-hover-right-gray:hover { + border-right-color: #272727; +} + +.border, +.border-bottom { + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: var(--el-border-color); +} + +.border-white, +.border-bottom-white { + border-bottom-color: var(--el-bg-color); +} + +.border-hover-white:hover, +.border-hover-bottom-white:hover { + border-bottom-color: var(--el-bg-color); +} + +.border-primary, +.border-bottom-primary { + border-bottom-color: var(--el-color-primary); +} + +.border-hover-primary:hover, +.border-hover-bottom-primary:hover { + border-bottom-color: var(--el-color-primary); +} + +.border-primary-light-3, +.border-bottom-primary-light-3 { + border-bottom-color: var(--el-color-primary-light-3); +} + +.border-hover-primary-light-3:hover, +.border-hover-bottom-primary-light-3:hover { + border-bottom-color: var(--el-color-primary-light-3); +} + +.border-primary-light-9, +.border-bottom-primary-light-9 { + border-bottom-color: var(--el-color-primary-light-9); +} + +.border-hover-primary-light-9:hover, +.border-hover-bottom-primary-light-9:hover { + border-bottom-color: var(--el-color-primary-light-9); +} + +.border-success, +.border-bottom-success { + border-bottom-color: var(--el-color-success); +} + +.border-hover-success:hover, +.border-hover-bottom-success:hover { + border-bottom-color: var(--el-color-success); +} + +.border-success-light-3, +.border-bottom-success-light-3 { + border-bottom-color: var(--el-color-success-light-3); +} + +.border-hover-success-light-3:hover, +.border-hover-bottom-success-light-3:hover { + border-bottom-color: var(--el-color-success-light-3); +} + +.border-warning, +.border-bottom-warning { + border-bottom-color: var(--el-color-warning); +} + +.border-hover-warning:hover, +.border-hover-bottom-warning:hover { + border-bottom-color: var(--el-color-warning); +} + +.border-warning-light-3, +.border-bottom-warning-light-3 { + border-bottom-color: var(--el-color-warning-light-3); +} + +.border-hover-warning-light-3:hover, +.border-hover-bottom-warning-light-3:hover { + border-bottom-color: var(--el-color-warning-light-3); +} + +.border-danger, +.border-bottom-danger { + border-bottom-color: var(--el-color-danger); +} + +.border-hover-danger:hover, +.border-hover-bottom-danger:hover { + border-bottom-color: var(--el-color-danger); +} + +.border-danger-light-3, +.border-bottom-danger-light-3 { + border-bottom-color: var(--el-color-danger-light-3); +} + +.border-hover-danger-light-3:hover, +.border-hover-bottom-danger-light-3:hover { + border-bottom-color: var(--el-color-danger-light-3); +} + +.border-danger-dark-2, +.border-bottom-danger-dark-2 { + border-bottom-color: var(--el-color-danger-dark-2); +} + +.border-hover-danger-dark-2:hover, +.border-hover-bottom-danger-dark-2:hover { + border-bottom-color: var(--el-color-danger-dark-2); +} + +.border-error, +.border-bottom-error { + border-bottom-color: var(--el-color-error); +} + +.border-hover-error:hover, +.border-hover-bottom-error:hover { + border-bottom-color: var(--el-color-error); +} + +.border-hover, +.border-bottom-hover { + border-bottom-color: var(--el-bg-color-page); +} + +.border-hover-hover:hover, +.border-hover-bottom-hover:hover { + border-bottom-color: var(--el-bg-color-page); +} + +.border-overlay, +.border-bottom-overlay { + border-bottom-color: var(--el-bg-color-overlay); +} + +.border-hover-overlay:hover, +.border-hover-bottom-overlay:hover { + border-bottom-color: var(--el-bg-color-overlay); +} + +.border-mask, +.border-bottom-mask { + border-bottom-color: var(--el-mask-color); +} + +.border-hover-mask:hover, +.border-hover-bottom-mask:hover { + border-bottom-color: var(--el-mask-color); +} + +.border-mask-light, +.border-bottom-mask-light { + border-bottom-color: var(--el-mask-color-extra-light); +} + +.border-hover-mask-light:hover, +.border-hover-bottom-mask-light:hover { + border-bottom-color: var(--el-mask-color-extra-light); +} + +.border-dark, +.border-bottom-dark { + border-bottom-color: var(--el-color-black); +} + +.border-hover-dark:hover, +.border-hover-bottom-dark:hover { + border-bottom-color: var(--el-color-black); +} + +.border-page, +.border-bottom-page { + border-bottom-color: var(--el-bg-color-page); +} + +.border-hover-page:hover, +.border-hover-bottom-page:hover { + border-bottom-color: var(--el-bg-color-page); +} + +.border-info, +.border-bottom-info { + border-bottom-color: var(--el-color-info); +} + +.border-hover-info:hover, +.border-hover-bottom-info:hover { + border-bottom-color: var(--el-color-info); +} + +.border-gray, +.border-bottom-gray { + border-bottom-color: #272727; +} + +.border-hover-gray:hover, +.border-hover-bottom-gray:hover { + border-bottom-color: #272727; +} + +.border, +.border-left { + border-left-width: 1px; + border-left-style: solid; + border-left-color: var(--el-border-color); +} + +.border-white, +.border-left-white { + border-left-color: var(--el-bg-color); +} + +.border-hover-white:hover, +.border-hover-left-white:hover { + border-left-color: var(--el-bg-color); +} + +.border-primary, +.border-left-primary { + border-left-color: var(--el-color-primary); +} + +.border-hover-primary:hover, +.border-hover-left-primary:hover { + border-left-color: var(--el-color-primary); +} + +.border-primary-light-3, +.border-left-primary-light-3 { + border-left-color: var(--el-color-primary-light-3); +} + +.border-hover-primary-light-3:hover, +.border-hover-left-primary-light-3:hover { + border-left-color: var(--el-color-primary-light-3); +} + +.border-primary-light-9, +.border-left-primary-light-9 { + border-left-color: var(--el-color-primary-light-9); +} + +.border-hover-primary-light-9:hover, +.border-hover-left-primary-light-9:hover { + border-left-color: var(--el-color-primary-light-9); +} + +.border-success, +.border-left-success { + border-left-color: var(--el-color-success); +} + +.border-hover-success:hover, +.border-hover-left-success:hover { + border-left-color: var(--el-color-success); +} + +.border-success-light-3, +.border-left-success-light-3 { + border-left-color: var(--el-color-success-light-3); +} + +.border-hover-success-light-3:hover, +.border-hover-left-success-light-3:hover { + border-left-color: var(--el-color-success-light-3); +} + +.border-warning, +.border-left-warning { + border-left-color: var(--el-color-warning); +} + +.border-hover-warning:hover, +.border-hover-left-warning:hover { + border-left-color: var(--el-color-warning); +} + +.border-warning-light-3, +.border-left-warning-light-3 { + border-left-color: var(--el-color-warning-light-3); +} + +.border-hover-warning-light-3:hover, +.border-hover-left-warning-light-3:hover { + border-left-color: var(--el-color-warning-light-3); +} + +.border-danger, +.border-left-danger { + border-left-color: var(--el-color-danger); +} + +.border-hover-danger:hover, +.border-hover-left-danger:hover { + border-left-color: var(--el-color-danger); +} + +.border-danger-light-3, +.border-left-danger-light-3 { + border-left-color: var(--el-color-danger-light-3); +} + +.border-hover-danger-light-3:hover, +.border-hover-left-danger-light-3:hover { + border-left-color: var(--el-color-danger-light-3); +} + +.border-danger-dark-2, +.border-left-danger-dark-2 { + border-left-color: var(--el-color-danger-dark-2); +} + +.border-hover-danger-dark-2:hover, +.border-hover-left-danger-dark-2:hover { + border-left-color: var(--el-color-danger-dark-2); +} + +.border-error, +.border-left-error { + border-left-color: var(--el-color-error); +} + +.border-hover-error:hover, +.border-hover-left-error:hover { + border-left-color: var(--el-color-error); +} + +.border-hover, +.border-left-hover { + border-left-color: var(--el-bg-color-page); +} + +.border-hover-hover:hover, +.border-hover-left-hover:hover { + border-left-color: var(--el-bg-color-page); +} + +.border-overlay, +.border-left-overlay { + border-left-color: var(--el-bg-color-overlay); +} + +.border-hover-overlay:hover, +.border-hover-left-overlay:hover { + border-left-color: var(--el-bg-color-overlay); +} + +.border-mask, +.border-left-mask { + border-left-color: var(--el-mask-color); +} + +.border-hover-mask:hover, +.border-hover-left-mask:hover { + border-left-color: var(--el-mask-color); +} + +.border-mask-light, +.border-left-mask-light { + border-left-color: var(--el-mask-color-extra-light); +} + +.border-hover-mask-light:hover, +.border-hover-left-mask-light:hover { + border-left-color: var(--el-mask-color-extra-light); +} + +.border-dark, +.border-left-dark { + border-left-color: var(--el-color-black); +} + +.border-hover-dark:hover, +.border-hover-left-dark:hover { + border-left-color: var(--el-color-black); +} + +.border-page, +.border-left-page { + border-left-color: var(--el-bg-color-page); +} + +.border-hover-page:hover, +.border-hover-left-page:hover { + border-left-color: var(--el-bg-color-page); +} + +.border-info, +.border-left-info { + border-left-color: var(--el-color-info); +} + +.border-hover-info:hover, +.border-hover-left-info:hover { + border-left-color: var(--el-color-info); +} + +.border-gray, +.border-left-gray { + border-left-color: #272727; +} + +.border-hover-gray:hover, +.border-hover-left-gray:hover { + border-left-color: #272727; +} + +.border-0, +.border-top-0 { + border-top-width: 0px; +} + +.border-0, +.border-right-0 { + border-right-width: 0px; +} + +.border-0, +.border-bottom-0 { + border-bottom-width: 0px; +} + +.border-0, +.border-left-0 { + border-left-width: 0px; +} + +.border-1, +.border-top-1 { + border-top-width: 1px; +} + +.border-1, +.border-right-1 { + border-right-width: 1px; +} + +.border-1, +.border-bottom-1 { + border-bottom-width: 1px; +} + +.border-1, +.border-left-1 { + border-left-width: 1px; +} + +.border-2, +.border-top-2 { + border-top-width: 2px; +} + +.border-2, +.border-right-2 { + border-right-width: 2px; +} + +.border-2, +.border-bottom-2 { + border-bottom-width: 2px; +} + +.border-2, +.border-left-2 { + border-left-width: 2px; +} + +.border-3, +.border-top-3 { + border-top-width: 3px; +} + +.border-3, +.border-right-3 { + border-right-width: 3px; +} + +.border-3, +.border-bottom-3 { + border-bottom-width: 3px; +} + +.border-3, +.border-left-3 { + border-left-width: 3px; +} + +.border-4, +.border-top-4 { + border-top-width: 4px; +} + +.border-4, +.border-right-4 { + border-right-width: 4px; +} + +.border-4, +.border-bottom-4 { + border-bottom-width: 4px; +} + +.border-4, +.border-left-4 { + border-left-width: 4px; +} + +.border-5, +.border-top-5 { + border-top-width: 5px; +} + +.border-5, +.border-right-5 { + border-right-width: 5px; +} + +.border-5, +.border-bottom-5 { + border-bottom-width: 5px; +} + +.border-5, +.border-left-5 { + border-left-width: 5px; +} + +.border-6, +.border-top-6 { + border-top-width: 6px; +} + +.border-6, +.border-right-6 { + border-right-width: 6px; +} + +.border-6, +.border-bottom-6 { + border-bottom-width: 6px; +} + +.border-6, +.border-left-6 { + border-left-width: 6px; +} + +.border-solid, +.border-top-solid { + border-top-style: solid; +} + +.border-solid, +.border-right-solid { + border-right-style: solid; +} + +.border-solid, +.border-bottom-solid { + border-bottom-style: solid; +} + +.border-solid, +.border-left-solid { + border-left-style: solid; +} + +.border-dotted, +.border-top-dotted { + border-top-style: dotted; +} + +.border-dotted, +.border-right-dotted { + border-right-style: dotted; +} + +.border-dotted, +.border-bottom-dotted { + border-bottom-style: dotted; +} + +.border-dotted, +.border-left-dotted { + border-left-style: dotted; +} + +.border-dashed, +.border-top-dashed { + border-top-style: dashed; +} + +.border-dashed, +.border-right-dashed { + border-right-style: dashed; +} + +.border-dashed, +.border-bottom-dashed { + border-bottom-style: dashed; +} + +.border-dashed, +.border-left-dashed { + border-left-style: dashed; +} + +.border-double, +.border-top-double { + border-top-style: double; +} + +.border-double, +.border-right-double { + border-right-style: double; +} + +.border-double, +.border-bottom-double { + border-bottom-style: double; +} + +.border-double, +.border-left-double { + border-left-style: double; +} + +.border-groove, +.border-top-groove { + border-top-style: groove; +} + +.border-groove, +.border-right-groove { + border-right-style: groove; +} + +.border-groove, +.border-bottom-groove { + border-bottom-style: groove; +} + +.border-groove, +.border-left-groove { + border-left-style: groove; +} + +.border-ridge, +.border-top-ridge { + border-top-style: ridge; +} + +.border-ridge, +.border-right-ridge { + border-right-style: ridge; +} + +.border-ridge, +.border-bottom-ridge { + border-bottom-style: ridge; +} + +.border-ridge, +.border-left-ridge { + border-left-style: ridge; +} + +.border-inset, +.border-top-inset { + border-top-style: inset; +} + +.border-inset, +.border-right-inset { + border-right-style: inset; +} + +.border-inset, +.border-bottom-inset { + border-bottom-style: inset; +} + +.border-inset, +.border-left-inset { + border-left-style: inset; +} + +.border-outset, +.border-top-outset { + border-top-style: outset; +} + +.border-outset, +.border-right-outset { + border-right-style: outset; +} + +.border-outset, +.border-bottom-outset { + border-bottom-style: outset; +} + +.border-outset, +.border-left-outset { + border-left-style: outset; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-round { + border-radius: 100px !important; +} + +.rounded-0, +.rounded-left-0, +.rounded-bottom-0, +.rounded-bottom-left-0 { + border-bottom-left-radius: 0px !important; +} + +.rounded-0, +.rounded-left-0, +.rounded-top-0, +.rounded-top-left-0 { + border-top-left-radius: 0px !important; +} + +.rounded-0, +.rounded-right-0, +.rounded-bottom-0, +.rounded-bottom-right-0 { + border-bottom-right-radius: 0px !important; +} + +.rounded-0, +.rounded-right-0, +.rounded-top-0, +.rounded-top-right-0 { + border-top-right-radius: 0px !important; +} + +.rounded, +.rounded-left, +.rounded-bottom, +.rounded-bottom-left { + border-bottom-left-radius: 2px; +} + +.rounded, +.rounded-left, +.rounded-top, +.rounded-top-left { + border-top-left-radius: 2px; +} + +.rounded, +.rounded-right, +.rounded-bottom, +.rounded-bottom-right { + border-bottom-right-radius: 2px; +} + +.rounded, +.rounded-right, +.rounded-top, +.rounded-top-right { + border-top-right-radius: 2px; +} + +.rounded-2, +.rounded-left-2, +.rounded-bottom-2, +.rounded-bottom-left-2 { + border-bottom-left-radius: 4px !important; +} + +.rounded-2, +.rounded-left-2, +.rounded-top-2, +.rounded-top-left-2 { + border-top-left-radius: 4px !important; +} + +.rounded-2, +.rounded-right-2, +.rounded-bottom-2, +.rounded-bottom-right-2 { + border-bottom-right-radius: 4px !important; +} + +.rounded-2, +.rounded-right-2, +.rounded-top-2, +.rounded-top-right-2 { + border-top-right-radius: 4px !important; +} + +.rounded-3, +.rounded-left-3, +.rounded-bottom-3, +.rounded-bottom-left-3 { + border-bottom-left-radius: 6px !important; +} + +.rounded-3, +.rounded-left-3, +.rounded-top-3, +.rounded-top-left-3 { + border-top-left-radius: 6px !important; +} + +.rounded-3, +.rounded-right-3, +.rounded-bottom-3, +.rounded-bottom-right-3 { + border-bottom-right-radius: 6px !important; +} + +.rounded-3, +.rounded-right-3, +.rounded-top-3, +.rounded-top-right-3 { + border-top-right-radius: 6px !important; +} + +.rounded-4, +.rounded-left-4, +.rounded-bottom-4, +.rounded-bottom-left-4 { + border-bottom-left-radius: 8px !important; +} + +.rounded-4, +.rounded-left-4, +.rounded-top-4, +.rounded-top-left-4 { + border-top-left-radius: 8px !important; +} + +.rounded-4, +.rounded-right-4, +.rounded-bottom-4, +.rounded-bottom-right-4 { + border-bottom-right-radius: 8px !important; +} + +.rounded-4, +.rounded-right-4, +.rounded-top-4, +.rounded-top-right-4 { + border-top-right-radius: 8px !important; +} + +.rounded-5, +.rounded-left-5, +.rounded-bottom-5, +.rounded-bottom-left-5 { + border-bottom-left-radius: 10px !important; +} + +.rounded-5, +.rounded-left-5, +.rounded-top-5, +.rounded-top-left-5 { + border-top-left-radius: 10px !important; +} + +.rounded-5, +.rounded-right-5, +.rounded-bottom-5, +.rounded-bottom-right-5 { + border-bottom-right-radius: 10px !important; +} + +.rounded-5, +.rounded-right-5, +.rounded-top-5, +.rounded-top-right-5 { + border-top-right-radius: 10px !important; +} + +.rounded-6, +.rounded-left-6, +.rounded-bottom-6, +.rounded-bottom-left-6 { + border-bottom-left-radius: 12px !important; +} + +.rounded-6, +.rounded-left-6, +.rounded-top-6, +.rounded-top-left-6 { + border-top-left-radius: 12px !important; +} + +.rounded-6, +.rounded-right-6, +.rounded-bottom-6, +.rounded-bottom-right-6 { + border-bottom-right-radius: 12px !important; +} + +.rounded-6, +.rounded-right-6, +.rounded-top-6, +.rounded-top-right-6 { + border-top-right-radius: 12px !important; +} + +.font-weight-bold { + font-weight: bold; +} + +.font-weight-normal { + font-weight: normal; +} + +.font-weight-100 { + font-weight: 100; +} + +.font-weight-200 { + font-weight: 200; +} + +.font-weight-300 { + font-weight: 300; +} + +.font-weight-400 { + font-weight: 400; +} + +.font-weight-500 { + font-weight: 500; +} + +.font-weight-600 { + font-weight: 600; +} + +.font-weight-700 { + font-weight: 700; +} + +.font-weight-800 { + font-weight: 800; +} + +.font-weight-900 { + font-weight: 900; +} + +.h1 { + font-size: 30px; +} + +.h2 { + font-size: 28px; +} + +.h3 { + font-size: 26px; +} + +.h4 { + font-size: 24px; +} + +.h5 { + font-size: 22px; +} + +.h6 { + font-size: 20px; +} + +.h7 { + font-size: 18px; +} + +.h8 { + font-size: 16px; +} + +.h9 { + font-size: 14px; +} + +.h10 { + font-size: 12px; +} + +.line-height { + line-height: 1; +} + +.line-height-1 { + line-height: 22px; +} + +.line-height-2 { + line-height: 24px; +} + +.line-height-3 { + line-height: 26px; +} + +.line-height-4 { + line-height: 28px; +} + +.line-height-5 { + line-height: 30px; +} + +.line-height-6 { + line-height: 32px; +} + +.line-height-7 { + line-height: 34px; +} + +.line-height-8 { + line-height: 36px; +} + +.line-height-9 { + line-height: 38px; +} + +.line-height-10 { + line-height: 40px; +} + +.overflow-hidden, +.overflow-x-hidden { + overflow-x: hidden; +} + +.overflow-hidden, +.overflow-y-hidden { + overflow-y: hidden; +} + +.overflow-visible, +.overflow-x-visible { + overflow-x: visible; +} + +.overflow-visible, +.overflow-y-visible { + overflow-y: visible; +} + +.overflow-auto, +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-auto, +.overflow-y-auto { + overflow-y: auto; +} + +.overflow-scroll, +.overflow-x-scroll { + overflow-x: scroll; +} + +.overflow-scroll, +.overflow-y-scroll { + overflow-y: scroll; +} + +img { + vertical-align: middle; +} + +.shadow { + -webkit-box-shadow: var(--el-box-shadow); + box-shadow: var(--el-box-shadow); +} + +.shadow-light { + -webkit-box-shadow: var(--el-box-shadow-light); + box-shadow: var(--el-box-shadow-light); +} + +.shadow-lighter { + -webkit-box-shadow: var(--el-box-shadow-lighter); + box-shadow: var(--el-box-shadow-lighter); +} + +.shadow-dark { + -webkit-box-shadow: var(--el-box-shadow-dark); + box-shadow: var(--el-box-shadow-dark); +} + +.pointer { + cursor: pointer; +} +/*# sourceMappingURL=common.css.map */ \ No newline at end of file diff --git a/fastmovie-vue/scss/css/common.css.map b/fastmovie-vue/scss/css/common.css.map new file mode 100644 index 0000000..84a47f8 --- /dev/null +++ b/fastmovie-vue/scss/css/common.css.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "mappings": "AAEA,AAAA,aAAa,CAAC;EACV,cAAc,EAAE,gCAAgC,CAAC,UAAU;EAC3D,cAAc,EAAE,2BAA2B,CAAC,UAAU;CACzD;;AAED,AAAA,aAAa,CAAC;EACV,WAAW,EAAE,6BAA6B,CAAC,UAAU;EACrD,WAAW,EAAE,wBAAwB,CAAC,UAAU;CACnD;;AAED,AAAA,QAAQ;AACR,QAAQ,CAAC;EACL,WAAW,EAAE,eAAe;CAC/B;;AAED,AAAA,QAAQ;AACR,QAAQ,CAAC;EACL,YAAY,EAAE,eAAe;CAChC;;AAIG,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,GAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,GAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,GAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,GAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,GAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,GAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,GAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,GAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,GAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,GAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,GAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,GAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,GAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,GAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,GAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,GAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,IAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,IAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,IAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,IAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,GAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,GAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,GAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,GAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,GAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,GAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,IAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,IAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,IAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,IAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,GAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,GAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,GAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,GAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,GAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,GAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,GAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,GAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,GAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,IAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,IAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,IAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,IAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,IAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,IAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,IAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,IAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,IAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,KAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,KAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,KAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,KAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,IAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,IAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,IAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,IAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,IAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,KAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,KAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,KAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,KAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,IAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,IAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,IAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,IAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,IAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,KAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,KAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,KAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,KAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,IAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,IAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,IAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,IAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,IAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,KAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,KAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,KAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,KAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,IAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,IAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,IAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,IAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,IAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,KAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,KAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,KAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,KAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,cAAc,EAAE,IAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAED,AAAA,IAAI;AACJ,KAAK;AACL,KAAK,CAAK;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,MAAM,CAAK;EACP,GAAG,EAAE,IAAa,CAAC,UAAU;CAChC;;AAED,AAAA,OAAO,CAAK;EACR,IAAI,EAAE,IAAa,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,KAAK,EAAE,IAAa,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,MAAM,EAAE,IAAa,CAAC,UAAU;CACnC;;AAED,AAAA,OAAO,CAAK;EACR,GAAG,EAAE,KAAc,CAAC,UAAU;CACjC;;AAED,AAAA,QAAQ,CAAK;EACT,IAAI,EAAE,KAAc,CAAC,UAAU;CAClC;;AAED,AAAA,SAAS,CAAK;EACV,KAAK,EAAE,KAAc,CAAC,UAAU;CACnC;;AAED,AAAA,UAAU,CAAK;EACX,MAAM,EAAE,KAAc,CAAC,UAAU;CACpC;;AA9ED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,cAAc,EAAE,IAAa,CAAC,UAAU;CAC3C;;AAED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,WAAW,EAAE,IAAa,CAAC,UAAU;CACxC;;AAED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,YAAY,EAAE,IAAa,CAAC,UAAU;CACzC;;AAED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAED,AAAA,KAAK;AACL,MAAM;AACN,MAAM,CAAI;EACN,aAAa,EAAE,IAAa,CAAC,UAAU;CAC1C;;AAED,AAAA,OAAO,CAAI;EACP,GAAG,EAAE,IAAa,CAAC,UAAU;CAChC;;AAED,AAAA,QAAQ,CAAI;EACR,IAAI,EAAE,IAAa,CAAC,UAAU;CACjC;;AAED,AAAA,SAAS,CAAI;EACT,KAAK,EAAE,IAAa,CAAC,UAAU;CAClC;;AAED,AAAA,UAAU,CAAI;EACV,MAAM,EAAE,IAAa,CAAC,UAAU;CACnC;;AAED,AAAA,QAAQ,CAAI;EACR,GAAG,EAAE,KAAc,CAAC,UAAU;CACjC;;AAED,AAAA,SAAS,CAAI;EACT,IAAI,EAAE,KAAc,CAAC,UAAU;CAClC;;AAED,AAAA,UAAU,CAAI;EACV,KAAK,EAAE,KAAc,CAAC,UAAU;CACnC;;AAED,AAAA,WAAW,CAAI;EACX,MAAM,EAAE,KAAc,CAAC,UAAU;CACpC;;AAGL,AAAA,YAAY,CAAC;EACT,OAAO,EAAE,WAAW;CACvB;;AAED,AAAA,KAAK,CAAC;EACF,OAAO,EAAE,IAAI;CAqDhB;;AAnDI,AAAD,cAAU,CAAC;EACP,WAAW,EAAE,CAAC;CACjB;;AAII,AAAD,UAAM,CAAI;EACN,SAAS,EAHH,IAAI;CAIb;;AAFA,AAAD,YAAQ,CAAE;EACN,SAAS,EAFjB,MAAM;CAGD;;AAOA,AAAD,YAAQ,CAAE;EACN,cAAc,EALR,MAAM;CAMf;;AAFA,AAAD,oBAAgB,CAAN;EACN,cAAc,EAJtB,cAAc;CAKT;;AAFA,AAAD,SAAK,CAAK;EACN,cAAc,EAHtB,GAAG;CAIE;;AAFA,AAAD,iBAAa,CAAH;EACN,cAAc,EAFtB,WAAW;CAGN;;AAOA,AAAD,cAAU,CAAE;EACR,WAAW,EALL,MAAM;CAMf;;AAFA,AAAD,gBAAY,CAAA;EACR,WAAW,EAJnB,QAAQ;CAKH;;AAFA,AAAD,kBAAc,CAAF;EACR,WAAW,EAHnB,UAAU;CAIL;;AAFA,AAAD,gBAAY,CAAA;EACR,WAAW,EAFnB,QAAQ;CAGH;;AASA,AAAD,cAAU,CAAE;EACR,eAAe,EAPT,MAAM;CAQf;;AAFA,AAAD,kBAAc,CAAF;EACR,eAAe,EANvB,UAAU;CAOL;;AAFA,AAAD,gBAAY,CAAA;EACR,eAAe,EALvB,QAAQ;CAMH;;AAFA,AAAD,oBAAgB,CAAJ;EACR,eAAe,EAJvB,YAAY;CAKP;;AAFA,AAAD,qBAAiB,CAAL;EACR,eAAe,EAHvB,aAAa;CAIR;;AAFA,AAAD,oBAAgB,CAAJ;EACR,eAAe,EAFvB,YAAY;CAGP;;AAIA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,OAAG,CAAK;EACJ,IAAI,EAFC,CAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,QAAI,CAAI;EACJ,IAAI,EAFC,EAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,QAAI,CAAI;EACJ,IAAI,EAFC,EAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAHA,AAAD,QAAI,CAAI;EACJ,IAAI,EAFC,EAAC;EAGN,SAAS,EAAE,CAAC;CACf;;AAGJ,AAAD,YAAQ,CAAC;EACL,WAAW,EAAE,MAAM;EACnB,eAAe,EAAE,MAAM;CAC1B;;CAGL,AAAA,AAAA,KAAC,EAAO,eAAe,AAAtB,EAAwB;EACrB,OAAO,EAAE,IAAI;EACb,aAAa,EAAE,UAAU;CAC5B;;AAED,AAAA,KAAK,CAAC;EACF,OAAO,EAAE,IAAI;EACb,qBAAqB,EAAE,4BAA4B;CAgDtD;;AA7CQ,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,eAAW,CAAK;EACZ,qBAAqB,EAAE,2BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAFA,AAAD,gBAAY,CAAI;EACZ,qBAAqB,EAAE,4BAA4B;CACtD;;AAKA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,GAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,GAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,GAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,GAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,GAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,GAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,IAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,IAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,IAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,IAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,IAAa,CAAC,UAAU;CACpC;;AARA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,WAAO,EACN,aAAQ,CAAK;EACV,OAAO,EAAE,IAAa,CAAC,UAAU;CACpC;;AARA,AAAD,YAAQ,EACP,cAAS,CAAI;EACV,UAAU,EAAE,IAAa,CAAC,UAAU;CACvC;;AAEA,AAAD,YAAQ,EACP,cAAS,CAAI;EACV,OAAO,EAAE,IAAa,CAAC,UAAU;CACpC;;AAIA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,cAAU,CAAK;EACX,WAAW,EAAE,IAAI,CAAC,MAAS,CAFtB,CAAC;CAGT;;AAEA,AAAD,WAAO,CAAK;EACR,QAAQ,EAAE,IAAI,CAAC,MAAS,CANnB,CAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AANA,AAAD,eAAW,CAAI;EACX,WAAW,EAAE,IAAI,CAAC,OAAS,CAFtB,EAAC;CAGT;;AAEA,AAAD,YAAQ,CAAI;EACR,QAAQ,EAAE,IAAI,CAAC,OAAS,CANnB,EAAC;CAOT;;AAID,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,KAAK;EAE/B,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAZT,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,KAAK;EAE/B,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,iBAAa,CAAW;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,gBAAY,CAAW;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,aAAS,CAAW;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAZT,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,KAAK;EAE/B,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAZT,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,KAAK;EAE/B,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAZT,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,MAAM;EAEhC,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAZT,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,MAAM;EAEhC,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAZT,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,MAAM;EAEhC,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,kBAAc,CAAU;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,iBAAa,CAAU;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,cAAU,CAAU;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAZT,MAAM,MAAM,MAAM,MAAM,SAAS,EAAE,MAAM;EAEhC,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,mBAAe,CAAS;IACpB,qBAAqB,EAAE,2BAA4B;GACtD;EAEA,AAAD,kBAAc,CAAS;IACnB,WAAW,EAAE,IAAI,CAAC,MAAS,CANtB,CAAC;GAOT;EAEA,AAAD,eAAW,CAAS;IAChB,QAAQ,EAAE,IAAI,CAAC,MAAS,CAVnB,CAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;EAVA,AAAD,oBAAgB,CAAQ;IACpB,qBAAqB,EAAE,4BAA4B;GACtD;EAEA,AAAD,mBAAe,CAAQ;IACnB,WAAW,EAAE,IAAI,CAAC,OAAS,CANtB,EAAC;GAOT;EAEA,AAAD,gBAAY,CAAQ;IAChB,QAAQ,EAAE,IAAI,CAAC,OAAS,CAVnB,EAAC;GAWT;;;AAUb,AAAA,kBAAkB,CAAA;EACd,QAAQ,EALF,QAAQ;CAMjB;;AAFD,AAAA,kBAAkB,CAAA;EACd,QAAQ,EAJhB,QAAQ;CAKH;;AAFD,AAAA,eAAe,CAAG;EACd,QAAQ,EAHhB,KAAK;CAIA;;AAFD,AAAA,gBAAgB,CAAE;EACd,QAAQ,EAFhB,MAAM;CAGD;;AAID,AAAA,KAAK,CAAS;EACV,MAAM,EAAE,GAAQ;CACnB;;AAED,AAAA,KAAK,CAAS;EACV,KAAK,EAAE,GAAQ;CAClB;;AAED,AAAA,IAAI,CAAS;EACT,MAAM,EAAE,EAAO;CAClB;;AAED,AAAA,IAAI,CAAS;EACT,KAAK,EAAE,EAAO;CACjB;;AAED,AAAA,QAAQ,CAAS;EACb,UAAU,EAAE,EAAO;CACtB;;AAED,AAAA,QAAQ,CAAS;EACb,SAAS,EAAE,EAAO;CACrB;;AAED,AAAA,SAAS,CAAS;EACd,UAAU,EAAE,GAAQ;CACvB;;AAED,AAAA,SAAS,CAAS;EACd,SAAS,EAAE,GAAQ;CACtB;;AAED,AAAA,QAAQ,CAAS;EACb,UAAU,EAAE,EAAO;CACtB;;AAED,AAAA,QAAQ,CAAS;EACb,SAAS,EAAE,EAAO;CACrB;;AAED,AAAA,SAAS,CAAS;EACd,UAAU,EAAE,GAAQ;CACvB;;AAED,AAAA,SAAS,CAAS;EACd,SAAS,EAAE,GAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,MAAM,CAAQ;EACV,MAAM,EAAE,IAAQ;CACnB;;AAED,AAAA,MAAM,CAAQ;EACV,KAAK,EAAE,IAAQ;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,MAAM,EAAE,GAAO;CAClB;;AAED,AAAA,KAAK,CAAQ;EACT,KAAK,EAAE,GAAO;CACjB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,UAAU,EAAE,GAAO;CACtB;;AAED,AAAA,SAAS,CAAQ;EACb,SAAS,EAAE,GAAO;CACrB;;AAED,AAAA,UAAU,CAAQ;EACd,UAAU,EAAE,IAAQ;CACvB;;AAED,AAAA,UAAU,CAAQ;EACd,SAAS,EAAE,IAAQ;CACtB;;AA9CD,AAAA,OAAO,CAAO;EACV,MAAM,EAAE,KAAQ;CACnB;;AAED,AAAA,OAAO,CAAO;EACV,KAAK,EAAE,KAAQ;CAClB;;AAED,AAAA,MAAM,CAAO;EACT,MAAM,EAAE,IAAO;CAClB;;AAED,AAAA,MAAM,CAAO;EACT,KAAK,EAAE,IAAO;CACjB;;AAED,AAAA,UAAU,CAAO;EACb,UAAU,EAAE,IAAO;CACtB;;AAED,AAAA,UAAU,CAAO;EACb,SAAS,EAAE,IAAO;CACrB;;AAED,AAAA,WAAW,CAAO;EACd,UAAU,EAAE,KAAQ;CACvB;;AAED,AAAA,WAAW,CAAO;EACd,SAAS,EAAE,KAAQ;CACtB;;AAED,AAAA,UAAU,CAAO;EACb,UAAU,EAAE,IAAO;CACtB;;AAED,AAAA,UAAU,CAAO;EACb,SAAS,EAAE,IAAO;CACrB;;AAED,AAAA,WAAW,CAAO;EACd,UAAU,EAAE,KAAQ;CACvB;;AAED,AAAA,WAAW,CAAO;EACd,SAAS,EAAE,KAAQ;CACtB;;AAGL,AAAA,GAAG,CAAC;EACA,gBAAgB,EAAE,kBAAkB;CACvC;;AAED,AAAA,UAAU,CAAC;EACP,eAAe,EAAE,SAAS;CAC7B;;AAED,AAAA,UAAU;AACV,UAAU,AAAA,UAAU;AACpB,IAAI,CAAC,UAAU,CAAC;EACZ,oBAAoB,CAAA,OAAC;EACrB;;;;;+DAK2D;CAC9D;;AAKG,AAAA,SAAS;AACT,eAAe,AAAA,MAAM,CAAG;EACpB,gBAAgB,ECxSZ,kBAAkB;CDySzB;;AAHD,AAAA,WAAW;AACX,iBAAiB,AAAA,MAAM,CAAC;EACpB,gBAAgB,ECvSV,uBAAuB;CDwShC;;AAHD,AAAA,mBAAmB;AACnB,yBAAyB,AAAA,MAAM,CAAP;EACpB,gBAAgB,ECtSF,+BAA+B;CDuShD;;AAHD,AAAA,mBAAmB;AACnB,yBAAyB,AAAA,MAAM,CAAP;EACpB,gBAAgB,ECrSF,+BAA+B;CDsShD;;AAHD,AAAA,WAAW;AACX,iBAAiB,AAAA,MAAM,CAAC;EACpB,gBAAgB,ECpSV,uBAAuB;CDqShC;;AAHD,AAAA,mBAAmB;AACnB,yBAAyB,AAAA,MAAM,CAAP;EACpB,gBAAgB,ECnSF,+BAA+B;CDoShD;;AAHD,AAAA,WAAW;AACX,iBAAiB,AAAA,MAAM,CAAC;EACpB,gBAAgB,EClSV,uBAAuB;CDmShC;;AAHD,AAAA,mBAAmB;AACnB,yBAAyB,AAAA,MAAM,CAAP;EACpB,gBAAgB,ECjSF,+BAA+B;CDkShD;;AAHD,AAAA,UAAU;AACV,gBAAgB,AAAA,MAAM,CAAE;EACpB,gBAAgB,EChSX,sBAAsB;CDiS9B;;AAHD,AAAA,kBAAkB;AAClB,wBAAwB,AAAA,MAAM,CAAN;EACpB,gBAAgB,EC/RH,8BAA8B;CDgS9C;;AAHD,AAAA,iBAAiB;AACjB,uBAAuB,AAAA,MAAM,CAAL;EACpB,gBAAgB,EC9RJ,6BAA6B;CD+R5C;;AAHD,AAAA,SAAS;AACT,eAAe,AAAA,MAAM,CAAG;EACpB,gBAAgB,EC7RZ,qBAAqB;CD8R5B;;AAHD,AAAA,SAAS;AACT,eAAe,AAAA,MAAM,CAAG;EACpB,gBAAgB,EC5RZ,uBAAuB;CD6R9B;;AAHD,AAAA,WAAW;AACX,iBAAiB,AAAA,MAAM,CAAC;EACpB,gBAAgB,EC3RV,0BAA0B;CD4RnC;;AAHD,AAAA,QAAQ;AACR,cAAc,AAAA,MAAM,CAAI;EACpB,gBAAgB,EC1Rb,oBAAoB;CD2R1B;;AAHD,AAAA,cAAc;AACd,oBAAoB,AAAA,MAAM,CAAF;EACpB,gBAAgB,ECzRP,gCAAgC;CD0R5C;;AAHD,AAAA,QAAQ;AACR,cAAc,AAAA,MAAM,CAAI;EACpB,gBAAgB,ECxRb,qBAAqB;CDyR3B;;AAHD,AAAA,QAAQ;AACR,cAAc,AAAA,MAAM,CAAI;EACpB,gBAAgB,ECvRb,uBAAuB;CDwR7B;;AAHD,AAAA,QAAQ;AACR,cAAc,AAAA,MAAM,CAAI;EACpB,gBAAgB,ECtRb,oBAAoB;CDuR1B;;AAHD,AAAA,QAAQ;AACR,cAAc,AAAA,MAAM,CAAI;EACpB,gBAAgB,ECrRb,OAAO;CDsRb;;AAID,AAAA,KAAK,CAAS;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,uFAA6F;CAClH;;AAFD,AAAA,MAAM,CAAQ;EACV,gBAAgB,EAAE,wFAA6F;CAClH;;AAFD,AAAA,OAAO,CAAO;EACV,gBAAgB,EAAE,qFAA6F;CAClH;;AAQI,AAAD,aAAS,CAAC;EACN,KAAK,ECjSH,uBAAuB;CDkS5B;;AAFA,AAAD,qBAAiB,CAAP;EACN,KAAK,EChSK,+BAA+B;CDiS5C;;AAFA,AAAD,aAAS,CAAC;EACN,KAAK,EC/RH,uBAAuB;CDgS5B;;AAFA,AAAD,YAAQ,CAAE;EACN,KAAK,EC9RJ,sBAAsB;CD+R1B;;AAFA,AAAD,aAAS,CAAC;EACN,KAAK,EC7RH,uBAAuB;CD8R5B;;AAFA,AAAD,WAAO,CAAG;EACN,KAAK,EC5RL,qBAAqB;CD6RxB;;AAFA,AAAD,WAAO,CAAG;EACN,KAAK,EC3RL,kBAAkB;CD4RrB;;AAFA,AAAD,UAAM,CAAI;EACN,KAAK,EC1RN,oBAAoB;CD2RtB;;AAFA,AAAD,UAAM,CAAI;EACN,KAAK,ECzRN,oBAAoB;CD0RtB;;AAFA,AAAD,kBAAc,CAAJ;EACN,KAAK,ECxRE,4BAA4B;CDyRtC;;AAFA,AAAD,aAAS,CAAC;EACN,KAAK,ECvRH,4BAA4B;CDwRjC;;AAFA,AAAD,eAAW,CAAD;EACN,KAAK,ECtRD,8BAA8B;CDuRrC;;AAFA,AAAD,iBAAa,CAAH;EACN,KAAK,ECrRC,gCAAgC;CDsRzC;;AAFA,AAAD,aAAS,CAAC;EACN,KAAK,ECpRH,6BAA6B;CDqRlC;;AAFA,AAAD,UAAM,CAAI;EACN,KAAK,ECnRN,4BAA4B;CDoR9B;;AAFA,AAAD,QAAI,CAAM;EACN,KAAK,EClRR,kBAAkB;CDmRlB;;AAFA,AAAD,WAAO,CAAG;EACN,KAAK,ECjRL,OAAO;CDkRV;;AAKA,AAAD,YAAQ,CAAE;EACN,WAAW,EAHL,MAAM;CAIf;;AAFA,AAAD,UAAM,CAAI;EACN,WAAW,EAFnB,IAAI;CAGC;;AAMA,AAAD,eAAW,CAAD;EACN,UAAU,EAJJ,SAAS;CAKlB;;AAFA,AAAD,gBAAY,CAAF;EACN,UAAU,EAHlB,UAAU;CAIL;;AAFA,AAAD,cAAU,CAAA;EACN,UAAU,EAFlB,QAAQ;CAGH;;AAIA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,gBAAY,CAAK;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,CAAC;EAMN,kBAAkB,EANb,CAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAPA,AAAD,iBAAa,CAAI;EACb,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,OAAO,EAAE,WAAW;EACpB,UAAU,EALL,EAAC;EAMN,kBAAkB,EANb,EAAC;EAON,kBAAkB,EAAE,QAAQ;CAC/B;;AAIA,AAAD,eAAW,CAAK;EACZ,YAAY,EAAE,SAAc;CAC/B;;AAFA,AAAD,eAAW,CAAK;EACZ,YAAY,EAAE,SAAc;CAC/B;;AAFA,AAAD,eAAW,CAAK;EACZ,YAAY,EAAE,SAAc;CAC/B;;AAFA,AAAD,eAAW,CAAK;EACZ,YAAY,EAAE,SAAc;CAC/B;;AAFA,AAAD,eAAW,CAAK;EACZ,YAAY,EAAE,SAAc;CAC/B;;AAIA,AAAD,sBAAkB,CAAK;EACnB,cAAc,EAAE,SAAc;CACjC;;AAFA,AAAD,sBAAkB,CAAK;EACnB,cAAc,EAAE,SAAc;CACjC;;AAFA,AAAD,sBAAkB,CAAK;EACnB,cAAc,EAAE,SAAc;CACjC;;AAFA,AAAD,sBAAkB,CAAK;EACnB,cAAc,EAAE,SAAc;CACjC;;AAFA,AAAD,sBAAkB,CAAK;EACnB,cAAc,EAAE,SAAc;CACjC;;AAOA,AAAD,UAAM,CAAI;EACN,UAAU,EALJ,IAAI;CAMb;;AAFA,AAAD,YAAQ,CAAE;EACN,UAAU,EAJlB,MAAM;CAKD;;AAFA,AAAD,WAAO,CAAG;EACN,UAAU,EAHlB,KAAK;CAIA;;AAFA,AAAD,aAAS,CAAC;EACN,UAAU,EAFlB,OAAO;CAGF;;AAMA,AAAD,gBAAY,CAAF;EACN,cAAc,EAJR,UAAU;CAKnB;;AAFA,AAAD,eAAW,CAAD;EACN,cAAc,EAHtB,SAAS;CAIJ;;AAFA,AAAD,eAAW,CAAD;EACN,cAAc,EAFtB,SAAS;CAGJ;;AAGJ,AAAD,OAAG,CAAC;EACA,eAAe,EAAE,YAAY;CAChC;;AAKD,AAAA,OAAO;AACP,WAAW,CAAG;EACV,gBAAkB,EC/XZ,GAAG;EDgYT,gBAAkB,EAAe,KAAK;EACtC,gBAAkB,EAAe,sBAAsB;CAC1D;;AAKG,AAAA,aAAa;AACb,iBAAiB,CAAK;EAClB,gBAAkB,ECtYlB,kBAAkB;CDuYrB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,gBAAkB,EC3YlB,kBAAkB;CD4YrB;;AARD,AAAA,eAAe;AACf,mBAAmB,CAAG;EAClB,gBAAkB,ECrYhB,uBAAuB;CDsY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,yBAAyB,AAAA,MAAM,CAAG;EAC9B,gBAAkB,EC1YhB,uBAAuB;CD2Y5B;;AARD,AAAA,uBAAuB;AACvB,2BAA2B,CAAL;EAClB,gBAAkB,ECpYR,+BAA+B;CDqY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,iCAAiC,AAAA,MAAM,CAAL;EAC9B,gBAAkB,ECzYR,+BAA+B;CD0Y5C;;AARD,AAAA,uBAAuB;AACvB,2BAA2B,CAAL;EAClB,gBAAkB,ECnYR,+BAA+B;CDoY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,iCAAiC,AAAA,MAAM,CAAL;EAC9B,gBAAkB,ECxYR,+BAA+B;CDyY5C;;AARD,AAAA,eAAe;AACf,mBAAmB,CAAG;EAClB,gBAAkB,EClYhB,uBAAuB;CDmY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,yBAAyB,AAAA,MAAM,CAAG;EAC9B,gBAAkB,ECvYhB,uBAAuB;CDwY5B;;AARD,AAAA,uBAAuB;AACvB,2BAA2B,CAAL;EAClB,gBAAkB,ECjYR,+BAA+B;CDkY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,iCAAiC,AAAA,MAAM,CAAL;EAC9B,gBAAkB,ECtYR,+BAA+B;CDuY5C;;AARD,AAAA,eAAe;AACf,mBAAmB,CAAG;EAClB,gBAAkB,EChYhB,uBAAuB;CDiY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,yBAAyB,AAAA,MAAM,CAAG;EAC9B,gBAAkB,ECrYhB,uBAAuB;CDsY5B;;AARD,AAAA,uBAAuB;AACvB,2BAA2B,CAAL;EAClB,gBAAkB,EC/XR,+BAA+B;CDgY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,iCAAiC,AAAA,MAAM,CAAL;EAC9B,gBAAkB,ECpYR,+BAA+B;CDqY5C;;AARD,AAAA,cAAc;AACd,kBAAkB,CAAI;EAClB,gBAAkB,EC9XjB,sBAAsB;CD+X1B;;AAED,AAAA,oBAAoB,AAAA,MAAM;AAC1B,wBAAwB,AAAA,MAAM,CAAI;EAC9B,gBAAkB,ECnYjB,sBAAsB;CDoY1B;;AARD,AAAA,sBAAsB;AACtB,0BAA0B,CAAJ;EAClB,gBAAkB,EC7XT,8BAA8B;CD8X1C;;AAED,AAAA,4BAA4B,AAAA,MAAM;AAClC,gCAAgC,AAAA,MAAM,CAAJ;EAC9B,gBAAkB,EClYT,8BAA8B;CDmY1C;;AARD,AAAA,qBAAqB;AACrB,yBAAyB,CAAH;EAClB,gBAAkB,EC5XV,6BAA6B;CD6XxC;;AAED,AAAA,2BAA2B,AAAA,MAAM;AACjC,+BAA+B,AAAA,MAAM,CAAH;EAC9B,gBAAkB,ECjYV,6BAA6B;CDkYxC;;AARD,AAAA,aAAa;AACb,iBAAiB,CAAK;EAClB,gBAAkB,EC3XlB,qBAAqB;CD4XxB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,gBAAkB,EChYlB,qBAAqB;CDiYxB;;AARD,AAAA,aAAa;AACb,iBAAiB,CAAK;EAClB,gBAAkB,EC1XlB,uBAAuB;CD2X1B;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,gBAAkB,EC/XlB,uBAAuB;CDgY1B;;AARD,AAAA,eAAe;AACf,mBAAmB,CAAG;EAClB,gBAAkB,ECzXhB,0BAA0B;CD0X/B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,yBAAyB,AAAA,MAAM,CAAG;EAC9B,gBAAkB,EC9XhB,0BAA0B;CD+X/B;;AARD,AAAA,YAAY;AACZ,gBAAgB,CAAM;EAClB,gBAAkB,ECxXnB,oBAAoB;CDyXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,sBAAsB,AAAA,MAAM,CAAM;EAC9B,gBAAkB,EC7XnB,oBAAoB;CD8XtB;;AARD,AAAA,kBAAkB;AAClB,sBAAsB,CAAA;EAClB,gBAAkB,ECvXb,gCAAgC;CDwXxC;;AAED,AAAA,wBAAwB,AAAA,MAAM;AAC9B,4BAA4B,AAAA,MAAM,CAAA;EAC9B,gBAAkB,EC5Xb,gCAAgC;CD6XxC;;AARD,AAAA,YAAY;AACZ,gBAAgB,CAAM;EAClB,gBAAkB,ECtXnB,qBAAqB;CDuXvB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,sBAAsB,AAAA,MAAM,CAAM;EAC9B,gBAAkB,EC3XnB,qBAAqB;CD4XvB;;AARD,AAAA,YAAY;AACZ,gBAAgB,CAAM;EAClB,gBAAkB,ECrXnB,uBAAuB;CDsXzB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,sBAAsB,AAAA,MAAM,CAAM;EAC9B,gBAAkB,EC1XnB,uBAAuB;CD2XzB;;AARD,AAAA,YAAY;AACZ,gBAAgB,CAAM;EAClB,gBAAkB,ECpXnB,oBAAoB;CDqXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,sBAAsB,AAAA,MAAM,CAAM;EAC9B,gBAAkB,ECzXnB,oBAAoB;CD0XtB;;AARD,AAAA,YAAY;AACZ,gBAAgB,CAAM;EAClB,gBAAkB,ECnXnB,OAAO;CDoXT;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,sBAAsB,AAAA,MAAM,CAAM;EAC9B,gBAAkB,ECxXnB,OAAO;CDyXT;;AAlBL,AAAA,OAAO;AACP,aAAa,CAAC;EACV,kBAAkB,EC/XZ,GAAG;EDgYT,kBAAkB,EAAe,KAAK;EACtC,kBAAkB,EAAe,sBAAsB;CAC1D;;AAKG,AAAA,aAAa;AACb,mBAAmB,CAAG;EAClB,kBAAkB,ECtYlB,kBAAkB;CDuYrB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,kBAAkB,EC3YlB,kBAAkB;CD4YrB;;AARD,AAAA,eAAe;AACf,qBAAqB,CAAC;EAClB,kBAAkB,ECrYhB,uBAAuB;CDsY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,2BAA2B,AAAA,MAAM,CAAC;EAC9B,kBAAkB,EC1YhB,uBAAuB;CD2Y5B;;AARD,AAAA,uBAAuB;AACvB,6BAA6B,CAAP;EAClB,kBAAkB,ECpYR,+BAA+B;CDqY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,mCAAmC,AAAA,MAAM,CAAP;EAC9B,kBAAkB,ECzYR,+BAA+B;CD0Y5C;;AARD,AAAA,uBAAuB;AACvB,6BAA6B,CAAP;EAClB,kBAAkB,ECnYR,+BAA+B;CDoY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,mCAAmC,AAAA,MAAM,CAAP;EAC9B,kBAAkB,ECxYR,+BAA+B;CDyY5C;;AARD,AAAA,eAAe;AACf,qBAAqB,CAAC;EAClB,kBAAkB,EClYhB,uBAAuB;CDmY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,2BAA2B,AAAA,MAAM,CAAC;EAC9B,kBAAkB,ECvYhB,uBAAuB;CDwY5B;;AARD,AAAA,uBAAuB;AACvB,6BAA6B,CAAP;EAClB,kBAAkB,ECjYR,+BAA+B;CDkY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,mCAAmC,AAAA,MAAM,CAAP;EAC9B,kBAAkB,ECtYR,+BAA+B;CDuY5C;;AARD,AAAA,eAAe;AACf,qBAAqB,CAAC;EAClB,kBAAkB,EChYhB,uBAAuB;CDiY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,2BAA2B,AAAA,MAAM,CAAC;EAC9B,kBAAkB,ECrYhB,uBAAuB;CDsY5B;;AARD,AAAA,uBAAuB;AACvB,6BAA6B,CAAP;EAClB,kBAAkB,EC/XR,+BAA+B;CDgY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,mCAAmC,AAAA,MAAM,CAAP;EAC9B,kBAAkB,ECpYR,+BAA+B;CDqY5C;;AARD,AAAA,cAAc;AACd,oBAAoB,CAAE;EAClB,kBAAkB,EC9XjB,sBAAsB;CD+X1B;;AAED,AAAA,oBAAoB,AAAA,MAAM;AAC1B,0BAA0B,AAAA,MAAM,CAAE;EAC9B,kBAAkB,ECnYjB,sBAAsB;CDoY1B;;AARD,AAAA,sBAAsB;AACtB,4BAA4B,CAAN;EAClB,kBAAkB,EC7XT,8BAA8B;CD8X1C;;AAED,AAAA,4BAA4B,AAAA,MAAM;AAClC,kCAAkC,AAAA,MAAM,CAAN;EAC9B,kBAAkB,EClYT,8BAA8B;CDmY1C;;AARD,AAAA,qBAAqB;AACrB,2BAA2B,CAAL;EAClB,kBAAkB,EC5XV,6BAA6B;CD6XxC;;AAED,AAAA,2BAA2B,AAAA,MAAM;AACjC,iCAAiC,AAAA,MAAM,CAAL;EAC9B,kBAAkB,ECjYV,6BAA6B;CDkYxC;;AARD,AAAA,aAAa;AACb,mBAAmB,CAAG;EAClB,kBAAkB,EC3XlB,qBAAqB;CD4XxB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,kBAAkB,EChYlB,qBAAqB;CDiYxB;;AARD,AAAA,aAAa;AACb,mBAAmB,CAAG;EAClB,kBAAkB,EC1XlB,uBAAuB;CD2X1B;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,kBAAkB,EC/XlB,uBAAuB;CDgY1B;;AARD,AAAA,eAAe;AACf,qBAAqB,CAAC;EAClB,kBAAkB,ECzXhB,0BAA0B;CD0X/B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,2BAA2B,AAAA,MAAM,CAAC;EAC9B,kBAAkB,EC9XhB,0BAA0B;CD+X/B;;AARD,AAAA,YAAY;AACZ,kBAAkB,CAAI;EAClB,kBAAkB,ECxXnB,oBAAoB;CDyXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,kBAAkB,EC7XnB,oBAAoB;CD8XtB;;AARD,AAAA,kBAAkB;AAClB,wBAAwB,CAAF;EAClB,kBAAkB,ECvXb,gCAAgC;CDwXxC;;AAED,AAAA,wBAAwB,AAAA,MAAM;AAC9B,8BAA8B,AAAA,MAAM,CAAF;EAC9B,kBAAkB,EC5Xb,gCAAgC;CD6XxC;;AARD,AAAA,YAAY;AACZ,kBAAkB,CAAI;EAClB,kBAAkB,ECtXnB,qBAAqB;CDuXvB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,kBAAkB,EC3XnB,qBAAqB;CD4XvB;;AARD,AAAA,YAAY;AACZ,kBAAkB,CAAI;EAClB,kBAAkB,ECrXnB,uBAAuB;CDsXzB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,kBAAkB,EC1XnB,uBAAuB;CD2XzB;;AARD,AAAA,YAAY;AACZ,kBAAkB,CAAI;EAClB,kBAAkB,ECpXnB,oBAAoB;CDqXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,kBAAkB,ECzXnB,oBAAoB;CD0XtB;;AARD,AAAA,YAAY;AACZ,kBAAkB,CAAI;EAClB,kBAAkB,ECnXnB,OAAO;CDoXT;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,kBAAkB,ECxXnB,OAAO;CDyXT;;AAlBL,AAAA,OAAO;AACP,cAAc,CAAA;EACV,mBAAkB,EC/XZ,GAAG;EDgYT,mBAAkB,EAAe,KAAK;EACtC,mBAAkB,EAAe,sBAAsB;CAC1D;;AAKG,AAAA,aAAa;AACb,oBAAoB,CAAE;EAClB,mBAAkB,ECtYlB,kBAAkB;CDuYrB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,0BAA0B,AAAA,MAAM,CAAE;EAC9B,mBAAkB,EC3YlB,kBAAkB;CD4YrB;;AARD,AAAA,eAAe;AACf,sBAAsB,CAAA;EAClB,mBAAkB,ECrYhB,uBAAuB;CDsY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,4BAA4B,AAAA,MAAM,CAAA;EAC9B,mBAAkB,EC1YhB,uBAAuB;CD2Y5B;;AARD,AAAA,uBAAuB;AACvB,8BAA8B,CAAR;EAClB,mBAAkB,ECpYR,+BAA+B;CDqY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,oCAAoC,AAAA,MAAM,CAAR;EAC9B,mBAAkB,ECzYR,+BAA+B;CD0Y5C;;AARD,AAAA,uBAAuB;AACvB,8BAA8B,CAAR;EAClB,mBAAkB,ECnYR,+BAA+B;CDoY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,oCAAoC,AAAA,MAAM,CAAR;EAC9B,mBAAkB,ECxYR,+BAA+B;CDyY5C;;AARD,AAAA,eAAe;AACf,sBAAsB,CAAA;EAClB,mBAAkB,EClYhB,uBAAuB;CDmY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,4BAA4B,AAAA,MAAM,CAAA;EAC9B,mBAAkB,ECvYhB,uBAAuB;CDwY5B;;AARD,AAAA,uBAAuB;AACvB,8BAA8B,CAAR;EAClB,mBAAkB,ECjYR,+BAA+B;CDkY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,oCAAoC,AAAA,MAAM,CAAR;EAC9B,mBAAkB,ECtYR,+BAA+B;CDuY5C;;AARD,AAAA,eAAe;AACf,sBAAsB,CAAA;EAClB,mBAAkB,EChYhB,uBAAuB;CDiY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,4BAA4B,AAAA,MAAM,CAAA;EAC9B,mBAAkB,ECrYhB,uBAAuB;CDsY5B;;AARD,AAAA,uBAAuB;AACvB,8BAA8B,CAAR;EAClB,mBAAkB,EC/XR,+BAA+B;CDgY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,oCAAoC,AAAA,MAAM,CAAR;EAC9B,mBAAkB,ECpYR,+BAA+B;CDqY5C;;AARD,AAAA,cAAc;AACd,qBAAqB,CAAC;EAClB,mBAAkB,EC9XjB,sBAAsB;CD+X1B;;AAED,AAAA,oBAAoB,AAAA,MAAM;AAC1B,2BAA2B,AAAA,MAAM,CAAC;EAC9B,mBAAkB,ECnYjB,sBAAsB;CDoY1B;;AARD,AAAA,sBAAsB;AACtB,6BAA6B,CAAP;EAClB,mBAAkB,EC7XT,8BAA8B;CD8X1C;;AAED,AAAA,4BAA4B,AAAA,MAAM;AAClC,mCAAmC,AAAA,MAAM,CAAP;EAC9B,mBAAkB,EClYT,8BAA8B;CDmY1C;;AARD,AAAA,qBAAqB;AACrB,4BAA4B,CAAN;EAClB,mBAAkB,EC5XV,6BAA6B;CD6XxC;;AAED,AAAA,2BAA2B,AAAA,MAAM;AACjC,kCAAkC,AAAA,MAAM,CAAN;EAC9B,mBAAkB,ECjYV,6BAA6B;CDkYxC;;AARD,AAAA,aAAa;AACb,oBAAoB,CAAE;EAClB,mBAAkB,EC3XlB,qBAAqB;CD4XxB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,0BAA0B,AAAA,MAAM,CAAE;EAC9B,mBAAkB,EChYlB,qBAAqB;CDiYxB;;AARD,AAAA,aAAa;AACb,oBAAoB,CAAE;EAClB,mBAAkB,EC1XlB,uBAAuB;CD2X1B;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,0BAA0B,AAAA,MAAM,CAAE;EAC9B,mBAAkB,EC/XlB,uBAAuB;CDgY1B;;AARD,AAAA,eAAe;AACf,sBAAsB,CAAA;EAClB,mBAAkB,ECzXhB,0BAA0B;CD0X/B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,4BAA4B,AAAA,MAAM,CAAA;EAC9B,mBAAkB,EC9XhB,0BAA0B;CD+X/B;;AARD,AAAA,YAAY;AACZ,mBAAmB,CAAG;EAClB,mBAAkB,ECxXnB,oBAAoB;CDyXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,mBAAkB,EC7XnB,oBAAoB;CD8XtB;;AARD,AAAA,kBAAkB;AAClB,yBAAyB,CAAH;EAClB,mBAAkB,ECvXb,gCAAgC;CDwXxC;;AAED,AAAA,wBAAwB,AAAA,MAAM;AAC9B,+BAA+B,AAAA,MAAM,CAAH;EAC9B,mBAAkB,EC5Xb,gCAAgC;CD6XxC;;AARD,AAAA,YAAY;AACZ,mBAAmB,CAAG;EAClB,mBAAkB,ECtXnB,qBAAqB;CDuXvB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,mBAAkB,EC3XnB,qBAAqB;CD4XvB;;AARD,AAAA,YAAY;AACZ,mBAAmB,CAAG;EAClB,mBAAkB,ECrXnB,uBAAuB;CDsXzB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,mBAAkB,EC1XnB,uBAAuB;CD2XzB;;AARD,AAAA,YAAY;AACZ,mBAAmB,CAAG;EAClB,mBAAkB,ECpXnB,oBAAoB;CDqXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,mBAAkB,ECzXnB,oBAAoB;CD0XtB;;AARD,AAAA,YAAY;AACZ,mBAAmB,CAAG;EAClB,mBAAkB,ECnXnB,OAAO;CDoXT;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,yBAAyB,AAAA,MAAM,CAAG;EAC9B,mBAAkB,ECxXnB,OAAO;CDyXT;;AAlBL,AAAA,OAAO;AACP,YAAY,CAAE;EACV,iBAAkB,EC/XZ,GAAG;EDgYT,iBAAkB,EAAe,KAAK;EACtC,iBAAkB,EAAe,sBAAsB;CAC1D;;AAKG,AAAA,aAAa;AACb,kBAAkB,CAAI;EAClB,iBAAkB,ECtYlB,kBAAkB;CDuYrB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,iBAAkB,EC3YlB,kBAAkB;CD4YrB;;AARD,AAAA,eAAe;AACf,oBAAoB,CAAE;EAClB,iBAAkB,ECrYhB,uBAAuB;CDsY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,0BAA0B,AAAA,MAAM,CAAE;EAC9B,iBAAkB,EC1YhB,uBAAuB;CD2Y5B;;AARD,AAAA,uBAAuB;AACvB,4BAA4B,CAAN;EAClB,iBAAkB,ECpYR,+BAA+B;CDqY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,kCAAkC,AAAA,MAAM,CAAN;EAC9B,iBAAkB,ECzYR,+BAA+B;CD0Y5C;;AARD,AAAA,uBAAuB;AACvB,4BAA4B,CAAN;EAClB,iBAAkB,ECnYR,+BAA+B;CDoY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,kCAAkC,AAAA,MAAM,CAAN;EAC9B,iBAAkB,ECxYR,+BAA+B;CDyY5C;;AARD,AAAA,eAAe;AACf,oBAAoB,CAAE;EAClB,iBAAkB,EClYhB,uBAAuB;CDmY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,0BAA0B,AAAA,MAAM,CAAE;EAC9B,iBAAkB,ECvYhB,uBAAuB;CDwY5B;;AARD,AAAA,uBAAuB;AACvB,4BAA4B,CAAN;EAClB,iBAAkB,ECjYR,+BAA+B;CDkY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,kCAAkC,AAAA,MAAM,CAAN;EAC9B,iBAAkB,ECtYR,+BAA+B;CDuY5C;;AARD,AAAA,eAAe;AACf,oBAAoB,CAAE;EAClB,iBAAkB,EChYhB,uBAAuB;CDiY5B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,0BAA0B,AAAA,MAAM,CAAE;EAC9B,iBAAkB,ECrYhB,uBAAuB;CDsY5B;;AARD,AAAA,uBAAuB;AACvB,4BAA4B,CAAN;EAClB,iBAAkB,EC/XR,+BAA+B;CDgY5C;;AAED,AAAA,6BAA6B,AAAA,MAAM;AACnC,kCAAkC,AAAA,MAAM,CAAN;EAC9B,iBAAkB,ECpYR,+BAA+B;CDqY5C;;AARD,AAAA,cAAc;AACd,mBAAmB,CAAG;EAClB,iBAAkB,EC9XjB,sBAAsB;CD+X1B;;AAED,AAAA,oBAAoB,AAAA,MAAM;AAC1B,yBAAyB,AAAA,MAAM,CAAG;EAC9B,iBAAkB,ECnYjB,sBAAsB;CDoY1B;;AARD,AAAA,sBAAsB;AACtB,2BAA2B,CAAL;EAClB,iBAAkB,EC7XT,8BAA8B;CD8X1C;;AAED,AAAA,4BAA4B,AAAA,MAAM;AAClC,iCAAiC,AAAA,MAAM,CAAL;EAC9B,iBAAkB,EClYT,8BAA8B;CDmY1C;;AARD,AAAA,qBAAqB;AACrB,0BAA0B,CAAJ;EAClB,iBAAkB,EC5XV,6BAA6B;CD6XxC;;AAED,AAAA,2BAA2B,AAAA,MAAM;AACjC,gCAAgC,AAAA,MAAM,CAAJ;EAC9B,iBAAkB,ECjYV,6BAA6B;CDkYxC;;AARD,AAAA,aAAa;AACb,kBAAkB,CAAI;EAClB,iBAAkB,EC3XlB,qBAAqB;CD4XxB;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,iBAAkB,EChYlB,qBAAqB;CDiYxB;;AARD,AAAA,aAAa;AACb,kBAAkB,CAAI;EAClB,iBAAkB,EC1XlB,uBAAuB;CD2X1B;;AAED,AAAA,mBAAmB,AAAA,MAAM;AACzB,wBAAwB,AAAA,MAAM,CAAI;EAC9B,iBAAkB,EC/XlB,uBAAuB;CDgY1B;;AARD,AAAA,eAAe;AACf,oBAAoB,CAAE;EAClB,iBAAkB,ECzXhB,0BAA0B;CD0X/B;;AAED,AAAA,qBAAqB,AAAA,MAAM;AAC3B,0BAA0B,AAAA,MAAM,CAAE;EAC9B,iBAAkB,EC9XhB,0BAA0B;CD+X/B;;AARD,AAAA,YAAY;AACZ,iBAAiB,CAAK;EAClB,iBAAkB,ECxXnB,oBAAoB;CDyXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,iBAAkB,EC7XnB,oBAAoB;CD8XtB;;AARD,AAAA,kBAAkB;AAClB,uBAAuB,CAAD;EAClB,iBAAkB,ECvXb,gCAAgC;CDwXxC;;AAED,AAAA,wBAAwB,AAAA,MAAM;AAC9B,6BAA6B,AAAA,MAAM,CAAD;EAC9B,iBAAkB,EC5Xb,gCAAgC;CD6XxC;;AARD,AAAA,YAAY;AACZ,iBAAiB,CAAK;EAClB,iBAAkB,ECtXnB,qBAAqB;CDuXvB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,iBAAkB,EC3XnB,qBAAqB;CD4XvB;;AARD,AAAA,YAAY;AACZ,iBAAiB,CAAK;EAClB,iBAAkB,ECrXnB,uBAAuB;CDsXzB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,iBAAkB,EC1XnB,uBAAuB;CD2XzB;;AARD,AAAA,YAAY;AACZ,iBAAiB,CAAK;EAClB,iBAAkB,ECpXnB,oBAAoB;CDqXtB;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,iBAAkB,ECzXnB,oBAAoB;CD0XtB;;AARD,AAAA,YAAY;AACZ,iBAAiB,CAAK;EAClB,iBAAkB,ECnXnB,OAAO;CDoXT;;AAED,AAAA,kBAAkB,AAAA,MAAM;AACxB,uBAAuB,AAAA,MAAM,CAAK;EAC9B,iBAAkB,ECxXnB,OAAO;CDyXT;;AAOD,AAAA,SAAS;AACT,aAAa,CAAO;EAChB,gBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,eAAe,CAAK;EAChB,kBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,gBAAgB,CAAI;EAChB,mBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,cAAc,CAAM;EAChB,iBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,aAAa,CAAO;EAChB,gBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,eAAe,CAAK;EAChB,kBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,gBAAgB,CAAI;EAChB,mBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,cAAc,CAAM;EAChB,iBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,aAAa,CAAO;EAChB,gBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,eAAe,CAAK;EAChB,kBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,gBAAgB,CAAI;EAChB,mBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,cAAc,CAAM;EAChB,iBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,aAAa,CAAO;EAChB,gBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,eAAe,CAAK;EAChB,kBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,gBAAgB,CAAI;EAChB,mBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,cAAc,CAAM;EAChB,iBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,aAAa,CAAO;EAChB,gBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,eAAe,CAAK;EAChB,kBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,gBAAgB,CAAI;EAChB,mBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,cAAc,CAAM;EAChB,iBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,aAAa,CAAO;EAChB,gBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,eAAe,CAAK;EAChB,kBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,gBAAgB,CAAI;EAChB,mBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,cAAc,CAAM;EAChB,iBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,aAAa,CAAO;EAChB,gBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,eAAe,CAAK;EAChB,kBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,gBAAgB,CAAI;EAChB,mBAAkB,EAAe,GAAiB;CACrD;;AAHD,AAAA,SAAS;AACT,cAAc,CAAM;EAChB,iBAAkB,EAAe,GAAiB;CACrD;;AAcD,AAAA,aAAa;AACb,iBAAiB,CAAG;EAChB,gBAAkB,EAZlB,KAAK;CAaR;;AAHD,AAAA,aAAa;AACb,mBAAmB,CAAC;EAChB,kBAAkB,EAZlB,KAAK;CAaR;;AAHD,AAAA,aAAa;AACb,oBAAoB,CAAA;EAChB,mBAAkB,EAZlB,KAAK;CAaR;;AAHD,AAAA,aAAa;AACb,kBAAkB,CAAE;EAChB,iBAAkB,EAZlB,KAAK;CAaR;;AAHD,AAAA,cAAc;AACd,kBAAkB,CAAE;EAChB,gBAAkB,EAX9B,MAAM;CAYG;;AAHD,AAAA,cAAc;AACd,oBAAoB,CAAA;EAChB,kBAAkB,EAX9B,MAAM;CAYG;;AAHD,AAAA,cAAc;AACd,qBAAqB,CAAD;EAChB,mBAAkB,EAX9B,MAAM;CAYG;;AAHD,AAAA,cAAc;AACd,mBAAmB,CAAC;EAChB,iBAAkB,EAX9B,MAAM;CAYG;;AAHD,AAAA,cAAc;AACd,kBAAkB,CAAE;EAChB,gBAAkB,EAV9B,MAAM;CAWG;;AAHD,AAAA,cAAc;AACd,oBAAoB,CAAA;EAChB,kBAAkB,EAV9B,MAAM;CAWG;;AAHD,AAAA,cAAc;AACd,qBAAqB,CAAD;EAChB,mBAAkB,EAV9B,MAAM;CAWG;;AAHD,AAAA,cAAc;AACd,mBAAmB,CAAC;EAChB,iBAAkB,EAV9B,MAAM;CAWG;;AAHD,AAAA,cAAc;AACd,kBAAkB,CAAE;EAChB,gBAAkB,EAT9B,MAAM;CAUG;;AAHD,AAAA,cAAc;AACd,oBAAoB,CAAA;EAChB,kBAAkB,EAT9B,MAAM;CAUG;;AAHD,AAAA,cAAc;AACd,qBAAqB,CAAD;EAChB,mBAAkB,EAT9B,MAAM;CAUG;;AAHD,AAAA,cAAc;AACd,mBAAmB,CAAC;EAChB,iBAAkB,EAT9B,MAAM;CAUG;;AAHD,AAAA,cAAc;AACd,kBAAkB,CAAE;EAChB,gBAAkB,EAR9B,MAAM;CASG;;AAHD,AAAA,cAAc;AACd,oBAAoB,CAAA;EAChB,kBAAkB,EAR9B,MAAM;CASG;;AAHD,AAAA,cAAc;AACd,qBAAqB,CAAD;EAChB,mBAAkB,EAR9B,MAAM;CASG;;AAHD,AAAA,cAAc;AACd,mBAAmB,CAAC;EAChB,iBAAkB,EAR9B,MAAM;CASG;;AAHD,AAAA,aAAa;AACb,iBAAiB,CAAG;EAChB,gBAAkB,EAP9B,KAAK;CAQI;;AAHD,AAAA,aAAa;AACb,mBAAmB,CAAC;EAChB,kBAAkB,EAP9B,KAAK;CAQI;;AAHD,AAAA,aAAa;AACb,oBAAoB,CAAA;EAChB,mBAAkB,EAP9B,KAAK;CAQI;;AAHD,AAAA,aAAa;AACb,kBAAkB,CAAE;EAChB,iBAAkB,EAP9B,KAAK;CAQI;;AAHD,AAAA,aAAa;AACb,iBAAiB,CAAG;EAChB,gBAAkB,EAN9B,KAAK;CAOI;;AAHD,AAAA,aAAa;AACb,mBAAmB,CAAC;EAChB,kBAAkB,EAN9B,KAAK;CAOI;;AAHD,AAAA,aAAa;AACb,oBAAoB,CAAA;EAChB,mBAAkB,EAN9B,KAAK;CAOI;;AAHD,AAAA,aAAa;AACb,kBAAkB,CAAE;EAChB,iBAAkB,EAN9B,KAAK;CAOI;;AAHD,AAAA,cAAc;AACd,kBAAkB,CAAE;EAChB,gBAAkB,EAL9B,MAAM;CAMG;;AAHD,AAAA,cAAc;AACd,oBAAoB,CAAA;EAChB,kBAAkB,EAL9B,MAAM;CAMG;;AAHD,AAAA,cAAc;AACd,qBAAqB,CAAD;EAChB,mBAAkB,EAL9B,MAAM;CAMG;;AAHD,AAAA,cAAc;AACd,mBAAmB,CAAC;EAChB,iBAAkB,EAL9B,MAAM;CAMG;;AAIT,AAAA,eAAe,CAAC;EACZ,aAAa,EAAE,cAAc;CAChC;;AAED,AAAA,cAAc,CAAC;EACX,aAAa,EAAE,gBAAgB;CAClC;;AAoCO,AAAA,UAAU;AACV,eAAe;AACf,iBAAiB;AACjB,sBAAsB,CAAK;EACvB,yBAAyB,EAAE,GAAkB,CAAC,UAAU;CAC3D;;AAED,AAAA,UAAU;AACV,eAAe;AACf,cAAc;AACd,mBAAmB,CAAK;EACpB,sBAAsB,EAAE,GAAkB,CAAC,UAAU;CACxD;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,iBAAiB;AACjB,uBAAuB,CAAK;EACxB,0BAA0B,EAAE,GAAkB,CAAC,UAAU;CAC5D;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,cAAc;AACd,oBAAoB,CAAK;EACrB,uBAAuB,EAAE,GAAkB,CAAC,UAAU;CACzD;;AAzDD,AAAA,QAAQ;AACR,aAAa;AACb,eAAe;AACf,oBAAoB,CAAC;EACjB,yBAAyB,EAAE,GAAkB;CAChD;;AAED,AAAA,QAAQ;AACR,aAAa;AACb,YAAY;AACZ,iBAAiB,CAAC;EACd,sBAAsB,EAAE,GAAkB;CAC7C;;AAED,AAAA,QAAQ;AACR,cAAc;AACd,eAAe;AACf,qBAAqB,CAAC;EAClB,0BAA0B,EAAE,GAAkB;CACjD;;AAED,AAAA,QAAQ;AACR,cAAc;AACd,YAAY;AACZ,kBAAkB,CAAC;EACf,uBAAuB,EAAE,GAAkB;CAC9C;;AAKD,AAAA,UAAU;AACV,eAAe;AACf,iBAAiB;AACjB,sBAAsB,CAAK;EACvB,yBAAyB,EAAE,GAAkB,CAAC,UAAU;CAC3D;;AAED,AAAA,UAAU;AACV,eAAe;AACf,cAAc;AACd,mBAAmB,CAAK;EACpB,sBAAsB,EAAE,GAAkB,CAAC,UAAU;CACxD;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,iBAAiB;AACjB,uBAAuB,CAAK;EACxB,0BAA0B,EAAE,GAAkB,CAAC,UAAU;CAC5D;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,cAAc;AACd,oBAAoB,CAAK;EACrB,uBAAuB,EAAE,GAAkB,CAAC,UAAU;CACzD;;AA1BD,AAAA,UAAU;AACV,eAAe;AACf,iBAAiB;AACjB,sBAAsB,CAAK;EACvB,yBAAyB,EAAE,GAAkB,CAAC,UAAU;CAC3D;;AAED,AAAA,UAAU;AACV,eAAe;AACf,cAAc;AACd,mBAAmB,CAAK;EACpB,sBAAsB,EAAE,GAAkB,CAAC,UAAU;CACxD;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,iBAAiB;AACjB,uBAAuB,CAAK;EACxB,0BAA0B,EAAE,GAAkB,CAAC,UAAU;CAC5D;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,cAAc;AACd,oBAAoB,CAAK;EACrB,uBAAuB,EAAE,GAAkB,CAAC,UAAU;CACzD;;AA1BD,AAAA,UAAU;AACV,eAAe;AACf,iBAAiB;AACjB,sBAAsB,CAAK;EACvB,yBAAyB,EAAE,GAAkB,CAAC,UAAU;CAC3D;;AAED,AAAA,UAAU;AACV,eAAe;AACf,cAAc;AACd,mBAAmB,CAAK;EACpB,sBAAsB,EAAE,GAAkB,CAAC,UAAU;CACxD;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,iBAAiB;AACjB,uBAAuB,CAAK;EACxB,0BAA0B,EAAE,GAAkB,CAAC,UAAU;CAC5D;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,cAAc;AACd,oBAAoB,CAAK;EACrB,uBAAuB,EAAE,GAAkB,CAAC,UAAU;CACzD;;AA1BD,AAAA,UAAU;AACV,eAAe;AACf,iBAAiB;AACjB,sBAAsB,CAAK;EACvB,yBAAyB,EAAE,IAAkB,CAAC,UAAU;CAC3D;;AAED,AAAA,UAAU;AACV,eAAe;AACf,cAAc;AACd,mBAAmB,CAAK;EACpB,sBAAsB,EAAE,IAAkB,CAAC,UAAU;CACxD;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,iBAAiB;AACjB,uBAAuB,CAAK;EACxB,0BAA0B,EAAE,IAAkB,CAAC,UAAU;CAC5D;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,cAAc;AACd,oBAAoB,CAAK;EACrB,uBAAuB,EAAE,IAAkB,CAAC,UAAU;CACzD;;AA1BD,AAAA,UAAU;AACV,eAAe;AACf,iBAAiB;AACjB,sBAAsB,CAAK;EACvB,yBAAyB,EAAE,IAAkB,CAAC,UAAU;CAC3D;;AAED,AAAA,UAAU;AACV,eAAe;AACf,cAAc;AACd,mBAAmB,CAAK;EACpB,sBAAsB,EAAE,IAAkB,CAAC,UAAU;CACxD;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,iBAAiB;AACjB,uBAAuB,CAAK;EACxB,0BAA0B,EAAE,IAAkB,CAAC,UAAU;CAC5D;;AAED,AAAA,UAAU;AACV,gBAAgB;AAChB,cAAc;AACd,oBAAoB,CAAK;EACrB,uBAAuB,EAAE,IAAkB,CAAC,UAAU;CACzD;;AAKJ,AAAD,iBAAM,CAAC;EACH,WAAW,EAAE,IAAI;CACpB;;AAEA,AAAD,mBAAQ,CAAC;EACL,WAAW,EAAE,MAAM;CACtB;;AAGI,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAFA,AAAD,gBAAK,CAAS;EACV,WAAW,EAFN,GAAC;CAGT;;AAKL,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,GAAG,CAAK;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAFD,AAAA,IAAI,CAAI;EACJ,SAAS,EAAE,IAAiB;CAC/B;;AAGL,AAAA,YAAY,CAAC;EACT,WAAW,EAAE,CAAC;CAOjB;;AAJQ,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,cAAG,CAAK;EACJ,WAAW,EAAE,IAAiB;CACjC;;AAFA,AAAD,eAAI,CAAI;EACJ,WAAW,EAAE,IAAiB;CACjC;;AASL,AAAA,gBAAgB;AAChB,kBAAkB,CAAA;EACd,UAAU,EAPN,MAAM;CAQb;;AAED,AAAA,gBAAgB;AAChB,kBAAkB,CAAA;EACd,UAAU,EAZN,MAAM;CAab;;AARD,AAAA,iBAAiB;AACjB,mBAAmB,CAAD;EACd,UAAU,EANlB,OAAO;CAOF;;AAED,AAAA,iBAAiB;AACjB,mBAAmB,CAAD;EACd,UAAU,EAXlB,OAAO;CAYF;;AARD,AAAA,cAAc;AACd,gBAAgB,CAAE;EACd,UAAU,EALlB,IAAI;CAMC;;AAED,AAAA,cAAc;AACd,gBAAgB,CAAE;EACd,UAAU,EAVlB,IAAI;CAWC;;AARD,AAAA,gBAAgB;AAChB,kBAAkB,CAAA;EACd,UAAU,EAJlB,MAAM;CAKD;;AAED,AAAA,gBAAgB;AAChB,kBAAkB,CAAA;EACd,UAAU,EATlB,MAAM;CAUD;;AAGL,AAAA,GAAG,CAAC;EACA,cAAc,EAAE,MAAM;CACzB;;AAED,AAAA,OAAO,CAAC;EACJ,UAAU,EAAE,oBAAoB;CASnC;;AAJQ,AAAD,aAAO,CAAC;EACJ,UAAU,EAAC,0BAA0C;CACxD;;AAFA,AAAD,eAAS,CAAD;EACJ,UAAU,EAAC,4BAA0C;CACxD;;AAFA,AAAD,YAAM,CAAE;EACJ,UAAU,EAAC,yBAA0C;CACxD;;AAIT,AAAA,QAAQ,CAAC;EACL,MAAM,EAAE,OAAO;CAClB", + "sources": [ + "../common.scss", + "../_variables.scss" + ], + "names": [], + "file": "common.css" +} \ No newline at end of file diff --git a/fastmovie-vue/scss/css/style.css b/fastmovie-vue/scss/css/style.css new file mode 100644 index 0000000..aba3431 --- /dev/null +++ b/fastmovie-vue/scss/css/style.css @@ -0,0 +1,698 @@ +html, +body { + padding: 0; + margin: 0; + font-size: 14px; + font-synthesis: none; + color: var(--el-text-color-primary); + background-color: var(--el-bg-color); +} + +a { + text-decoration: none; +} + +* { + outline: none; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.layouts { + width: 100%; + max-width: var(--layouts-width); + margin: 0 auto; + padding-left: 10px; + padding-right: 10px; +} + +.layouts-empty { + min-height: calc(100vh - var(--header-height) - var(--footer-height)); +} + +.el-main.layouts-main-scrollbar { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + overflow: hidden; +} + +.table-layouts { + overflow: hidden; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.z-index { + z-index: 1; +} + +.z-index-10 { + z-index: 10; +} + +.z-index-100 { + z-index: 100; +} + +.z-index-1000 { + z-index: 1000; +} + +.z-index-10000 { + z-index: 10000; +} + +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar:horizontal { + height: 6px; +} + +::-webkit-scrollbar-track { + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background-color: #E5EAF3; + border-radius: 10px; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} + +::-webkit-scrollbar-thumb:hover { + cursor: pointer; + background-color: #E5EAF3; +} + +.circular { + display: inline; + height: var(--el-loading-spinner-size); + width: var(--el-loading-spinner-size); + -webkit-animation: 2s linear 0s infinite normal none running loading-rotate; + animation: 2s linear 0s infinite normal none running loading-rotate; +} + +.el-text--small { + --el-link-font-size: var(--el-font-size-extra-small) !important; +} + +.el-switch__core .el-switch__action { + color: var(--el-switch-on-color) !important; +} + +.el-switch.is-checked .el-switch__core .el-switch__action { + color: var(--el-switch-on-color) !important; +} + +.el-radio { + margin-right: 10px !important; +} + +.el-button.el-button--success:not(.is-text) { + --el-button-text-color: var(--el-bg-color); +} + +.el-popper.is-red { + /* Set padding to ensure the height is 32px */ + padding: 6px 12px; + background: #f04d4d; + color: #FFFFFF; +} + +.el-popper.is-red .el-popper__arrow::before { + background: #f04d4d; + right: 0; +} + +.el-messagebox-width .el-message-box__message { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + overflow: hidden; +} + +.userinfo-menu { + width: 180px; + --el-dropdown-menuItem-hover-fill: var(--el-bg-color-page) !important; + --el-dropdown-menuItem-hover-color: var(--el-text-color-primary) !important; + --el-text-color-regular: var(--el-text-color-primary) !important; +} + +.userinfo-menu .el-dropdown-menu { + padding: 10px; +} + +.userinfo-menu .el-dropdown-menu__item { + border-radius: 6px; +} + +.userinfo-menu .el-dropdown-menu__item .el-icon { + color: var(--el-text-color-primary); + font-size: 16px; +} + +.userinfo-menu .el-dropdown-menu__item:hover { + background-color: var(--el-bg-color-page); +} + +.el-button--dark { + --el-button-bg-color: #303133; + --el-button-text-color: var(--el-color-white); + --el-button-border-color: #303133; + --el-button-hover-bg-color: rgb(110, 111, 112); + --el-button-hover-text-color: var(--el-color-white); + --el-button-hover-border-color: rgb(110, 111, 112); + --el-button-active-bg-color: rgb(42, 43, 45); + --el-button-active-border-color: rgb(42, 43, 45); +} + +.el-messagebox-payment { + padding: 0; +} + +.el-messagebox-payment .el-message-box__header, +.el-messagebox-payment .el-message-box__btns { + display: none; +} + +.el-messagebox-payment .el-message-box__message { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.el-messagebox-payment .recharge .close { + display: block !important; +} + +.languare-list .languare-item { + padding: 6px; + border-radius: 6px; + cursor: pointer; +} + +.languare-list .languare-item:hover { + color: var(--el-menu-hover-text-color); + background-color: var(--el-menu-hover-bg-color); +} + +.w-e-text-container [data-slate-editor] pre > code { + text-shadow: none !important; +} + +.el-loading-parent--relative .el-loading-spinner { + --el-loading-spinner-size: 20px; + --el-color-primary: #FFFFFF; +} + +.el-loading-parent--relative .el-loading-spinner .el-loading-text { + font-size: 12px; +} + +.action-dialog { + --el-dialog-border-radius: 10px; + --el-messagebox-font-size: 14px; + --max-height: calc(100vh - 32px); + max-height: var(--max-height); +} + +.action-dialog .el-message-box__header { + padding: 20px; + margin-right: 0; +} + +.action-dialog .el-message-box__header .el-message-box__title { + font-weight: 600; +} + +.action-dialog .el-message-box__header .el-message-box__headerbtn { + top: 26px; + right: 12px; +} + +.action-dialog .el-message-box__message { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding: 20px; + max-height: calc(var(--max-height) - 24px - 64px); +} + +.action-dialog .el-message-box__message .el-scrollbar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.action-dialog .el-message-box__message .el-scrollbar .el-form.layouts { + padding-top: 0px; +} + +.action-dialog .el-dialog__body .item-label { + width: 140px; +} + +.action-dialog .el-message-box__btns { + display: none; +} + +.full-action-dialog { + height: var(--max-height); +} + +.full-action-dialog .el-message-box__message { + height: calc(var(--max-height) - 24px - 64px); +} + +.el-form .none-label .el-form-item__label { + display: none !important; +} + +.el-form .el-form-item__content { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.el-form .submit-item .el-form-item__content { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.el-select .el-select-dropdown__item { + height: auto; +} + +.el-table td.el-table__cell .cell { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: 6px; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.el-message-box.x-login-message-box { + --el-messagebox-width: min(980px, 100%); + --el-messagebox-height: 550px; + --el-messagebox-padding-primary: 0px; + --el-messagebox-border-radius: 16px; + height: var(--el-messagebox-height); +} + +.el-message-box.x-login-message-box .el-message-box__message { + width: 100%; +} + +.el-message-box.x-login-message-box .el-message-box__btns { + display: none; +} + +.x-loading-message-box-modal .el-overlay-message-box { + padding: 0px !important; +} + +.el-message-box.x-loading-message-box { + --el-messagebox-width: 100vw; + --el-messagebox-height: 100vh; + --el-messagebox-padding-primary: 0px; + --el-messagebox-border-radius: 0px; + height: var(--el-messagebox-height); +} + +.el-message-box.x-loading-message-box .el-message-box__message { + height: var(--el-messagebox-height); + width: 100%; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 10px; +} + +.el-message-box.x-loading-message-box .el-message-box__btns { + display: none; +} + +.text-active { + color: transparent; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-image: -webkit-gradient(linear, right top, left top, from(#79FFFF), to(#0DF283)); + background-image: linear-gradient(to left, #79FFFF 0%, #0DF283 100%); +} + +.bg-active { + background-image: -webkit-gradient(linear, right top, left top, from(#79FFFF), to(#0DF283)); + background-image: linear-gradient(to left, #79FFFF 0%, #0DF283 100%); +} + +.el-overlay.el-modal-dialog { + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); +} + +.el-dialog.drama-dialog { + --el-dialog-width: min(100vw, 500px); + --el-dialog-padding-primary: 0; + --el-dialog-border-radius: 16px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 10px; +} + +.el-dialog.drama-dialog .el-dialog__header { + padding: 20px; + border-bottom: 1px solid #262626; + position: relative; +} + +.el-dialog.drama-dialog .el-dialog__header .el-dialog__headerbtn { + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + font-size: 20px; +} + +.el-dialog.drama-dialog .el-dialog__header .el-dialog__headerbtn .el-dialog__close { + color: var(--el-text-color-regular); +} + +.el-dialog.drama-dialog .el-dialog__body { + padding: 20px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 20px; +} + +.el-dialog.drama-dialog .el-dialog__footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding: 20px; + gap: 20px; +} + +.el-dialog.drama-dialog .el-dialog__footer .el-button { + min-width: 100px; + font-weight: 600; +} + +.el-dialog.drama-dialog .el-dialog__footer .el-button + .el-button { + margin-left: 0; +} + +.el-dialog.generate-scene-dialog, +.el-dialog.generate-storyboard-dialog { + --el-dialog-width: min(100vw, 500px); + --el-dialog-padding-primary: 0; + --el-dialog-border-radius: 16px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 10px; +} + +.el-dialog.generate-scene-dialog .el-dialog__header, +.el-dialog.generate-storyboard-dialog .el-dialog__header { + padding: 20px; + border-bottom: 1px solid #262626; + position: relative; +} + +.el-dialog.generate-scene-dialog .el-dialog__header .el-dialog__headerbtn, +.el-dialog.generate-storyboard-dialog .el-dialog__header .el-dialog__headerbtn { + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + font-size: 20px; +} + +.el-dialog.generate-scene-dialog .el-dialog__header .el-dialog__headerbtn .el-dialog__close, +.el-dialog.generate-storyboard-dialog .el-dialog__header .el-dialog__headerbtn .el-dialog__close { + color: var(--el-text-color-regular); +} + +.el-dialog.generate-scene-dialog .el-dialog__body, +.el-dialog.generate-storyboard-dialog .el-dialog__body { + padding: 20px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 20px; +} + +.el-dialog.generate-scene-dialog .el-dialog__footer, +.el-dialog.generate-storyboard-dialog .el-dialog__footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding: 20px; + gap: 20px; +} + +.el-dialog.generate-scene-dialog .el-dialog__footer .el-button, +.el-dialog.generate-storyboard-dialog .el-dialog__footer .el-button { + min-width: 100px; + font-weight: 600; +} + +.el-dialog.generate-scene-dialog .el-dialog__footer .el-button + .el-button, +.el-dialog.generate-storyboard-dialog .el-dialog__footer .el-button + .el-button { + margin-left: 0; +} + +.tabs-segmented.el-segmented { + --el-border-radius-base: 8px; + --el-segmented-bg-color: rgba(30, 30, 30, 0.4) !important; + --el-border-color: rgba(255, 255, 255, 0.3); + --el-segmented-item-hover-bg-color: rgba(255, 255, 255, 0.1); + --el-segmented-item-active-bg-color: rgba(255, 255, 255, 0.1); + --el-segmented-padding: 4px; + --el-segmented-item-selected-color: var(--el-bg-color); + --el-segmented-item-selected-bg-color: #FFFFFF; + --el-segmented-item-selected-color: var(--el-bg-color); + font-weight: 600; +} + +.tabs-segmented.el-segmented .el-segmented__item { + padding: 8px 0; + width: 120px; +} + +.tabs-segmented.el-segmented .el-segmented__group { + gap: 10px; +} + +.prompt-popper .el-mention-dropdown { + --el-mention-option-height: auto; +} + +.prompt-popper .el-mention-dropdown .el-mention-dropdown__list { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 6px; +} + +.prompt-popper .el-mention-dropdown .el-mention-dropdown__list .el-mention-dropdown__item { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + gap: 6px; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.episode-popover.el-popover { + --el-popover-bg-color: var(--el-mask-color-extra-light); + --el-bg-color-overlay: var(--el-popover-bg-color); + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); + min-width: 100px !important; +} + +.episode-popover.el-popover.is-light > .el-popper__arrow::before { + background: var(--el-popover-bg-color); + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +.el-popper.model-popover { + --el-popover-bg-color: rgba(30, 30, 30, 0.5); + -webkit-backdrop-filter: blur(20px); + backdrop-filter: blur(20px); + border-radius: 20px; + --el-popover-border-color: rgba(255, 255, 255, 0.1); + --el-box-shadow-light: none; + /* border: 0px solid rgba(255,255,255,0.3); */ +} + +.el-popper.model-popover .el-popper__arrow::before { + background-color: rgba(30, 30, 30, 0.5); +} + +.icon-aspect-ratio { + width: 34px; + height: 34px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.icon-aspect-ratio:before { + content: ''; + display: block; + border-radius: 4px; + -webkit-box-shadow: inset 0 0 0 2px var(--el-text-color-primary); + box-shadow: inset 0 0 0 2px var(--el-text-color-primary); +} + +.icon-aspect-ratio[view="9:16"]:before { + width: 14.625px; + height: 26px; +} + +.icon-aspect-ratio[view="16:9"]:before { + width: 26px; + height: 14.625px; +} + +.icon-aspect-ratio[view="3:4"]:before { + width: 19.5px; + height: 26px; +} + +.icon-aspect-ratio[view="4:3"]:before { + width: 26px; + height: 19.5px; +} + +.icon-aspect-ratio[view="1:1"]:before { + width: 26px; + height: 26px; +} + +.icon-aspect-ratio[view="2:3"]:before { + width: 17.333px; + height: 26px; +} + +.icon-aspect-ratio[view="3:2"]:before { + width: 26px; + height: 17.333px; +} + +.input-button { + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border-radius: 20px; + cursor: pointer; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding-top: 2px; + padding-bottom: 2px; +} + +.input-button-selected { + background: rgba(255, 255, 255, 0.08); +} + +.input-button:hover { + background: rgba(255, 255, 255, 0.16); +} + +.el-dialog__headerbtn:focus .el-dialog__close, .el-dialog__headerbtn:hover .el-dialog__close { + color: var(--el-color-success) !important; +} +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/fastmovie-vue/scss/css/style.css.map b/fastmovie-vue/scss/css/style.css.map new file mode 100644 index 0000000..00fa504 --- /dev/null +++ b/fastmovie-vue/scss/css/style.css.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "mappings": "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", + "sources": [ + "../style.scss" + ], + "names": [], + "file": "style.css" +} \ No newline at end of file diff --git a/fastmovie-vue/scss/css/theme.css b/fastmovie-vue/scss/css/theme.css new file mode 100644 index 0000000..48e96b0 --- /dev/null +++ b/fastmovie-vue/scss/css/theme.css @@ -0,0 +1,120 @@ +html[data-theme="dark"] { + color-scheme: dark; + --el-color-primary: #006EFF; + --el-color-primary-light-3: rgb(6, 83, 185); + --el-color-primary-light-5: #2a598a; + --el-color-primary-light-7: #213d5b; + --el-color-primary-light-8: #1d3043; + --el-color-primary-light-9: #18222c; + --el-color-primary-dark-2: rgb(51, 139, 255); + --el-color-success: #0DF283; + --el-color-success-light-3: rgb(25, 150, 75); + --el-color-success-light-5: #3e6b27; + --el-color-success-light-7: #2d481f; + --el-color-success-light-8: #25371c; + --el-color-success-light-9: #1c2518; + --el-color-success-dark-2: rgb(73, 215, 130); + --el-color-warning: #FBAC15; + --el-color-warning-light-3: rgb(182, 126, 21); + --el-color-warning-light-5: #7d5b28; + --el-color-warning-light-7: #533f20; + --el-color-warning-light-8: #3e301c; + --el-color-warning-light-9: #292218; + --el-color-warning-dark-2: rgb(252, 189, 68); + --el-color-danger: #E4221C; + --el-color-danger-light-3: rgb(166, 30, 26); + --el-color-danger-light-5: #854040; + --el-color-danger-light-7: #582e2e; + --el-color-danger-light-8: #412626; + --el-color-danger-light-9: #2b1d1d; + --el-color-danger-dark-2: rgb(233, 78, 73); + --el-color-error: #E4221C; + --el-color-error-light-3: rgb(166, 30, 26); + --el-color-error-light-5: #854040; + --el-color-error-light-7: #582e2e; + --el-color-error-light-8: #412626; + --el-color-error-light-9: #2b1d1d; + --el-color-error-dark-2: rgb(233, 78, 73); + --el-color-info: #909399; + --el-color-info-light-3: #6b6d71; + --el-color-info-light-5: #525457; + --el-color-info-light-7: #393a3c; + --el-color-info-light-8: #2d2d2f; + --el-color-info-light-9: #202121; + --el-color-info-dark-2: #a6a9ad; + --el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, .36), 0px 8px 20px rgba(0, 0, 0, .72); + --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, .72); + --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .72); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, .72), 0px 12px 32px #000000, 0px 8px 16px -8px #000000; + --el-bg-color-page: #1E1E1E; + --el-bg-color: #0C0C0D; + --el-bg-color-rgb-r: 10; + --el-bg-color-rgb-g: 10; + --el-bg-color-rgb-b: 10; + --el-bg-color-overlay: #171717; + --el-text-color-primary: #E5EAF3; + --el-text-color-regular: #CFD3DC; + --el-text-color-secondary: #A3A6AD; + --el-text-color-placeholder: #8D9095; + --el-text-color-disabled: #6C6E72; + --el-border-color-darker: #636466; + --el-border-color-dark: #58585B; + --el-border-color: #272727; + --el-border-color-light: #414243; + --el-border-color-lighter: #363637; + --el-border-color-extra-light: #2B2B2C; + --el-fill-color-darker: #424243; + --el-fill-color-dark: #39393A; + --el-fill-color: #303030; + --el-fill-color-light: #262727; + --el-fill-color-lighter: #1D1D1D; + --el-fill-color-extra-light: #191919; + --el-fill-color-blank: transparent; + --el-mask-color: rgba(0, 0, 0, .8); + --el-mask-color-extra-light: rgba(0, 0, 0, .3); +} + +html[data-theme="dark"] .el-button { + --el-button-disabled-text-color: rgba(255, 255, 255, .5) +; +} + +html[data-theme="dark"] .el-card { + --el-card-bg-color: var(--el-bg-color-overlay) +; +} + +html[data-theme="dark"] .el-empty { + --el-empty-fill-color-0: var(--el-color-black); + --el-empty-fill-color-1: #4b4b52; + --el-empty-fill-color-2: #36383d; + --el-empty-fill-color-3: #1e1e20; + --el-empty-fill-color-4: #262629; + --el-empty-fill-color-5: #202124; + --el-empty-fill-color-6: #212224; + --el-empty-fill-color-7: #1b1c1f; + --el-empty-fill-color-8: #1c1d1f; + --el-empty-fill-color-9: #18181a; +} + +html[data-theme="light"] { + --el-bg-color-rgb-r: 255; + --el-bg-color-rgb-g: 255; + --el-bg-color-rgb-b: 255; + --el-color-primary: #006EFF; + --el-color-primary-light-3: rgb(77, 154, 255); + --el-color-primary-dark-2: rgb(4, 92, 208); + --el-color-success: #1BCD63; + --el-color-success-light-3: rgb(95, 220, 146); + --el-color-success-dark-2: rgb(26, 168, 83); + --el-color-warning: #FBAC15; + --el-color-warning-light-3: rgb(252, 197, 91); + --el-color-warning-dark-2: rgb(205, 142, 21); + --el-color-danger: #E4221C; + --el-color-danger-light-3: rgb(236, 100, 96); + --el-color-danger-dark-2: rgb(186, 31, 26); + --el-color-error: #E4221C; + --el-color-error-light-3: rgb(236, 100, 96); + --el-color-error-dark-2: rgb(186, 31, 26); +} +/*# sourceMappingURL=theme.css.map */ \ No newline at end of file diff --git a/fastmovie-vue/scss/css/theme.css.map b/fastmovie-vue/scss/css/theme.css.map new file mode 100644 index 0000000..f683f29 --- /dev/null +++ b/fastmovie-vue/scss/css/theme.css.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "mappings": "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", + "sources": [ + "../theme.scss" + ], + "names": [], + "file": "theme.css" +} \ No newline at end of file diff --git a/fastmovie-vue/scss/style.scss b/fastmovie-vue/scss/style.scss new file mode 100644 index 0000000..6d22b33 --- /dev/null +++ b/fastmovie-vue/scss/style.scss @@ -0,0 +1,619 @@ +html, +body { + padding: 0; + margin: 0; + font-size: 14px; + font-synthesis: none; + color: var(--el-text-color-primary); + background-color: var(--el-bg-color); +} + +a { + text-decoration: none; +} + +* { + outline: none; + box-sizing: border-box; +} + +.layouts { + width: 100%; + max-width: var(--layouts-width); + margin: 0 auto; + padding-left: 10px; + padding-right: 10px; +} + +.layouts-empty { + min-height: calc(100vh - var(--header-height) - var(--footer-height)); +} + +.el-main.layouts-main-scrollbar { + display: flex !important; + flex-direction: column; + overflow: hidden; +} + +.table-layouts { + overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; +} + +.z-index { + z-index: 1 +} + +.z-index-10 { + z-index: 10; +} + +.z-index-100 { + z-index: 100; +} + +.z-index-1000 { + z-index: 1000; +} + +.z-index-10000 { + z-index: 10000; +} + +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar:horizontal { + height: 6px +} + +::-webkit-scrollbar-track { + border-radius: 10px +} + +::-webkit-scrollbar-thumb { + background-color: #E5EAF3; + border-radius: 10px; + transition: all .2s ease-in-out +} + +::-webkit-scrollbar-thumb:hover { + cursor: pointer; + background-color: #E5EAF3 +} + +.circular { + display: inline; + height: var(--el-loading-spinner-size); + width: var(--el-loading-spinner-size); + animation: 2s linear 0s infinite normal none running loading-rotate; +} + +.el-text--small { + --el-link-font-size: var(--el-font-size-extra-small) !important; +} + +.el-switch__core .el-switch__action { + color: var(--el-switch-on-color) !important; +} + +.el-switch.is-checked .el-switch__core .el-switch__action { + color: var(--el-switch-on-color) !important; +} + +.el-radio { + margin-right: 10px !important; +} + +.el-button.el-button--success:not(.is-text) { + --el-button-text-color: var(--el-bg-color); +} + +.el-popper.is-red { + /* Set padding to ensure the height is 32px */ + padding: 6px 12px; + background: rgb(240, 77, 77); + color: #FFFFFF; +} + +.el-popper.is-red .el-popper__arrow::before { + background: rgb(240, 77, 77); + right: 0; +} + + +.el-messagebox-width { + .el-message-box__message { + flex: 1; + overflow: hidden; + } +} + +.userinfo-menu { + width: 180px; + --el-dropdown-menuItem-hover-fill: var(--el-bg-color-page) !important; + --el-dropdown-menuItem-hover-color: var(--el-text-color-primary) !important; + --el-text-color-regular: var(--el-text-color-primary) !important; + + .el-dropdown-menu { + padding: 10px; + } + + .el-dropdown-menu__item { + .el-icon { + color: var(--el-text-color-primary); + font-size: 16px; + } + + border-radius: 6px; + } + + .el-dropdown-menu__item:hover { + background-color: var(--el-bg-color-page); + } +} + +.el-button--dark { + --el-button-bg-color: #303133; + --el-button-text-color: var(--el-color-white); + --el-button-border-color: #303133; + --el-button-hover-bg-color: rgb(110, 111, 112); + --el-button-hover-text-color: var(--el-color-white); + --el-button-hover-border-color: rgb(110, 111, 112); + --el-button-active-bg-color: rgb(42, 43, 45); + --el-button-active-border-color: rgb(42, 43, 45); +} + +.el-messagebox-payment { + + .el-message-box__header, + .el-message-box__btns { + display: none; + } + + .el-message-box__message { + flex: 1; + } + + padding: 0; + + .recharge { + .close { + display: block !important; + } + } +} + +.languare-list { + .languare-item { + padding: 6px; + border-radius: 6px; + cursor: pointer; + + &:hover { + color: var(--el-menu-hover-text-color); + background-color: var(--el-menu-hover-bg-color); + } + } + +} + +.w-e-text-container [data-slate-editor] pre>code { + text-shadow: none !important; +} + +.el-loading-parent--relative { + .el-loading-spinner { + --el-loading-spinner-size: 20px; + --el-color-primary: #FFFFFF; + + .el-loading-text { + font-size: 12px; + } + } +} + +.action-dialog { + --el-dialog-border-radius: 10px; + --el-messagebox-font-size: 14px; + --max-height: calc(100vh - 32px); + max-height: var(--max-height); + + .el-message-box__header { + padding: 20px; + margin-right: 0; + + .el-message-box__title { + font-weight: 600; + } + + .el-message-box__headerbtn { + top: 26px; + right: 12px; + } + } + + .el-message-box__message { + display: flex; + flex-direction: column; + padding: 20px; + max-height: calc(var(--max-height) - 24px - 64px); + + .el-scrollbar { + display: flex; + flex-direction: column; + + .el-form.layouts { + padding-top: 0px; + } + } + } + + .el-dialog__body .item-label { + width: 140px; + } + + .el-message-box__btns { + display: none; + } +} + +.full-action-dialog { + height: var(--max-height); + + .el-message-box__message { + height: calc(var(--max-height) - 24px - 64px); + } +} + +.el-form { + .none-label { + .el-form-item__label { + display: none !important; + } + } + + .el-form-item__content { + flex-direction: column; + justify-content: center; + align-items: flex-start; + } + + .submit-item { + .el-form-item__content { + flex-direction: row; + justify-content: flex-start; + align-items: center; + } + } +} + +.el-select { + .el-select-dropdown__item { + height: auto; + } +} + +.el-table td.el-table__cell .cell { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.el-message-box.x-login-message-box { + --el-messagebox-width: min(980px, 100%); + --el-messagebox-height: 550px; + --el-messagebox-padding-primary: 0px; + --el-messagebox-border-radius: 16px; + height: var(--el-messagebox-height); + + .el-message-box__message { + width: 100%; + } + + .el-message-box__btns { + display: none; + } +} + +.x-loading-message-box-modal { + .el-overlay-message-box { + padding: 0px !important; + } +} + +.el-message-box.x-loading-message-box { + --el-messagebox-width: 100vw; + --el-messagebox-height: 100vh; + --el-messagebox-padding-primary: 0px; + --el-messagebox-border-radius: 0px; + height: var(--el-messagebox-height); + + .el-message-box__message { + height: var(--el-messagebox-height); + width: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 10px; + } + + .el-message-box__btns { + display: none; + } +} + +.text-active { + color: transparent; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-image: linear-gradient(to left, #79FFFF 0%, #0DF283 100%); +} + +.bg-active { + background-image: linear-gradient(to left, #79FFFF 0%, #0DF283 100%); +} + +.el-overlay.el-modal-dialog { + backdrop-filter: blur(6px); +} + +.el-dialog.drama-dialog { + --el-dialog-width: min(100vw, 500px); + --el-dialog-padding-primary: 0; + --el-dialog-border-radius: 16px; + display: flex; + flex-direction: column; + gap: 10px; + + .el-dialog__header { + padding: 20px; + border-bottom: 1px solid #262626; + position: relative; + + .el-dialog__headerbtn { + top: 50%; + transform: translateY(-50%); + font-size: 20px; + + .el-dialog__close { + color: var(--el-text-color-regular); + } + } + } + + .el-dialog__body { + padding: 20px; + display: flex; + flex-direction: column; + gap: 20px; + } + + .el-dialog__footer { + display: flex; + justify-content: center; + padding: 20px; + gap: 20px; + + .el-button { + min-width: 100px; + font-weight: 600; + } + + .el-button+.el-button { + margin-left: 0; + } + } +} + +.el-dialog.generate-scene-dialog, +.el-dialog.generate-storyboard-dialog { + --el-dialog-width: min(100vw, 500px); + --el-dialog-padding-primary: 0; + --el-dialog-border-radius: 16px; + display: flex; + flex-direction: column; + gap: 10px; + + .el-dialog__header { + padding: 20px; + border-bottom: 1px solid #262626; + position: relative; + + .el-dialog__headerbtn { + top: 50%; + transform: translateY(-50%); + font-size: 20px; + + .el-dialog__close { + color: var(--el-text-color-regular); + } + } + } + + .el-dialog__body { + padding: 20px; + display: flex; + flex-direction: column; + gap: 20px; + } + + .el-dialog__footer { + display: flex; + justify-content: center; + padding: 20px; + gap: 20px; + + .el-button { + min-width: 100px; + font-weight: 600; + } + + .el-button+.el-button { + margin-left: 0; + } + } +} + +.tabs-segmented.el-segmented { + --el-border-radius-base: 8px; + --el-segmented-bg-color: rgba(30, 30, 30, 0.4) !important; + --el-border-color: rgba(255, 255, 255, 0.3); + --el-segmented-item-hover-bg-color: rgba(255, 255, 255, 0.1); + --el-segmented-item-active-bg-color: rgba(255, 255, 255, 0.1); + --el-segmented-padding: 4px; + --el-segmented-item-selected-color: var(--el-bg-color); + --el-segmented-item-selected-bg-color: #FFFFFF; + --el-segmented-item-selected-color: var(--el-bg-color); + font-weight: 600; + + .el-segmented__item { + padding: 8px 0; + width: 120px; + } + + .el-segmented__group { + gap: 10px; + } + + // .el-segmented__item-selected { + // background-image: linear-gradient(to left, #79FFFF 0%, #0DF283 100%); + // } +} + +.prompt-popper { + .el-mention-dropdown { + --el-mention-option-height: auto; + + .el-mention-dropdown__list { + display: flex; + flex-direction: column; + gap: 6px; + + .el-mention-dropdown__item { + display: flex; + gap: 6px; + align-items: center; + justify-content: center; + } + } + } +} + +.episode-popover.el-popover { + --el-popover-bg-color: var(--el-mask-color-extra-light); + --el-bg-color-overlay: var(--el-popover-bg-color); + backdrop-filter: blur(6px); + min-width: 100px !important; + + &.is-light>.el-popper__arrow::before { + background: var(--el-popover-bg-color); + backdrop-filter: blur(4px); + } +} + +.el-popper.model-popover { + --el-popover-bg-color: rgba(30, 30, 30, 0.5); + backdrop-filter: blur(20px); + border-radius: 20px; + --el-popover-border-color: rgba(255, 255, 255, 0.1); + --el-box-shadow-light: none; + + /* border: 0px solid rgba(255,255,255,0.3); */ + .el-popper__arrow::before { + background-color: rgba(30, 30, 30, 0.5); + } +} + + +.icon-aspect-ratio { + width: 34px; + height: 34px; + display: flex; + align-items: center; + justify-content: center; + + &:before { + content: ''; + display: block; + border-radius: 4px; + box-shadow: inset 0 0 0 2px var(--el-text-color-primary); + } + + &[view="9:16"] { + &:before { + width: 14.625px; + height: 26px; + } + } + + &[view="16:9"] { + &:before { + width: 26px; + height: 14.625px; + } + } + + &[view="3:4"] { + &:before { + width: 19.5px; + height: 26px; + } + } + + &[view="4:3"] { + &:before { + width: 26px; + height: 19.5px; + } + } + + &[view="1:1"] { + &:before { + width: 26px; + height: 26px; + } + } + + &[view="2:3"] { + &:before { + width: 17.333px; + height: 26px; + } + } + + &[view="3:2"] { + &:before { + width: 26px; + height: 17.333px; + } + } +} + +.input-button { + backdrop-filter: blur(10px); + border-radius: 20px; + cursor: pointer; + display: flex; + padding-top: 2px; + padding-bottom: 2px; + + &-selected { + background: rgba(255, 255, 255, 0.08); + } + + &:hover { + background: rgba(255, 255, 255, 0.16); + } +} + + +.el-dialog__headerbtn:focus .el-dialog__close, .el-dialog__headerbtn:hover .el-dialog__close{ + color: var(--el-color-success) !important; +} \ No newline at end of file diff --git a/fastmovie-vue/scss/theme.scss b/fastmovie-vue/scss/theme.scss new file mode 100644 index 0000000..c8fa19d --- /dev/null +++ b/fastmovie-vue/scss/theme.scss @@ -0,0 +1,117 @@ +html[data-theme="dark"] { + color-scheme: dark; + --el-color-primary: #006EFF; + --el-color-primary-light-3: rgb(6, 83, 185); + --el-color-primary-light-5: #2a598a; + --el-color-primary-light-7: #213d5b; + --el-color-primary-light-8: #1d3043; + --el-color-primary-light-9: #18222c; + --el-color-primary-dark-2: rgb(51, 139, 255); + --el-color-success: #0DF283; + --el-color-success-light-3: rgb(25, 150, 75); + --el-color-success-light-5: #3e6b27; + --el-color-success-light-7: #2d481f; + --el-color-success-light-8: #25371c; + --el-color-success-light-9: #1c2518; + --el-color-success-dark-2: rgb(73, 215, 130); + --el-color-warning: #FBAC15; + --el-color-warning-light-3: rgb(182, 126, 21); + --el-color-warning-light-5: #7d5b28; + --el-color-warning-light-7: #533f20; + --el-color-warning-light-8: #3e301c; + --el-color-warning-light-9: #292218; + --el-color-warning-dark-2: rgb(252, 189, 68); + --el-color-danger: #E4221C; + --el-color-danger-light-3: rgb(166, 30, 26); + --el-color-danger-light-5: #854040; + --el-color-danger-light-7: #582e2e; + --el-color-danger-light-8: #412626; + --el-color-danger-light-9: #2b1d1d; + --el-color-danger-dark-2: rgb(233, 78, 73); + --el-color-error: #E4221C; + --el-color-error-light-3: rgb(166, 30, 26); + --el-color-error-light-5: #854040; + --el-color-error-light-7: #582e2e; + --el-color-error-light-8: #412626; + --el-color-error-light-9: #2b1d1d; + --el-color-error-dark-2: rgb(233, 78, 73); + --el-color-info: #909399; + --el-color-info-light-3: #6b6d71; + --el-color-info-light-5: #525457; + --el-color-info-light-7: #393a3c; + --el-color-info-light-8: #2d2d2f; + --el-color-info-light-9: #202121; + --el-color-info-dark-2: #a6a9ad; + --el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, .36), 0px 8px 20px rgba(0, 0, 0, .72); + --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, .72); + --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .72); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, .72), 0px 12px 32px #000000, 0px 8px 16px -8px #000000; + --el-bg-color-page: #1E1E1E; + --el-bg-color: #0C0C0D; + --el-bg-color-rgb-r: 10; + --el-bg-color-rgb-g: 10; + --el-bg-color-rgb-b: 10; + --el-bg-color-overlay: #171717; + --el-text-color-primary: #E5EAF3; + --el-text-color-regular: #CFD3DC; + --el-text-color-secondary: #A3A6AD; + --el-text-color-placeholder: #8D9095; + --el-text-color-disabled: #6C6E72; + --el-border-color-darker: #636466; + --el-border-color-dark: #58585B; + --el-border-color: #272727; + --el-border-color-light: #414243; + --el-border-color-lighter: #363637; + --el-border-color-extra-light: #2B2B2C; + --el-fill-color-darker: #424243; + --el-fill-color-dark: #39393A; + --el-fill-color: #303030; + --el-fill-color-light: #262727; + --el-fill-color-lighter: #1D1D1D; + --el-fill-color-extra-light: #191919; + --el-fill-color-blank: transparent; + --el-mask-color: rgba(0, 0, 0, .8); + --el-mask-color-extra-light: rgba(0, 0, 0, .3); +} + +html[data-theme="dark"] .el-button { + --el-button-disabled-text-color: rgba(255, 255, 255, .5) +} + +html[data-theme="dark"] .el-card { + --el-card-bg-color: var(--el-bg-color-overlay) +} + +html[data-theme="dark"] .el-empty { + --el-empty-fill-color-0: var(--el-color-black); + --el-empty-fill-color-1: #4b4b52; + --el-empty-fill-color-2: #36383d; + --el-empty-fill-color-3: #1e1e20; + --el-empty-fill-color-4: #262629; + --el-empty-fill-color-5: #202124; + --el-empty-fill-color-6: #212224; + --el-empty-fill-color-7: #1b1c1f; + --el-empty-fill-color-8: #1c1d1f; + --el-empty-fill-color-9: #18181a; +} + +html[data-theme="light"] { + --el-bg-color-rgb-r: 255; + --el-bg-color-rgb-g: 255; + --el-bg-color-rgb-b: 255; + --el-color-primary: #006EFF; + --el-color-primary-light-3: rgb(77, 154, 255); + --el-color-primary-dark-2: rgb(4, 92, 208); + --el-color-success: #1BCD63; + --el-color-success-light-3: rgb(95, 220, 146); + --el-color-success-dark-2: rgb(26, 168, 83); + --el-color-warning: #FBAC15; + --el-color-warning-light-3: rgb(252, 197, 91); + --el-color-warning-dark-2: rgb(205, 142, 21); + --el-color-danger: #E4221C; + --el-color-danger-light-3: rgb(236, 100, 96); + --el-color-danger-dark-2: rgb(186, 31, 26); + --el-color-error: #E4221C; + --el-color-error-light-3: rgb(236, 100, 96); + --el-color-error-dark-2: rgb(186, 31, 26); +} \ No newline at end of file diff --git a/fastmovie-vue/shared/types/index.d.ts b/fastmovie-vue/shared/types/index.d.ts new file mode 100644 index 0000000..45ef84e --- /dev/null +++ b/fastmovie-vue/shared/types/index.d.ts @@ -0,0 +1,105 @@ +/// +interface ImportMateEnv { + readonly VITE_APP_TITLE: string, + readonly VITE_REQUEST_BASE_URL: string | undefined +} +interface ImportMate { + readonly env: ImportMateEnv +} +export { }; +declare global { + + interface ObjectInterface { + [propName: string]: any + } + type StorageInterface = string | string[] | object | object[] | number | number[] | null | ObjectInterface | ObjectInterface[] + type LanguageInterface = 'zh-CN' | 'en'; + + interface WebConfigInterface { + web_name?: string, + web_logo?: string, + web_icp?: string, + web_mps?: string, + web_mps_text?: string, + web_title?: string, + version_name?: string, + version?: number, + copyright?: string, + [propName: string]: any + } + interface WalletInterface { + uid: string, + channels_uid: number, + balance: number, + balance_sum: number, + balance_used: number, + points: number, + points_sum: number, + points_used: number, + tmp_points: number, + tmp_points_sum: number, + tmp_points_used: number, + available_points: number + } + interface StateInterface { + AsideState: boolean + NotMenusAsideState: boolean + language: LanguageInterface + InputFocusState: boolean + } + interface LanguageListInterface { + label: string, + value: LanguageInterface + } + interface UserInfoInterface { + nickname: string, + token: string, + [propName: string]: any + } + interface ModelInterface { + creative_script: any + creative_episode: any + creative_scenes: any + creative_storyboards: any + drama_cover: any + scene_image: any + actor_image: any + actor_three_view_image: any + storyboard_image: any + character_look_costume: any + actor_costume: any + actor_costume_three_view: any + prop_image: any + prop_three_view_image: any + storyboard_video: any + dialogue_voice: any + storyboard_narration_voice: any + storyboard_sfx_voice: any + storyboard_music_voice: any + [propName: string]: any + } + interface TrackResourceInterface { + id: string; + duration: number; + narration: string; + use_material_type: string; + video: string; + image: string; + dialogue_audio: string[]; + narration_audio: string; + sfx_audio: string; + dialogues: any[]; + } + interface TrackInterface { + video: string[]; + video_audio: string[]; + dialogue_audio: string[]; + narration_audio: string[]; + sfx_audio: string[]; + } + interface TimeTask { + nextTime: number; // 下一次触发时间(绝对时间) + cancelled: boolean; + run(now: number): void; + } +} \ No newline at end of file diff --git a/fastmovie-vue/src/App.vue b/fastmovie-vue/src/App.vue new file mode 100644 index 0000000..2628e51 --- /dev/null +++ b/fastmovie-vue/src/App.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/fastmovie-vue/src/assets/css/common.min.css b/fastmovie-vue/src/assets/css/common.min.css new file mode 100644 index 0000000..411fb0f --- /dev/null +++ b/fastmovie-vue/src/assets/css/common.min.css @@ -0,0 +1,2 @@ +.pb-safe-area{padding-bottom:constant(safe-area-inset-bottom) !important;padding-bottom:env(safe-area-inset-bottom) !important}.pt-safe-area{padding-top:constant(safe-area-inset-top) !important;padding-top:env(safe-area-inset-top) !important}.mx-auto,.ml-auto{margin-left:auto !important}.mx-auto,.mr-auto{margin-right:auto !important}.p-0,.pl-0,.px-0{padding-left:0px !important}.p-0,.pr-0,.px-0{padding-right:0px !important}.p-0,.pt-0,.py-0{padding-top:0px !important}.p-0,.pb-0,.py-0{padding-bottom:0px !important}.m-0,.ml-0,.mx-0{margin-left:0px !important}.m-0,.mr-0,.mx-0{margin-right:0px !important}.m-0,.mt-0,.my-0{margin-top:0px !important}.m-0,.mb-0,.my-0{margin-bottom:0px !important}.top-0{top:0px !important}.left-0{left:0px !important}.right-0{right:0px !important}.bottom-0{bottom:0px !important}.top--0{top:0px !important}.left--0{left:0px !important}.right--0{right:0px !important}.bottom--0{bottom:0px !important}.p-1,.pl-1,.px-1{padding-left:3px !important}.p-1,.pr-1,.px-1{padding-right:3px !important}.p-1,.pt-1,.py-1{padding-top:3px !important}.p-1,.pb-1,.py-1{padding-bottom:3px !important}.m-1,.ml-1,.mx-1{margin-left:3px !important}.m-1,.mr-1,.mx-1{margin-right:3px !important}.m-1,.mt-1,.my-1{margin-top:3px !important}.m-1,.mb-1,.my-1{margin-bottom:3px !important}.top-1{top:3px !important}.left-1{left:3px !important}.right-1{right:3px !important}.bottom-1{bottom:3px !important}.top--1{top:-3px !important}.left--1{left:-3px !important}.right--1{right:-3px !important}.bottom--1{bottom:-3px !important}.p-2,.pl-2,.px-2{padding-left:6px !important}.p-2,.pr-2,.px-2{padding-right:6px !important}.p-2,.pt-2,.py-2{padding-top:6px !important}.p-2,.pb-2,.py-2{padding-bottom:6px !important}.m-2,.ml-2,.mx-2{margin-left:6px !important}.m-2,.mr-2,.mx-2{margin-right:6px !important}.m-2,.mt-2,.my-2{margin-top:6px !important}.m-2,.mb-2,.my-2{margin-bottom:6px !important}.top-2{top:6px !important}.left-2{left:6px !important}.right-2{right:6px !important}.bottom-2{bottom:6px !important}.top--2{top:-6px !important}.left--2{left:-6px !important}.right--2{right:-6px !important}.bottom--2{bottom:-6px !important}.p-3,.pl-3,.px-3{padding-left:9px !important}.p-3,.pr-3,.px-3{padding-right:9px !important}.p-3,.pt-3,.py-3{padding-top:9px !important}.p-3,.pb-3,.py-3{padding-bottom:9px !important}.m-3,.ml-3,.mx-3{margin-left:9px !important}.m-3,.mr-3,.mx-3{margin-right:9px !important}.m-3,.mt-3,.my-3{margin-top:9px !important}.m-3,.mb-3,.my-3{margin-bottom:9px !important}.top-3{top:9px !important}.left-3{left:9px !important}.right-3{right:9px !important}.bottom-3{bottom:9px !important}.top--3{top:-9px !important}.left--3{left:-9px !important}.right--3{right:-9px !important}.bottom--3{bottom:-9px !important}.p-4,.pl-4,.px-4{padding-left:12px !important}.p-4,.pr-4,.px-4{padding-right:12px !important}.p-4,.pt-4,.py-4{padding-top:12px !important}.p-4,.pb-4,.py-4{padding-bottom:12px !important}.m-4,.ml-4,.mx-4{margin-left:12px !important}.m-4,.mr-4,.mx-4{margin-right:12px !important}.m-4,.mt-4,.my-4{margin-top:12px !important}.m-4,.mb-4,.my-4{margin-bottom:12px !important}.top-4{top:12px !important}.left-4{left:12px !important}.right-4{right:12px !important}.bottom-4{bottom:12px !important}.top--4{top:-12px !important}.left--4{left:-12px !important}.right--4{right:-12px !important}.bottom--4{bottom:-12px !important}.p-5,.pl-5,.px-5{padding-left:15px !important}.p-5,.pr-5,.px-5{padding-right:15px !important}.p-5,.pt-5,.py-5{padding-top:15px !important}.p-5,.pb-5,.py-5{padding-bottom:15px !important}.m-5,.ml-5,.mx-5{margin-left:15px !important}.m-5,.mr-5,.mx-5{margin-right:15px !important}.m-5,.mt-5,.my-5{margin-top:15px !important}.m-5,.mb-5,.my-5{margin-bottom:15px !important}.top-5{top:15px !important}.left-5{left:15px !important}.right-5{right:15px !important}.bottom-5{bottom:15px !important}.top--5{top:-15px !important}.left--5{left:-15px !important}.right--5{right:-15px !important}.bottom--5{bottom:-15px !important}.p-6,.pl-6,.px-6{padding-left:18px !important}.p-6,.pr-6,.px-6{padding-right:18px !important}.p-6,.pt-6,.py-6{padding-top:18px !important}.p-6,.pb-6,.py-6{padding-bottom:18px !important}.m-6,.ml-6,.mx-6{margin-left:18px !important}.m-6,.mr-6,.mx-6{margin-right:18px !important}.m-6,.mt-6,.my-6{margin-top:18px !important}.m-6,.mb-6,.my-6{margin-bottom:18px !important}.top-6{top:18px !important}.left-6{left:18px !important}.right-6{right:18px !important}.bottom-6{bottom:18px !important}.top--6{top:-18px !important}.left--6{left:-18px !important}.right--6{right:-18px !important}.bottom--6{bottom:-18px !important}.p-7,.pl-7,.px-7{padding-left:21px !important}.p-7,.pr-7,.px-7{padding-right:21px !important}.p-7,.pt-7,.py-7{padding-top:21px !important}.p-7,.pb-7,.py-7{padding-bottom:21px !important}.m-7,.ml-7,.mx-7{margin-left:21px !important}.m-7,.mr-7,.mx-7{margin-right:21px !important}.m-7,.mt-7,.my-7{margin-top:21px !important}.m-7,.mb-7,.my-7{margin-bottom:21px !important}.top-7{top:21px !important}.left-7{left:21px !important}.right-7{right:21px !important}.bottom-7{bottom:21px !important}.top--7{top:-21px !important}.left--7{left:-21px !important}.right--7{right:-21px !important}.bottom--7{bottom:-21px !important}.p-8,.pl-8,.px-8{padding-left:24px !important}.p-8,.pr-8,.px-8{padding-right:24px !important}.p-8,.pt-8,.py-8{padding-top:24px !important}.p-8,.pb-8,.py-8{padding-bottom:24px !important}.m-8,.ml-8,.mx-8{margin-left:24px !important}.m-8,.mr-8,.mx-8{margin-right:24px !important}.m-8,.mt-8,.my-8{margin-top:24px !important}.m-8,.mb-8,.my-8{margin-bottom:24px !important}.top-8{top:24px !important}.left-8{left:24px !important}.right-8{right:24px !important}.bottom-8{bottom:24px !important}.top--8{top:-24px !important}.left--8{left:-24px !important}.right--8{right:-24px !important}.bottom--8{bottom:-24px !important}.p-9,.pl-9,.px-9{padding-left:27px !important}.p-9,.pr-9,.px-9{padding-right:27px !important}.p-9,.pt-9,.py-9{padding-top:27px !important}.p-9,.pb-9,.py-9{padding-bottom:27px !important}.m-9,.ml-9,.mx-9{margin-left:27px !important}.m-9,.mr-9,.mx-9{margin-right:27px !important}.m-9,.mt-9,.my-9{margin-top:27px !important}.m-9,.mb-9,.my-9{margin-bottom:27px !important}.top-9{top:27px !important}.left-9{left:27px !important}.right-9{right:27px !important}.bottom-9{bottom:27px !important}.top--9{top:-27px !important}.left--9{left:-27px !important}.right--9{right:-27px !important}.bottom--9{bottom:-27px !important}.p-10,.pl-10,.px-10{padding-left:30px !important}.p-10,.pr-10,.px-10{padding-right:30px !important}.p-10,.pt-10,.py-10{padding-top:30px !important}.p-10,.pb-10,.py-10{padding-bottom:30px !important}.m-10,.ml-10,.mx-10{margin-left:30px !important}.m-10,.mr-10,.mx-10{margin-right:30px !important}.m-10,.mt-10,.my-10{margin-top:30px !important}.m-10,.mb-10,.my-10{margin-bottom:30px !important}.top-10{top:30px !important}.left-10{left:30px !important}.right-10{right:30px !important}.bottom-10{bottom:30px !important}.top--10{top:-30px !important}.left--10{left:-30px !important}.right--10{right:-30px !important}.bottom--10{bottom:-30px !important}.inline-flex{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.flex-shrink-0{-ms-flex-negative:0;flex-shrink:0}.flex-wrap{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-nowrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.flex-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.flex-row-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flex-y-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flex-y-baseline{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.flex-y-flex-start{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flex-y-flex-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flex-x-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.flex-x-flex-start{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.flex-x-flex-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.flex-x-space-around{-ms-flex-pack:distribute;justify-content:space-around}.flex-x-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flex-x-space-evenly{-webkit-box-pack:space-evenly;-ms-flex-pack:space-evenly;justify-content:space-evenly}.flex-1{-webkit-box-flex:1;-ms-flex:1;flex:1;min-width:0}.flex-2{-webkit-box-flex:2;-ms-flex:2;flex:2;min-width:0}.flex-3{-webkit-box-flex:3;-ms-flex:3;flex:3;min-width:0}.flex-4{-webkit-box-flex:4;-ms-flex:4;flex:4;min-width:0}.flex-5{-webkit-box-flex:5;-ms-flex:5;flex:5;min-width:0}.flex-6{-webkit-box-flex:6;-ms-flex:6;flex:6;min-width:0}.flex-7{-webkit-box-flex:7;-ms-flex:7;flex:7;min-width:0}.flex-8{-webkit-box-flex:8;-ms-flex:8;flex:8;min-width:0}.flex-9{-webkit-box-flex:9;-ms-flex:9;flex:9;min-width:0}.flex-10{-webkit-box-flex:10;-ms-flex:10;flex:10;min-width:0}.flex-11{-webkit-box-flex:11;-ms-flex:11;flex:11;min-width:0}.flex-12{-webkit-box-flex:12;-ms-flex:12;flex:12;min-width:0}.flex-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}[class*="grid-columns-"]{display:-ms-grid;display:grid;-ms-flex-line-pack:start;align-content:flex-start}.grid{display:-ms-grid;display:grid;-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-columns-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-columns-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-columns-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-columns-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-columns-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-columns-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-columns-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-columns-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-columns-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-columns-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-columns-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-columns-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-columns-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-columns-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-columns-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-columns-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-columns-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-columns-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-columns-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-columns-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-columns-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-columns-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-columns-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-gap-1,.grid-gap-x-1{-webkit-column-gap:3px !important;column-gap:3px !important}.grid-gap-1,.grid-gap-y-1{row-gap:3px !important}.grid-gap-2,.grid-gap-x-2{-webkit-column-gap:6px !important;column-gap:6px !important}.grid-gap-2,.grid-gap-y-2{row-gap:6px !important}.grid-gap-3,.grid-gap-x-3{-webkit-column-gap:9px !important;column-gap:9px !important}.grid-gap-3,.grid-gap-y-3{row-gap:9px !important}.grid-gap-4,.grid-gap-x-4{-webkit-column-gap:12px !important;column-gap:12px !important}.grid-gap-4,.grid-gap-y-4{row-gap:12px !important}.grid-gap-5,.grid-gap-x-5{-webkit-column-gap:15px !important;column-gap:15px !important}.grid-gap-5,.grid-gap-y-5{row-gap:15px !important}.grid-gap-6,.grid-gap-x-6{-webkit-column-gap:18px !important;column-gap:18px !important}.grid-gap-6,.grid-gap-y-6{row-gap:18px !important}.grid-gap-7,.grid-gap-x-7{-webkit-column-gap:21px !important;column-gap:21px !important}.grid-gap-7,.grid-gap-y-7{row-gap:21px !important}.grid-gap-8,.grid-gap-x-8{-webkit-column-gap:24px !important;column-gap:24px !important}.grid-gap-8,.grid-gap-y-8{row-gap:24px !important}.grid-gap-9,.grid-gap-x-9{-webkit-column-gap:27px !important;column-gap:27px !important}.grid-gap-9,.grid-gap-y-9{row-gap:27px !important}.grid-gap-10,.grid-gap-x-10{-webkit-column-gap:30px !important;column-gap:30px !important}.grid-gap-10,.grid-gap-y-10{row-gap:30px !important}.grid-column-1{grid-column:span 1/span 1}.grid-row-1{grid-row:span 1/span 1}.grid-column-2{grid-column:span 2/span 2}.grid-row-2{grid-row:span 2/span 2}.grid-column-3{grid-column:span 3/span 3}.grid-row-3{grid-row:span 3/span 3}.grid-column-4{grid-column:span 4/span 4}.grid-row-4{grid-row:span 4/span 4}.grid-column-5{grid-column:span 5/span 5}.grid-row-5{grid-row:span 5/span 5}.grid-column-6{grid-column:span 6/span 6}.grid-row-6{grid-row:span 6/span 6}.grid-column-7{grid-column:span 7/span 7}.grid-row-7{grid-row:span 7/span 7}.grid-column-8{grid-column:span 8/span 8}.grid-row-8{grid-row:span 8/span 8}.grid-column-9{grid-column:span 9/span 9}.grid-row-9{grid-row:span 9/span 9}.grid-column-10{grid-column:span 10/span 10}.grid-row-10{grid-row:span 10/span 10}.grid-column-11{grid-column:span 11/span 11}.grid-row-11{grid-row:span 11/span 11}.grid-column-12{grid-column:span 12/span 12}.grid-row-12{grid-row:span 12/span 12}.grid-column-13{grid-column:span 13/span 13}.grid-row-13{grid-row:span 13/span 13}.grid-column-14{grid-column:span 14/span 14}.grid-row-14{grid-row:span 14/span 14}.grid-column-15{grid-column:span 15/span 15}.grid-row-15{grid-row:span 15/span 15}.grid-column-16{grid-column:span 16/span 16}.grid-row-16{grid-row:span 16/span 16}.grid-column-17{grid-column:span 17/span 17}.grid-row-17{grid-row:span 17/span 17}.grid-column-18{grid-column:span 18/span 18}.grid-row-18{grid-row:span 18/span 18}.grid-column-19{grid-column:span 19/span 19}.grid-row-19{grid-row:span 19/span 19}.grid-column-20{grid-column:span 20/span 20}.grid-row-20{grid-row:span 20/span 20}.grid-column-21{grid-column:span 21/span 21}.grid-row-21{grid-row:span 21/span 21}.grid-column-22{grid-column:span 22/span 22}.grid-row-22{grid-row:span 22/span 22}.grid-column-23{grid-column:span 23/span 23}.grid-row-23{grid-row:span 23/span 23}.grid-column-24{grid-column:span 24/span 24}.grid-row-24{grid-row:span 24/span 24}@media only screen and (min-width: 375px){.grid-columns-m-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-m-1{grid-column:span 1/span 1}.grid-row-m-1{grid-row:span 1/span 1}.grid-columns-m-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-m-2{grid-column:span 2/span 2}.grid-row-m-2{grid-row:span 2/span 2}.grid-columns-m-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-m-3{grid-column:span 3/span 3}.grid-row-m-3{grid-row:span 3/span 3}.grid-columns-m-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-m-4{grid-column:span 4/span 4}.grid-row-m-4{grid-row:span 4/span 4}.grid-columns-m-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-m-5{grid-column:span 5/span 5}.grid-row-m-5{grid-row:span 5/span 5}.grid-columns-m-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-m-6{grid-column:span 6/span 6}.grid-row-m-6{grid-row:span 6/span 6}.grid-columns-m-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-m-7{grid-column:span 7/span 7}.grid-row-m-7{grid-row:span 7/span 7}.grid-columns-m-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-m-8{grid-column:span 8/span 8}.grid-row-m-8{grid-row:span 8/span 8}.grid-columns-m-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-m-9{grid-column:span 9/span 9}.grid-row-m-9{grid-row:span 9/span 9}.grid-columns-m-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-m-10{grid-column:span 10/span 10}.grid-row-m-10{grid-row:span 10/span 10}.grid-columns-m-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-m-11{grid-column:span 11/span 11}.grid-row-m-11{grid-row:span 11/span 11}.grid-columns-m-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-m-12{grid-column:span 12/span 12}.grid-row-m-12{grid-row:span 12/span 12}.grid-columns-m-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-m-13{grid-column:span 13/span 13}.grid-row-m-13{grid-row:span 13/span 13}.grid-columns-m-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-m-14{grid-column:span 14/span 14}.grid-row-m-14{grid-row:span 14/span 14}.grid-columns-m-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-m-15{grid-column:span 15/span 15}.grid-row-m-15{grid-row:span 15/span 15}.grid-columns-m-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-m-16{grid-column:span 16/span 16}.grid-row-m-16{grid-row:span 16/span 16}.grid-columns-m-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-m-17{grid-column:span 17/span 17}.grid-row-m-17{grid-row:span 17/span 17}.grid-columns-m-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-m-18{grid-column:span 18/span 18}.grid-row-m-18{grid-row:span 18/span 18}.grid-columns-m-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-m-19{grid-column:span 19/span 19}.grid-row-m-19{grid-row:span 19/span 19}.grid-columns-m-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-m-20{grid-column:span 20/span 20}.grid-row-m-20{grid-row:span 20/span 20}.grid-columns-m-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-m-21{grid-column:span 21/span 21}.grid-row-m-21{grid-row:span 21/span 21}.grid-columns-m-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-m-22{grid-column:span 22/span 22}.grid-row-m-22{grid-row:span 22/span 22}.grid-columns-m-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-m-23{grid-column:span 23/span 23}.grid-row-m-23{grid-row:span 23/span 23}.grid-columns-m-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-m-24{grid-column:span 24/span 24}.grid-row-m-24{grid-row:span 24/span 24}}@media only screen and (min-width: 480px){.grid-columns-p-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-p-1{grid-column:span 1/span 1}.grid-row-p-1{grid-row:span 1/span 1}.grid-columns-p-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-p-2{grid-column:span 2/span 2}.grid-row-p-2{grid-row:span 2/span 2}.grid-columns-p-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-p-3{grid-column:span 3/span 3}.grid-row-p-3{grid-row:span 3/span 3}.grid-columns-p-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-p-4{grid-column:span 4/span 4}.grid-row-p-4{grid-row:span 4/span 4}.grid-columns-p-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-p-5{grid-column:span 5/span 5}.grid-row-p-5{grid-row:span 5/span 5}.grid-columns-p-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-p-6{grid-column:span 6/span 6}.grid-row-p-6{grid-row:span 6/span 6}.grid-columns-p-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-p-7{grid-column:span 7/span 7}.grid-row-p-7{grid-row:span 7/span 7}.grid-columns-p-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-p-8{grid-column:span 8/span 8}.grid-row-p-8{grid-row:span 8/span 8}.grid-columns-p-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-p-9{grid-column:span 9/span 9}.grid-row-p-9{grid-row:span 9/span 9}.grid-columns-p-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-p-10{grid-column:span 10/span 10}.grid-row-p-10{grid-row:span 10/span 10}.grid-columns-p-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-p-11{grid-column:span 11/span 11}.grid-row-p-11{grid-row:span 11/span 11}.grid-columns-p-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-p-12{grid-column:span 12/span 12}.grid-row-p-12{grid-row:span 12/span 12}.grid-columns-p-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-p-13{grid-column:span 13/span 13}.grid-row-p-13{grid-row:span 13/span 13}.grid-columns-p-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-p-14{grid-column:span 14/span 14}.grid-row-p-14{grid-row:span 14/span 14}.grid-columns-p-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-p-15{grid-column:span 15/span 15}.grid-row-p-15{grid-row:span 15/span 15}.grid-columns-p-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-p-16{grid-column:span 16/span 16}.grid-row-p-16{grid-row:span 16/span 16}.grid-columns-p-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-p-17{grid-column:span 17/span 17}.grid-row-p-17{grid-row:span 17/span 17}.grid-columns-p-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-p-18{grid-column:span 18/span 18}.grid-row-p-18{grid-row:span 18/span 18}.grid-columns-p-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-p-19{grid-column:span 19/span 19}.grid-row-p-19{grid-row:span 19/span 19}.grid-columns-p-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-p-20{grid-column:span 20/span 20}.grid-row-p-20{grid-row:span 20/span 20}.grid-columns-p-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-p-21{grid-column:span 21/span 21}.grid-row-p-21{grid-row:span 21/span 21}.grid-columns-p-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-p-22{grid-column:span 22/span 22}.grid-row-p-22{grid-row:span 22/span 22}.grid-columns-p-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-p-23{grid-column:span 23/span 23}.grid-row-p-23{grid-row:span 23/span 23}.grid-columns-p-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-p-24{grid-column:span 24/span 24}.grid-row-p-24{grid-row:span 24/span 24}}@media only screen and (min-width: 768px){.grid-columns-xs-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-xs-1{grid-column:span 1/span 1}.grid-row-xs-1{grid-row:span 1/span 1}.grid-columns-xs-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-xs-2{grid-column:span 2/span 2}.grid-row-xs-2{grid-row:span 2/span 2}.grid-columns-xs-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-xs-3{grid-column:span 3/span 3}.grid-row-xs-3{grid-row:span 3/span 3}.grid-columns-xs-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-xs-4{grid-column:span 4/span 4}.grid-row-xs-4{grid-row:span 4/span 4}.grid-columns-xs-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-xs-5{grid-column:span 5/span 5}.grid-row-xs-5{grid-row:span 5/span 5}.grid-columns-xs-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-xs-6{grid-column:span 6/span 6}.grid-row-xs-6{grid-row:span 6/span 6}.grid-columns-xs-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-xs-7{grid-column:span 7/span 7}.grid-row-xs-7{grid-row:span 7/span 7}.grid-columns-xs-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-xs-8{grid-column:span 8/span 8}.grid-row-xs-8{grid-row:span 8/span 8}.grid-columns-xs-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-xs-9{grid-column:span 9/span 9}.grid-row-xs-9{grid-row:span 9/span 9}.grid-columns-xs-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-xs-10{grid-column:span 10/span 10}.grid-row-xs-10{grid-row:span 10/span 10}.grid-columns-xs-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-xs-11{grid-column:span 11/span 11}.grid-row-xs-11{grid-row:span 11/span 11}.grid-columns-xs-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-xs-12{grid-column:span 12/span 12}.grid-row-xs-12{grid-row:span 12/span 12}.grid-columns-xs-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-xs-13{grid-column:span 13/span 13}.grid-row-xs-13{grid-row:span 13/span 13}.grid-columns-xs-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-xs-14{grid-column:span 14/span 14}.grid-row-xs-14{grid-row:span 14/span 14}.grid-columns-xs-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-xs-15{grid-column:span 15/span 15}.grid-row-xs-15{grid-row:span 15/span 15}.grid-columns-xs-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-xs-16{grid-column:span 16/span 16}.grid-row-xs-16{grid-row:span 16/span 16}.grid-columns-xs-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-xs-17{grid-column:span 17/span 17}.grid-row-xs-17{grid-row:span 17/span 17}.grid-columns-xs-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-xs-18{grid-column:span 18/span 18}.grid-row-xs-18{grid-row:span 18/span 18}.grid-columns-xs-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-xs-19{grid-column:span 19/span 19}.grid-row-xs-19{grid-row:span 19/span 19}.grid-columns-xs-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-xs-20{grid-column:span 20/span 20}.grid-row-xs-20{grid-row:span 20/span 20}.grid-columns-xs-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-xs-21{grid-column:span 21/span 21}.grid-row-xs-21{grid-row:span 21/span 21}.grid-columns-xs-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-xs-22{grid-column:span 22/span 22}.grid-row-xs-22{grid-row:span 22/span 22}.grid-columns-xs-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-xs-23{grid-column:span 23/span 23}.grid-row-xs-23{grid-row:span 23/span 23}.grid-columns-xs-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-xs-24{grid-column:span 24/span 24}.grid-row-xs-24{grid-row:span 24/span 24}}@media only screen and (min-width: 960px){.grid-columns-sm-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-sm-1{grid-column:span 1/span 1}.grid-row-sm-1{grid-row:span 1/span 1}.grid-columns-sm-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-sm-2{grid-column:span 2/span 2}.grid-row-sm-2{grid-row:span 2/span 2}.grid-columns-sm-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-sm-3{grid-column:span 3/span 3}.grid-row-sm-3{grid-row:span 3/span 3}.grid-columns-sm-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-sm-4{grid-column:span 4/span 4}.grid-row-sm-4{grid-row:span 4/span 4}.grid-columns-sm-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-sm-5{grid-column:span 5/span 5}.grid-row-sm-5{grid-row:span 5/span 5}.grid-columns-sm-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-sm-6{grid-column:span 6/span 6}.grid-row-sm-6{grid-row:span 6/span 6}.grid-columns-sm-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-sm-7{grid-column:span 7/span 7}.grid-row-sm-7{grid-row:span 7/span 7}.grid-columns-sm-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-sm-8{grid-column:span 8/span 8}.grid-row-sm-8{grid-row:span 8/span 8}.grid-columns-sm-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-sm-9{grid-column:span 9/span 9}.grid-row-sm-9{grid-row:span 9/span 9}.grid-columns-sm-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-sm-10{grid-column:span 10/span 10}.grid-row-sm-10{grid-row:span 10/span 10}.grid-columns-sm-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-sm-11{grid-column:span 11/span 11}.grid-row-sm-11{grid-row:span 11/span 11}.grid-columns-sm-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-sm-12{grid-column:span 12/span 12}.grid-row-sm-12{grid-row:span 12/span 12}.grid-columns-sm-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-sm-13{grid-column:span 13/span 13}.grid-row-sm-13{grid-row:span 13/span 13}.grid-columns-sm-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-sm-14{grid-column:span 14/span 14}.grid-row-sm-14{grid-row:span 14/span 14}.grid-columns-sm-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-sm-15{grid-column:span 15/span 15}.grid-row-sm-15{grid-row:span 15/span 15}.grid-columns-sm-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-sm-16{grid-column:span 16/span 16}.grid-row-sm-16{grid-row:span 16/span 16}.grid-columns-sm-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-sm-17{grid-column:span 17/span 17}.grid-row-sm-17{grid-row:span 17/span 17}.grid-columns-sm-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-sm-18{grid-column:span 18/span 18}.grid-row-sm-18{grid-row:span 18/span 18}.grid-columns-sm-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-sm-19{grid-column:span 19/span 19}.grid-row-sm-19{grid-row:span 19/span 19}.grid-columns-sm-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-sm-20{grid-column:span 20/span 20}.grid-row-sm-20{grid-row:span 20/span 20}.grid-columns-sm-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-sm-21{grid-column:span 21/span 21}.grid-row-sm-21{grid-row:span 21/span 21}.grid-columns-sm-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-sm-22{grid-column:span 22/span 22}.grid-row-sm-22{grid-row:span 22/span 22}.grid-columns-sm-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-sm-23{grid-column:span 23/span 23}.grid-row-sm-23{grid-row:span 23/span 23}.grid-columns-sm-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-sm-24{grid-column:span 24/span 24}.grid-row-sm-24{grid-row:span 24/span 24}}@media only screen and (min-width: 1280px){.grid-columns-md-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-md-1{grid-column:span 1/span 1}.grid-row-md-1{grid-row:span 1/span 1}.grid-columns-md-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-md-2{grid-column:span 2/span 2}.grid-row-md-2{grid-row:span 2/span 2}.grid-columns-md-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-md-3{grid-column:span 3/span 3}.grid-row-md-3{grid-row:span 3/span 3}.grid-columns-md-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-md-4{grid-column:span 4/span 4}.grid-row-md-4{grid-row:span 4/span 4}.grid-columns-md-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-md-5{grid-column:span 5/span 5}.grid-row-md-5{grid-row:span 5/span 5}.grid-columns-md-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-md-6{grid-column:span 6/span 6}.grid-row-md-6{grid-row:span 6/span 6}.grid-columns-md-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-md-7{grid-column:span 7/span 7}.grid-row-md-7{grid-row:span 7/span 7}.grid-columns-md-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-md-8{grid-column:span 8/span 8}.grid-row-md-8{grid-row:span 8/span 8}.grid-columns-md-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-md-9{grid-column:span 9/span 9}.grid-row-md-9{grid-row:span 9/span 9}.grid-columns-md-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-md-10{grid-column:span 10/span 10}.grid-row-md-10{grid-row:span 10/span 10}.grid-columns-md-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-md-11{grid-column:span 11/span 11}.grid-row-md-11{grid-row:span 11/span 11}.grid-columns-md-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-md-12{grid-column:span 12/span 12}.grid-row-md-12{grid-row:span 12/span 12}.grid-columns-md-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-md-13{grid-column:span 13/span 13}.grid-row-md-13{grid-row:span 13/span 13}.grid-columns-md-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-md-14{grid-column:span 14/span 14}.grid-row-md-14{grid-row:span 14/span 14}.grid-columns-md-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-md-15{grid-column:span 15/span 15}.grid-row-md-15{grid-row:span 15/span 15}.grid-columns-md-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-md-16{grid-column:span 16/span 16}.grid-row-md-16{grid-row:span 16/span 16}.grid-columns-md-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-md-17{grid-column:span 17/span 17}.grid-row-md-17{grid-row:span 17/span 17}.grid-columns-md-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-md-18{grid-column:span 18/span 18}.grid-row-md-18{grid-row:span 18/span 18}.grid-columns-md-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-md-19{grid-column:span 19/span 19}.grid-row-md-19{grid-row:span 19/span 19}.grid-columns-md-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-md-20{grid-column:span 20/span 20}.grid-row-md-20{grid-row:span 20/span 20}.grid-columns-md-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-md-21{grid-column:span 21/span 21}.grid-row-md-21{grid-row:span 21/span 21}.grid-columns-md-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-md-22{grid-column:span 22/span 22}.grid-row-md-22{grid-row:span 22/span 22}.grid-columns-md-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-md-23{grid-column:span 23/span 23}.grid-row-md-23{grid-row:span 23/span 23}.grid-columns-md-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-md-24{grid-column:span 24/span 24}.grid-row-md-24{grid-row:span 24/span 24}}@media only screen and (min-width: 1440px){.grid-columns-lg-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-lg-1{grid-column:span 1/span 1}.grid-row-lg-1{grid-row:span 1/span 1}.grid-columns-lg-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-lg-2{grid-column:span 2/span 2}.grid-row-lg-2{grid-row:span 2/span 2}.grid-columns-lg-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-lg-3{grid-column:span 3/span 3}.grid-row-lg-3{grid-row:span 3/span 3}.grid-columns-lg-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-lg-4{grid-column:span 4/span 4}.grid-row-lg-4{grid-row:span 4/span 4}.grid-columns-lg-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-lg-5{grid-column:span 5/span 5}.grid-row-lg-5{grid-row:span 5/span 5}.grid-columns-lg-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-lg-6{grid-column:span 6/span 6}.grid-row-lg-6{grid-row:span 6/span 6}.grid-columns-lg-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-lg-7{grid-column:span 7/span 7}.grid-row-lg-7{grid-row:span 7/span 7}.grid-columns-lg-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-lg-8{grid-column:span 8/span 8}.grid-row-lg-8{grid-row:span 8/span 8}.grid-columns-lg-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-lg-9{grid-column:span 9/span 9}.grid-row-lg-9{grid-row:span 9/span 9}.grid-columns-lg-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-lg-10{grid-column:span 10/span 10}.grid-row-lg-10{grid-row:span 10/span 10}.grid-columns-lg-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-lg-11{grid-column:span 11/span 11}.grid-row-lg-11{grid-row:span 11/span 11}.grid-columns-lg-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-lg-12{grid-column:span 12/span 12}.grid-row-lg-12{grid-row:span 12/span 12}.grid-columns-lg-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-lg-13{grid-column:span 13/span 13}.grid-row-lg-13{grid-row:span 13/span 13}.grid-columns-lg-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-lg-14{grid-column:span 14/span 14}.grid-row-lg-14{grid-row:span 14/span 14}.grid-columns-lg-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-lg-15{grid-column:span 15/span 15}.grid-row-lg-15{grid-row:span 15/span 15}.grid-columns-lg-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-lg-16{grid-column:span 16/span 16}.grid-row-lg-16{grid-row:span 16/span 16}.grid-columns-lg-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-lg-17{grid-column:span 17/span 17}.grid-row-lg-17{grid-row:span 17/span 17}.grid-columns-lg-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-lg-18{grid-column:span 18/span 18}.grid-row-lg-18{grid-row:span 18/span 18}.grid-columns-lg-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-lg-19{grid-column:span 19/span 19}.grid-row-lg-19{grid-row:span 19/span 19}.grid-columns-lg-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-lg-20{grid-column:span 20/span 20}.grid-row-lg-20{grid-row:span 20/span 20}.grid-columns-lg-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-lg-21{grid-column:span 21/span 21}.grid-row-lg-21{grid-row:span 21/span 21}.grid-columns-lg-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-lg-22{grid-column:span 22/span 22}.grid-row-lg-22{grid-row:span 22/span 22}.grid-columns-lg-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-lg-23{grid-column:span 23/span 23}.grid-row-lg-23{grid-row:span 23/span 23}.grid-columns-lg-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-lg-24{grid-column:span 24/span 24}.grid-row-lg-24{grid-row:span 24/span 24}}@media only screen and (min-width: 1920px){.grid-columns-xl-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-xl-1{grid-column:span 1/span 1}.grid-row-xl-1{grid-row:span 1/span 1}.grid-columns-xl-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-xl-2{grid-column:span 2/span 2}.grid-row-xl-2{grid-row:span 2/span 2}.grid-columns-xl-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-xl-3{grid-column:span 3/span 3}.grid-row-xl-3{grid-row:span 3/span 3}.grid-columns-xl-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-xl-4{grid-column:span 4/span 4}.grid-row-xl-4{grid-row:span 4/span 4}.grid-columns-xl-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-xl-5{grid-column:span 5/span 5}.grid-row-xl-5{grid-row:span 5/span 5}.grid-columns-xl-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-xl-6{grid-column:span 6/span 6}.grid-row-xl-6{grid-row:span 6/span 6}.grid-columns-xl-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-xl-7{grid-column:span 7/span 7}.grid-row-xl-7{grid-row:span 7/span 7}.grid-columns-xl-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-xl-8{grid-column:span 8/span 8}.grid-row-xl-8{grid-row:span 8/span 8}.grid-columns-xl-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-xl-9{grid-column:span 9/span 9}.grid-row-xl-9{grid-row:span 9/span 9}.grid-columns-xl-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-xl-10{grid-column:span 10/span 10}.grid-row-xl-10{grid-row:span 10/span 10}.grid-columns-xl-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-xl-11{grid-column:span 11/span 11}.grid-row-xl-11{grid-row:span 11/span 11}.grid-columns-xl-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-xl-12{grid-column:span 12/span 12}.grid-row-xl-12{grid-row:span 12/span 12}.grid-columns-xl-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-xl-13{grid-column:span 13/span 13}.grid-row-xl-13{grid-row:span 13/span 13}.grid-columns-xl-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-xl-14{grid-column:span 14/span 14}.grid-row-xl-14{grid-row:span 14/span 14}.grid-columns-xl-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-xl-15{grid-column:span 15/span 15}.grid-row-xl-15{grid-row:span 15/span 15}.grid-columns-xl-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-xl-16{grid-column:span 16/span 16}.grid-row-xl-16{grid-row:span 16/span 16}.grid-columns-xl-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-xl-17{grid-column:span 17/span 17}.grid-row-xl-17{grid-row:span 17/span 17}.grid-columns-xl-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-xl-18{grid-column:span 18/span 18}.grid-row-xl-18{grid-row:span 18/span 18}.grid-columns-xl-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-xl-19{grid-column:span 19/span 19}.grid-row-xl-19{grid-row:span 19/span 19}.grid-columns-xl-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-xl-20{grid-column:span 20/span 20}.grid-row-xl-20{grid-row:span 20/span 20}.grid-columns-xl-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-xl-21{grid-column:span 21/span 21}.grid-row-xl-21{grid-row:span 21/span 21}.grid-columns-xl-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-xl-22{grid-column:span 22/span 22}.grid-row-xl-22{grid-row:span 22/span 22}.grid-columns-xl-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-xl-23{grid-column:span 23/span 23}.grid-row-xl-23{grid-row:span 23/span 23}.grid-columns-xl-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-xl-24{grid-column:span 24/span 24}.grid-row-xl-24{grid-row:span 24/span 24}}@media only screen and (min-width: 2560px){.grid-columns-xxl-1{-ms-grid-columns:(minmax(0px, 1fr))[1];grid-template-columns:repeat(1, minmax(0px, 1fr))}.grid-column-xxl-1{grid-column:span 1/span 1}.grid-row-xxl-1{grid-row:span 1/span 1}.grid-columns-xxl-2{-ms-grid-columns:(minmax(0px, 1fr))[2];grid-template-columns:repeat(2, minmax(0px, 1fr))}.grid-column-xxl-2{grid-column:span 2/span 2}.grid-row-xxl-2{grid-row:span 2/span 2}.grid-columns-xxl-3{-ms-grid-columns:(minmax(0px, 1fr))[3];grid-template-columns:repeat(3, minmax(0px, 1fr))}.grid-column-xxl-3{grid-column:span 3/span 3}.grid-row-xxl-3{grid-row:span 3/span 3}.grid-columns-xxl-4{-ms-grid-columns:(minmax(0px, 1fr))[4];grid-template-columns:repeat(4, minmax(0px, 1fr))}.grid-column-xxl-4{grid-column:span 4/span 4}.grid-row-xxl-4{grid-row:span 4/span 4}.grid-columns-xxl-5{-ms-grid-columns:(minmax(0px, 1fr))[5];grid-template-columns:repeat(5, minmax(0px, 1fr))}.grid-column-xxl-5{grid-column:span 5/span 5}.grid-row-xxl-5{grid-row:span 5/span 5}.grid-columns-xxl-6{-ms-grid-columns:(minmax(0px, 1fr))[6];grid-template-columns:repeat(6, minmax(0px, 1fr))}.grid-column-xxl-6{grid-column:span 6/span 6}.grid-row-xxl-6{grid-row:span 6/span 6}.grid-columns-xxl-7{-ms-grid-columns:(minmax(0px, 1fr))[7];grid-template-columns:repeat(7, minmax(0px, 1fr))}.grid-column-xxl-7{grid-column:span 7/span 7}.grid-row-xxl-7{grid-row:span 7/span 7}.grid-columns-xxl-8{-ms-grid-columns:(minmax(0px, 1fr))[8];grid-template-columns:repeat(8, minmax(0px, 1fr))}.grid-column-xxl-8{grid-column:span 8/span 8}.grid-row-xxl-8{grid-row:span 8/span 8}.grid-columns-xxl-9{-ms-grid-columns:(minmax(0px, 1fr))[9];grid-template-columns:repeat(9, minmax(0px, 1fr))}.grid-column-xxl-9{grid-column:span 9/span 9}.grid-row-xxl-9{grid-row:span 9/span 9}.grid-columns-xxl-10{-ms-grid-columns:(minmax(0px, 1fr))[10];grid-template-columns:repeat(10, minmax(0px, 1fr))}.grid-column-xxl-10{grid-column:span 10/span 10}.grid-row-xxl-10{grid-row:span 10/span 10}.grid-columns-xxl-11{-ms-grid-columns:(minmax(0px, 1fr))[11];grid-template-columns:repeat(11, minmax(0px, 1fr))}.grid-column-xxl-11{grid-column:span 11/span 11}.grid-row-xxl-11{grid-row:span 11/span 11}.grid-columns-xxl-12{-ms-grid-columns:(minmax(0px, 1fr))[12];grid-template-columns:repeat(12, minmax(0px, 1fr))}.grid-column-xxl-12{grid-column:span 12/span 12}.grid-row-xxl-12{grid-row:span 12/span 12}.grid-columns-xxl-13{-ms-grid-columns:(minmax(0px, 1fr))[13];grid-template-columns:repeat(13, minmax(0px, 1fr))}.grid-column-xxl-13{grid-column:span 13/span 13}.grid-row-xxl-13{grid-row:span 13/span 13}.grid-columns-xxl-14{-ms-grid-columns:(minmax(0px, 1fr))[14];grid-template-columns:repeat(14, minmax(0px, 1fr))}.grid-column-xxl-14{grid-column:span 14/span 14}.grid-row-xxl-14{grid-row:span 14/span 14}.grid-columns-xxl-15{-ms-grid-columns:(minmax(0px, 1fr))[15];grid-template-columns:repeat(15, minmax(0px, 1fr))}.grid-column-xxl-15{grid-column:span 15/span 15}.grid-row-xxl-15{grid-row:span 15/span 15}.grid-columns-xxl-16{-ms-grid-columns:(minmax(0px, 1fr))[16];grid-template-columns:repeat(16, minmax(0px, 1fr))}.grid-column-xxl-16{grid-column:span 16/span 16}.grid-row-xxl-16{grid-row:span 16/span 16}.grid-columns-xxl-17{-ms-grid-columns:(minmax(0px, 1fr))[17];grid-template-columns:repeat(17, minmax(0px, 1fr))}.grid-column-xxl-17{grid-column:span 17/span 17}.grid-row-xxl-17{grid-row:span 17/span 17}.grid-columns-xxl-18{-ms-grid-columns:(minmax(0px, 1fr))[18];grid-template-columns:repeat(18, minmax(0px, 1fr))}.grid-column-xxl-18{grid-column:span 18/span 18}.grid-row-xxl-18{grid-row:span 18/span 18}.grid-columns-xxl-19{-ms-grid-columns:(minmax(0px, 1fr))[19];grid-template-columns:repeat(19, minmax(0px, 1fr))}.grid-column-xxl-19{grid-column:span 19/span 19}.grid-row-xxl-19{grid-row:span 19/span 19}.grid-columns-xxl-20{-ms-grid-columns:(minmax(0px, 1fr))[20];grid-template-columns:repeat(20, minmax(0px, 1fr))}.grid-column-xxl-20{grid-column:span 20/span 20}.grid-row-xxl-20{grid-row:span 20/span 20}.grid-columns-xxl-21{-ms-grid-columns:(minmax(0px, 1fr))[21];grid-template-columns:repeat(21, minmax(0px, 1fr))}.grid-column-xxl-21{grid-column:span 21/span 21}.grid-row-xxl-21{grid-row:span 21/span 21}.grid-columns-xxl-22{-ms-grid-columns:(minmax(0px, 1fr))[22];grid-template-columns:repeat(22, minmax(0px, 1fr))}.grid-column-xxl-22{grid-column:span 22/span 22}.grid-row-xxl-22{grid-row:span 22/span 22}.grid-columns-xxl-23{-ms-grid-columns:(minmax(0px, 1fr))[23];grid-template-columns:repeat(23, minmax(0px, 1fr))}.grid-column-xxl-23{grid-column:span 23/span 23}.grid-row-xxl-23{grid-row:span 23/span 23}.grid-columns-xxl-24{-ms-grid-columns:(minmax(0px, 1fr))[24];grid-template-columns:repeat(24, minmax(0px, 1fr))}.grid-column-xxl-24{grid-column:span 24/span 24}.grid-row-xxl-24{grid-row:span 24/span 24}}.position-relative{position:relative}.position-absolute{position:absolute}.position-fixed{position:fixed}.position-sticky{position:-webkit-sticky;position:sticky}.vh-5{height:5vh}.vw-5{width:5vw}.h-5{height:5%}.w-5{width:5%}.min-h-5{min-height:5%}.min-w-5{min-width:5%}.min-vh-5{min-height:5vh}.min-vw-5{min-width:5vw}.max-h-5{max-height:5%}.max-w-5{max-width:5%}.max-vh-5{max-height:5vh}.max-vw-5{max-width:5vw}.vh-10{height:10vh}.vw-10{width:10vw}.h-10{height:10%}.w-10{width:10%}.min-h-10{min-height:10%}.min-w-10{min-width:10%}.min-vh-10{min-height:10vh}.min-vw-10{min-width:10vw}.max-h-10{max-height:10%}.max-w-10{max-width:10%}.max-vh-10{max-height:10vh}.max-vw-10{max-width:10vw}.vh-15{height:15vh}.vw-15{width:15vw}.h-15{height:15%}.w-15{width:15%}.min-h-15{min-height:15%}.min-w-15{min-width:15%}.min-vh-15{min-height:15vh}.min-vw-15{min-width:15vw}.max-h-15{max-height:15%}.max-w-15{max-width:15%}.max-vh-15{max-height:15vh}.max-vw-15{max-width:15vw}.vh-20{height:20vh}.vw-20{width:20vw}.h-20{height:20%}.w-20{width:20%}.min-h-20{min-height:20%}.min-w-20{min-width:20%}.min-vh-20{min-height:20vh}.min-vw-20{min-width:20vw}.max-h-20{max-height:20%}.max-w-20{max-width:20%}.max-vh-20{max-height:20vh}.max-vw-20{max-width:20vw}.vh-25{height:25vh}.vw-25{width:25vw}.h-25{height:25%}.w-25{width:25%}.min-h-25{min-height:25%}.min-w-25{min-width:25%}.min-vh-25{min-height:25vh}.min-vw-25{min-width:25vw}.max-h-25{max-height:25%}.max-w-25{max-width:25%}.max-vh-25{max-height:25vh}.max-vw-25{max-width:25vw}.vh-30{height:30vh}.vw-30{width:30vw}.h-30{height:30%}.w-30{width:30%}.min-h-30{min-height:30%}.min-w-30{min-width:30%}.min-vh-30{min-height:30vh}.min-vw-30{min-width:30vw}.max-h-30{max-height:30%}.max-w-30{max-width:30%}.max-vh-30{max-height:30vh}.max-vw-30{max-width:30vw}.vh-35{height:35vh}.vw-35{width:35vw}.h-35{height:35%}.w-35{width:35%}.min-h-35{min-height:35%}.min-w-35{min-width:35%}.min-vh-35{min-height:35vh}.min-vw-35{min-width:35vw}.max-h-35{max-height:35%}.max-w-35{max-width:35%}.max-vh-35{max-height:35vh}.max-vw-35{max-width:35vw}.vh-40{height:40vh}.vw-40{width:40vw}.h-40{height:40%}.w-40{width:40%}.min-h-40{min-height:40%}.min-w-40{min-width:40%}.min-vh-40{min-height:40vh}.min-vw-40{min-width:40vw}.max-h-40{max-height:40%}.max-w-40{max-width:40%}.max-vh-40{max-height:40vh}.max-vw-40{max-width:40vw}.vh-45{height:45vh}.vw-45{width:45vw}.h-45{height:45%}.w-45{width:45%}.min-h-45{min-height:45%}.min-w-45{min-width:45%}.min-vh-45{min-height:45vh}.min-vw-45{min-width:45vw}.max-h-45{max-height:45%}.max-w-45{max-width:45%}.max-vh-45{max-height:45vh}.max-vw-45{max-width:45vw}.vh-50{height:50vh}.vw-50{width:50vw}.h-50{height:50%}.w-50{width:50%}.min-h-50{min-height:50%}.min-w-50{min-width:50%}.min-vh-50{min-height:50vh}.min-vw-50{min-width:50vw}.max-h-50{max-height:50%}.max-w-50{max-width:50%}.max-vh-50{max-height:50vh}.max-vw-50{max-width:50vw}.vh-55{height:55vh}.vw-55{width:55vw}.h-55{height:55%}.w-55{width:55%}.min-h-55{min-height:55%}.min-w-55{min-width:55%}.min-vh-55{min-height:55vh}.min-vw-55{min-width:55vw}.max-h-55{max-height:55%}.max-w-55{max-width:55%}.max-vh-55{max-height:55vh}.max-vw-55{max-width:55vw}.vh-60{height:60vh}.vw-60{width:60vw}.h-60{height:60%}.w-60{width:60%}.min-h-60{min-height:60%}.min-w-60{min-width:60%}.min-vh-60{min-height:60vh}.min-vw-60{min-width:60vw}.max-h-60{max-height:60%}.max-w-60{max-width:60%}.max-vh-60{max-height:60vh}.max-vw-60{max-width:60vw}.vh-65{height:65vh}.vw-65{width:65vw}.h-65{height:65%}.w-65{width:65%}.min-h-65{min-height:65%}.min-w-65{min-width:65%}.min-vh-65{min-height:65vh}.min-vw-65{min-width:65vw}.max-h-65{max-height:65%}.max-w-65{max-width:65%}.max-vh-65{max-height:65vh}.max-vw-65{max-width:65vw}.vh-70{height:70vh}.vw-70{width:70vw}.h-70{height:70%}.w-70{width:70%}.min-h-70{min-height:70%}.min-w-70{min-width:70%}.min-vh-70{min-height:70vh}.min-vw-70{min-width:70vw}.max-h-70{max-height:70%}.max-w-70{max-width:70%}.max-vh-70{max-height:70vh}.max-vw-70{max-width:70vw}.vh-75{height:75vh}.vw-75{width:75vw}.h-75{height:75%}.w-75{width:75%}.min-h-75{min-height:75%}.min-w-75{min-width:75%}.min-vh-75{min-height:75vh}.min-vw-75{min-width:75vw}.max-h-75{max-height:75%}.max-w-75{max-width:75%}.max-vh-75{max-height:75vh}.max-vw-75{max-width:75vw}.vh-80{height:80vh}.vw-80{width:80vw}.h-80{height:80%}.w-80{width:80%}.min-h-80{min-height:80%}.min-w-80{min-width:80%}.min-vh-80{min-height:80vh}.min-vw-80{min-width:80vw}.max-h-80{max-height:80%}.max-w-80{max-width:80%}.max-vh-80{max-height:80vh}.max-vw-80{max-width:80vw}.vh-85{height:85vh}.vw-85{width:85vw}.h-85{height:85%}.w-85{width:85%}.min-h-85{min-height:85%}.min-w-85{min-width:85%}.min-vh-85{min-height:85vh}.min-vw-85{min-width:85vw}.max-h-85{max-height:85%}.max-w-85{max-width:85%}.max-vh-85{max-height:85vh}.max-vw-85{max-width:85vw}.vh-90{height:90vh}.vw-90{width:90vw}.h-90{height:90%}.w-90{width:90%}.min-h-90{min-height:90%}.min-w-90{min-width:90%}.min-vh-90{min-height:90vh}.min-vw-90{min-width:90vw}.max-h-90{max-height:90%}.max-w-90{max-width:90%}.max-vh-90{max-height:90vh}.max-vw-90{max-width:90vw}.vh-95{height:95vh}.vw-95{width:95vw}.h-95{height:95%}.w-95{width:95%}.min-h-95{min-height:95%}.min-w-95{min-width:95%}.min-vh-95{min-height:95vh}.min-vw-95{min-width:95vw}.max-h-95{max-height:95%}.max-w-95{max-width:95%}.max-vh-95{max-height:95vh}.max-vw-95{max-width:95vw}.vh-100{height:100vh}.vw-100{width:100vw}.h-100{height:100%}.w-100{width:100%}.min-h-100{min-height:100%}.min-w-100{min-width:100%}.min-vh-100{min-height:100vh}.min-vw-100{min-width:100vw}.max-h-100{max-height:100%}.max-w-100{max-width:100%}.max-vh-100{max-height:100vh}.max-vw-100{max-width:100vw}.bg{background-color:var(--el-bg-color)}.bg-filter{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}.bg-mosaic,.el-avatar.bg-mosaic,#app .el-avatar{--el-avatar-bg-color:#1E1E1E}.bg-white,.hover-bg-white:hover{background-color:var(--el-bg-color)}.bg-primary,.hover-bg-primary:hover{background-color:var(--el-color-primary)}.bg-primary-light-3,.hover-bg-primary-light-3:hover{background-color:var(--el-color-primary-light-3)}.bg-primary-light-9,.hover-bg-primary-light-9:hover{background-color:var(--el-color-primary-light-9)}.bg-success,.hover-bg-success:hover{background-color:var(--el-color-success)}.bg-success-light-3,.hover-bg-success-light-3:hover{background-color:var(--el-color-success-light-3)}.bg-warning,.hover-bg-warning:hover{background-color:var(--el-color-warning)}.bg-warning-light-3,.hover-bg-warning-light-3:hover{background-color:var(--el-color-warning-light-3)}.bg-danger,.hover-bg-danger:hover{background-color:var(--el-color-danger)}.bg-danger-light-3,.hover-bg-danger-light-3:hover{background-color:var(--el-color-danger-light-3)}.bg-danger-dark-2,.hover-bg-danger-dark-2:hover{background-color:var(--el-color-danger-dark-2)}.bg-error,.hover-bg-error:hover{background-color:var(--el-color-error)}.bg-hover,.hover-bg-hover:hover{background-color:var(--el-bg-color-page)}.bg-overlay,.hover-bg-overlay:hover{background-color:var(--el-bg-color-overlay)}.bg-mask,.hover-bg-mask:hover{background-color:var(--el-mask-color)}.bg-mask-light,.hover-bg-mask-light:hover{background-color:var(--el-mask-color-extra-light)}.bg-dark,.hover-bg-dark:hover{background-color:var(--el-color-black)}.bg-page,.hover-bg-page:hover{background-color:var(--el-bg-color-page)}.bg-info,.hover-bg-info:hover{background-color:var(--el-color-info)}.bg-gray,.hover-bg-gray:hover{background-color:#272727}.bg-5{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.05)}.bg-10{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.1)}.bg-15{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.15)}.bg-20{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.2)}.bg-25{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.25)}.bg-30{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.3)}.bg-35{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.35)}.bg-40{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.4)}.bg-45{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.45)}.bg-50{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.5)}.bg-55{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.55)}.bg-60{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.6)}.bg-65{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.65)}.bg-70{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.7)}.bg-75{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.75)}.bg-80{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.8)}.bg-85{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.85)}.bg-90{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.9)}.bg-95{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 0.95)}.bg-100{background-color:rgba(var(--el-bg-color-rgb-r), var(--el-bg-color-rgb-g), var(--el-bg-color-rgb-b), 1)}.text-primary{color:var(--el-color-primary)}.text-primary-light-3{color:var(--el-color-primary-light-3)}.text-success{color:var(--el-color-success)}.text-danger{color:var(--el-color-danger)}.text-warning{color:var(--el-color-warning)}.text-error{color:var(--el-color-error)}.text-white{color:var(--el-bg-color)}.text-grey{color:var(--el-color-info)}.text-info{color:var(--el-color-info)}.text-text-primary{color:var(--el-text-color-primary)}.text-regular{color:var(--el-text-color-regular)}.text-secondary{color:var(--el-text-color-secondary)}.text-placeholder{color:var(--el-text-color-placeholder)}.text-disable{color:var(--el-text-color-disabled)}.text-dark{color:var(--el-text-color-primary)}.text-bg{color:var(--el-bg-color)}.text-price{color:#f56c6c}.text-nowrap{white-space:nowrap}.text-wrap{white-space:wrap}.text-break-all{word-break:break-all}.text-break-word{word-break:break-word}.text-keep-all{word-break:keep-all}.text-ellipsis-1{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical}.text-ellipsis-2{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:2;-webkit-line-clamp:2;-webkit-box-orient:vertical}.text-ellipsis-3{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:3;-webkit-line-clamp:3;-webkit-box-orient:vertical}.text-ellipsis-4{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:4;-webkit-line-clamp:4;-webkit-box-orient:vertical}.text-ellipsis-5{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:5;-webkit-line-clamp:5;-webkit-box-orient:vertical}.text-ellipsis-6{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:6;-webkit-line-clamp:6;-webkit-box-orient:vertical}.text-ellipsis-7{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:7;-webkit-line-clamp:7;-webkit-box-orient:vertical}.text-ellipsis-8{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:8;-webkit-line-clamp:8;-webkit-box-orient:vertical}.text-ellipsis-9{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:9;-webkit-line-clamp:9;-webkit-box-orient:vertical}.text-ellipsis-10{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;line-clamp:10;-webkit-line-clamp:10;-webkit-box-orient:vertical}.text-spacing-1{word-spacing:.53333vw}.text-spacing-2{word-spacing:1.06666vw}.text-spacing-3{word-spacing:1.59999vw}.text-spacing-4{word-spacing:2.13332vw}.text-spacing-5{word-spacing:2.66665vw}.text-letter-spacing-1{letter-spacing:.53333vw}.text-letter-spacing-2{letter-spacing:1.06666vw}.text-letter-spacing-3{letter-spacing:1.59999vw}.text-letter-spacing-4{letter-spacing:2.13332vw}.text-letter-spacing-5{letter-spacing:2.66665vw}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-initial{text-align:initial}.text-capitalize{text-transform:capitalize}.text-uppercase{text-transform:uppercase}.text-lowercase{text-transform:lowercase}.text-s{text-decoration:line-through}.border,.border-top{border-top-width:1px;border-top-style:solid;border-top-color:var(--el-border-color)}.border-white,.border-top-white{border-top-color:var(--el-bg-color)}.border-hover-white:hover,.border-hover-top-white:hover{border-top-color:var(--el-bg-color)}.border-primary,.border-top-primary{border-top-color:var(--el-color-primary)}.border-hover-primary:hover,.border-hover-top-primary:hover{border-top-color:var(--el-color-primary)}.border-primary-light-3,.border-top-primary-light-3{border-top-color:var(--el-color-primary-light-3)}.border-hover-primary-light-3:hover,.border-hover-top-primary-light-3:hover{border-top-color:var(--el-color-primary-light-3)}.border-primary-light-9,.border-top-primary-light-9{border-top-color:var(--el-color-primary-light-9)}.border-hover-primary-light-9:hover,.border-hover-top-primary-light-9:hover{border-top-color:var(--el-color-primary-light-9)}.border-success,.border-top-success{border-top-color:var(--el-color-success)}.border-hover-success:hover,.border-hover-top-success:hover{border-top-color:var(--el-color-success)}.border-success-light-3,.border-top-success-light-3{border-top-color:var(--el-color-success-light-3)}.border-hover-success-light-3:hover,.border-hover-top-success-light-3:hover{border-top-color:var(--el-color-success-light-3)}.border-warning,.border-top-warning{border-top-color:var(--el-color-warning)}.border-hover-warning:hover,.border-hover-top-warning:hover{border-top-color:var(--el-color-warning)}.border-warning-light-3,.border-top-warning-light-3{border-top-color:var(--el-color-warning-light-3)}.border-hover-warning-light-3:hover,.border-hover-top-warning-light-3:hover{border-top-color:var(--el-color-warning-light-3)}.border-danger,.border-top-danger{border-top-color:var(--el-color-danger)}.border-hover-danger:hover,.border-hover-top-danger:hover{border-top-color:var(--el-color-danger)}.border-danger-light-3,.border-top-danger-light-3{border-top-color:var(--el-color-danger-light-3)}.border-hover-danger-light-3:hover,.border-hover-top-danger-light-3:hover{border-top-color:var(--el-color-danger-light-3)}.border-danger-dark-2,.border-top-danger-dark-2{border-top-color:var(--el-color-danger-dark-2)}.border-hover-danger-dark-2:hover,.border-hover-top-danger-dark-2:hover{border-top-color:var(--el-color-danger-dark-2)}.border-error,.border-top-error{border-top-color:var(--el-color-error)}.border-hover-error:hover,.border-hover-top-error:hover{border-top-color:var(--el-color-error)}.border-hover,.border-top-hover{border-top-color:var(--el-bg-color-page)}.border-hover-hover:hover,.border-hover-top-hover:hover{border-top-color:var(--el-bg-color-page)}.border-overlay,.border-top-overlay{border-top-color:var(--el-bg-color-overlay)}.border-hover-overlay:hover,.border-hover-top-overlay:hover{border-top-color:var(--el-bg-color-overlay)}.border-mask,.border-top-mask{border-top-color:var(--el-mask-color)}.border-hover-mask:hover,.border-hover-top-mask:hover{border-top-color:var(--el-mask-color)}.border-mask-light,.border-top-mask-light{border-top-color:var(--el-mask-color-extra-light)}.border-hover-mask-light:hover,.border-hover-top-mask-light:hover{border-top-color:var(--el-mask-color-extra-light)}.border-dark,.border-top-dark{border-top-color:var(--el-color-black)}.border-hover-dark:hover,.border-hover-top-dark:hover{border-top-color:var(--el-color-black)}.border-page,.border-top-page{border-top-color:var(--el-bg-color-page)}.border-hover-page:hover,.border-hover-top-page:hover{border-top-color:var(--el-bg-color-page)}.border-info,.border-top-info{border-top-color:var(--el-color-info)}.border-hover-info:hover,.border-hover-top-info:hover{border-top-color:var(--el-color-info)}.border-gray,.border-top-gray{border-top-color:#272727}.border-hover-gray:hover,.border-hover-top-gray:hover{border-top-color:#272727}.border,.border-right{border-right-width:1px;border-right-style:solid;border-right-color:var(--el-border-color)}.border-white,.border-right-white{border-right-color:var(--el-bg-color)}.border-hover-white:hover,.border-hover-right-white:hover{border-right-color:var(--el-bg-color)}.border-primary,.border-right-primary{border-right-color:var(--el-color-primary)}.border-hover-primary:hover,.border-hover-right-primary:hover{border-right-color:var(--el-color-primary)}.border-primary-light-3,.border-right-primary-light-3{border-right-color:var(--el-color-primary-light-3)}.border-hover-primary-light-3:hover,.border-hover-right-primary-light-3:hover{border-right-color:var(--el-color-primary-light-3)}.border-primary-light-9,.border-right-primary-light-9{border-right-color:var(--el-color-primary-light-9)}.border-hover-primary-light-9:hover,.border-hover-right-primary-light-9:hover{border-right-color:var(--el-color-primary-light-9)}.border-success,.border-right-success{border-right-color:var(--el-color-success)}.border-hover-success:hover,.border-hover-right-success:hover{border-right-color:var(--el-color-success)}.border-success-light-3,.border-right-success-light-3{border-right-color:var(--el-color-success-light-3)}.border-hover-success-light-3:hover,.border-hover-right-success-light-3:hover{border-right-color:var(--el-color-success-light-3)}.border-warning,.border-right-warning{border-right-color:var(--el-color-warning)}.border-hover-warning:hover,.border-hover-right-warning:hover{border-right-color:var(--el-color-warning)}.border-warning-light-3,.border-right-warning-light-3{border-right-color:var(--el-color-warning-light-3)}.border-hover-warning-light-3:hover,.border-hover-right-warning-light-3:hover{border-right-color:var(--el-color-warning-light-3)}.border-danger,.border-right-danger{border-right-color:var(--el-color-danger)}.border-hover-danger:hover,.border-hover-right-danger:hover{border-right-color:var(--el-color-danger)}.border-danger-light-3,.border-right-danger-light-3{border-right-color:var(--el-color-danger-light-3)}.border-hover-danger-light-3:hover,.border-hover-right-danger-light-3:hover{border-right-color:var(--el-color-danger-light-3)}.border-danger-dark-2,.border-right-danger-dark-2{border-right-color:var(--el-color-danger-dark-2)}.border-hover-danger-dark-2:hover,.border-hover-right-danger-dark-2:hover{border-right-color:var(--el-color-danger-dark-2)}.border-error,.border-right-error{border-right-color:var(--el-color-error)}.border-hover-error:hover,.border-hover-right-error:hover{border-right-color:var(--el-color-error)}.border-hover,.border-right-hover{border-right-color:var(--el-bg-color-page)}.border-hover-hover:hover,.border-hover-right-hover:hover{border-right-color:var(--el-bg-color-page)}.border-overlay,.border-right-overlay{border-right-color:var(--el-bg-color-overlay)}.border-hover-overlay:hover,.border-hover-right-overlay:hover{border-right-color:var(--el-bg-color-overlay)}.border-mask,.border-right-mask{border-right-color:var(--el-mask-color)}.border-hover-mask:hover,.border-hover-right-mask:hover{border-right-color:var(--el-mask-color)}.border-mask-light,.border-right-mask-light{border-right-color:var(--el-mask-color-extra-light)}.border-hover-mask-light:hover,.border-hover-right-mask-light:hover{border-right-color:var(--el-mask-color-extra-light)}.border-dark,.border-right-dark{border-right-color:var(--el-color-black)}.border-hover-dark:hover,.border-hover-right-dark:hover{border-right-color:var(--el-color-black)}.border-page,.border-right-page{border-right-color:var(--el-bg-color-page)}.border-hover-page:hover,.border-hover-right-page:hover{border-right-color:var(--el-bg-color-page)}.border-info,.border-right-info{border-right-color:var(--el-color-info)}.border-hover-info:hover,.border-hover-right-info:hover{border-right-color:var(--el-color-info)}.border-gray,.border-right-gray{border-right-color:#272727}.border-hover-gray:hover,.border-hover-right-gray:hover{border-right-color:#272727}.border,.border-bottom{border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:var(--el-border-color)}.border-white,.border-bottom-white{border-bottom-color:var(--el-bg-color)}.border-hover-white:hover,.border-hover-bottom-white:hover{border-bottom-color:var(--el-bg-color)}.border-primary,.border-bottom-primary{border-bottom-color:var(--el-color-primary)}.border-hover-primary:hover,.border-hover-bottom-primary:hover{border-bottom-color:var(--el-color-primary)}.border-primary-light-3,.border-bottom-primary-light-3{border-bottom-color:var(--el-color-primary-light-3)}.border-hover-primary-light-3:hover,.border-hover-bottom-primary-light-3:hover{border-bottom-color:var(--el-color-primary-light-3)}.border-primary-light-9,.border-bottom-primary-light-9{border-bottom-color:var(--el-color-primary-light-9)}.border-hover-primary-light-9:hover,.border-hover-bottom-primary-light-9:hover{border-bottom-color:var(--el-color-primary-light-9)}.border-success,.border-bottom-success{border-bottom-color:var(--el-color-success)}.border-hover-success:hover,.border-hover-bottom-success:hover{border-bottom-color:var(--el-color-success)}.border-success-light-3,.border-bottom-success-light-3{border-bottom-color:var(--el-color-success-light-3)}.border-hover-success-light-3:hover,.border-hover-bottom-success-light-3:hover{border-bottom-color:var(--el-color-success-light-3)}.border-warning,.border-bottom-warning{border-bottom-color:var(--el-color-warning)}.border-hover-warning:hover,.border-hover-bottom-warning:hover{border-bottom-color:var(--el-color-warning)}.border-warning-light-3,.border-bottom-warning-light-3{border-bottom-color:var(--el-color-warning-light-3)}.border-hover-warning-light-3:hover,.border-hover-bottom-warning-light-3:hover{border-bottom-color:var(--el-color-warning-light-3)}.border-danger,.border-bottom-danger{border-bottom-color:var(--el-color-danger)}.border-hover-danger:hover,.border-hover-bottom-danger:hover{border-bottom-color:var(--el-color-danger)}.border-danger-light-3,.border-bottom-danger-light-3{border-bottom-color:var(--el-color-danger-light-3)}.border-hover-danger-light-3:hover,.border-hover-bottom-danger-light-3:hover{border-bottom-color:var(--el-color-danger-light-3)}.border-danger-dark-2,.border-bottom-danger-dark-2{border-bottom-color:var(--el-color-danger-dark-2)}.border-hover-danger-dark-2:hover,.border-hover-bottom-danger-dark-2:hover{border-bottom-color:var(--el-color-danger-dark-2)}.border-error,.border-bottom-error{border-bottom-color:var(--el-color-error)}.border-hover-error:hover,.border-hover-bottom-error:hover{border-bottom-color:var(--el-color-error)}.border-hover,.border-bottom-hover{border-bottom-color:var(--el-bg-color-page)}.border-hover-hover:hover,.border-hover-bottom-hover:hover{border-bottom-color:var(--el-bg-color-page)}.border-overlay,.border-bottom-overlay{border-bottom-color:var(--el-bg-color-overlay)}.border-hover-overlay:hover,.border-hover-bottom-overlay:hover{border-bottom-color:var(--el-bg-color-overlay)}.border-mask,.border-bottom-mask{border-bottom-color:var(--el-mask-color)}.border-hover-mask:hover,.border-hover-bottom-mask:hover{border-bottom-color:var(--el-mask-color)}.border-mask-light,.border-bottom-mask-light{border-bottom-color:var(--el-mask-color-extra-light)}.border-hover-mask-light:hover,.border-hover-bottom-mask-light:hover{border-bottom-color:var(--el-mask-color-extra-light)}.border-dark,.border-bottom-dark{border-bottom-color:var(--el-color-black)}.border-hover-dark:hover,.border-hover-bottom-dark:hover{border-bottom-color:var(--el-color-black)}.border-page,.border-bottom-page{border-bottom-color:var(--el-bg-color-page)}.border-hover-page:hover,.border-hover-bottom-page:hover{border-bottom-color:var(--el-bg-color-page)}.border-info,.border-bottom-info{border-bottom-color:var(--el-color-info)}.border-hover-info:hover,.border-hover-bottom-info:hover{border-bottom-color:var(--el-color-info)}.border-gray,.border-bottom-gray{border-bottom-color:#272727}.border-hover-gray:hover,.border-hover-bottom-gray:hover{border-bottom-color:#272727}.border,.border-left{border-left-width:1px;border-left-style:solid;border-left-color:var(--el-border-color)}.border-white,.border-left-white{border-left-color:var(--el-bg-color)}.border-hover-white:hover,.border-hover-left-white:hover{border-left-color:var(--el-bg-color)}.border-primary,.border-left-primary{border-left-color:var(--el-color-primary)}.border-hover-primary:hover,.border-hover-left-primary:hover{border-left-color:var(--el-color-primary)}.border-primary-light-3,.border-left-primary-light-3{border-left-color:var(--el-color-primary-light-3)}.border-hover-primary-light-3:hover,.border-hover-left-primary-light-3:hover{border-left-color:var(--el-color-primary-light-3)}.border-primary-light-9,.border-left-primary-light-9{border-left-color:var(--el-color-primary-light-9)}.border-hover-primary-light-9:hover,.border-hover-left-primary-light-9:hover{border-left-color:var(--el-color-primary-light-9)}.border-success,.border-left-success{border-left-color:var(--el-color-success)}.border-hover-success:hover,.border-hover-left-success:hover{border-left-color:var(--el-color-success)}.border-success-light-3,.border-left-success-light-3{border-left-color:var(--el-color-success-light-3)}.border-hover-success-light-3:hover,.border-hover-left-success-light-3:hover{border-left-color:var(--el-color-success-light-3)}.border-warning,.border-left-warning{border-left-color:var(--el-color-warning)}.border-hover-warning:hover,.border-hover-left-warning:hover{border-left-color:var(--el-color-warning)}.border-warning-light-3,.border-left-warning-light-3{border-left-color:var(--el-color-warning-light-3)}.border-hover-warning-light-3:hover,.border-hover-left-warning-light-3:hover{border-left-color:var(--el-color-warning-light-3)}.border-danger,.border-left-danger{border-left-color:var(--el-color-danger)}.border-hover-danger:hover,.border-hover-left-danger:hover{border-left-color:var(--el-color-danger)}.border-danger-light-3,.border-left-danger-light-3{border-left-color:var(--el-color-danger-light-3)}.border-hover-danger-light-3:hover,.border-hover-left-danger-light-3:hover{border-left-color:var(--el-color-danger-light-3)}.border-danger-dark-2,.border-left-danger-dark-2{border-left-color:var(--el-color-danger-dark-2)}.border-hover-danger-dark-2:hover,.border-hover-left-danger-dark-2:hover{border-left-color:var(--el-color-danger-dark-2)}.border-error,.border-left-error{border-left-color:var(--el-color-error)}.border-hover-error:hover,.border-hover-left-error:hover{border-left-color:var(--el-color-error)}.border-hover,.border-left-hover{border-left-color:var(--el-bg-color-page)}.border-hover-hover:hover,.border-hover-left-hover:hover{border-left-color:var(--el-bg-color-page)}.border-overlay,.border-left-overlay{border-left-color:var(--el-bg-color-overlay)}.border-hover-overlay:hover,.border-hover-left-overlay:hover{border-left-color:var(--el-bg-color-overlay)}.border-mask,.border-left-mask{border-left-color:var(--el-mask-color)}.border-hover-mask:hover,.border-hover-left-mask:hover{border-left-color:var(--el-mask-color)}.border-mask-light,.border-left-mask-light{border-left-color:var(--el-mask-color-extra-light)}.border-hover-mask-light:hover,.border-hover-left-mask-light:hover{border-left-color:var(--el-mask-color-extra-light)}.border-dark,.border-left-dark{border-left-color:var(--el-color-black)}.border-hover-dark:hover,.border-hover-left-dark:hover{border-left-color:var(--el-color-black)}.border-page,.border-left-page{border-left-color:var(--el-bg-color-page)}.border-hover-page:hover,.border-hover-left-page:hover{border-left-color:var(--el-bg-color-page)}.border-info,.border-left-info{border-left-color:var(--el-color-info)}.border-hover-info:hover,.border-hover-left-info:hover{border-left-color:var(--el-color-info)}.border-gray,.border-left-gray{border-left-color:#272727}.border-hover-gray:hover,.border-hover-left-gray:hover{border-left-color:#272727}.border-0,.border-top-0{border-top-width:0px}.border-0,.border-right-0{border-right-width:0px}.border-0,.border-bottom-0{border-bottom-width:0px}.border-0,.border-left-0{border-left-width:0px}.border-1,.border-top-1{border-top-width:1px}.border-1,.border-right-1{border-right-width:1px}.border-1,.border-bottom-1{border-bottom-width:1px}.border-1,.border-left-1{border-left-width:1px}.border-2,.border-top-2{border-top-width:2px}.border-2,.border-right-2{border-right-width:2px}.border-2,.border-bottom-2{border-bottom-width:2px}.border-2,.border-left-2{border-left-width:2px}.border-3,.border-top-3{border-top-width:3px}.border-3,.border-right-3{border-right-width:3px}.border-3,.border-bottom-3{border-bottom-width:3px}.border-3,.border-left-3{border-left-width:3px}.border-4,.border-top-4{border-top-width:4px}.border-4,.border-right-4{border-right-width:4px}.border-4,.border-bottom-4{border-bottom-width:4px}.border-4,.border-left-4{border-left-width:4px}.border-5,.border-top-5{border-top-width:5px}.border-5,.border-right-5{border-right-width:5px}.border-5,.border-bottom-5{border-bottom-width:5px}.border-5,.border-left-5{border-left-width:5px}.border-6,.border-top-6{border-top-width:6px}.border-6,.border-right-6{border-right-width:6px}.border-6,.border-bottom-6{border-bottom-width:6px}.border-6,.border-left-6{border-left-width:6px}.border-solid,.border-top-solid{border-top-style:solid}.border-solid,.border-right-solid{border-right-style:solid}.border-solid,.border-bottom-solid{border-bottom-style:solid}.border-solid,.border-left-solid{border-left-style:solid}.border-dotted,.border-top-dotted{border-top-style:dotted}.border-dotted,.border-right-dotted{border-right-style:dotted}.border-dotted,.border-bottom-dotted{border-bottom-style:dotted}.border-dotted,.border-left-dotted{border-left-style:dotted}.border-dashed,.border-top-dashed{border-top-style:dashed}.border-dashed,.border-right-dashed{border-right-style:dashed}.border-dashed,.border-bottom-dashed{border-bottom-style:dashed}.border-dashed,.border-left-dashed{border-left-style:dashed}.border-double,.border-top-double{border-top-style:double}.border-double,.border-right-double{border-right-style:double}.border-double,.border-bottom-double{border-bottom-style:double}.border-double,.border-left-double{border-left-style:double}.border-groove,.border-top-groove{border-top-style:groove}.border-groove,.border-right-groove{border-right-style:groove}.border-groove,.border-bottom-groove{border-bottom-style:groove}.border-groove,.border-left-groove{border-left-style:groove}.border-ridge,.border-top-ridge{border-top-style:ridge}.border-ridge,.border-right-ridge{border-right-style:ridge}.border-ridge,.border-bottom-ridge{border-bottom-style:ridge}.border-ridge,.border-left-ridge{border-left-style:ridge}.border-inset,.border-top-inset{border-top-style:inset}.border-inset,.border-right-inset{border-right-style:inset}.border-inset,.border-bottom-inset{border-bottom-style:inset}.border-inset,.border-left-inset{border-left-style:inset}.border-outset,.border-top-outset{border-top-style:outset}.border-outset,.border-right-outset{border-right-style:outset}.border-outset,.border-bottom-outset{border-bottom-style:outset}.border-outset,.border-left-outset{border-left-style:outset}.rounded-circle{border-radius:50% !important}.rounded-round{border-radius:100px !important}.rounded-0,.rounded-left-0,.rounded-bottom-0,.rounded-bottom-left-0{border-bottom-left-radius:0px !important}.rounded-0,.rounded-left-0,.rounded-top-0,.rounded-top-left-0{border-top-left-radius:0px !important}.rounded-0,.rounded-right-0,.rounded-bottom-0,.rounded-bottom-right-0{border-bottom-right-radius:0px !important}.rounded-0,.rounded-right-0,.rounded-top-0,.rounded-top-right-0{border-top-right-radius:0px !important}.rounded,.rounded-left,.rounded-bottom,.rounded-bottom-left{border-bottom-left-radius:2px}.rounded,.rounded-left,.rounded-top,.rounded-top-left{border-top-left-radius:2px}.rounded,.rounded-right,.rounded-bottom,.rounded-bottom-right{border-bottom-right-radius:2px}.rounded,.rounded-right,.rounded-top,.rounded-top-right{border-top-right-radius:2px}.rounded-2,.rounded-left-2,.rounded-bottom-2,.rounded-bottom-left-2{border-bottom-left-radius:4px !important}.rounded-2,.rounded-left-2,.rounded-top-2,.rounded-top-left-2{border-top-left-radius:4px !important}.rounded-2,.rounded-right-2,.rounded-bottom-2,.rounded-bottom-right-2{border-bottom-right-radius:4px !important}.rounded-2,.rounded-right-2,.rounded-top-2,.rounded-top-right-2{border-top-right-radius:4px !important}.rounded-3,.rounded-left-3,.rounded-bottom-3,.rounded-bottom-left-3{border-bottom-left-radius:6px !important}.rounded-3,.rounded-left-3,.rounded-top-3,.rounded-top-left-3{border-top-left-radius:6px !important}.rounded-3,.rounded-right-3,.rounded-bottom-3,.rounded-bottom-right-3{border-bottom-right-radius:6px !important}.rounded-3,.rounded-right-3,.rounded-top-3,.rounded-top-right-3{border-top-right-radius:6px !important}.rounded-4,.rounded-left-4,.rounded-bottom-4,.rounded-bottom-left-4{border-bottom-left-radius:8px !important}.rounded-4,.rounded-left-4,.rounded-top-4,.rounded-top-left-4{border-top-left-radius:8px !important}.rounded-4,.rounded-right-4,.rounded-bottom-4,.rounded-bottom-right-4{border-bottom-right-radius:8px !important}.rounded-4,.rounded-right-4,.rounded-top-4,.rounded-top-right-4{border-top-right-radius:8px !important}.rounded-5,.rounded-left-5,.rounded-bottom-5,.rounded-bottom-left-5{border-bottom-left-radius:10px !important}.rounded-5,.rounded-left-5,.rounded-top-5,.rounded-top-left-5{border-top-left-radius:10px !important}.rounded-5,.rounded-right-5,.rounded-bottom-5,.rounded-bottom-right-5{border-bottom-right-radius:10px !important}.rounded-5,.rounded-right-5,.rounded-top-5,.rounded-top-right-5{border-top-right-radius:10px !important}.rounded-6,.rounded-left-6,.rounded-bottom-6,.rounded-bottom-left-6{border-bottom-left-radius:12px !important}.rounded-6,.rounded-left-6,.rounded-top-6,.rounded-top-left-6{border-top-left-radius:12px !important}.rounded-6,.rounded-right-6,.rounded-bottom-6,.rounded-bottom-right-6{border-bottom-right-radius:12px !important}.rounded-6,.rounded-right-6,.rounded-top-6,.rounded-top-right-6{border-top-right-radius:12px !important}.font-weight-bold{font-weight:bold}.font-weight-normal{font-weight:normal}.font-weight-100{font-weight:100}.font-weight-200{font-weight:200}.font-weight-300{font-weight:300}.font-weight-400{font-weight:400}.font-weight-500{font-weight:500}.font-weight-600{font-weight:600}.font-weight-700{font-weight:700}.font-weight-800{font-weight:800}.font-weight-900{font-weight:900}.h1{font-size:30px}.h2{font-size:28px}.h3{font-size:26px}.h4{font-size:24px}.h5{font-size:22px}.h6{font-size:20px}.h7{font-size:18px}.h8{font-size:16px}.h9{font-size:14px}.h10{font-size:12px}.line-height{line-height:1}.line-height-1{line-height:22px}.line-height-2{line-height:24px}.line-height-3{line-height:26px}.line-height-4{line-height:28px}.line-height-5{line-height:30px}.line-height-6{line-height:32px}.line-height-7{line-height:34px}.line-height-8{line-height:36px}.line-height-9{line-height:38px}.line-height-10{line-height:40px}.overflow-hidden,.overflow-x-hidden{overflow-x:hidden}.overflow-hidden,.overflow-y-hidden{overflow-y:hidden}.overflow-visible,.overflow-x-visible{overflow-x:visible}.overflow-visible,.overflow-y-visible{overflow-y:visible}.overflow-auto,.overflow-x-auto{overflow-x:auto}.overflow-auto,.overflow-y-auto{overflow-y:auto}.overflow-scroll,.overflow-x-scroll{overflow-x:scroll}.overflow-scroll,.overflow-y-scroll{overflow-y:scroll}img{vertical-align:middle}.shadow{-webkit-box-shadow:var(--el-box-shadow);box-shadow:var(--el-box-shadow)}.shadow-light{-webkit-box-shadow:var(--el-box-shadow-light);box-shadow:var(--el-box-shadow-light)}.shadow-lighter{-webkit-box-shadow:var(--el-box-shadow-lighter);box-shadow:var(--el-box-shadow-lighter)}.shadow-dark{-webkit-box-shadow:var(--el-box-shadow-dark);box-shadow:var(--el-box-shadow-dark)}.pointer{cursor:pointer} +/*# sourceMappingURL=common.min.css.map */ \ No newline at end of file diff --git a/fastmovie-vue/src/assets/css/common.min.css.map b/fastmovie-vue/src/assets/css/common.min.css.map new file mode 100644 index 0000000..2c89387 --- /dev/null +++ b/fastmovie-vue/src/assets/css/common.min.css.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "mappings": "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", + "sources": [ + "../../../scss/common.scss", + "../../../scss/_variables.scss" + ], + "names": [], + "file": "common.min.css" +} \ No newline at end of file diff --git a/fastmovie-vue/src/assets/css/style.min.css b/fastmovie-vue/src/assets/css/style.min.css new file mode 100644 index 0000000..ed85505 --- /dev/null +++ b/fastmovie-vue/src/assets/css/style.min.css @@ -0,0 +1,2 @@ +html,body{padding:0;margin:0;font-size:14px;font-synthesis:none;color:var(--el-text-color-primary);background-color:var(--el-bg-color)}a{text-decoration:none}*{outline:none;-webkit-box-sizing:border-box;box-sizing:border-box}.layouts{width:100%;max-width:var(--layouts-width);margin:0 auto;padding-left:10px;padding-right:10px}.layouts-empty{min-height:calc(100vh - var(--header-height) - var(--footer-height))}.el-main.layouts-main-scrollbar{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;overflow:hidden}.table-layouts{overflow:hidden;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.z-index{z-index:1}.z-index-10{z-index:10}.z-index-100{z-index:100}.z-index-1000{z-index:1000}.z-index-10000{z-index:10000}::-webkit-scrollbar{width:6px}::-webkit-scrollbar:horizontal{height:6px}::-webkit-scrollbar-track{border-radius:10px}::-webkit-scrollbar-thumb{background-color:#E5EAF3;border-radius:10px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}::-webkit-scrollbar-thumb:hover{cursor:pointer;background-color:#E5EAF3}.circular{display:inline;height:var(--el-loading-spinner-size);width:var(--el-loading-spinner-size);-webkit-animation:2s linear 0s infinite normal none running loading-rotate;animation:2s linear 0s infinite normal none running loading-rotate}.el-text--small{--el-link-font-size: var(--el-font-size-extra-small) !important}.el-switch__core .el-switch__action{color:var(--el-switch-on-color) !important}.el-switch.is-checked .el-switch__core .el-switch__action{color:var(--el-switch-on-color) !important}.el-radio{margin-right:10px !important}.el-button.el-button--success:not(.is-text){--el-button-text-color: var(--el-bg-color)}.el-popper.is-red{padding:6px 12px;background:#f04d4d;color:#FFFFFF}.el-popper.is-red .el-popper__arrow::before{background:#f04d4d;right:0}.el-messagebox-width .el-message-box__message{-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:hidden}.userinfo-menu{width:180px;--el-dropdown-menuItem-hover-fill: var(--el-bg-color-page) !important;--el-dropdown-menuItem-hover-color: var(--el-text-color-primary) !important;--el-text-color-regular: var(--el-text-color-primary) !important}.userinfo-menu .el-dropdown-menu{padding:10px}.userinfo-menu .el-dropdown-menu__item{border-radius:6px}.userinfo-menu .el-dropdown-menu__item .el-icon{color:var(--el-text-color-primary);font-size:16px}.userinfo-menu .el-dropdown-menu__item:hover{background-color:var(--el-bg-color-page)}.el-button--dark{--el-button-bg-color: #303133;--el-button-text-color: var(--el-color-white);--el-button-border-color: #303133;--el-button-hover-bg-color: rgb(110, 111, 112);--el-button-hover-text-color: var(--el-color-white);--el-button-hover-border-color: rgb(110, 111, 112);--el-button-active-bg-color: rgb(42, 43, 45);--el-button-active-border-color: rgb(42, 43, 45)}.el-messagebox-payment{padding:0}.el-messagebox-payment .el-message-box__header,.el-messagebox-payment .el-message-box__btns{display:none}.el-messagebox-payment .el-message-box__message{-webkit-box-flex:1;-ms-flex:1;flex:1}.el-messagebox-payment .recharge .close{display:block !important}.languare-list .languare-item{padding:6px;border-radius:6px;cursor:pointer}.languare-list .languare-item:hover{color:var(--el-menu-hover-text-color);background-color:var(--el-menu-hover-bg-color)}.w-e-text-container [data-slate-editor] pre>code{text-shadow:none !important}.el-loading-parent--relative .el-loading-spinner{--el-loading-spinner-size: 20px;--el-color-primary: #FFFFFF}.el-loading-parent--relative .el-loading-spinner .el-loading-text{font-size:12px}.action-dialog{--el-dialog-border-radius: 10px;--el-messagebox-font-size: 14px;--max-height: calc(100vh - 32px);max-height:var(--max-height)}.action-dialog .el-message-box__header{padding:20px;margin-right:0}.action-dialog .el-message-box__header .el-message-box__title{font-weight:600}.action-dialog .el-message-box__header .el-message-box__headerbtn{top:26px;right:12px}.action-dialog .el-message-box__message{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding:20px;max-height:calc(var(--max-height) - 24px - 64px)}.action-dialog .el-message-box__message .el-scrollbar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.action-dialog .el-message-box__message .el-scrollbar .el-form.layouts{padding-top:0px}.action-dialog .el-dialog__body .item-label{width:140px}.action-dialog .el-message-box__btns{display:none}.full-action-dialog{height:var(--max-height)}.full-action-dialog .el-message-box__message{height:calc(var(--max-height) - 24px - 64px)}.el-form .none-label .el-form-item__label{display:none !important}.el-form .el-form-item__content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.el-form .submit-item .el-form-item__content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-select .el-select-dropdown__item{height:auto}.el-table td.el-table__cell .cell{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:6px;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-message-box.x-login-message-box{--el-messagebox-width: min(980px, 100%);--el-messagebox-height: 550px;--el-messagebox-padding-primary: 0px;--el-messagebox-border-radius: 16px;height:var(--el-messagebox-height)}.el-message-box.x-login-message-box .el-message-box__message{width:100%}.el-message-box.x-login-message-box .el-message-box__btns{display:none}.x-loading-message-box-modal .el-overlay-message-box{padding:0px !important}.el-message-box.x-loading-message-box{--el-messagebox-width: 100vw;--el-messagebox-height: 100vh;--el-messagebox-padding-primary: 0px;--el-messagebox-border-radius: 0px;height:var(--el-messagebox-height)}.el-message-box.x-loading-message-box .el-message-box__message{height:var(--el-messagebox-height);width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:10px}.el-message-box.x-loading-message-box .el-message-box__btns{display:none}.text-active{color:transparent;background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-image:-webkit-gradient(linear, right top, left top, from(#79FFFF), to(#0DF283));background-image:linear-gradient(to left, #79FFFF 0%, #0DF283 100%)}.bg-active{background-image:-webkit-gradient(linear, right top, left top, from(#79FFFF), to(#0DF283));background-image:linear-gradient(to left, #79FFFF 0%, #0DF283 100%)}.el-overlay.el-modal-dialog{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}.el-dialog.drama-dialog{--el-dialog-width: min(100vw, 500px);--el-dialog-padding-primary: 0;--el-dialog-border-radius: 16px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:10px}.el-dialog.drama-dialog .el-dialog__header{padding:20px;border-bottom:1px solid #262626;position:relative}.el-dialog.drama-dialog .el-dialog__header .el-dialog__headerbtn{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:20px}.el-dialog.drama-dialog .el-dialog__header .el-dialog__headerbtn .el-dialog__close{color:var(--el-text-color-regular)}.el-dialog.drama-dialog .el-dialog__body{padding:20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:20px}.el-dialog.drama-dialog .el-dialog__footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:20px;gap:20px}.el-dialog.drama-dialog .el-dialog__footer .el-button{min-width:100px;font-weight:600}.el-dialog.drama-dialog .el-dialog__footer .el-button+.el-button{margin-left:0}.el-dialog.generate-scene-dialog,.el-dialog.generate-storyboard-dialog{--el-dialog-width: min(100vw, 500px);--el-dialog-padding-primary: 0;--el-dialog-border-radius: 16px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:10px}.el-dialog.generate-scene-dialog .el-dialog__header,.el-dialog.generate-storyboard-dialog .el-dialog__header{padding:20px;border-bottom:1px solid #262626;position:relative}.el-dialog.generate-scene-dialog .el-dialog__header .el-dialog__headerbtn,.el-dialog.generate-storyboard-dialog .el-dialog__header .el-dialog__headerbtn{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:20px}.el-dialog.generate-scene-dialog .el-dialog__header .el-dialog__headerbtn .el-dialog__close,.el-dialog.generate-storyboard-dialog .el-dialog__header .el-dialog__headerbtn .el-dialog__close{color:var(--el-text-color-regular)}.el-dialog.generate-scene-dialog .el-dialog__body,.el-dialog.generate-storyboard-dialog .el-dialog__body{padding:20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:20px}.el-dialog.generate-scene-dialog .el-dialog__footer,.el-dialog.generate-storyboard-dialog .el-dialog__footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:20px;gap:20px}.el-dialog.generate-scene-dialog .el-dialog__footer .el-button,.el-dialog.generate-storyboard-dialog .el-dialog__footer .el-button{min-width:100px;font-weight:600}.el-dialog.generate-scene-dialog .el-dialog__footer .el-button+.el-button,.el-dialog.generate-storyboard-dialog .el-dialog__footer .el-button+.el-button{margin-left:0}.tabs-segmented.el-segmented{--el-border-radius-base: 8px;--el-segmented-bg-color: rgba(30, 30, 30, 0.4) !important;--el-border-color: rgba(255, 255, 255, 0.3);--el-segmented-item-hover-bg-color: rgba(255, 255, 255, 0.1);--el-segmented-item-active-bg-color: rgba(255, 255, 255, 0.1);--el-segmented-padding: 4px;--el-segmented-item-selected-color: var(--el-bg-color);--el-segmented-item-selected-bg-color: #FFFFFF;--el-segmented-item-selected-color: var(--el-bg-color);font-weight:600}.tabs-segmented.el-segmented .el-segmented__item{padding:8px 0;width:120px}.tabs-segmented.el-segmented .el-segmented__group{gap:10px}.prompt-popper .el-mention-dropdown{--el-mention-option-height: auto}.prompt-popper .el-mention-dropdown .el-mention-dropdown__list{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:6px}.prompt-popper .el-mention-dropdown .el-mention-dropdown__list .el-mention-dropdown__item{display:-webkit-box;display:-ms-flexbox;display:flex;gap:6px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.episode-popover.el-popover{--el-popover-bg-color: var(--el-mask-color-extra-light);--el-bg-color-overlay: var(--el-popover-bg-color);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);min-width:100px !important}.episode-popover.el-popover.is-light>.el-popper__arrow::before{background:var(--el-popover-bg-color);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.el-popper.model-popover{--el-popover-bg-color: rgba(30, 30, 30, 0.5);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);border-radius:20px;--el-popover-border-color: rgba(255, 255, 255, 0.1);--el-box-shadow-light: none}.el-popper.model-popover .el-popper__arrow::before{background-color:rgba(30,30,30,0.5)}.icon-aspect-ratio{width:34px;height:34px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.icon-aspect-ratio:before{content:'';display:block;border-radius:4px;-webkit-box-shadow:inset 0 0 0 2px var(--el-text-color-primary);box-shadow:inset 0 0 0 2px var(--el-text-color-primary)}.icon-aspect-ratio[view="9:16"]:before{width:14.625px;height:26px}.icon-aspect-ratio[view="16:9"]:before{width:26px;height:14.625px}.icon-aspect-ratio[view="3:4"]:before{width:19.5px;height:26px}.icon-aspect-ratio[view="4:3"]:before{width:26px;height:19.5px}.icon-aspect-ratio[view="1:1"]:before{width:26px;height:26px}.icon-aspect-ratio[view="2:3"]:before{width:17.333px;height:26px}.icon-aspect-ratio[view="3:2"]:before{width:26px;height:17.333px}.input-button{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:20px;cursor:pointer;display:-webkit-box;display:-ms-flexbox;display:flex;padding-top:2px;padding-bottom:2px}.input-button-selected{background:rgba(255,255,255,0.08)}.input-button:hover{background:rgba(255,255,255,0.16)}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:var(--el-color-success) !important} +/*# sourceMappingURL=style.min.css.map */ \ No newline at end of file diff --git a/fastmovie-vue/src/assets/css/style.min.css.map b/fastmovie-vue/src/assets/css/style.min.css.map new file mode 100644 index 0000000..ecb6d3a --- /dev/null +++ b/fastmovie-vue/src/assets/css/style.min.css.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "mappings": "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", + "sources": [ + "../../../scss/style.scss" + ], + "names": [], + "file": "style.min.css" +} \ No newline at end of file diff --git a/fastmovie-vue/src/assets/css/theme.min.css b/fastmovie-vue/src/assets/css/theme.min.css new file mode 100644 index 0000000..4e2636b --- /dev/null +++ b/fastmovie-vue/src/assets/css/theme.min.css @@ -0,0 +1,4 @@ +html[data-theme="dark"]{color-scheme:dark;--el-color-primary: #006EFF;--el-color-primary-light-3: rgb(6, 83, 185);--el-color-primary-light-5: #2a598a;--el-color-primary-light-7: #213d5b;--el-color-primary-light-8: #1d3043;--el-color-primary-light-9: #18222c;--el-color-primary-dark-2: rgb(51, 139, 255);--el-color-success: #0DF283;--el-color-success-light-3: rgb(25, 150, 75);--el-color-success-light-5: #3e6b27;--el-color-success-light-7: #2d481f;--el-color-success-light-8: #25371c;--el-color-success-light-9: #1c2518;--el-color-success-dark-2: rgb(73, 215, 130);--el-color-warning: #FBAC15;--el-color-warning-light-3: rgb(182, 126, 21);--el-color-warning-light-5: #7d5b28;--el-color-warning-light-7: #533f20;--el-color-warning-light-8: #3e301c;--el-color-warning-light-9: #292218;--el-color-warning-dark-2: rgb(252, 189, 68);--el-color-danger: #E4221C;--el-color-danger-light-3: rgb(166, 30, 26);--el-color-danger-light-5: #854040;--el-color-danger-light-7: #582e2e;--el-color-danger-light-8: #412626;--el-color-danger-light-9: #2b1d1d;--el-color-danger-dark-2: rgb(233, 78, 73);--el-color-error: #E4221C;--el-color-error-light-3: rgb(166, 30, 26);--el-color-error-light-5: #854040;--el-color-error-light-7: #582e2e;--el-color-error-light-8: #412626;--el-color-error-light-9: #2b1d1d;--el-color-error-dark-2: rgb(233, 78, 73);--el-color-info: #909399;--el-color-info-light-3: #6b6d71;--el-color-info-light-5: #525457;--el-color-info-light-7: #393a3c;--el-color-info-light-8: #2d2d2f;--el-color-info-light-9: #202121;--el-color-info-dark-2: #a6a9ad;--el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, .36), 0px 8px 20px rgba(0, 0, 0, .72);--el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, .72);--el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .72);--el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, .72), 0px 12px 32px #000000, 0px 8px 16px -8px #000000;--el-bg-color-page: #1E1E1E;--el-bg-color: #0C0C0D;--el-bg-color-rgb-r: 10;--el-bg-color-rgb-g: 10;--el-bg-color-rgb-b: 10;--el-bg-color-overlay: #171717;--el-text-color-primary: #E5EAF3;--el-text-color-regular: #CFD3DC;--el-text-color-secondary: #A3A6AD;--el-text-color-placeholder: #8D9095;--el-text-color-disabled: #6C6E72;--el-border-color-darker: #636466;--el-border-color-dark: #58585B;--el-border-color: #272727;--el-border-color-light: #414243;--el-border-color-lighter: #363637;--el-border-color-extra-light: #2B2B2C;--el-fill-color-darker: #424243;--el-fill-color-dark: #39393A;--el-fill-color: #303030;--el-fill-color-light: #262727;--el-fill-color-lighter: #1D1D1D;--el-fill-color-extra-light: #191919;--el-fill-color-blank: transparent;--el-mask-color: rgba(0, 0, 0, .8);--el-mask-color-extra-light: rgba(0, 0, 0, .3)}html[data-theme="dark"] .el-button{--el-button-disabled-text-color: rgba(255, 255, 255, .5) +}html[data-theme="dark"] .el-card{--el-card-bg-color: var(--el-bg-color-overlay) +}html[data-theme="dark"] .el-empty{--el-empty-fill-color-0: var(--el-color-black);--el-empty-fill-color-1: #4b4b52;--el-empty-fill-color-2: #36383d;--el-empty-fill-color-3: #1e1e20;--el-empty-fill-color-4: #262629;--el-empty-fill-color-5: #202124;--el-empty-fill-color-6: #212224;--el-empty-fill-color-7: #1b1c1f;--el-empty-fill-color-8: #1c1d1f;--el-empty-fill-color-9: #18181a}html[data-theme="light"]{--el-bg-color-rgb-r: 255;--el-bg-color-rgb-g: 255;--el-bg-color-rgb-b: 255;--el-color-primary: #006EFF;--el-color-primary-light-3: rgb(77, 154, 255);--el-color-primary-dark-2: rgb(4, 92, 208);--el-color-success: #1BCD63;--el-color-success-light-3: rgb(95, 220, 146);--el-color-success-dark-2: rgb(26, 168, 83);--el-color-warning: #FBAC15;--el-color-warning-light-3: rgb(252, 197, 91);--el-color-warning-dark-2: rgb(205, 142, 21);--el-color-danger: #E4221C;--el-color-danger-light-3: rgb(236, 100, 96);--el-color-danger-dark-2: rgb(186, 31, 26);--el-color-error: #E4221C;--el-color-error-light-3: rgb(236, 100, 96);--el-color-error-dark-2: rgb(186, 31, 26)} +/*# sourceMappingURL=theme.min.css.map */ \ No newline at end of file diff --git a/fastmovie-vue/src/assets/css/theme.min.css.map b/fastmovie-vue/src/assets/css/theme.min.css.map new file mode 100644 index 0000000..d62fd9d --- /dev/null +++ b/fastmovie-vue/src/assets/css/theme.min.css.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "mappings": "AAAA,AAAA,IAAI,CAAA,AAAA,UAAC,CAAW,MAAM,AAAjB,CAAmB,CACvB,YAAY,CAAE,IAAI,CAClB,kBAAkB,CAAA,QAAC,CACnB,0BAA0B,CAAA,gBAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,yBAAyB,CAAA,kBAAC,CAC1B,kBAAkB,CAAA,QAAC,CACnB,0BAA0B,CAAA,iBAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,yBAAyB,CAAA,kBAAC,CAC1B,kBAAkB,CAAA,QAAC,CACnB,0BAA0B,CAAA,kBAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,0BAA0B,CAAA,QAAC,CAC3B,yBAAyB,CAAA,kBAAC,CAC1B,iBAAiB,CAAA,QAAC,CAClB,yBAAyB,CAAA,iBAAC,CAC1B,yBAAyB,CAAA,QAAC,CAC1B,yBAAyB,CAAA,QAAC,CAC1B,yBAAyB,CAAA,QAAC,CAC1B,yBAAyB,CAAA,QAAC,CAC1B,wBAAwB,CAAA,iBAAC,CACzB,gBAAgB,CAAA,QAAC,CACjB,wBAAwB,CAAA,iBAAC,CACzB,wBAAwB,CAAA,QAAC,CACzB,wBAAwB,CAAA,QAAC,CACzB,wBAAwB,CAAA,QAAC,CACzB,wBAAwB,CAAA,QAAC,CACzB,uBAAuB,CAAA,iBAAC,CACxB,eAAe,CAAA,QAAC,CAChB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,sBAAsB,CAAA,QAAC,CACvB,eAAe,CAAA,sEAAC,CAChB,qBAAqB,CAAA,gCAAC,CACtB,uBAAuB,CAAA,+BAAC,CACxB,oBAAoB,CAAA,wFAAC,CACrB,kBAAkB,CAAA,QAAC,CACnB,aAAa,CAAA,QAAC,CACd,mBAAmB,CAAA,GAAC,CACpB,mBAAmB,CAAA,GAAC,CACpB,mBAAmB,CAAA,GAAC,CACpB,qBAAqB,CAAA,QAAC,CACtB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,yBAAyB,CAAA,QAAC,CAC1B,2BAA2B,CAAA,QAAC,CAC5B,wBAAwB,CAAA,QAAC,CACzB,wBAAwB,CAAA,QAAC,CACzB,sBAAsB,CAAA,QAAC,CACvB,iBAAiB,CAAA,QAAC,CAClB,uBAAuB,CAAA,QAAC,CACxB,yBAAyB,CAAA,QAAC,CAC1B,6BAA6B,CAAA,QAAC,CAC9B,sBAAsB,CAAA,QAAC,CACvB,oBAAoB,CAAA,QAAC,CACrB,eAAe,CAAA,QAAC,CAChB,qBAAqB,CAAA,QAAC,CACtB,uBAAuB,CAAA,QAAC,CACxB,2BAA2B,CAAA,QAAC,CAC5B,qBAAqB,CAAA,YAAC,CACtB,eAAe,CAAA,kBAAC,CAChB,2BAA2B,CAAA,kBAAC,CAC5B,AAED,AAAA,IAAI,CAAA,AAAA,UAAC,CAAW,MAAM,AAAjB,EAAmB,UAAU,AAAC,CAClC,+BAA+B,CAAA;AAAC,CAChC,AAED,AAAA,IAAI,CAAA,AAAA,UAAC,CAAW,MAAM,AAAjB,EAAmB,QAAQ,AAAC,CAChC,kBAAkB,CAAA;AAAC,CACnB,AAED,AAAA,IAAI,CAAA,AAAA,UAAC,CAAW,MAAM,AAAjB,EAAmB,SAAS,AAAC,CACjC,uBAAuB,CAAA,sBAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,uBAAuB,CAAA,QAAC,CACxB,AAED,AAAA,IAAI,CAAA,AAAA,UAAC,CAAW,OAAO,AAAlB,CAAoB,CACxB,mBAAmB,CAAA,IAAC,CACpB,mBAAmB,CAAA,IAAC,CACpB,mBAAmB,CAAA,IAAC,CACpB,kBAAkB,CAAA,QAAC,CACnB,0BAA0B,CAAA,kBAAC,CAC3B,yBAAyB,CAAA,gBAAC,CAC1B,kBAAkB,CAAA,QAAC,CACnB,0BAA0B,CAAA,kBAAC,CAC3B,yBAAyB,CAAA,iBAAC,CAC1B,kBAAkB,CAAA,QAAC,CACnB,0BAA0B,CAAA,kBAAC,CAC3B,yBAAyB,CAAA,kBAAC,CAC1B,iBAAiB,CAAA,QAAC,CAClB,yBAAyB,CAAA,kBAAC,CAC1B,wBAAwB,CAAA,iBAAC,CACzB,gBAAgB,CAAA,QAAC,CACjB,wBAAwB,CAAA,kBAAC,CACzB,uBAAuB,CAAA,iBAAC,CACxB", + "sources": [ + "../../../scss/theme.scss" + ], + "names": [], + "file": "theme.min.css" +} \ No newline at end of file diff --git a/fastmovie-vue/src/assets/vue.svg b/fastmovie-vue/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/fastmovie-vue/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/common/PricingCalculator.ts b/fastmovie-vue/src/common/PricingCalculator.ts new file mode 100644 index 0000000..af963b9 --- /dev/null +++ b/fastmovie-vue/src/common/PricingCalculator.ts @@ -0,0 +1,122 @@ +type FormRuleOption = { + value: string | number | boolean + base_point?: number + multiplier?: number +} + +type FormRule = { + component: 'select' | 'radio' | 'checkbox' | 'switch' | string + helper_field: string + base_point?: number + multiplier?: number + options?: FormRuleOption[] +} + +type FormData = Record + +export class PricingCalculator { + protected formRules: FormRule[] + protected formData: FormData + + protected baseCost: number // 系统级基础积分 + protected extraPoint = 0 // 规则附加积分 + protected multiplier = 1.0 + + protected chargeableComponents = [ + 'select', + 'radio', + 'checkbox', + 'switch', + ] + + constructor(formRules: FormRule[], formData: FormData, baseCost: number) { + this.formRules = formRules + this.formData = formData + this.baseCost = baseCost + } + + /** + * 主入口 + */ + calculate(): number { + for (const rule of this.formRules) { + if (!this.chargeableComponents.includes(rule.component)) { + continue + } + + const value = this.getSubmittedValue(rule) + if (value === null || value === undefined) { + continue + } + + // Field 级计费(switch) + this.applyFieldPricing(rule, value) + + // Option 级计费 + if (rule.options && rule.options.length > 0) { + this.applyOptionPricing(rule, value) + } + } + return Math.ceil(this.baseCost * this.multiplier + this.extraPoint) + } + + protected getSubmittedValue(rule: FormRule): any { + const field = rule.helper_field + if (field in this.formData) { + return this.formData[field] + } + return null + } + + /** + * Field 级计费(switch) + */ + protected applyFieldPricing(rule: FormRule, value: any): void { + if (rule.component === 'switch' && value !== true) { + return + } + + if (rule.base_point !== undefined && Number(rule.base_point)) { + this.extraPoint += Number(rule.base_point) + } + + if (rule.multiplier !== undefined && Number(rule.multiplier)) { + this.multiplier *= Number(rule.multiplier) + } + } + + /** + * Option 级计费 + */ + protected applyOptionPricing(rule: FormRule, value: any): void { + for (const option of rule.options!) { + if (!this.optionMatched(rule.component, option, value)) { + continue + } + + if (option.base_point !== undefined) { + if (typeof value === 'number') { + this.extraPoint += Number(option.base_point) * value + } else { + this.extraPoint += Number(option.base_point) + } + } + if (option.multiplier !== undefined) { + this.multiplier = this.multiplier * Number(option.multiplier) + console.log(this.multiplier, option.multiplier); + } + } + } + + protected optionMatched( + component: string, + option: FormRuleOption, + value: any + ): boolean { + if (component === 'checkbox') { + return Array.isArray(value) && value.includes(option.value) + } + + return String(option.value) === String(value) + } +} diff --git a/fastmovie-vue/src/common/av-cliper/ClipExporter.ts b/fastmovie-vue/src/common/av-cliper/ClipExporter.ts new file mode 100644 index 0000000..04d5c7b --- /dev/null +++ b/fastmovie-vue/src/common/av-cliper/ClipExporter.ts @@ -0,0 +1,353 @@ +import { + Combinator, + AudioClip, + EmbedSubtitlesClip, + ImgClip, + MP4Clip, + renderTxt2ImgBitmap, + OffscreenSprite, +} from '@webav/av-cliper'; + +type ClipType = 'video' | 'audio' | 'image' | 'text'; +/** + * 将时间(微秒)转换为 SRT 时间格式 (HH:MM:SS,mmm) + */ +function formatSrtTime(us: number): string { + const totalMs = Math.floor(us / 1000); + const hours = Math.floor(totalMs / 3600000); + const minutes = Math.floor((totalMs % 3600000) / 60000); + const seconds = Math.floor((totalMs % 60000) / 1000); + const milliseconds = totalMs % 1000; + return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')},${String(milliseconds).padStart(3, '0')}`; +} + + +/** + * 将单个文本片段转换为 SRT 格式 + * @param text 文本内容 + * @param startTimeUs 开始时间(微秒) + * @param endTimeUs 结束时间(微秒) + * @param index 字幕序号(从1开始) + * @returns SRT 格式的字符串 + */ +export function textToSrt( + text: string, + startTimeUs: number, + endTimeUs: number, + index = 1, +): string { + return `${index}\n${formatSrtTime(startTimeUs)} --> ${formatSrtTime(endTimeUs)}\n${text}\n\n`; +} + +export interface SubtitleStyle { + color?: string; + textBgColor?: string | null; + type?: 'srt'; + fontFamily?: string; + fontSize?: number; + letterSpacing?: string | null; + bottomOffset?: number; + strokeStyle?: string; + lineWidth?: number | null; + lineCap?: CanvasLineCap | null; + lineJoin?: CanvasLineJoin | null; + textShadow?: { + offsetX: number; + offsetY: number; + blur: number; + color: string; + }; + videoWidth: number; + videoHeight: number; + fontWeight?: string | number; + fontStyle?: 'normal' | 'italic'; +} + +export interface ClipOptions { + type: ClipType; + stream?: ReadableStream|ImageBitmap; + text?: string; + style?: string; + // 字幕相关配置 + subtitleSrt?: string; // SRT 格式的字幕内容 + subtitleStyle?: SubtitleStyle; // 字幕样式配置 + durationUs?: number; // 可选,默认用素材本身长度 + trackId?: string | number; // 轨道ID,默认为 'main'(主轨道) + offsetUs?: number; // 插入时间(微秒),非主轨道可以使用此参数指定插入时间 +} + +export class ClipExporter { + private combinator: Combinator; + private tracks: any; + private width: number; + private height: number; + private durationUs: number = 0; + private cancelRef: boolean = false; + // 保存所有创建的资源,以便在取消时释放 + private clips: Array = []; + private sprites: OffscreenSprite[] = []; + constructor(width = 1280, height = 720, bgColor = '#000') { + this.width = width; + this.height = height; + this.combinator = new Combinator({ + width, + height, + bgColor, + }); + } + setTracks(tracks: any) { + this.tracks = tracks; + return this; + } + async initializationDialogues() { + if (this.cancelRef) { + return; + } + this.events['dialogues']?.(0); + const dialogueSubtitles: any[] = []; + const dialoguesLength = this.tracks.dialogues.length; + for (let i = 0; i < dialoguesLength; i++) { + if (this.cancelRef) { + return; + } + const track = this.tracks.dialogues[i]; + dialogueSubtitles.push({ + text: track.text, + start: track.startTimeUs, + end: track.endTimeUs, + }); + } + if (this.cancelRef) { + return; + } + const clip = new EmbedSubtitlesClip(dialogueSubtitles, { + videoWidth: this.width, + videoHeight: this.height, + fontSize: 24, + fontFamily: 'Noto Sans SC', + strokeStyle: '#000', + lineWidth: 2, + lineJoin: 'round', + lineCap: 'round', + bottomOffset: 100, + }); + this.clips.push(clip); + const spr = new OffscreenSprite(clip); + spr.time.offset = 0; + spr.time.duration = this.durationUs; + this.sprites.push(spr); + if (this.cancelRef) { + return; + } + await this.combinator.addSprite(spr); + this.events['dialogues']?.(100); + } + private narrationStyle = ` + font-size: 20px; + font-weight:600; + color: white; + -webkit-text-stroke: 1px #000; + writing-mode: vertical-lr; + text-orientation: upright; + white-space:wrap; + padding:10px;`; + async initializationNarrations() { + if (this.cancelRef) { + return; + } + this.events['narrations']?.(0); + this.narrationStyle = ` + height:${this.height / 2}px; + ${this.narrationStyle}`; + const narrationsLength = this.tracks.narrations.length; + for (let i = 0; i < narrationsLength; i++) { + if (this.cancelRef) { + return; + } + const track = this.tracks.narrations[i]; + const clip = new ImgClip( + await renderTxt2ImgBitmap( + track.text, + this.narrationStyle, + ), + ); + this.clips.push(clip); + await clip.ready; + if (this.cancelRef) { + return; + } + const spr = new OffscreenSprite(clip); + spr.time.offset = track.startTimeUs; + spr.time.duration = track.endTimeUs - track.startTimeUs; + this.sprites.push(spr); + if (this.cancelRef) { + return; + } + await this.combinator.addSprite(spr); + this.events['narrations']?.(i / narrationsLength * 100); + } + this.events['narrations']?.(100); + } + private events: any = { + video: () => { }, + dialogues: () => { }, + dialogues_audio: () => { }, + narrations: () => { }, + narrations_audio: () => { }, + }; + on(event: keyof typeof this.events, callback: (data: any) => void) { + this.events[event] = callback; + } + off(event: keyof typeof this.events) { + delete this.events[event]; + } + async initialization() { + if (this.cancelRef) { + return; + } + let spr: OffscreenSprite; + let clip: MP4Clip | ImgClip | AudioClip; + this.events['video']?.(0); + const videoLength = this.tracks.video.length; + for (let i = 0; i < videoLength; i++) { + if (this.cancelRef) { + return; + } + const track = this.tracks.video[i]; + if (track.type === 'video') { + clip = new MP4Clip(track.stream); + this.clips.push(clip); + spr = new OffscreenSprite(clip); + spr.time.offset = track.offsetUs; + spr.time.duration = track.durationUs; + } else { + clip = new ImgClip(track.stream); + this.clips.push(clip); + spr = new OffscreenSprite(clip); + spr.time.offset = track.offsetUs; + spr.time.duration = track.durationUs; + } + this.sprites.push(spr); + this.durationUs += track.durationUs; + if (this.cancelRef) { + return; + } + await this.combinator.addSprite(spr); + this.events['video']?.(i / videoLength * 100); + } + if (this.cancelRef) { + return; + } + this.events['dialogues_audio']?.(0); + const dialoguesAudioLength = this.tracks.dialogues_audio.length; + for (let i = 0; i < dialoguesAudioLength; i++) { + if (this.cancelRef) { + return; + } + const track = this.tracks.dialogues_audio[i]; + clip = new AudioClip(track.stream); + this.clips.push(clip); + await clip.ready; + if (this.cancelRef) { + return; + } + spr = new OffscreenSprite(clip); + spr.time.offset = track.offsetUs; + spr.time.duration = clip.meta.duration; + this.sprites.push(spr); + if (this.cancelRef) { + return; + } + await this.combinator.addSprite(spr); + this.events['dialogues_audio']?.(i / dialoguesAudioLength * 100); + } + if (this.cancelRef) { + return; + } + this.events['narrations_audio']?.(0); + const narrationsAudioLength = this.tracks.narrations_audio.length; + for (let i = 0; i < narrationsAudioLength; i++) { + if (this.cancelRef) { + return; + } + const track = this.tracks.narrations_audio[i]; + clip = new AudioClip(track.stream); + this.clips.push(clip); + await clip.ready; + if (this.cancelRef) { + return; + } + spr = new OffscreenSprite(clip); + spr.time.offset = track.offsetUs; + spr.time.duration = clip.meta.duration; + this.sprites.push(spr); + if (this.cancelRef) { + return; + } + await this.combinator.addSprite(spr); + this.events['narrations_audio']?.(i / narrationsAudioLength * 100); + } + if (this.cancelRef) { + return; + } + await this.initializationDialogues(); + if (this.cancelRef) { + return; + } + await this.initializationNarrations(); + if (this.cancelRef) { + return; + } + } + OutputProgress(callback: (data: any) => void) { + this.combinator.on('OutputProgress', (progress: number) => { + callback(progress * 100); + }); + } + async export(writer: WritableStream) { + if(this.cancelRef){ + return; + } + await this.combinator.output().pipeTo(writer); + } + + destroy() { + // 释放所有 clips + this.clips.forEach(clip => { + try { + if (clip && typeof (clip as any).destroy === 'function') { + (clip as any).destroy(); + } + } catch (error) { + console.warn('销毁 clip 时出错:', error); + } + }); + this.clips = []; + + // 释放所有 sprites + this.sprites.forEach(sprite => { + try { + if (sprite && typeof sprite.destroy === 'function') { + sprite.destroy(); + } + } catch (error) { + console.warn('销毁 sprite 时出错:', error); + } + }); + this.sprites = []; + + // 销毁 combinator + if (this.combinator) { + try { + this.combinator.destroy(); + } catch (error) { + console.warn('销毁 combinator 时出错:', error); + } + } + } + cancel() { + this.cancelRef = true; + // 取消时立即释放所有资源 + this.destroy(); + } +} diff --git a/fastmovie-vue/src/common/av-cliper/ImageDurationClip.ts b/fastmovie-vue/src/common/av-cliper/ImageDurationClip.ts new file mode 100644 index 0000000..477a436 --- /dev/null +++ b/fastmovie-vue/src/common/av-cliper/ImageDurationClip.ts @@ -0,0 +1,71 @@ +import type { IClip } from '@webav/av-cliper'; +import { ImgClip } from '@webav/av-cliper'; + +export class ImageDurationClip implements IClip { + private img: ImgClip; + private durationMs: number; + + ready: IClip['ready']; + + constructor(img: ImgClip, durationMs: number) { + this.img = img; + this.durationMs = durationMs; + this.ready = img.ready; + } + + get meta() { + const meta = this.img.meta; + return { + width: meta.width, + height: meta.height, + displayWidth: meta.width, + codedWidth: meta.width, + displayHeight: meta.height, + codedHeight: meta.height, + duration: this.durationMs, + }; + } + + async tick(time: number): Promise>> { + if (time >= this.durationMs) { + return { + state: 'done', + }; + } + + const ret = await this.img.tick(time); + return { + ...ret, + state: 'success', + }; + } + + async split(time: number): Promise<[this, this]> { + if (time <= 0 || time >= this.durationMs) { + throw new Error('split time out of range'); + } + + const left = new ImageDurationClip( + await this.img.clone(), + time, + ) as this; + + const right = new ImageDurationClip( + await this.img.clone(), + this.durationMs - time, + ) as this; + + return [left, right]; + } + + async clone(): Promise { + return new ImageDurationClip( + await this.img.clone(), + this.durationMs, + ) as this; + } + + destroy() { + this.img.destroy(); + } +} diff --git a/fastmovie-vue/src/common/const.ts b/fastmovie-vue/src/common/const.ts new file mode 100644 index 0000000..fad073a --- /dev/null +++ b/fastmovie-vue/src/common/const.ts @@ -0,0 +1,19 @@ +export const ResponseCode = { + SUCCESS: 200, + WAIT: 202, + SUCCESS_EVENT_PUSH: 201, + FAIL: 404, + NEED_LOGIN: 12000, + NEED_TWOFA: 12001, + CAPTCHA: 300, + SITE_ERROR: 400, + NEED_PAY: 9100, + PAY_SUCCESS: 9000, + PAY_FAIL: 9010, + REDIRECT: 302, + REDIRECT_CONFIRM: 303, + OPEN_NEW_WINDOW:304, + OPEN_NEW_WINDOW_CONFIRM:305, + NO_PERMISSION: 403, + LOCK: 423, +} as const \ No newline at end of file diff --git a/fastmovie-vue/src/common/functions.ts b/fastmovie-vue/src/common/functions.ts new file mode 100644 index 0000000..22c40cd --- /dev/null +++ b/fastmovie-vue/src/common/functions.ts @@ -0,0 +1,525 @@ +import { ElMessage } from "element-plus"; +import { useWebConfigStore } from "@/stores"; +import { i18n } from '@/locale'; +const { t } = i18n.global; +/** + * 节流函数会确保在指定的时间间隔内,函数被调用的次数不超过设定的阈值。 + * @param func 节流函数 + * @param wait 时间(毫秒) + * @returns function + */ +export function throttle void>(func: F, wait: number): F { + let timer: ReturnType | undefined; + return function (this: ThisParameterType, ...args: Parameters) { + if (!timer) { + timer = setTimeout(() => { + timer = undefined; + func.apply(this, args); + }, wait); + } + } as F; +} +/** + * 防抖函数会延迟函数的执行,直到一定的静默期过去后,才真正执行该函数。 + * @param func 防抖函数 + * @param wait 时间(毫秒) + * @returns function + */ +export function debounce void>(func: F, wait: number): F { + let timer: ReturnType | undefined; + return function (this: ThisParameterType, ...args: Parameters) { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + timer = undefined; + func.apply(this, args); + }, wait); + } as F; +} +/** + * 监听浏览器滚动 + * @param func 回调函数 + * @param wait 回调频率 + */ +export function onScroll void>(func: F, wait: number = 0): void { + let event = func; + if (wait > 0) { + event = debounce(func, wait); + } + const eventScroll = () => { + const scrollTop = globalThis.pageYOffset || document.documentElement.scrollTop; + event(scrollTop); + } + onMounted(() => { + globalThis.addEventListener('scroll', eventScroll); + }); + onUnmounted(() => { + globalThis.removeEventListener('scroll', eventScroll); + }); +} +/** + * 监听浏览器尺寸变化 + * @param func 回调函数 + * @param wait 回调频率 + */ +export function onWindowResize void>(func: F, wait: number = 0): void { + let event = func; + if (wait > 0) { + event = debounce(func, wait); + } + const eventResize = () => { + event({ w: globalThis.innerWidth, h: globalThis.innerHeight }); + } + onMounted(() => { + globalThis.addEventListener('resize', eventResize); + }); + onUnmounted(() => { + globalThis.removeEventListener('resize', eventResize); + }); +} +/** + * 观察目标元素与其祖先元素或视窗之间的交叉状态。提供一种有效且高性能的方式来检测元素是否进入或离开视图区域。 + * @param el 元素 + * @param func 回调方法 + */ +export function onObserve void>(el: any, func: F): void { + const done = (target: Element) => { + observer.unobserve(target); + } + const observer = new IntersectionObserver((e) => { + func(e, done); + }); + onMounted(() => { + el.value && observer.observe(el.value); + }); + onUnmounted(() => { + el.value && done(el.value); + }); +} +/** + * 监听storage变化 + * @param key 监听的key + * @param func 回调方法 + * @returns F() + * @example onStoreageChange('key',()=>{}) +**/ +export function onStoreageChange void>(key: string, func: F): F { + const event = (e: StorageEvent): any => { + if (e.key === null) { + func(); + } else if (e.key === key) { + func(e); + } + } + globalThis.addEventListener('storage', event); + return function () { + globalThis.removeEventListener('storage', event); + } as F +} +/** + * 秒转换为天时分秒 + * @param time 时间(秒) + * @returns string + */ +export function timetostr(time: number | undefined): string { + let str = ''; + if (!time) { return str } + let d = 0, h = 0, m = 0, s = 0; + if (time >= 3600 * 24) { + d = Math.floor(time / (3600 * 24)); + time = time % (3600 * 24); + } + if (time >= 3600) { + h = Math.floor(time / (60 * 60)); + time = time % (60 * 60); + } + if (time >= 60) { + m = Math.floor(time / 60); + } + s = time % 60; + if (d > 0) { + str = d + t('date.day'); + } + if (d > 0 || h > 0) { + str += h + t('date.hour'); + } + if (d > 0 || h > 0 || m > 0) { + str += m + t('date.minute'); + } + if (d > 0 || h > 0 || m > 0 || s > 0) { + str += s + t('date.second'); + } + return str +} +const copyText = (text: string) => { + return new Promise((resolve, reject) => { + let input = document.createElement('input'); + input.value = text; + document.body.appendChild(input); + input.select(); + input.setSelectionRange(0, input.value.length); + if (document.execCommand('Copy')) { + resolve(); + } else { + reject(); + } + input.remove(); + }) +} +/** + * 设置剪切板内容 + * @param text 内容 + * @returns void + */ +export function setClipboard(text: string): void { + if (navigator.clipboard && globalThis.isSecureContext) { + navigator.clipboard.writeText(text).then(() => { + ElMessage.success(t('message.copySuccess')); + }).catch(() => { + copyText(text).then(() => { + ElMessage.success(t('message.copySuccess')); + }).catch(() => { + ElMessage.error(t('message.copyFail')); + }); + }); + } else { + copyText(text).then(() => { + ElMessage.success(t('message.copySuccess')); + }).catch(() => { + ElMessage.error(t('message.copyFail')); + }); + } +} +/** + * 数字转千分位 + * @param num 数字 + * @returns string + * @example 10000 => 10,000 + * @example 10000.123 => 10,000.123 + * @example 10000.123456 => 10,000.123456 + */ +export function toThousands(num: number | string): string { + let result = '', counter = 0; + num = (num || 0).toString(); + for (let i = num.length - 1; i >= 0; i--) { + counter++; + result = num.charAt(i) + result; + if (!(counter % 3) && i !== 0) { result = ',' + result; } + } + return result; +} +export function getRandomNumber(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1) + min); +} +export function openWin(url: string) { + globalThis.open(url); +} +/** + * 替换地址栏#参数不跳转 + * @param key 参数名 + * @param value 参数值 + * @returns void + * @example replaceUrlParam('key','value') + */ +export function replaceUrlParam(key: string, value: string): void { + const url = new URL(globalThis.location.href); + url.searchParams.set(key, value); + globalThis.history.replaceState({}, '', url.href); +} +/** + * 批量替换地址栏#参数不跳转 + * @param params 参数对象 + * @returns void + * @example replaceUrlParams({key1:'value1',key2:'value2'}) + */ +export function replaceUrlParams(params: Record): void { + const url = new URL(globalThis.location.href); + for (const key in params) { + url.searchParams.set(key, params[key]); + } + globalThis.history.replaceState({}, '', url.href); +} +/** + * 设置网站标题 + * @param title 标题 + * @returns void + * @example setDocumentTitle('标题') + */ +export function setDocumentTitle(title: string): void { + const { WEBCONFIG } = useWebConfigStore(); + let webName = WEBCONFIG.web_title; + let subTitle = ''; + if (webName) { + subTitle = ` - ${webName}`; + } + globalThis.document.title = `${title}${subTitle}` +} +/** + * 分时函数 + * @param arr 数组 + * @param fn 回调函数 + * @returns {clear:()=>void} + * @example timeChunk([1,2,3,4,5,6,7,8,9,10],(item)=>{console.log(item)}) + */ +export function timeChunk(arr: T[], fn: (item: T) => void): { clear: () => void } { + let isBreak = false; + const clear = () => { + isBreak = true; + } + let i = 0; + function _run() { + globalThis.requestIdleCallback((idle) => { + while (idle.timeRemaining() > 0 && i < arr.length) { + fn(arr[i++]); + } + if (i < arr.length && isBreak === false) { + _run(); + } + }) + } + if (arr.length > 0) _run(); + return { + clear + } +} +/** + * 数字金额转中文大写 + * @param num 金额 + * @returns string + * @example toChineseNumeral(10000) => 壹万 + * @example toChineseNumeral(10000.123) => 壹万零壹角贰分三厘 + */ + +export function toChineseNumeral(num: number): string { + if (!num) { + return '零元整'; + } + const fraction = ['角', '分', '厘']; + const digit = [ + '零', '壹', '贰', '叁', '肆', + '伍', '陆', '柒', '捌', '玖' + ]; + const unit = [ + ['元', '万 ', '亿 '], + ['', '拾', '佰', '仟'] + ]; + let head = num < 0 ? '欠' : ''; + num = Math.abs(num); + let s = ''; + for (let i = 0; i < fraction.length; i++) { + s += (digit[Math.floor(num * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, ''); + } + num = Math.floor(num); + for (let i = 0; i < unit[0].length && num > 0; i++) { + let p = ''; + for (let j = 0; j < unit[1].length && num > 0; j++) { + p = digit[num % 10] + unit[1][j] + p; + num = Math.floor(num / 10); + } + s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s; + } + return head + s.replace(/(零.)*零元/, '元') + .replace(/(零.)+/g, '零') + .replace(/^整$/, '零元整'); +} +export const getRoundImage = () => { + return new Promise((resolve, reject) => { + try { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://picsum.photos/1920/1080?random', true); + xhr.responseType = 'blob'; + xhr.onload = function () { + if (this.status === 200) { + const blob = this.response; + return resolve({ blob: URL.createObjectURL(blob) }); + } + reject(); + } + xhr.send(); + } catch (e) { + reject(); + } + }); +} +export const hasWhere = (extra: any, form: any): boolean => { + if (!extra?.where) return true; + // 把每一种比较集中到一个函数里,便于复用 + const match = (field: string, exp: string, value: any): boolean => { + const formValue = getTableValue(form, field); + if (Array.isArray(formValue) && Array.isArray(value)) { + switch (exp) { + case '=': return value.every(item => formValue.includes(item)); + case '!=': return !value.every(item => formValue.includes(item)); + case 'in': return value.some(item => formValue.includes(item)); + case 'not in': return !value.some(item => formValue.includes(item)); + default: return false; + } + } else if (Array.isArray(formValue)) { + switch (exp) { + case 'in': return formValue.includes(value); + case 'not in': return !formValue.includes(value); + default: return false; + } + } else { + switch (exp) { + case '=': return formValue === value; + case '!=': return formValue !== value; + case '>': return formValue > value; + case '>=': return formValue >= value; + case '<': return formValue < value; + case '<=': return formValue <= value; + case 'in': return Array.isArray(value) && value.includes(formValue); + case 'not in': return Array.isArray(value) && !value.includes(formValue); + case 'like': return typeof formValue === 'string' && formValue.includes(value); + case 'not like': return typeof formValue === 'string' && !formValue.includes(value); + case 'between': return formValue >= value[0] && formValue <= value[1]; + case 'not between': return formValue < value[0] || formValue > value[1]; + case 'null': return formValue === null; + case 'not null': return formValue !== null; + default: return false; + } + } + }; + + for (const condition of extra.where) { + const [field] = condition; + + // --- AND 情况(field 是字符串) --- + if (typeof field === 'string') { + if (!match(field, condition[1], condition[2])) return false; + continue; + } + + // --- OR 情况(field 不是字符串)--- + // condition 形如:[[f1, op1, v1], [f2, op2, v2], ...] + let anyPassed = false; + for (const [subField, subExp, subVal] of condition as Array<[string, string, any]>) { + if (match(subField, subExp, subVal)) { + anyPassed = true; + break; + } + } + if (!anyPassed) return false; // 整组 OR 都未命中 + } + + return true; // 所有 AND/OR 条件全部满足 +}; + +/** + * 截取指定长度字符串 + * @param str 字符串 + * @param length 长度 + * @returns string + * @example truncate('1234567890',5) => '12345' + * @example truncate('abcdefg',3)=>'abc' + * @example truncate('你好,世界!',1)=>'你' + */ +export function truncate(str: string, length: number, start: number = 0): string { + if (!str) return ''; + // 如果长度大于字符串长度,则返回字符串 + if (length >= str.length) return str; + // 如果取值不完整,则返回截取后的字符串 + return [...str].slice(start, start + length).join(''); +} +/** + * 递归取值 + * @param data 数据集(可以是任意层级的对象 / 数组) + * @param keys 键数组——按顺序描述要深入的属性/索引路径 + * @returns 对应层级上的值;若路径不存在则返回 undefined | null + */ +export function deepGet< + T = any, + K extends ReadonlyArray = Array +>(data: any, keys: K): T | undefined | null { + // ① 递归出口:keys 已取完,直接返回当前 data + if (keys.length === 0) { + return data as T; + } + + // ② 安全判空:中途遇到 null / undefined 立即返回 undefined + if (data === null) { + return null; + } + if (data === undefined) { + return undefined; + } + + // ③ 递归深入:取出首键,继续在剩余子集上查找 + const [firstKey, ...restKeys] = keys; + // 使用 `as any` 避免 TS “索引类型” 报错 + return deepGet((data as any)[firstKey], restKeys); +} +/** + * 根据点路径/下标递归取值 + * @param obj 任意对象/数组 + * @param path 如 'apps.title' | 'tags[0]' + * @returns 对应值,路径不存在返 undefined + */ +export function getTableValue(obj: any, path: string): T | undefined { + if (!path) return obj as T; + + // 先把 tags[0] 这类写法转成 tags.0,便于统一 split + const segments = path + .replace(/\[(\d+)\]/g, '.$1') // 把 [index] 转成 .index + .split('.') + .filter(Boolean); // 过滤空串,防止连点 + + let cur: any = obj; + for (const key of segments) { + if (cur == null) return undefined; // 短路判空 + cur = cur[key as keyof typeof cur]; + } + return cur as T; +} +/** + * 文件大小转换 + * @param size 文件大小 + * @returns string + * @example fileSizeToStr(1024) => '1KB' + * @example fileSizeToStr(1024*1024) => '1MB' + * @example fileSizeToStr(1024*1024*1024) => '1GB' + */ +export function fileSizeToStr(size: number): string { + if (size < 1024) { + return size + 'B'; + } + if (size < 1024 * 1024) { + return (size / 1024).toFixed(2) + 'KB'; + } + if (size < 1024 * 1024 * 1024) { + return (size / 1024 * 1024).toFixed(2) + 'MB'; + } + return (size / 1024 * 1024 * 1024).toFixed(2) + 'GB'; +} +/** + * 生成唯一ID + * @param prefix 前缀 + * @param moreEntropy 是否更多熵 + * @returns string + * @example uniqid() => '1719090240_abcdefg' + * @example uniqid('prefix_') => 'prefix_1719090240_abcdefg' + * @example uniqid('prefix_', true) => 'prefix_1719090240_abcdefg_hijklmn' + */ +export function uniqid(prefix: string = "", moreEntropy: boolean = false): string { + return prefix + Date.now().toString(16) + (moreEntropy ? Math.random().toString(36).substring(2, 15) : '') + Math.random().toString(36).substring(2, 15); +} +/** + * 格式化时间 + * @param ms 毫秒 + * @returns string + * @example formatDuration(60) => '01:00' + * @example formatDuration(60*60) => '01:00:00' + */ +export function formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const remainingSeconds = seconds % 60; + const remainingMinutes = minutes % 60; + const strArr = [remainingMinutes.toString().padStart(2, '0'), remainingSeconds.toString().padStart(2, '0')]; + if (hours) { + strArr.unshift(hours.toString().padStart(2, '0')); + } + return strArr.join(':'); +} \ No newline at end of file diff --git a/fastmovie-vue/src/common/http.ts b/fastmovie-vue/src/common/http.ts new file mode 100644 index 0000000..b3ea490 --- /dev/null +++ b/fastmovie-vue/src/common/http.ts @@ -0,0 +1,200 @@ +import { ElMessageBox } from "element-plus"; +import { useStorage } from "@/composables/useStorage"; +import axios, { AxiosResponse, AxiosError } from "axios"; +import { useRefs, useStateStore, useUserStore, useWebConfigStore } from "@/stores"; +import { getRoundImage } from "@/common/functions"; +import { i18n } from '@/locale'; +import { ResponseCode } from "./const"; +import { useLogin } from "@/composables/useLogin"; +const { t } = i18n.global; +let baseURL = globalThis.location.origin +if (import.meta.env.DEV) { + baseURL = baseURL + '/local' +} else if (import.meta.env.VITE_REQUEST_BASE_URL) { + baseURL = import.meta.env.VITE_REQUEST_BASE_URL +} else { + // 从meta中获取version + baseURL = document.querySelector('meta[name="base-url"]')?.getAttribute('content') || baseURL; +} +const getCompleteUrl = (path: string) => { + return `${baseURL}/${path}`; +} +const getHeaders = () => { + const headers: any = {}; + const { hasLogin, getToken } = useUserStore(); + if (hasLogin()) { + headers['Authorization'] = getToken(); + } + const storage = useStorage(); + if (storage.get('ICODE')) { + headers['X-ICODE'] = storage.get('ICODE') as string; + } + headers['X-Platform'] = 'pc'; + const stateStore = useStateStore(); + const { STATE } = useRefs(stateStore); + const lang = STATE.value.language; + headers['Accept-Language'] = lang; + headers['lang'] = lang; + if (import.meta.env.DEV) { + headers['X-developer'] = 'true'; + } + return headers; +} +axios.interceptors.request.use((_config) => { + const headers = getHeaders(); + Object.keys(headers).forEach((key) => { + _config.headers.set(key, headers[key as keyof typeof headers]); + }); + _config.baseURL = baseURL; + _config.timeout = 600000; + return _config; +}, (error) => { + return Promise.reject(error); +}); +axios.interceptors.response.use((response: AxiosResponse) => { + if (response?.data !== undefined) { + const userStore = useUserStore() + switch (response?.data?.code) { + case ResponseCode.NEED_LOGIN: + userStore.clearUserInfo(); + useLogin().open(); + break; + case ResponseCode.SUCCESS_EVENT_PUSH: + response.data.code = ResponseCode.SUCCESS; + if (response.data.data.event) { + if (Array.isArray(response.data.data.event)) { + response.data.data.event.forEach((e: string) => { + $eventBus.emit(e); + }) + } else { + $eventBus.emit(response.data.data.event); + } + } + break; + case ResponseCode.NO_PERMISSION: + showErrorBox(response.data); + break; + } + return response.data; + } +}, (error: AxiosError) => { + if (import.meta.env.DEV && error.code != 'ERR_CANCELED') { + showErrorBox({ + code: error.code, + msg: error.message, + data: { + method: error.config?.method, + url: error.config?.url, + } + }); + } + return Promise.reject(error); +}); +export const $http = { + getCompleteUrl, + getHeaders, + get: axios.get, + post: axios.post, + axios +} +const eventOn = (event: string, callback: Function) => { + globalThis.addEventListener(event, (e: any) => { + callback(e.detail) + }) +} +const eventRemove = (event: string, callback: Function) => { + globalThis.removeEventListener(event, (e: any) => { + callback(e.detail) + }) +} +const eventEmit = (event: string, data?: any) => { + globalThis.dispatchEvent(new CustomEvent(event, { detail: data })) +} +export const $eventBus = { + on: eventOn, + emit: eventEmit, + remove: eventRemove +} +export const showErrorBox = (res: any) => { + if (import.meta.env.PROD) return; + const content = []; + if (res.code) { + content.push(h('div', { class: 'flex py-2' }, [ + h('div', { class: 'text-grey text-right mr-2', style: { width: '100px' } }, t('message.error_code')), + h('div', {}, res.code) + ])) + } + content.push(h('div', { class: 'flex py-2' }, [ + h('div', { class: 'text-grey text-right mr-2', style: { width: '100px' } }, t('message.error_text')), + h('div', {}, res.msg) + ])) + if (res.data) { + for (const key in res.data) { + if (Object.prototype.hasOwnProperty.call(res.data, key)) { + const value = res.data[key]; + content.push(h('div', { class: 'flex py-2' }, [ + h('div', { class: 'text-grey text-right mr-2', style: { width: '100px' } }, key), + h('div', { class: 'text-break-all' }, value) + ])) + } + } + } + ElMessageBox({ + title: t('message.error'), + message: h('div', {}, content), + }) +} +export const useLoginImageBuild = () => { + const webConfigStore = useWebConfigStore(); + const { WEBCONFIG } = useRefs(webConfigStore); + const Image = ref(WEBCONFIG.value.login?.image); + const BgImage = ref('/static/bg.jpg'); + let BgImageEr: NodeJS.Timeout | undefined; + const getLoginBg = () => { + getRoundImage().then((res: any) => { + BgImage.value = res.blob; + }) + } + onMounted(() => { + switch (WEBCONFIG.value.login?.bg_image) { + case 'off': + BgImage.value = ''; + break; + case 'auto': + BgImageEr = setInterval(() => { + getLoginBg(); + }, 30000) + break; + default: + if (WEBCONFIG.value.login) { + if (Array.isArray(WEBCONFIG.value.login?.bg_image)) { + BgImageEr = setInterval(() => { + if (WEBCONFIG.value.login) { + BgImage.value = WEBCONFIG.value.login?.bg_image[Math.floor(Math.random() * WEBCONFIG.value.login?.bg_image.length)]; + } + }, 30000) + } else { + BgImage.value = WEBCONFIG.value.login?.bg_image; + } + } + break; + } + }) + onUnmounted(() => { + clearTimer(); + }) + const clear = () => { + BgImage.value = ''; + } + const clearTimer = () => { + if (BgImageEr) { + clearInterval(BgImageEr); + BgImageEr = undefined; + } + } + return { + BgImage, + Image, + clear + }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/common/push/channel.ts b/fastmovie-vue/src/common/push/channel.ts new file mode 100644 index 0000000..31cc655 --- /dev/null +++ b/fastmovie-vue/src/common/push/channel.ts @@ -0,0 +1,34 @@ +// channel.ts +import { Dispatcher } from "@/common/push/dispatcher"; +import { Connection } from "@/common/push/connection"; + +export class Channel extends Dispatcher { + public subscribed = false; + public subscribeCb: (() => void) | null = null; + private queue: (() => void)[] = []; + + constructor(public connection: Connection, public channelName: string) { + super(); + } + + processSubscribe() { + if (this.connection.state !== "connected") return; + this.subscribeCb?.(); + } + + processQueue() { + if (this.connection.state !== "connected" || !this.subscribed) return; + this.queue.forEach(fn => fn()); + this.queue = []; + } + + trigger(event: string, data: any) { + if (!event.startsWith("client-")) { + throw new Error(`Event '${event}' should start with 'client-'`); + } + this.queue.push(() => { + this.connection.send({ event, data, channel: this.channelName }); + }); + this.processQueue(); + } +} diff --git a/fastmovie-vue/src/common/push/connection.ts b/fastmovie-vue/src/common/push/connection.ts new file mode 100644 index 0000000..656e6c8 --- /dev/null +++ b/fastmovie-vue/src/common/push/connection.ts @@ -0,0 +1,130 @@ +// connection.ts +import { Dispatcher } from '@/common/push/dispatcher'; +export interface ConnectionOptions { + url: string; + app_key: string; + onOpen?: (ev: Event) => void; + onMessage?: (ev: MessageEvent) => void; + onClose?: (ev: CloseEvent) => void; + onError?: (ev: Event) => void; +} +export class Connection extends Dispatcher { + private options: ConnectionOptions; + private websocket: WebSocket | null = null; + public state: "initialized" | "connecting" | "connected" | "disconnected" = + "initialized"; + public doNotConnect = false; + public reconnectInterval = 1; + private reconnectTimer: number | null = null; + public socket_id?: string; + + constructor(options: ConnectionOptions) { + super(); + this.options = options; + this.connect(); + } + + private updateNetworkState(state: typeof this.state) { + const oldState = this.state; + this.state = state; + if (oldState !== state) { + this.emit("state_change", { previous: oldState, current: state }); + } + } + + connect() { + this.doNotConnect = false; + if (this.state === "connected") { + console.log(`networkState is "${this.state}" and do not need connect`); + return; + } + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + + this.closeAndClean(); + + const { url, app_key, onOpen, onMessage, onClose, onError } = this.options; + const ws = new WebSocket(`${url}/app/${app_key}`); + + this.updateNetworkState("connecting"); + + ws.onopen = ev => { + this.reconnectInterval = 1; + if (this.doNotConnect) { + this.updateNetworkState("disconnected"); + ws.close(); + return; + } + onOpen?.(ev); + }; + + if (onMessage) { + ws.onmessage = onMessage; + } + + ws.onclose = ev => { + this.cleanupHandlers(ws); + this.updateNetworkState("disconnected"); + if (!this.doNotConnect) this.waitReconnect(); + onClose?.(ev); + }; + + ws.onerror = ev => { + this.close(); + if (!this.doNotConnect) this.waitReconnect(); + onError?.(ev); + }; + + this.websocket = ws; + } + + private cleanupHandlers(ws: WebSocket) { + ws.onmessage = null; + ws.onopen = null; + ws.onclose = null; + ws.onerror = null; + } + + closeAndClean() { + if (this.websocket) { + this.cleanupHandlers(this.websocket); + try { + this.websocket.close(); + } catch { } + this.updateNetworkState("disconnected"); + } + } + + waitReconnect() { + if (this.state === "connected" || this.state === "connecting") return; + if (!this.doNotConnect) { + this.updateNetworkState("connecting"); + if (this.reconnectTimer) clearTimeout(this.reconnectTimer); + + this.reconnectTimer = window.setTimeout(() => { + this.connect(); + }, this.reconnectInterval); + + this.reconnectInterval = + this.reconnectInterval < 1000 + ? 1000 + : Math.min(this.reconnectInterval * 2, 2000); + } + } + + send(data: any) { + let dataString = JSON.stringify(data); + if (this.state !== "connected" || !this.websocket) { + console.trace(`networkState is "${this.state}", can not send ${dataString}`); + return; + } + this.websocket.send(dataString); + } + + close() { + this.updateNetworkState("disconnected"); + this.websocket?.close(); + } +} diff --git a/fastmovie-vue/src/common/push/dispatcher.ts b/fastmovie-vue/src/common/push/dispatcher.ts new file mode 100644 index 0000000..906236c --- /dev/null +++ b/fastmovie-vue/src/common/push/dispatcher.ts @@ -0,0 +1,90 @@ +// dispatcher.ts +export interface EventCallback { + fn: (data?: any) => void; + context?: any; +} +export interface EventHandler { + (event: string, data?: any): void; +} +export class CallbackRegistry { + private _callbacks: Record = {}; + + get(name: string) { + return this._callbacks[prefix(name)]; + } + + add(name: string, callback: (data?: any) => void, context?: any) { + const prefixed = prefix(name); + this._callbacks[prefixed] = this._callbacks[prefixed] || []; + this._callbacks[prefixed].push({ fn: callback, context }); + } + + remove(name?: string, callback?: Function, context?: any) { + if (!name && !callback && !context) { + this._callbacks = {}; + return; + } + const names = name ? [prefix(name)] : Object.keys(this._callbacks); + if (callback || context) { + this.removeCallback(names, callback, context); + } else { + this.removeAllCallbacks(names); + } + } + + private removeCallback(names: string[], callback?: Function, context?: any) { + names.forEach(name => { + this._callbacks[name] = (this._callbacks[name] || []).filter( + oning => + (callback && callback !== oning.fn) || + (context && context !== oning.context) + ); + if (this._callbacks[name].length === 0) { + delete this._callbacks[name]; + } + }); + } + + private removeAllCallbacks(names: string[]) { + names.forEach(name => { + delete this._callbacks[name]; + }); + } + } + + export class Dispatcher { + private callbacks = new CallbackRegistry(); + private globalCallbacks: EventHandler[] = []; + constructor(private failThrough?: EventHandler) {} + + on(event: string, callback: (data?: any) => void, context?: any) { + this.callbacks.add(event, callback, context); + return this; + } + + onGlobal(callback: EventHandler) { + this.globalCallbacks.push(callback); + return this; + } + + off(event?: string, callback?: Function, context?: any) { + this.callbacks.remove(event, callback, context); + return this; + } + + emit(event: string, data?: any) { + this.globalCallbacks.forEach(cb => cb(event, data)); + const cbs = this.callbacks.get(event); + if (cbs?.length) { + cbs.forEach(c => c.fn.call(c.context || window, data)); + } else if (this.failThrough) { + this.failThrough(event, data); + } + return this; + } + } + + function prefix(name: string) { + return "_" + name; + } + \ No newline at end of file diff --git a/fastmovie-vue/src/common/push/index.ts b/fastmovie-vue/src/common/push/index.ts new file mode 100644 index 0000000..f300eff --- /dev/null +++ b/fastmovie-vue/src/common/push/index.ts @@ -0,0 +1,164 @@ +// push.ts +import { Connection } from '@/common/push/connection'; +import { Channel } from '@/common/push/channel'; +import { ResponseCode } from '@/common/const'; +import { $http } from '../http'; + + +export interface PushOptions { + url: string; + app_key: string; + auth?: string; + heartbeat?: number; + pingTimeout?: number; +} +export class Push { + static instances: Push[] = []; + private pingTimeoutTimer: number | null = null; + public channels: Record = {}; + public connection: Connection | null = null; + + constructor(private config: PushOptions) { + this.config.heartbeat = this.config.heartbeat ?? 25000; + this.config.pingTimeout = this.config.pingTimeout ?? 10000; + Push.instances.push(this); + this.createConnection(); + } + + private checkoutPing = () => { + setTimeout(() => { + if (this.connection?.state === "connected") { + this.connection.send({ event: "pusher:ping", data: {} }); + if (this.pingTimeoutTimer) { + clearTimeout(this.pingTimeoutTimer); + } + this.pingTimeoutTimer = window.setTimeout(() => { + this.connection?.closeAndClean(); + if (this.connection && !this.connection.doNotConnect) { + this.connection.waitReconnect(); + } + }, this.config.pingTimeout); + } + }, this.config.heartbeat); + }; + /** + * 订阅频道 + * @param channelName 频道名称 + * @param api 接口函数,当频道为私有频道时,需要传递接口函数 + * @returns 频道实例 + */ + subscribe(channelName: string) { + if (this.channels[channelName]) return this.channels[channelName]; + + if (channelName.indexOf('private-') === 0) { + return this.createPrivateChannel(channelName); + } + if (channelName.indexOf('presence-') === 0) { + return this.createPresenceChannel(channelName); + } + return this.createChannel(channelName); + } + + + createChannel(channelName: string) { + const channel = new Channel(this.connection!, channelName); + this.channels[channelName] = channel; + + channel.subscribeCb = () => { + this.connection?.send({ event: "pusher:subscribe", data: { channel: channelName } }); + }; + channel.processSubscribe(); + return channel; + } + + createPrivateChannel(channelName: string) { + let channel = new Channel(this.connection!, channelName); + this.channels[channelName] = channel; + channel.subscribeCb = () => { + if (!this.config.auth) return; + $http.post(this.config.auth, { channel_name: channelName, socket_id: this.connection!.socket_id }).then((res: any) => { + if (res.code === ResponseCode.SUCCESS) { + res.data.channel = channelName; + this.connection!.send({ event: "pusher:subscribe", data: res.data }); + } else { + channel.emit('subscription_error', res.msg); + } + }).catch((err: any) => { + channel.emit('subscription_error', err.message); + }); + }; + channel.processSubscribe(); + return channel; + } + + createPresenceChannel(channelName: string) { + return this.createPrivateChannel(channelName); + } + + unsubscribe(channelName: string) { + if (this.channels[channelName]) { + delete this.channels[channelName]; + if (this.connection?.state === "connected") { + this.connection.send({ event: "pusher:unsubscribe", data: { channel: channelName } }); + } + } + } + + unsubscribeAll() { + Object.keys(this.channels).forEach(name => this.unsubscribe(name)); + this.channels = {}; + } + + private createConnection() { + if (this.connection) { + throw new Error("Connection already exist"); + } + + this.connection = new Connection({ + url: this.config.url, + app_key: this.config.app_key, + onOpen: () => { + this.connection!.state = "connecting"; + this.checkoutPing(); + }, + onMessage: ev => { + if (this.pingTimeoutTimer) { + clearTimeout(this.pingTimeoutTimer); + this.pingTimeoutTimer = null; + } + const params = JSON.parse(ev.data); + const { event, channel: channelName } = params; + + if (event === "pusher:pong") { + this.checkoutPing(); + return; + } + if (event === "pusher:connection_established") { + this.connection!.socket_id = JSON.parse(params.data).socket_id; + this.connection!.state = "connected"; + this.subscribeAll(); + } + const channel = this.channels[channelName]; + if (channel) channel.emit(event, JSON.parse(params.data)); + }, + onClose: () => { + Object.values(this.channels).forEach(c => (c.subscribed = false)); + }, + onError: () => { + Object.values(this.channels).forEach(c => (c.subscribed = false)); + } + }); + } + + private subscribeAll() { + if (this.connection?.state !== "connected") return; + Object.values(this.channels).forEach(ch => ch.processSubscribe()); + } + + disconnect() { + if (this.connection) { + this.connection.doNotConnect = true; + this.connection.close(); + } + } +} diff --git a/fastmovie-vue/src/common/time/IntervalTask.ts b/fastmovie-vue/src/common/time/IntervalTask.ts new file mode 100644 index 0000000..0529d29 --- /dev/null +++ b/fastmovie-vue/src/common/time/IntervalTask.ts @@ -0,0 +1,19 @@ +export class IntervalTask implements TimeTask { + cancelled = false; + count = 0; + + constructor( + public interval: number, + public nextTime: number, + private cb: (count: number, now: number) => void + ) {} + + run(now: number) { + // 处理跳帧(后台 / tab 冻结) + const n = Math.floor((now - this.nextTime) / this.interval) + 1; + this.count += n; + this.nextTime += n * this.interval; + + this.cb(this.count, now); + } + } \ No newline at end of file diff --git a/fastmovie-vue/src/common/time/MinHeap.ts b/fastmovie-vue/src/common/time/MinHeap.ts new file mode 100644 index 0000000..d621bb4 --- /dev/null +++ b/fastmovie-vue/src/common/time/MinHeap.ts @@ -0,0 +1,52 @@ +export class MinHeap { + private data: T[] = []; + + constructor(private compare: (a: T, b: T) => number) {} + + peek(): T | undefined { + return this.data[0]; + } + + push(item: T) { + this.data.push(item); + this.bubbleUp(this.data.length - 1); + } + + pop(): T | undefined { + if (this.data.length === 1) return this.data.pop(); + const top = this.data[0]; + this.data[0] = this.data.pop()!; + this.bubbleDown(0); + return top; + } + + get size() { + return this.data.length; + } + + private bubbleUp(i: number) { + while (i > 0) { + const p = (i - 1) >> 1; + if (this.compare(this.data[i], this.data[p]) >= 0) break; + [this.data[i], this.data[p]] = [this.data[p], this.data[i]]; + i = p; + } + } + + private bubbleDown(i: number) { + const n = this.data.length; + while (true) { + let s = i; + const l = i * 2 + 1; + const r = l + 1; + + if (l < n && this.compare(this.data[l], this.data[s]) < 0) s = l; + if (r < n && this.compare(this.data[r], this.data[s]) < 0) s = r; + if (s === i) break; + + [this.data[i], this.data[s]] = [this.data[s], this.data[i]]; + i = s; + } + } + } + \ No newline at end of file diff --git a/fastmovie-vue/src/common/time/Scheduler.ts b/fastmovie-vue/src/common/time/Scheduler.ts new file mode 100644 index 0000000..85caf7c --- /dev/null +++ b/fastmovie-vue/src/common/time/Scheduler.ts @@ -0,0 +1,74 @@ +import { MinHeap } from './MinHeap'; +import { TimeoutTask } from './TimeoutTask'; +import { IntervalTask } from './IntervalTask'; +export class Scheduler { + private heap = new MinHeap( + (a, b) => a.nextTime - b.nextTime + ); + private ticking = false; + + constructor(private now: () => number) {} + + /* ========== Timeout ========== */ + + setTimeout(cb: (now: number) => void, delay: number) { + const task = new TimeoutTask(this.now() + delay, cb); + this.heap.push(task); + this.ensureTicking(); + return task; + } + + clearTimeout(task: TimeTask) { + task.cancelled = true; + } + + /* ========== Interval ========== */ + + setInterval( + cb: (count: number, now: number) => void, + interval: number + ) { + const task = new IntervalTask( + interval, + this.now() + interval, + cb + ); + this.heap.push(task); + this.ensureTicking(); + return task; + } + + clearInterval(task: TimeTask) { + task.cancelled = true; + } + + /* ========== 调度循环 ========== */ + + private ensureTicking() { + if (!this.ticking) this.tick(); + } + + private tick = () => { + this.ticking = true; + const now = this.now(); + + while (this.heap.size && this.heap.peek()!.nextTime <= now) { + const task = this.heap.pop()!; + if (task.cancelled) continue; + + task.run(now); + + // interval 会重新入堆,timeout 不会 + if (!task.cancelled) { + this.heap.push(task); + } + } + + if (this.heap.size) { + requestAnimationFrame(this.tick); + } else { + this.ticking = false; + } + }; + } + \ No newline at end of file diff --git a/fastmovie-vue/src/common/time/TimeoutTask.ts b/fastmovie-vue/src/common/time/TimeoutTask.ts new file mode 100644 index 0000000..fee7ff5 --- /dev/null +++ b/fastmovie-vue/src/common/time/TimeoutTask.ts @@ -0,0 +1,14 @@ +export class TimeoutTask implements TimeTask { + cancelled = false; + + constructor( + public nextTime: number, + private cb: (now: number) => void + ) {} + + run(now: number) { + this.cb(now); + this.cancelled = true; + } + } + \ No newline at end of file diff --git a/fastmovie-vue/src/components/x-header-tools/index.vue b/fastmovie-vue/src/components/x-header-tools/index.vue new file mode 100644 index 0000000..8635fa7 --- /dev/null +++ b/fastmovie-vue/src/components/x-header-tools/index.vue @@ -0,0 +1,194 @@ + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/x-header-tools/modules/code.vue b/fastmovie-vue/src/components/x-header-tools/modules/code.vue new file mode 100644 index 0000000..aba2148 --- /dev/null +++ b/fastmovie-vue/src/components/x-header-tools/modules/code.vue @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/x-login/index.vue b/fastmovie-vue/src/components/x-login/index.vue new file mode 100644 index 0000000..b5fe494 --- /dev/null +++ b/fastmovie-vue/src/components/x-login/index.vue @@ -0,0 +1,496 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/x-vcode/index.vue b/fastmovie-vue/src/components/x-vcode/index.vue new file mode 100644 index 0000000..3fb091b --- /dev/null +++ b/fastmovie-vue/src/components/x-vcode/index.vue @@ -0,0 +1,102 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-actor/index.vue b/fastmovie-vue/src/components/xl-actor/index.vue new file mode 100644 index 0000000..91aa3d7 --- /dev/null +++ b/fastmovie-vue/src/components/xl-actor/index.vue @@ -0,0 +1,150 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-actor/xl-actor-create.vue b/fastmovie-vue/src/components/xl-actor/xl-actor-create.vue new file mode 100644 index 0000000..4878702 --- /dev/null +++ b/fastmovie-vue/src/components/xl-actor/xl-actor-create.vue @@ -0,0 +1,621 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-aspect-ratio/index.vue b/fastmovie-vue/src/components/xl-aspect-ratio/index.vue new file mode 100644 index 0000000..696ba60 --- /dev/null +++ b/fastmovie-vue/src/components/xl-aspect-ratio/index.vue @@ -0,0 +1,47 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-character-look/index.vue b/fastmovie-vue/src/components/xl-character-look/index.vue new file mode 100644 index 0000000..3bf0404 --- /dev/null +++ b/fastmovie-vue/src/components/xl-character-look/index.vue @@ -0,0 +1,336 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-character-look/xl-character-look-create.vue b/fastmovie-vue/src/components/xl-character-look/xl-character-look-create.vue new file mode 100644 index 0000000..f388a12 --- /dev/null +++ b/fastmovie-vue/src/components/xl-character-look/xl-character-look-create.vue @@ -0,0 +1,242 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-dialogue/xl-dialogue-create.vue b/fastmovie-vue/src/components/xl-dialogue/xl-dialogue-create.vue new file mode 100644 index 0000000..8dbdf31 --- /dev/null +++ b/fastmovie-vue/src/components/xl-dialogue/xl-dialogue-create.vue @@ -0,0 +1,200 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-episode-duration/index.vue b/fastmovie-vue/src/components/xl-episode-duration/index.vue new file mode 100644 index 0000000..dceed43 --- /dev/null +++ b/fastmovie-vue/src/components/xl-episode-duration/index.vue @@ -0,0 +1,56 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-episode-sum/index.vue b/fastmovie-vue/src/components/xl-episode-sum/index.vue new file mode 100644 index 0000000..abaaf16 --- /dev/null +++ b/fastmovie-vue/src/components/xl-episode-sum/index.vue @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-header-userinfo/index.vue b/fastmovie-vue/src/components/xl-header-userinfo/index.vue new file mode 100644 index 0000000..01325ab --- /dev/null +++ b/fastmovie-vue/src/components/xl-header-userinfo/index.vue @@ -0,0 +1,168 @@ + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-icode/index.vue b/fastmovie-vue/src/components/xl-icode/index.vue new file mode 100644 index 0000000..58316b9 --- /dev/null +++ b/fastmovie-vue/src/components/xl-icode/index.vue @@ -0,0 +1,361 @@ + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-invitation-code/index.vue b/fastmovie-vue/src/components/xl-invitation-code/index.vue new file mode 100644 index 0000000..4858986 --- /dev/null +++ b/fastmovie-vue/src/components/xl-invitation-code/index.vue @@ -0,0 +1,280 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-loading/index.vue b/fastmovie-vue/src/components/xl-loading/index.vue new file mode 100644 index 0000000..c411714 --- /dev/null +++ b/fastmovie-vue/src/components/xl-loading/index.vue @@ -0,0 +1,150 @@ + + + diff --git a/fastmovie-vue/src/components/xl-models/index.vue b/fastmovie-vue/src/components/xl-models/index.vue new file mode 100644 index 0000000..bb86385 --- /dev/null +++ b/fastmovie-vue/src/components/xl-models/index.vue @@ -0,0 +1,121 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-notice/index.vue b/fastmovie-vue/src/components/xl-notice/index.vue new file mode 100644 index 0000000..38c31eb --- /dev/null +++ b/fastmovie-vue/src/components/xl-notice/index.vue @@ -0,0 +1,166 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-pay/index.vue b/fastmovie-vue/src/components/xl-pay/index.vue new file mode 100644 index 0000000..d98fb79 --- /dev/null +++ b/fastmovie-vue/src/components/xl-pay/index.vue @@ -0,0 +1,203 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-prop/index.vue b/fastmovie-vue/src/components/xl-prop/index.vue new file mode 100644 index 0000000..cd108d0 --- /dev/null +++ b/fastmovie-vue/src/components/xl-prop/index.vue @@ -0,0 +1,96 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-prop/xl-prop-create.vue b/fastmovie-vue/src/components/xl-prop/xl-prop-create.vue new file mode 100644 index 0000000..967a423 --- /dev/null +++ b/fastmovie-vue/src/components/xl-prop/xl-prop-create.vue @@ -0,0 +1,543 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-qrcode-view/index.vue b/fastmovie-vue/src/components/xl-qrcode-view/index.vue new file mode 100644 index 0000000..2933170 --- /dev/null +++ b/fastmovie-vue/src/components/xl-qrcode-view/index.vue @@ -0,0 +1,138 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-qrcode/index.vue b/fastmovie-vue/src/components/xl-qrcode/index.vue new file mode 100644 index 0000000..118c46c --- /dev/null +++ b/fastmovie-vue/src/components/xl-qrcode/index.vue @@ -0,0 +1,25 @@ + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-scene/xl-scene-create.vue b/fastmovie-vue/src/components/xl-scene/xl-scene-create.vue new file mode 100644 index 0000000..82daeb4 --- /dev/null +++ b/fastmovie-vue/src/components/xl-scene/xl-scene-create.vue @@ -0,0 +1,272 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-storyboard/index.vue b/fastmovie-vue/src/components/xl-storyboard/index.vue new file mode 100644 index 0000000..fa92cbe --- /dev/null +++ b/fastmovie-vue/src/components/xl-storyboard/index.vue @@ -0,0 +1,88 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-style/index.vue b/fastmovie-vue/src/components/xl-style/index.vue new file mode 100644 index 0000000..2dc2838 --- /dev/null +++ b/fastmovie-vue/src/components/xl-style/index.vue @@ -0,0 +1,134 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-tabs-item/index.vue b/fastmovie-vue/src/components/xl-tabs-item/index.vue new file mode 100644 index 0000000..b330aa4 --- /dev/null +++ b/fastmovie-vue/src/components/xl-tabs-item/index.vue @@ -0,0 +1,63 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-tabs/index.vue b/fastmovie-vue/src/components/xl-tabs/index.vue new file mode 100644 index 0000000..052ecc3 --- /dev/null +++ b/fastmovie-vue/src/components/xl-tabs/index.vue @@ -0,0 +1,155 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-user-points/index.vue b/fastmovie-vue/src/components/xl-user-points/index.vue new file mode 100644 index 0000000..1229340 --- /dev/null +++ b/fastmovie-vue/src/components/xl-user-points/index.vue @@ -0,0 +1,128 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-userinfo/index.vue b/fastmovie-vue/src/components/xl-userinfo/index.vue new file mode 100644 index 0000000..1cdad39 --- /dev/null +++ b/fastmovie-vue/src/components/xl-userinfo/index.vue @@ -0,0 +1,122 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-userinfo/modules/xl-user.vue b/fastmovie-vue/src/components/xl-userinfo/modules/xl-user.vue new file mode 100644 index 0000000..ab9041d --- /dev/null +++ b/fastmovie-vue/src/components/xl-userinfo/modules/xl-user.vue @@ -0,0 +1,501 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/components/xl-voice/index.vue b/fastmovie-vue/src/components/xl-voice/index.vue new file mode 100644 index 0000000..e3ed5b5 --- /dev/null +++ b/fastmovie-vue/src/components/xl-voice/index.vue @@ -0,0 +1,190 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useAVCanvas.ts b/fastmovie-vue/src/composables/useAVCanvas.ts new file mode 100644 index 0000000..70b8df3 --- /dev/null +++ b/fastmovie-vue/src/composables/useAVCanvas.ts @@ -0,0 +1,228 @@ +import { onWindowResize } from "@/common/functions"; + +export const useAVCanvas = (cvsWrapEl: Ref, cvsWrapElWrapper: Ref) => { + let offscreen: OffscreenCanvas | undefined; + const isPlaying = ref(false); + let currentTrackResourceIndex: number = 0; + // 播放进度事件回调 + const currentTimeEvents: ((currentTime: number) => void)[] = []; + const onCurrentTime = (callback: (currentTime: number) => void) => { + currentTimeEvents.push(callback); + } + const offCurrentTime = (callback: (currentTime: number) => void) => { + currentTimeEvents.splice(currentTimeEvents.indexOf(callback), 1); + } + // 切换素材事件 + const switchToNextClipEvents: ((currentTrackResource: TrackResourceInterface) => void)[] = []; + const onSwitchToNextClip = (callback: (currentTrackResource: TrackResourceInterface) => void) => { + switchToNextClipEvents.push(callback); + } + const offSwitchToNextClip = (callback: (currentTrackResource: TrackResourceInterface) => void) => { + switchToNextClipEvents.splice(switchToNextClipEvents.indexOf(callback), 1); + } + const worker = new Worker(new URL('@/workers/loadStream.ts', import.meta.url), { type: 'module' }); + worker.onmessage = (e) => { + const data = e.data; + switch (data.type) { + case 'stop': + isPlaying.value = false; + break; + case 'play': + currentTrackResourceIndex = data.index; + isPlaying.value = true; + const currentTrackResource = TrackResource[currentTrackResourceIndex]; + initAudio(currentTrackResource); + switchToNextClipEvents.forEach((callback) => callback(currentTrackResource)); + break; + case 'currentTime': + currentTimeEvents.forEach((callback) => callback(data.currentTime || 0)); + break; + } + } + worker.onerror = (e) => { + console.log('worker error', e); + } + const audioEls: HTMLAudioElement[] = []; + const initAudio = (currentTrackResource: TrackResourceInterface) => { + const dialogueAudio = currentTrackResource.dialogue_audio; + const loopAudio = (index: number = 0) => { + const dialogue = dialogueAudio[index]; + if (dialogue) { + const audio = new Audio(); + audio.src = dialogue; + audio.play(); + const audioElIndex = audioEls.push(audio); + audio.addEventListener('ended', () => { + index++; + audioEls.splice(audioElIndex - 1, 1); + if (isPlaying.value) { + loopAudio(index); + } + }); + } + } + loopAudio(0); + const narrationAudio = currentTrackResource.narration_audio; + if (narrationAudio) { + const audio = new Audio(); + audio.src = narrationAudio; + audio.play(); + const audioElIndex = audioEls.push(audio); + audio.addEventListener('ended', () => { + audioEls.splice(audioElIndex - 1, 1); + }); + } + const sfxAudio = currentTrackResource.sfx_audio; + if (sfxAudio) { + const audio = new Audio(); + audio.src = sfxAudio; + audio.play(); + const audioElIndex = audioEls.push(audio); + audio.addEventListener('ended', () => { + audioEls.splice(audioElIndex - 1, 1); + }); + } + } + /** + * 轨道资源 + * video: 视频资源 + * dialogue_audio: 对话音频资源 + * narration_audio: 旁白音频资源 + * sfx_audio: 音效音频资源 + */ + let TrackResource: TrackResourceInterface[] = []; + /** + * 解析轨道资源 + * @param currentStoryboard 当前片段 + * @param storyboards 所有片段 + * @returns + */ + const parseTrackResource = async (storyboards: any[], currentStoryboard?: any) => { + if (currentStoryboard) { + currentTrackResourceIndex = storyboards.findIndex((item: any) => item.id === currentStoryboard.id) || 0; + } + TrackResource = []; + const storyboardsLen = storyboards.length; + for (let i = 0; i < storyboardsLen; i++) { + const storyboard = storyboards[i]; + const resource: TrackResourceInterface = { + id: storyboard.id, + video: storyboard.video, + image: storyboard.image, + use_material_type: storyboard.use_material_type, + duration: storyboard.duration, + dialogue_audio: [], + narration: storyboard.narration, + narration_audio: storyboard.narration_audio, + sfx_audio: storyboard.sfx_audio, + dialogues: [], + }; + if (storyboard.dialogues) { + for (const dialogue of storyboard.dialogues) { + resource.dialogue_audio.push(dialogue.audio); + resource.dialogues.push({ + start_time: dialogue.start_time, + end_time: dialogue.end_time, + content: dialogue.actor.name + ':' + dialogue.content, + }); + } + } + TrackResource.push(resource); + } + if (offscreen) { + worker.postMessage({ + type: 'load', + TrackResource, + index: currentTrackResourceIndex, + }); + } else { + offscreen = cvsWrapEl.value.transferControlToOffscreen(); + offscreen.width = cvsWrapElWrapper.value.clientWidth; + offscreen.height = cvsWrapElWrapper.value.clientHeight; + worker.postMessage({ + type: 'load', + offscreen, + TrackResource, + index: currentTrackResourceIndex, + }, [offscreen]); + } + } + const resize = () => { + worker.postMessage({ + type: 'resize', + size: { + width: cvsWrapElWrapper.value.clientWidth, + height: cvsWrapElWrapper.value.clientHeight, + }, + }); + } + onWindowResize(resize, 300); + const play = (currentStoryboard?: any) => { + if (currentStoryboard) { + const find = TrackResource.findIndex((item: TrackResourceInterface) => item.id === currentStoryboard.id); + if (find !== -1) { + currentTrackResourceIndex = find; + } + } + worker.postMessage({ + type: 'play', + index: currentTrackResourceIndex, + }); + isPlaying.value = true; + } + const pause = () => { + isPlaying.value = false; + worker.postMessage({ + type: 'pause', + }); + audioEls.forEach((audio) => { + audio.pause(); + }); + audioEls.splice(0, audioEls.length); + } + const seek = (time: number) => { + worker.postMessage({ + type: 'seek', + time, + }); + } + const stop = () => { + worker.postMessage({ + type: 'stop', + }); + isPlaying.value = false; + audioEls.forEach((audio) => { + audio.pause(); + }); + audioEls.splice(0, audioEls.length); + } + const destroy = () => { + stop(); + worker.terminate(); + offscreen = undefined; + } + const switchClip = (currentStoryboard: any) => { + const find = TrackResource.findIndex((item: TrackResourceInterface) => item.id === currentStoryboard.id); + if (find !== -1) { + currentTrackResourceIndex = find; + } + worker.postMessage({ + type: 'switchClip', + index: currentTrackResourceIndex, + }); + } + return { + isPlaying, + play, + pause, + stop, + parseTrackResource, + destroy, + seek, + onCurrentTime, + offCurrentTime, + onSwitchToNextClip, + offSwitchToNextClip, + switchClip, + }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useCompsite.ts b/fastmovie-vue/src/composables/useCompsite.ts new file mode 100644 index 0000000..137160c --- /dev/null +++ b/fastmovie-vue/src/composables/useCompsite.ts @@ -0,0 +1,405 @@ +import { ClipExporter, ClipOptions } from '@/common/av-cliper/ClipExporter'; +import { ResponseCode } from '@/common/const'; +import { $http } from '@/common/http'; +import { AxiosProgressEvent } from 'axios'; +export const COMPSITE_EVENTS = { + PROGRESS: 'progress', + SAVE_FILE: 'saveFile', + PARSE_RESOURCE: 'parseResource', + COMPLETE: 'complete', + VIDEO: 'video', + DIALOGUES: 'dialogues', + DIALOGUES_AUDIO: 'dialogues_audio', + NARRATIONS: 'narrations', + NARRATIONS_AUDIO: 'narrations_audio', + CANCEL: 'cancel', + UPLOAD_VIDEO: 'uploadVideo', +} +export const useCompsite = (_options?: any) => { + const w = 720; + const h = 1280; + const cancelRef = ref(false); + const abortControllers: AbortController[] = []; // 保存所有 AbortController,用于取消网络请求 + const events = ref({ + [COMPSITE_EVENTS.SAVE_FILE]: () => { } + }); + const createFileWriter = async (): Promise<{ writable: WritableStream, fileHandle: FileSystemFileHandle }> => { + events.value[COMPSITE_EVENTS.SAVE_FILE]?.(); + + try { + const handle: FileSystemFileHandle = await (window as any).showSaveFilePicker({ + suggestedName: _options.output || 'output.mp4', + types: [ + { + description: 'MP4 Video', + accept: { 'video/mp4': ['.mp4'] }, + }, + ], + }); + + const writable: WritableStream = await handle.createWritable(); + return { writable, fileHandle: handle }; + } catch (err) { + console.error('创建文件失败:', err); + throw err; + } + }; + + + const Tracks: any = { + video: [], + dialogues: [], + dialogues_audio: [], + narrations: [], + narrations_audio: [], + }; + let composer: ClipExporter | null = null; + let currentStream: WritableStream | null = null; // 保存当前的文件流,用于取消时关闭 + + // 清理所有资源的辅助函数 + const cleanup = () => { + // 取消所有网络请求 + abortControllers.forEach(controller => { + try { + controller.abort(); + } catch (error) { + console.warn('取消网络请求时出错:', error); + } + }); + abortControllers.length = 0; + + // 清理 composer + if (composer) { + try { + composer.destroy(); + } catch (error) { + console.warn('销毁 composer 时出错:', error); + } + composer = null; + } + + // 关闭文件流 + if (currentStream) { + try { + // 尝试获取 writer 并 abort + const writer = currentStream.getWriter(); + if (writer) { + writer.abort().catch(() => { }); + writer.releaseLock(); + } + } catch (error) { + // 如果无法获取 writer,尝试直接关闭 + try { + if (currentStream && typeof (currentStream as any).abort === 'function') { + (currentStream as any).abort(); + } + } catch (e) { + console.warn('关闭文件流时出错:', e); + } + } + currentStream = null; + } + + // 清空 Tracks + Tracks.video = []; + Tracks.dialogues = []; + Tracks.dialogues_audio = []; + Tracks.narrations = []; + Tracks.narrations_audio = []; + }; + // 替换原来的 uploadVideo 方法 + const uploadVideo = async (file: File, chunkSize = 1 * 1024 * 1024) => { + const totalChunks = Math.ceil(file.size / chunkSize); + + try { + await $http.post('/app/shortplay/api/drama/uploadChunkCheck', { + drama_id: _options.drama_id, + episode_id: _options.episode_id, + fileName: _options.output || 'output.mp4', + }).then((res: any) => { + if (res.code != ResponseCode.SUCCESS) { + throw new Error(res.msg); + } + }).catch((err: any) => { + console.error('分片上传检查出错:', err); + throw err; + }); + for (let index = 0; index < totalChunks; index++) { + if (cancelRef.value) { + events.value[COMPSITE_EVENTS.CANCEL]?.(); + return; + } + + const start = index * chunkSize; + const end = Math.min(file.size, start + chunkSize); + const chunk = file.slice(start, end); + + const formData = new FormData(); + formData.append('drama_id', _options.drama_id); + formData.append('episode_id', _options.episode_id); + formData.append('chunkIndex', index.toString()); + formData.append('totalChunks', totalChunks.toString()); + formData.append('chunkData', chunk); + + await $http.post('/app/shortplay/api/drama/uploadChunk', formData, { + onUploadProgress: (progressEvent: AxiosProgressEvent) => { + if (progressEvent.lengthComputable) { + const chunkPercent = (progressEvent.loaded / (progressEvent.total || 1)) * 100; + const totalPercent = ((index + chunkPercent / 100) / totalChunks) * 100; + events.value[COMPSITE_EVENTS.UPLOAD_VIDEO]?.(Math.round(totalPercent)); + } + } + }).catch(err => { + console.error('分片上传出错:', err); + throw err; + }); + } + + // 所有分片上传完成,通知服务端合并 + await $http.post('/app/shortplay/api/drama/mergeChunks', { + totalChunks, + drama_id: _options.drama_id, + episode_id: _options.episode_id, + }); + + events.value[COMPSITE_EVENTS.COMPLETE]?.(true); + } catch (err) { + console.error('上传失败:', err); + if (!cancelRef.value) { + events.value[COMPSITE_EVENTS.COMPLETE]?.(false); + } + } + }; + + const start = async (stream: WritableStream, fileHandle: FileSystemFileHandle, data: any) => { + currentStream = stream; + const len = data.length; + let offsetUs = 0; + events.value[COMPSITE_EVENTS.PARSE_RESOURCE]?.(0); + try { + for (let i = 0; i < len; i++) { + if (cancelRef.value) { + events.value[COMPSITE_EVENTS.CANCEL]?.(); + cleanup(); + return; + } + const item = data[i]; + let temp: ClipOptions; + // 主轨道素材:不设置 offsetUs,让系统自动按顺序追加 + if (item.use_material_type === 'video') { + const controller = new AbortController(); + abortControllers.push(controller); + const response = await fetch(item.video, { signal: controller.signal }); + if (cancelRef.value) { + cleanup(); + return; + } + temp = { + type: 'video', + stream: response.body!, + // 主轨道不需要设置 offsetUs,会自动计算 + offsetUs, + durationUs: item.duration * 1000, + }; + } else { + if (item.image) { + const controller = new AbortController(); + abortControllers.push(controller); + const response = await fetch(item.image, { signal: controller.signal }); + if (cancelRef.value) { + cleanup(); + return; + } + temp = { + type: 'image', + stream: response.body!, + // 主轨道不需要设置 offsetUs,会自动计算 + offsetUs, + durationUs: item.duration * 1000, + }; + } else { + const offscreen = new OffscreenCanvas(w, h); + const offscreenCtx = offscreen.getContext('2d')!; + offscreenCtx.fillStyle = 'black'; + offscreenCtx.fillRect(0, 0, w, h); + const bitmap = offscreen.transferToImageBitmap(); + temp = { + type: 'image', + stream: bitmap, + // 主轨道不需要设置 offsetUs,会自动计算 + offsetUs, + durationUs: item.duration * 1000, + }; + } + } + Tracks.video.push(temp); + + // 收集对话字幕片段 + if (item.dialogues && item.dialogues.length > 0) { + for (const dialogue of item.dialogues) { + Tracks.dialogues.push({ + text: dialogue.content, + startTimeUs: offsetUs + dialogue.start_time * 1000, + endTimeUs: offsetUs + dialogue.end_time * 1000, + }); + + if (dialogue.audio) { + const controller = new AbortController(); + abortControllers.push(controller); + const response = await fetch(dialogue.audio, { signal: controller.signal }); + if (cancelRef.value) { + cleanup(); + return; + } + const audioTemp: ClipOptions = { + type: 'audio', + stream: response.body!, + trackId: 'dialogue_audio', // 对话音频轨道 + offsetUs: offsetUs + dialogue.start_time * 1000, + // durationUs 不设置,使用音频的实际时长 + }; + Tracks.dialogues_audio.push(audioTemp); + } + } + } + + // 收集旁白字幕片段 + if (item.narration) { + Tracks.narrations.push({ + text: item.narration, + startTimeUs: offsetUs, + endTimeUs: offsetUs + item.duration * 1000, + }); + + if (item.narration_audio) { + const controller = new AbortController(); + abortControllers.push(controller); + const response = await fetch(item.narration_audio, { signal: controller.signal }); + if (cancelRef.value) { + cleanup(); + return; + } + const audioTemp: ClipOptions = { + type: 'audio', + stream: response.body!, + trackId: 'narration_audio', // 旁白音频轨道 + offsetUs: offsetUs, + // durationUs 不设置,使用音频的实际时长 + }; + Tracks.narrations_audio.push(audioTemp); + } + } + offsetUs += item.duration * 1000; + events.value[COMPSITE_EVENTS.PARSE_RESOURCE]?.(i / len * 100); + } + events.value[COMPSITE_EVENTS.PARSE_RESOURCE]?.(100); + events.value[COMPSITE_EVENTS.PROGRESS]?.(0); + if (cancelRef.value) { + events.value[COMPSITE_EVENTS.CANCEL]?.(); + cleanup(); + return; + } + try { + composer = new ClipExporter(w, h); + composer.setTracks(Tracks); + composer.on(COMPSITE_EVENTS.VIDEO, events.value[COMPSITE_EVENTS.VIDEO]); + composer.on(COMPSITE_EVENTS.DIALOGUES, events.value[COMPSITE_EVENTS.DIALOGUES]); + composer.on(COMPSITE_EVENTS.DIALOGUES_AUDIO, events.value[COMPSITE_EVENTS.DIALOGUES_AUDIO]); + composer.on(COMPSITE_EVENTS.NARRATIONS, events.value[COMPSITE_EVENTS.NARRATIONS]); + composer.on(COMPSITE_EVENTS.NARRATIONS_AUDIO, events.value[COMPSITE_EVENTS.NARRATIONS_AUDIO]); + await composer.initialization(); + if (cancelRef.value) { + cleanup(); + return; + } + composer.OutputProgress((progress: number) => { + if (!cancelRef.value) { + events.value[COMPSITE_EVENTS.PROGRESS]?.(progress); + } + }); + // 导出 + if (cancelRef.value) { + cleanup(); + return; + } + await composer.export(stream); + if (cancelRef.value) { + cleanup(); + return; + } + // 释放 + composer.destroy(); + composer = null; + currentStream = null; + // 清理已完成的网络请求控制器 + abortControllers.length = 0; + const file = await fileHandle.getFile(); + events.value[COMPSITE_EVENTS.UPLOAD_VIDEO]?.(0); + uploadVideo(file); + } catch (error) { + // 如果是取消导致的错误,不触发 complete(false) + if (!cancelRef.value) { + events.value[COMPSITE_EVENTS.COMPLETE]?.(false); + } + console.info(error); + cleanup(); + } + } catch (error) { + // 如果是取消导致的错误,不触发 complete(false) + if (!cancelRef.value) { + events.value[COMPSITE_EVENTS.COMPLETE]?.(false); + } + console.info(error); + cleanup(); + } + } + const synthesis = (data: any) => { + // 重置取消标志 + cancelRef.value = false; + // 清理之前的资源 + cleanup(); + createFileWriter().then(({ writable, fileHandle }) => { + if (cancelRef.value) { + events.value[COMPSITE_EVENTS.CANCEL]?.(); + cleanup(); + return; + } + events.value[COMPSITE_EVENTS.SAVE_FILE]?.(true); + if (cancelRef.value) { + events.value[COMPSITE_EVENTS.CANCEL]?.(); + cleanup(); + return; + } + start(writable, fileHandle, data); + }).catch((err: any) => { + // 如果是用户取消文件选择,不触发错误事件 + if (!cancelRef.value) { + events.value[COMPSITE_EVENTS.SAVE_FILE]?.(false); + } + console.info(err); + cleanup(); + }); + } + const cancel = () => { + cancelRef.value = true; + if (composer) { + composer.cancel(); + } + // 清理所有资源 + cleanup(); + events.value[COMPSITE_EVENTS.CANCEL]?.(); + } + const on = (event: typeof COMPSITE_EVENTS[keyof typeof COMPSITE_EVENTS], callback: (data: any) => void) => { + events.value[event] = callback; + } + const off = (event: typeof COMPSITE_EVENTS[keyof typeof COMPSITE_EVENTS]) => { + delete events.value[event]; + } + return { + on, + off, + synthesis, + cancel, + cancelRef + } +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useInvitationCode.ts b/fastmovie-vue/src/composables/useInvitationCode.ts new file mode 100644 index 0000000..f056385 --- /dev/null +++ b/fastmovie-vue/src/composables/useInvitationCode.ts @@ -0,0 +1,41 @@ +import { ElMessageBox } from "element-plus" +import type { ElMessageBoxOptions } from "element-plus" +import { h } from "vue" +import XLInvitationCode from "@/components/xl-invitation-code/index.vue" + +export const useInvitationCode = () => { + const open = () => { + const options: ElMessageBoxOptions = { + showClose: true, + showCancelButton: false, + showConfirmButton: false, + customStyle: { + '--el-messagebox-width': 'min(586px,100%)', + '--el-messagebox-border-radius': '20px', + '--el-messagebox-padding-primary': '30px', + '--el-color-primary':'var(--el-color-success)', + }, + autofocus:false, + closeOnPressEscape: true, + closeOnClickModal: true, + title: '邀请好友', + message: () => h(XLInvitationCode), + } + ElMessageBox(options) + .then(() => { + console.log('打开邀请码成功') + }) + .catch(() => { + console.log('打开邀请码失败') + }) + } + + const close = () => { + ElMessageBox.close() + } + + return { + open, + close, + } +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useLoading.ts b/fastmovie-vue/src/composables/useLoading.ts new file mode 100644 index 0000000..c17a105 --- /dev/null +++ b/fastmovie-vue/src/composables/useLoading.ts @@ -0,0 +1,34 @@ +import { ElMessageBox, ElMessageBoxOptions } from "element-plus"; +import XLLoading from "@/components/xl-loading/index.vue"; +interface LoadingInstance { + open: () => Promise; + close: () => void; + xlLoadingRef: Ref; +} +export const useLoading = (props: any): LoadingInstance => { + const xlLoadingRef = ref(null); + const options: ElMessageBoxOptions = { + showClose: false, + showCancelButton: false, + showConfirmButton: false, + customClass: 'x-loading-message-box', + modalClass: 'x-loading-message-box-modal', + closeOnPressEscape: false, + closeOnClickModal: false, + message: () => h(XLLoading, { ...props, ref: xlLoadingRef }), + } + const open = (): Promise => { + return new Promise((resolve) => { + ElMessageBox(options).then((res: any) => { + console.log('打开loading成功', res); + }).catch(() => { + }); + resolve(true); + }); + } + const close = () => { + // @ts-ignore + options.onVanish?.() + } + return { open, close, xlLoadingRef }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useLogin.ts b/fastmovie-vue/src/composables/useLogin.ts new file mode 100644 index 0000000..159953c --- /dev/null +++ b/fastmovie-vue/src/composables/useLogin.ts @@ -0,0 +1,82 @@ +import { ElMessageBox } from "element-plus" +import type { ElMessageBoxOptions } from "element-plus" +import { h } from "vue" +import XLogin from "@/components/x-login/index.vue" +import XLIcode from "@/components/xl-icode/index.vue" +export const useLogin = () => { + const open = () => { + const options: ElMessageBoxOptions = { + showClose: false, + showCancelButton: false, + showConfirmButton: false, + customClass: 'x-login-message-box', + closeOnPressEscape: false, + closeOnClickModal: false, + message: () => h(XLogin, { + onSuccess: (e: any) => { + // @ts-ignore + options.onVanish?.() + if (e.data.activation_time == null || e.data.activation_time == '') { + // 打开邀请码 + openIcode() + } + }, + onClose: () => { + console.log('关闭登录') + // @ts-ignore + options.onVanish?.() + } + }), + } + ElMessageBox(options) + .then(() => { + console.log('打开登录成功') + }) + .catch(() => { + console.log('打开登录失败') + }) + } + const close = () => { + console.log('关闭登录') + ElMessageBox.close() + } + const openIcode = () => { + const options: ElMessageBoxOptions = { + showClose: false, + showCancelButton: false, + showConfirmButton: false, + title: '填写邀请码', + customStyle: { + '--el-messagebox-width': 'min(586px,100%)', + '--el-messagebox-border-radius':'20px', + '--el-messagebox-padding-primary':'30px' + }, + closeOnPressEscape: false, + closeOnClickModal: false, + message: () => h(XLIcode, { + onSuccess: (userInfo: any) => { + // @ts-ignore + options.onVanish?.() + console.log('邀请码绑定成功', userInfo) + }, + onClose: () => { + console.log('关闭邀请码') + // @ts-ignore + options.onVanish?.() + } + }), + } + ElMessageBox(options) + .then(() => { + console.log('打开邀请码成功') + }) + .catch(() => { + console.log('打开邀请码失败') + }) + } + return { + open, + close, + openIcode + } +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useNotify.ts b/fastmovie-vue/src/composables/useNotify.ts new file mode 100644 index 0000000..19b8b20 --- /dev/null +++ b/fastmovie-vue/src/composables/useNotify.ts @@ -0,0 +1,8 @@ +export const useNotify = () => { + const parseNotify = (notify: any) => { + console.log(notify); + } + return { + parse: parseNotify, + } +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/usePoints.ts b/fastmovie-vue/src/composables/usePoints.ts new file mode 100644 index 0000000..3750344 --- /dev/null +++ b/fastmovie-vue/src/composables/usePoints.ts @@ -0,0 +1,24 @@ +import { PricingCalculator } from "@/common/PricingCalculator"; + +export const usePoints = (models: any[], num: Ref = ref(1), form: Ref = ref()) => { + const points = computed(() => { + let totalPoints = 0; + for (const model of models) { + if (model && model.value && model.value.id) { + if (form.value) { + const calculator = new PricingCalculator(model.value.form, form.value, model.value.unit_point); + const point = calculator.calculate(); + totalPoints += point || 0; + } else { + totalPoints += model.value.unit_point || 0; + } + } + } + totalPoints *= num.value; + if (totalPoints === 0) { + return '免费'; + } + return totalPoints; + }) + return points +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/usePush.ts b/fastmovie-vue/src/composables/usePush.ts new file mode 100644 index 0000000..a8a2ed8 --- /dev/null +++ b/fastmovie-vue/src/composables/usePush.ts @@ -0,0 +1,37 @@ +export const usePush = () => { + const { appContext } = getCurrentInstance()! + const global = appContext.config.globalProperties; + const channels = new Map(); + const subscribe = (channel: string, callback: (data: any) => void) => { + if (global.$push) { + const channelItem = global.$push.subscribe(channel); + channelItem.on('message', callback); + channelItem.on('pusher_internal:subscription_succeeded', function () { + console.log('channels complete info subscription succeeded'); + }); + channelItem.on('subscription_error', function (message: any) { + console.log('channels complete info subscription error', message); + }); + channels.set(channel, channelItem); + } + } + const unsubscribe = (channel: string) => { + if (global.$push) { + global.$push.unsubscribe(channel); + channels.delete(channel); + } + } + const unsubscribeAll = () => { + channels.forEach((_channelItem, channel) => { + if (global.$push) { + global.$push.unsubscribe(channel); + } + channels.delete(channel); + }); + } + return { + subscribe, + unsubscribe, + unsubscribeAll, + } +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useStorage.ts b/fastmovie-vue/src/composables/useStorage.ts new file mode 100644 index 0000000..5ba1700 --- /dev/null +++ b/fastmovie-vue/src/composables/useStorage.ts @@ -0,0 +1,80 @@ +const storage = globalThis.localStorage; +const storagePrefix = 'SHORT-PLAY'; +export const useStorage = () => { + /** + * 设置储存数据 + * @param key 键 + * @param data 值 + * @param expire 过期时间(秒) + * @returns Promise + */ + const set = (key: string, data: StorageInterface, expire?: number): Promise => { + return new Promise((resolve, reject) => { + const obj = { + expire: 0, + data: data, + } + if (expire !== undefined) { + obj.expire = Date.now() + expire * 1000 + } + storage.setItem(getKey(key), JSON.stringify(obj)); + if (get(key) === null) { + reject(); + } else { + resolve(true); + } + }) + } + /** + * 获取储存数据 + * @param key 键 + * @returns StorageInterface + */ + const get = (key: string): StorageInterface => { + const data = storage.getItem(getKey(key)); + + if (data === null) { + return null; + } + const ret = JSON.parse(data); + + if (ret?.expire > 0 && ret?.expire < Date.now()) { + return null; + } + return ret?.data; + } + /** + * 删除储存数据 + * @param key 键 + * @returns Promise + */ + const remove = (key: string): Promise => { + return new Promise((resolve, reject) => { + storage.removeItem(getKey(key)); + if (get(key) === null) { + resolve(true); + } else { + reject(); + } + }) + } + /** + * 获取数据并删除 + * @param key 键 + * @returns StorageInterface + */ + const getOnce = (key: string): StorageInterface => { + const data = get(key); + remove(key); + return data; + } + /** + * 获取真实存储键名 + * @param key 键 + * @returns string + */ + const getKey = (key: string): string => { + return `${storagePrefix}.${key}`; + } + return { set, get, remove, getOnce, getKey }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/composables/useVcode.ts b/fastmovie-vue/src/composables/useVcode.ts new file mode 100644 index 0000000..3f11e08 --- /dev/null +++ b/fastmovie-vue/src/composables/useVcode.ts @@ -0,0 +1,66 @@ +import { ElMessageBox } from "element-plus" +import type { ElMessageBoxOptions } from "element-plus" +import XVcode from "@/components/x-vcode/index.vue" + +export const useVcode = () => { + const btnText = ref('获取验证码') + const btnDisabled = ref(false) + let timer: any; + let timerCount = 60; + const open = (params: any) => { + return new Promise((resolve, reject) => { + const options: ElMessageBoxOptions = { + showClose: false, + showCancelButton: false, + showConfirmButton: false, + customClass: 'x-login-message-box', + customStyle: { + '--el-messagebox-width': 'min(375px,100%)', + '--el-messagebox-height': '300px', + }, + closeOnPressEscape: false, + closeOnClickModal: false, + message: () => h(XVcode, { + data: params, + onSuccess: (res: any) => { + btnText.value = '重新获取' + btnDisabled.value = true + timer = setInterval(() => { + btnText.value = `重新获取(${timerCount--}s)` + if (timerCount <= 0) { + clearInterval(timer) + timer = undefined + btnText.value = '获取验证码' + btnDisabled.value = false + timerCount = 60 + } + }, 1000) + // @ts-ignore + options.onVanish?.() + resolve(res) + }, + onClose: () => { + // @ts-ignore + options.onVanish?.() + if (timer) { + clearInterval(timer) + timer = undefined + } + reject('关闭验证码') + } + }), + } + ElMessageBox(options) + .then(() => { + }) + .catch((error) => { + reject(error) + }) + }) + } + return { + open, + btnText, + btnDisabled + } +} \ No newline at end of file diff --git a/fastmovie-vue/src/layouts/control.vue b/fastmovie-vue/src/layouts/control.vue new file mode 100644 index 0000000..fb94dbf --- /dev/null +++ b/fastmovie-vue/src/layouts/control.vue @@ -0,0 +1,375 @@ + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/locale/en/index.ts b/fastmovie-vue/src/locale/en/index.ts new file mode 100644 index 0000000..419a0ea --- /dev/null +++ b/fastmovie-vue/src/locale/en/index.ts @@ -0,0 +1,50 @@ +const en={ + button:{ + confirmText:'Submit', + resetText:'Reset', + cancelText:'Cancel', + queryText:'Query', + allSelectText:'Select All', + redirectText:'Redirect', + loginText:'Login', + logoutText:'Logout', + registerText:'Register', + applyText:'Apply for Access', + myPayText:'I have paid', + retryText:'Click to retry', + cancelAuthorizationText:'Cancel Authorization', + addAuthorizationText:'Add Authorization', + uploadText:'Upload', + completeText:'Complete', + manageText:'Manage', + moveText:'Move', + deleteText:'Delete', + copyText:'Copy', + insertText:'Insert', + byBundleSelectText:'Select from the gallery' + }, + message:{ + tips:'Tips', + title:'Tips', + error:'Error', + error_code:'Error Code', + error_text:'Error Message', + requestFullscreenError:'Fullscreen Failed, Press F11 to Fullscreen', + exitFullscreenError:'Exit Fullscreen Failed, Press F11 to Exit Fullscreen', + confirmActionContent:'Are you sure you want to perform this action?', + confirmLogoutContent:'Are you sure you want to logout?', + noApplyContent:'No Apply Channel', + rejectContent:'Rejection Reason: {message}', + scanPayContent:'Scan and Pay', + qrcodeExpireContent:'QR Code Expired', + getQrcodeFail:'Get QR Code Failed', + userCannel:'User Cancel', + qrcodeWillExpireTitle:'QR Code Validity Period', + qrcodeWillExpireContent:'QR Code Will Expire in {time} Seconds', + searchMenuNotContent:'No Related Content Found', + uploadSizeError:'File Size Cannot Exceed {size}MB', + copySuccess:'Copy Success', + copyFail:'Copy Failed' + }, +}; +export default en; \ No newline at end of file diff --git a/fastmovie-vue/src/locale/index.ts b/fastmovie-vue/src/locale/index.ts new file mode 100644 index 0000000..f221762 --- /dev/null +++ b/fastmovie-vue/src/locale/index.ts @@ -0,0 +1,19 @@ +import { createI18n } from 'vue-i18n'; +import { default as en } from './en' +import { default as zhCn } from './zh-cn'; +import { App } from 'vue'; +export const i18n = createI18n({ + legacy: false, + locale: 'zh-CN', + globalInjection: true, + messages: { + en, + zh: zhCn, + 'zh-CN': zhCn + } +}); +export const setupI18n = { + install(app: App) { + app.use(i18n); + }, +}; \ No newline at end of file diff --git a/fastmovie-vue/src/locale/zh-cn/index.ts b/fastmovie-vue/src/locale/zh-cn/index.ts new file mode 100644 index 0000000..ab49096 --- /dev/null +++ b/fastmovie-vue/src/locale/zh-cn/index.ts @@ -0,0 +1,50 @@ +const zhCn={ + button:{ + confirmText:'提交', + resetText:'重置', + cancelText:'取消', + queryText:'查询', + allSelectText:'全选', + redirectText:'跳转', + loginText:'登录', + logoutText:'退出登录', + registerText:'注册', + applyText:'申请开通', + myPayText:'我已支付', + retryText:'点击重试', + cancelAuthorizationText:'取消授权', + addAuthorizationText:'添加授权', + uploadText:'上传', + completeText:'完成', + manageText:'管理', + moveText:'移动', + deleteText:'删除', + copyText:'复制', + insertText:'插入', + byBundleSelectText:'从图库中选择' + }, + message:{ + tips:'提示', + title:'温馨提示', + error:'错误', + error_code:'错误码', + error_text:'错误信息', + requestFullscreenError:'全屏失败,按F11全屏', + exitFullscreenError:'退出全屏失败,按F11退出全屏', + confirmActionContent:'您确定要执行此操作吗?', + confirmLogoutContent:'确定退出登录吗?', + noApplyContent:'暂未开通申请通道', + rejectContent:'驳回理由:{message}', + scanPayContent:'扫码支付', + qrcodeExpireContent:'二维码已过期', + getQrcodeFail:'获取二维码失败', + userCannel:'用户取消', + qrcodeWillExpireTitle:'二维码有效期', + qrcodeWillExpireContent:'二维码将在{time}秒后过期', + searchMenuNotContent:'没有找到相关内容', + uploadSizeError:'文件大小不能超过{size}MB', + copySuccess:'复制成功', + copyFail:'复制失败' + }, +}; +export default zhCn; \ No newline at end of file diff --git a/fastmovie-vue/src/main.ts b/fastmovie-vue/src/main.ts new file mode 100644 index 0000000..31295ee --- /dev/null +++ b/fastmovie-vue/src/main.ts @@ -0,0 +1,22 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import "@/assets/css/theme.min.css"; +import "@/assets/css/common.min.css"; +import "@/assets/css/style.min.css"; +import App from './App.vue' +import { createPinia } from 'pinia' +const pinia = createPinia() +import router from "@/routers"; +import zhCn from 'element-plus/es/locale/lang/zh-cn'; +const app = createApp(App) +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} +app.use(ElementPlus, { locale: zhCn }); +import { i18n } from '@/locale'; +app.use(i18n); +app.use(pinia); +app.use(router); +app.mount('#app') \ No newline at end of file diff --git a/fastmovie-vue/src/pages/actor/index.vue b/fastmovie-vue/src/pages/actor/index.vue new file mode 100644 index 0000000..e07cab0 --- /dev/null +++ b/fastmovie-vue/src/pages/actor/index.vue @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/article/content.vue b/fastmovie-vue/src/pages/article/content.vue new file mode 100644 index 0000000..033d823 --- /dev/null +++ b/fastmovie-vue/src/pages/article/content.vue @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/creative/index.vue b/fastmovie-vue/src/pages/creative/index.vue new file mode 100644 index 0000000..588f53b --- /dev/null +++ b/fastmovie-vue/src/pages/creative/index.vue @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/fail.vue b/fastmovie-vue/src/pages/fail.vue new file mode 100644 index 0000000..546925c --- /dev/null +++ b/fastmovie-vue/src/pages/fail.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/actors/index.vue b/fastmovie-vue/src/pages/generate/actors/index.vue new file mode 100644 index 0000000..29f8e9c --- /dev/null +++ b/fastmovie-vue/src/pages/generate/actors/index.vue @@ -0,0 +1,920 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/drama/index.vue b/fastmovie-vue/src/pages/generate/drama/index.vue new file mode 100644 index 0000000..d6d08b8 --- /dev/null +++ b/fastmovie-vue/src/pages/generate/drama/index.vue @@ -0,0 +1,271 @@ + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/drama/modules/episode.vue b/fastmovie-vue/src/pages/generate/drama/modules/episode.vue new file mode 100644 index 0000000..37b138d --- /dev/null +++ b/fastmovie-vue/src/pages/generate/drama/modules/episode.vue @@ -0,0 +1,73 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/drama/modules/scene.vue b/fastmovie-vue/src/pages/generate/drama/modules/scene.vue new file mode 100644 index 0000000..7e7d489 --- /dev/null +++ b/fastmovie-vue/src/pages/generate/drama/modules/scene.vue @@ -0,0 +1,467 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/drama/modules/storyboard.vue b/fastmovie-vue/src/pages/generate/drama/modules/storyboard.vue new file mode 100644 index 0000000..3ee2af5 --- /dev/null +++ b/fastmovie-vue/src/pages/generate/drama/modules/storyboard.vue @@ -0,0 +1,813 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/index.vue b/fastmovie-vue/src/pages/generate/index.vue new file mode 100644 index 0000000..edbfc27 --- /dev/null +++ b/fastmovie-vue/src/pages/generate/index.vue @@ -0,0 +1,177 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/props/index.vue b/fastmovie-vue/src/pages/generate/props/index.vue new file mode 100644 index 0000000..a431b7c --- /dev/null +++ b/fastmovie-vue/src/pages/generate/props/index.vue @@ -0,0 +1,771 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/scene/index.vue b/fastmovie-vue/src/pages/generate/scene/index.vue new file mode 100644 index 0000000..78a0129 --- /dev/null +++ b/fastmovie-vue/src/pages/generate/scene/index.vue @@ -0,0 +1,934 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/generate/storyboard/index.vue b/fastmovie-vue/src/pages/generate/storyboard/index.vue new file mode 100644 index 0000000..f803722 --- /dev/null +++ b/fastmovie-vue/src/pages/generate/storyboard/index.vue @@ -0,0 +1,2927 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/index/index.vue b/fastmovie-vue/src/pages/index/index.vue new file mode 100644 index 0000000..33123fe --- /dev/null +++ b/fastmovie-vue/src/pages/index/index.vue @@ -0,0 +1,736 @@ + + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/marketing/modules/price.vue b/fastmovie-vue/src/pages/marketing/modules/price.vue new file mode 100644 index 0000000..4fe93fb --- /dev/null +++ b/fastmovie-vue/src/pages/marketing/modules/price.vue @@ -0,0 +1,40 @@ + + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/marketing/points.vue b/fastmovie-vue/src/pages/marketing/points.vue new file mode 100644 index 0000000..3173778 --- /dev/null +++ b/fastmovie-vue/src/pages/marketing/points.vue @@ -0,0 +1,271 @@ + + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/marketing/vip.vue b/fastmovie-vue/src/pages/marketing/vip.vue new file mode 100644 index 0000000..fadd646 --- /dev/null +++ b/fastmovie-vue/src/pages/marketing/vip.vue @@ -0,0 +1,253 @@ + + + + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/play/index.vue b/fastmovie-vue/src/pages/play/index.vue new file mode 100644 index 0000000..f7f2cdc --- /dev/null +++ b/fastmovie-vue/src/pages/play/index.vue @@ -0,0 +1,238 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/user/code.vue b/fastmovie-vue/src/pages/user/code.vue new file mode 100644 index 0000000..beb14dd --- /dev/null +++ b/fastmovie-vue/src/pages/user/code.vue @@ -0,0 +1,407 @@ + + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/user/index.vue b/fastmovie-vue/src/pages/user/index.vue new file mode 100644 index 0000000..a1bac6b --- /dev/null +++ b/fastmovie-vue/src/pages/user/index.vue @@ -0,0 +1,112 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/user/modules/actors.vue b/fastmovie-vue/src/pages/user/modules/actors.vue new file mode 100644 index 0000000..2a73e8d --- /dev/null +++ b/fastmovie-vue/src/pages/user/modules/actors.vue @@ -0,0 +1,383 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/user/modules/share.vue b/fastmovie-vue/src/pages/user/modules/share.vue new file mode 100644 index 0000000..a77f034 --- /dev/null +++ b/fastmovie-vue/src/pages/user/modules/share.vue @@ -0,0 +1,243 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/user/modules/voice.vue b/fastmovie-vue/src/pages/user/modules/voice.vue new file mode 100644 index 0000000..db104c9 --- /dev/null +++ b/fastmovie-vue/src/pages/user/modules/voice.vue @@ -0,0 +1,111 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/user/modules/works.vue b/fastmovie-vue/src/pages/user/modules/works.vue new file mode 100644 index 0000000..7f501c5 --- /dev/null +++ b/fastmovie-vue/src/pages/user/modules/works.vue @@ -0,0 +1,241 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/voice/create.vue b/fastmovie-vue/src/pages/voice/create.vue new file mode 100644 index 0000000..dcbad7d --- /dev/null +++ b/fastmovie-vue/src/pages/voice/create.vue @@ -0,0 +1,798 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/voice/index.vue b/fastmovie-vue/src/pages/voice/index.vue new file mode 100644 index 0000000..efa98ae --- /dev/null +++ b/fastmovie-vue/src/pages/voice/index.vue @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/voice/modules/edit.vue b/fastmovie-vue/src/pages/voice/modules/edit.vue new file mode 100644 index 0000000..ed54328 --- /dev/null +++ b/fastmovie-vue/src/pages/voice/modules/edit.vue @@ -0,0 +1,382 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/voice/modules/recording.vue b/fastmovie-vue/src/pages/voice/modules/recording.vue new file mode 100644 index 0000000..740bbda --- /dev/null +++ b/fastmovie-vue/src/pages/voice/modules/recording.vue @@ -0,0 +1,497 @@ + + + + + diff --git a/fastmovie-vue/src/pages/voice/modules/voice-model.vue b/fastmovie-vue/src/pages/voice/modules/voice-model.vue new file mode 100644 index 0000000..1763e17 --- /dev/null +++ b/fastmovie-vue/src/pages/voice/modules/voice-model.vue @@ -0,0 +1,57 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/works/drama.vue b/fastmovie-vue/src/pages/works/drama.vue new file mode 100644 index 0000000..ff62786 --- /dev/null +++ b/fastmovie-vue/src/pages/works/drama.vue @@ -0,0 +1,127 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/works/index.vue b/fastmovie-vue/src/pages/works/index.vue new file mode 100644 index 0000000..f4c6d9f --- /dev/null +++ b/fastmovie-vue/src/pages/works/index.vue @@ -0,0 +1,342 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/works/modules/actors.vue b/fastmovie-vue/src/pages/works/modules/actors.vue new file mode 100644 index 0000000..8c35013 --- /dev/null +++ b/fastmovie-vue/src/pages/works/modules/actors.vue @@ -0,0 +1,826 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/works/modules/details.vue b/fastmovie-vue/src/pages/works/modules/details.vue new file mode 100644 index 0000000..f227748 --- /dev/null +++ b/fastmovie-vue/src/pages/works/modules/details.vue @@ -0,0 +1,465 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/works/modules/episodes.vue b/fastmovie-vue/src/pages/works/modules/episodes.vue new file mode 100644 index 0000000..fb6d806 --- /dev/null +++ b/fastmovie-vue/src/pages/works/modules/episodes.vue @@ -0,0 +1,348 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/works/modules/props.vue b/fastmovie-vue/src/pages/works/modules/props.vue new file mode 100644 index 0000000..f8461c5 --- /dev/null +++ b/fastmovie-vue/src/pages/works/modules/props.vue @@ -0,0 +1,746 @@ + + + \ No newline at end of file diff --git a/fastmovie-vue/src/pages/works/square.vue b/fastmovie-vue/src/pages/works/square.vue new file mode 100644 index 0000000..b67411a --- /dev/null +++ b/fastmovie-vue/src/pages/works/square.vue @@ -0,0 +1,232 @@ + + + + \ No newline at end of file diff --git a/fastmovie-vue/src/routers/index.ts b/fastmovie-vue/src/routers/index.ts new file mode 100644 index 0000000..9e9ccd0 --- /dev/null +++ b/fastmovie-vue/src/routers/index.ts @@ -0,0 +1,369 @@ + +import { ResponseCode } from "@/common/const"; +import { $http } from "@/common/http"; +import { useRefs, useUserStore, useWebConfigStore } from "@/stores"; +import { createRouter, createWebHashHistory } from "vue-router"; +import { useLogin } from "@/composables/useLogin"; +import { ElMessageBox } from "element-plus"; +import { useStorage } from "@/composables/useStorage"; +const router = createRouter({ + history: createWebHashHistory(), + routes: [ + { + path: '/main', + name: 'main', + component: () => import("@/layouts/control.vue"), + meta: { + title: '主页', + }, + redirect: '/', + children: [ + { + path: '/', + name: 'index', + component: () => import("@/pages/index/index.vue"), + meta: { + title: '首页', + menu: 'index', + } + }, + { + path: '/creative', + name: 'creative', + component: () => import("@/pages/creative/index.vue"), + meta: { + title: '创意圈', + menu: 'creative', + } + }, + { + path: '/actor', + name: 'actor', + component: () => import("@/pages/actor/index.vue"), + meta: { + title: '演员库', + menu: 'actor', + } + }, + { + path: '/voice', + name: 'voice', + component: () => import("@/pages/voice/index.vue"), + meta: { + title: '声音库', + menu: 'voice', + } + }, + { + path: '/user', + name: 'user', + component: () => import("@/pages/user/index.vue"), + meta: { + title: '个人中心', + menu: 'user', + } + }, + { + path: '/square', + name: 'square', + component: () => import("@/pages/works/square.vue"), + meta: { + title: '广场', + menu: 'square', + } + }, + { + path: '/works/:drama_id', + name: 'works-detail', + component: () => import("@/pages/works/drama.vue"), + meta: { + title: '短剧详情', + menu: 'works', + }, + beforeEnter: (to, _from, next) => { + const userStore = useUserStore(); + if (to.params.drama_id && userStore.hasLogin()) { + next() + } else { + next({ name: 'works' }) + } + }, + }, + { + path: '/play/:drama_id/:episode_id', + name: 'play-detail', + component: () => import("@/pages/play/index.vue"), + meta: { + title: '短剧播放', + }, + beforeEnter: (to, _from, next) => { + if (to.params.drama_id && to.params.episode_id) { + next() + } else { + next({ name: 'index' }) + } + }, + }, + { + path: '/voice/create', + name: 'voice-create', + component: () => import("@/pages/voice/create.vue"), + meta: { + title: '创建声音', + } + } + ] + }, + { + path: '/points', + name: 'points', + component: () => import("@/pages/marketing/points.vue"), + meta: { + title: '积分商城', + menu: 'points', + } + }, + { + path: '/code', + name: 'user-code', + component: () => import("@/pages/user/code.vue"), + meta: { + title: '邀请码', + menu: 'user', + } + }, + { + path: '/vip', + name: 'vip', + component: () => import("@/pages/marketing/vip.vue"), + meta: { + title: '会员中心', + menu: 'vip', + } + }, + { + path: '/article/:article', + name: 'article', + component: () => import("@/pages/article/content.vue"), + meta: { + title: '文章详情', + menu: 'article', + } + }, + { + path: '/generate/:drama_id/:episode_id?', + name: 'generate', + component: () => import("@/pages/generate/index.vue"), + meta: { + title: '生成剧本', + }, + redirect: (to) => { + return to.params.drama_id && to.params.episode_id ? `/generate/drama/${to.params.drama_id}/${to.params.episode_id}` : '/'; + }, + children: [ + { + path: '/generate/drama/:drama_id/:episode_id?', + name: 'generate-drama', + component: () => import("@/pages/generate/drama/index.vue"), + meta: { + title: '剧本调整', + }, + beforeEnter: (to, _from, next) => { + const userStore = useUserStore(); + if (!to.params.drama_id || !to.params.episode_id || !userStore.hasLogin()) { + next({ name: 'index' }) + } else { + next() + } + }, + }, + { + path: '/generate/actors/:drama_id/:episode_id?', + name: 'generate-actors', + component: () => import("@/pages/generate/actors/index.vue"), + meta: { + title: '剧本调整', + }, + beforeEnter: (to, _from, next) => { + const userStore = useUserStore(); + if (!to.params.drama_id || !to.params.episode_id || !userStore.hasLogin()) { + next({ name: 'index' }) + } else { + next() + } + }, + }, + { + path: '/generate/props/:drama_id/:episode_id?', + name: 'generate-props', + component: () => import("@/pages/generate/props/index.vue"), + meta: { + title: '剧本调整', + }, + beforeEnter: (to, _from, next) => { + const userStore = useUserStore(); + if (!to.params.drama_id || !to.params.episode_id || !userStore.hasLogin()) { + next({ name: 'index' }) + } else { + next() + } + }, + }, + { + path: '/generate/scene/:drama_id/:episode_id?', + name: 'generate-scene', + component: () => import("@/pages/generate/scene/index.vue"), + meta: { + title: '剧本调整', + }, + beforeEnter: (to, _from, next) => { + const userStore = useUserStore(); + if (!to.params.drama_id || !to.params.episode_id || !userStore.hasLogin()) { + next({ name: 'index' }) + } else { + next() + } + }, + }, + { + path: '/generate/storyboard/:drama_id/:episode_id?', + name: 'generate-storyboard', + component: () => import("@/pages/generate/storyboard/index.vue"), + meta: { + title: '剧本调整', + }, + beforeEnter: (to, _from, next) => { + const userStore = useUserStore(); + if (!to.params.drama_id || !to.params.episode_id || !userStore.hasLogin()) { + next({ name: 'index' }) + } else { + next() + } + }, + } + ] + } + ], +}) +const getWebConfig = () => { + return new Promise((resolve, reject) => { + $http.get('/app/control/api/Public/config').then((res: any) => { + if (res.code === ResponseCode.SUCCESS) { + const webConfigStore = useWebConfigStore(); + webConfigStore.setWebConfig(res.data as WebConfigInterface); + resolve(res.data); + } else { + reject(); + } + }).catch(() => { + reject(); + }); + }) +} + +// 验证邀请码是否被使用 +const checkInvitationCode = async (code: string): Promise => { + try { + const res: any = await $http.get(`/app/user/api/User/checkInvitationCode?code=${code}`); + // 如果接口返回成功,说明邀请码可用(未被使用) + if (res.code === ResponseCode.SUCCESS) { + return true; + } else { + // 接口返回失败,说明邀请码已被使用或无效 + return false; + } + } catch (error: any) { + // 如果接口返回错误,说明邀请码已被使用或无效 + const errorMsg = error?.response?.data?.msg || error?.msg || '邀请码验证失败'; + // 如果错误信息明确表示邀请码已被使用或无效,返回 false + if (errorMsg.includes('已被使用') || errorMsg.includes('无效') || errorMsg.includes('不存在')) { + return false; + } + // 其他错误也视为验证失败 + return false; + } +} + +// 处理邀请码验证(只在第一次访问或刷新时验证) +const handleInvitationCode = async (to: any, from: any) => { + const storage = useStorage(); + // 判断是否是第一次访问或刷新(from.name 为 null 或 undefined 表示刷新或首次访问) + const isFirstVisit = from.name === null || from.name === undefined; + + // 只在首次访问或刷新时验证,且 URL 中有 code 参数 + if (isFirstVisit && to.query.code) { + const code = to.query.code as string; + // 验证邀请码是否被使用 + try { + const isValid = await checkInvitationCode(code); + if (isValid) { + // 邀请码有效,存入缓存 + storage.set('ICODE', code); + } else { + // 邀请码已被使用或无效,提示用户 + ElMessageBox({ + title: '温馨提示', + message: '邀请码无效或已被使用', + type: 'warning', + }); + } + } catch (error) { + // 验证失败,提示用户 + ElMessageBox({ + title: '温馨提示', + message: '邀请码验证失败', + type: 'error', + }); + } + } +} +router.beforeEach(async (to, from, next) => { + const webConfigStore = useWebConfigStore(); + const { WEBCONFIG } = useRefs(webConfigStore); + webConfigStore.initWebConfig(); + if (!WEBCONFIG.value?.web_name) { + await getWebConfig().then(() => { + console.log('初始化网站配置成功'); + }).catch(() => { + console.error('初始化网站配置失败'); + }); + } + + // 处理邀请码验证(只在第一次访问或刷新时验证) + await handleInvitationCode(to, from); + + const userStore = useUserStore(); + userStore.initUserInfo(); + const { USERINFO } = useRefs(userStore); + const whiteList = ['index', 'article'] + if (!userStore.hasLogin() && !whiteList.includes(to.name as string)) { + return next({ name: 'index' }) + } + // 如果用户已登录但没有填写邀请码,打开邀请码弹窗(不阻止路由跳转) + if (userStore.hasLogin() && !USERINFO.value?.activation_time && to.name !== 'user-code') { + const { openIcode } = useLogin() + // 延迟打开弹窗,确保路由跳转完成后再显示 + setTimeout(() => { + openIcode() + }, 100) + } + if (userStore.hasLogin() && USERINFO.value?.activation_time && to.name === 'user-code') { + return next({ name: 'index' }) + } + next() +}) +router.afterEach((to, _from) => { + const { WEBCONFIG } = useWebConfigStore(); + if (to.name == 'index') { + globalThis.document.title = `${WEBCONFIG?.web_title}`; + return; + } + let subTitle = ''; + if (WEBCONFIG.web_title) { + subTitle = ` - ${WEBCONFIG.web_title}`; + } else if (WEBCONFIG.web_name) { + subTitle = ` - ${WEBCONFIG.web_name}`; + } + globalThis.document.title = `${to.meta?.title}${subTitle}` +}) +export default router \ No newline at end of file diff --git a/fastmovie-vue/src/stores/index.ts b/fastmovie-vue/src/stores/index.ts new file mode 100644 index 0000000..628d673 --- /dev/null +++ b/fastmovie-vue/src/stores/index.ts @@ -0,0 +1,14 @@ +import { StoreGeneric, defineStore, storeToRefs } from 'pinia' +import WebConfigStore from "@/stores/modules/webconfig"; +export const useWebConfigStore = defineStore('webconfig', WebConfigStore) +import StateStore from "@/stores/modules/state"; +export const useStateStore = defineStore('state', StateStore) +import UserStore from "@/stores/modules/user"; +export const useUserStore = defineStore('user', UserStore) +import ModelStore from "@/stores/modules/model"; +export const useModelStore = defineStore('model', ModelStore) +import WalletStore from "@/stores/modules/wallet"; +export const useWalletStore = defineStore('wallet', WalletStore) +export const useRefs = (store: StoreGeneric) => { + return storeToRefs(store); +} \ No newline at end of file diff --git a/fastmovie-vue/src/stores/modules/model.ts b/fastmovie-vue/src/stores/modules/model.ts new file mode 100644 index 0000000..39cf117 --- /dev/null +++ b/fastmovie-vue/src/stores/modules/model.ts @@ -0,0 +1,52 @@ +import { useStorage } from '@/composables/useStorage'; +export default () => { + const storage = useStorage(); + const MODEL = ref({ + creative_script: [], + creative_episode: [], + creative_scenes: [], + creative_storyboards: [], + drama_cover: [], + scene_image: [], + actor_image: [], + actor_three_view_image: [], + storyboard_image: [], + character_look_costume: [], + actor_costume: [], + actor_costume_three_view: [], + prop_image: [], + prop_three_view_image: [], + storyboard_video: [], + dialogue_voice: [], + storyboard_narration_voice: [], + storyboard_sfx_voice: [], + storyboard_music_voice: [], + }); + watch(() => MODEL.value, (data) => { + storage.set('MODEL', data, 10 * 60); + }, { + deep: true + }) + const initModel = () => { + const data = storage.get('MODEL') as ModelInterface; + if (data) { + MODEL.value = data; + } + } + const setModel = (model: ModelInterface) => { + MODEL.value = model; + } + const getModel = (scene: keyof ModelInterface,model_id?: any) => { + if(model_id){ + return MODEL.value[scene].find((item: any) => item.id === model_id); + } + return MODEL.value[scene] || []; + } + + return { + MODEL, + initModel, + setModel, + get: getModel + }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/stores/modules/state.ts b/fastmovie-vue/src/stores/modules/state.ts new file mode 100644 index 0000000..9e08e35 --- /dev/null +++ b/fastmovie-vue/src/stores/modules/state.ts @@ -0,0 +1,52 @@ +import { useStorage } from '@/composables/useStorage'; +type StateValueType = StateInterface[T]; +export default () => { + const storage = useStorage(); + const LANGUARE: LanguageListInterface[] = [ + { + label: '简体中文', + value: 'zh-CN' + }, + { + label: 'English', + value: 'en' + } + ]; + const STATE = ref({ + AsideState: true, + NotMenusAsideState: false, + language: LANGUARE[0].value, + InputFocusState: false + }); + watch(() => STATE.value, (data) => { + storage.set('STATE', data); + },{ + deep: true + }) + const initState = () => { + const data = storage.get('STATE') as StateInterface; + if (data) { + if(!data.language){ + data.language=LANGUARE[0].value; + } + if(data.InputFocusState === undefined){ + data.InputFocusState = false; + } + STATE.value = data; + } + } + const toggle = (key: T) => { + STATE.value[key] = !STATE.value[key] as StateValueType; + } + const setState = (key: T, value: StateValueType) => { + STATE.value[key] = value; + } + + return { + STATE, + LANGUARE, + initState, + setState, + toggle + }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/stores/modules/user.ts b/fastmovie-vue/src/stores/modules/user.ts new file mode 100644 index 0000000..a8d60e5 --- /dev/null +++ b/fastmovie-vue/src/stores/modules/user.ts @@ -0,0 +1,74 @@ +import { useStorage } from '@/composables/useStorage'; +import { onStoreageChange } from '@/common/functions'; +import router from '@/routers'; +export default () => { + const storage = useStorage() + const UserInfoStorageKey = 'USERINFO' + const USERINFO = ref(); + const hasLogin = (): boolean => { + return USERINFO.value !== undefined && USERINFO.value.token !== undefined && USERINFO.value.token !== null && USERINFO.value.token !== ''; + } + const getToken = (): string | undefined => { + return USERINFO.value?.token; + } + const setUserInfo = (UserInfo: UserInfoInterface): Promise => { + return new Promise((resolve, reject) => { + USERINFO.value = UserInfo; + storage.set(UserInfoStorageKey, UserInfo).then(() => { + if (hasLogin()) { + resolve(true); + } else { + reject('设置用户信息失败'); + } + }).catch(() => { + reject('设置本地储存失败') + }) + }) + } + const userListener = (): void => { + onStoreageChange(storage.getKey(UserInfoStorageKey), (e?: StorageEvent) => { + if (!e || e.newValue === null) { + USERINFO.value = undefined; + } else if (e.newValue !== null) { + if (router.currentRoute.value.meta.login_access === true) { + initUserInfo(); + } + } + }) + } + const initUserInfo = (): void => { + const user = storage.get(UserInfoStorageKey) + if (user !== null) { + setUserInfo(user as UserInfoInterface) + } + } + const clearUserInfo = (): Promise => { + USERINFO.value = undefined; + return storage.remove(UserInfoStorageKey) + } + const hasPermission = (permission?: string | string[]): boolean => { + if (USERINFO.value?.is_system) { + return true; + } + if (!permission) { + return true; + } + if (USERINFO.value?.permissions === undefined) { + return false; + } + if (Array.isArray(permission)) { + return permission.every(item => USERINFO.value?.permissions?.includes(item)); + } + return USERINFO.value.permissions.includes(permission); + } + return { + USERINFO, + hasLogin, + setUserInfo, + initUserInfo, + userListener, + clearUserInfo, + getToken, + hasPermission + }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/stores/modules/wallet.ts b/fastmovie-vue/src/stores/modules/wallet.ts new file mode 100644 index 0000000..60b9ec0 --- /dev/null +++ b/fastmovie-vue/src/stores/modules/wallet.ts @@ -0,0 +1,43 @@ +import { ResponseCode } from '@/common/const'; +import { $http } from '@/common/http'; +import { useStorage } from '@/composables/useStorage'; +export default () => { + const storage = useStorage(); + const WALLET = ref({ + uid: '', + channels_uid: 0, + balance: 0, + balance_sum: 0, + balance_used: 0, + points: 0, + points_sum: 0, + points_used: 0, + tmp_points: 0, + tmp_points_sum: 0, + tmp_points_used: 0, + available_points: 0, + }); + const initWallet = () => { + const data = storage.get('WALLET'); + if (data) { + WALLET.value = data as WalletInterface; + } + } + const setWallet = (data: WalletInterface) => { + WALLET.value = data; + storage.set('WALLET', data, 3600); + } + const getWallet = () => { + $http.get('/app/user/api/User/wallet').then((res: any) => { + if (res.code === ResponseCode.SUCCESS) { + setWallet(res.data as WalletInterface); + } + }); + } + return { + WALLET, + initWallet, + setWallet, + getWallet + }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/stores/modules/webconfig.ts b/fastmovie-vue/src/stores/modules/webconfig.ts new file mode 100644 index 0000000..6dff058 --- /dev/null +++ b/fastmovie-vue/src/stores/modules/webconfig.ts @@ -0,0 +1,20 @@ +import { useStorage } from '@/composables/useStorage'; +export default () => { + const storage = useStorage(); + const WEBCONFIG = ref({}); + const initWebConfig = () => { + const data = storage.get('WEBCONFIG'); + if (data) { + WEBCONFIG.value = data as WebConfigInterface; + } + } + const setWebConfig = (data: WebConfigInterface) => { + WEBCONFIG.value = data; + storage.set('WEBCONFIG', data,3600); + } + return { + WEBCONFIG, + initWebConfig, + setWebConfig + }; +} \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/drama.vue b/fastmovie-vue/src/svg/icon/drama.vue new file mode 100644 index 0000000..b636981 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/drama.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/helper.vue b/fastmovie-vue/src/svg/icon/helper.vue new file mode 100644 index 0000000..6493928 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/helper.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-about.vue b/fastmovie-vue/src/svg/icon/icon-about.vue new file mode 100644 index 0000000..750d9ee --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-about.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-actor-three-view.vue b/fastmovie-vue/src/svg/icon/icon-actor-three-view.vue new file mode 100644 index 0000000..9a09f2d --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-actor-three-view.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-actor.vue b/fastmovie-vue/src/svg/icon/icon-actor.vue new file mode 100644 index 0000000..b49b111 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-actor.vue @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-alipay.vue b/fastmovie-vue/src/svg/icon/icon-alipay.vue new file mode 100644 index 0000000..e1a9c89 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-alipay.vue @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-aspect-ratio.vue b/fastmovie-vue/src/svg/icon/icon-aspect-ratio.vue new file mode 100644 index 0000000..1aa5a97 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-aspect-ratio.vue @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-at.vue b/fastmovie-vue/src/svg/icon/icon-at.vue new file mode 100644 index 0000000..db084d9 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-at.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-batch.vue b/fastmovie-vue/src/svg/icon/icon-batch.vue new file mode 100644 index 0000000..dfeef67 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-batch.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-book.vue b/fastmovie-vue/src/svg/icon/icon-book.vue new file mode 100644 index 0000000..982054a --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-book.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-close.vue b/fastmovie-vue/src/svg/icon/icon-close.vue new file mode 100644 index 0000000..19675fe --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-close.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-clothes.vue b/fastmovie-vue/src/svg/icon/icon-clothes.vue new file mode 100644 index 0000000..7087881 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-clothes.vue @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-dubbing.vue b/fastmovie-vue/src/svg/icon/icon-dubbing.vue new file mode 100644 index 0000000..9d44a4f --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-dubbing.vue @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-dunpai.vue b/fastmovie-vue/src/svg/icon/icon-dunpai.vue new file mode 100644 index 0000000..28462db --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-dunpai.vue @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-edit.vue b/fastmovie-vue/src/svg/icon/icon-edit.vue new file mode 100644 index 0000000..ce52c04 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-edit.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-email.vue b/fastmovie-vue/src/svg/icon/icon-email.vue new file mode 100644 index 0000000..b0588d0 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-email.vue @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-help.vue b/fastmovie-vue/src/svg/icon/icon-help.vue new file mode 100644 index 0000000..f24e25a --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-help.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-image.vue b/fastmovie-vue/src/svg/icon/icon-image.vue new file mode 100644 index 0000000..4bebd8e --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-image.vue @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-like.vue b/fastmovie-vue/src/svg/icon/icon-like.vue new file mode 100644 index 0000000..10b30d5 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-like.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-login-phone.vue b/fastmovie-vue/src/svg/icon/icon-login-phone.vue new file mode 100644 index 0000000..2d388cf --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-login-phone.vue @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-login-wechat.vue b/fastmovie-vue/src/svg/icon/icon-login-wechat.vue new file mode 100644 index 0000000..795a5d2 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-login-wechat.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-logo.vue b/fastmovie-vue/src/svg/icon/icon-logo.vue new file mode 100644 index 0000000..5ebe2c1 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-logo.vue @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-logout.vue b/fastmovie-vue/src/svg/icon/icon-logout.vue new file mode 100644 index 0000000..66036f8 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-logout.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-model.vue b/fastmovie-vue/src/svg/icon/icon-model.vue new file mode 100644 index 0000000..f34d2c1 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-model.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-music.vue b/fastmovie-vue/src/svg/icon/icon-music.vue new file mode 100644 index 0000000..b89dca7 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-music.vue @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-next-step.vue b/fastmovie-vue/src/svg/icon/icon-next-step.vue new file mode 100644 index 0000000..7fff16b --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-next-step.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-optimize.vue b/fastmovie-vue/src/svg/icon/icon-optimize.vue new file mode 100644 index 0000000..fc108f8 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-optimize.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-pause.vue b/fastmovie-vue/src/svg/icon/icon-pause.vue new file mode 100644 index 0000000..c26ee11 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-pause.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-play-1.vue b/fastmovie-vue/src/svg/icon/icon-play-1.vue new file mode 100644 index 0000000..47da263 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-play-1.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-play.vue b/fastmovie-vue/src/svg/icon/icon-play.vue new file mode 100644 index 0000000..8dd0a3e --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-play.vue @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-points.vue b/fastmovie-vue/src/svg/icon/icon-points.vue new file mode 100644 index 0000000..8274660 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-points.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-prev-step.vue b/fastmovie-vue/src/svg/icon/icon-prev-step.vue new file mode 100644 index 0000000..e0fab0f --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-prev-step.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-prop.vue b/fastmovie-vue/src/svg/icon/icon-prop.vue new file mode 100644 index 0000000..fcb12f7 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-prop.vue @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-replace.vue b/fastmovie-vue/src/svg/icon/icon-replace.vue new file mode 100644 index 0000000..d9b16db --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-replace.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-security.vue b/fastmovie-vue/src/svg/icon/icon-security.vue new file mode 100644 index 0000000..3744e9c --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-security.vue @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-select.vue b/fastmovie-vue/src/svg/icon/icon-select.vue new file mode 100644 index 0000000..24f0a80 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-select.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-storyboard.vue b/fastmovie-vue/src/svg/icon/icon-storyboard.vue new file mode 100644 index 0000000..c0283e4 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-storyboard.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-style.vue b/fastmovie-vue/src/svg/icon/icon-style.vue new file mode 100644 index 0000000..d64cbf7 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-style.vue @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-tips.vue b/fastmovie-vue/src/svg/icon/icon-tips.vue new file mode 100644 index 0000000..2dab989 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-tips.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-transition.vue b/fastmovie-vue/src/svg/icon/icon-transition.vue new file mode 100644 index 0000000..d7dfbaf --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-transition.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-upload-image.vue b/fastmovie-vue/src/svg/icon/icon-upload-image.vue new file mode 100644 index 0000000..f221644 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-upload-image.vue @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-user.vue b/fastmovie-vue/src/svg/icon/icon-user.vue new file mode 100644 index 0000000..fc5beb7 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-user.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-video.vue b/fastmovie-vue/src/svg/icon/icon-video.vue new file mode 100644 index 0000000..3775e61 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-video.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-vip.vue b/fastmovie-vue/src/svg/icon/icon-vip.vue new file mode 100644 index 0000000..bad766b --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-vip.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-wechat.vue b/fastmovie-vue/src/svg/icon/icon-wechat.vue new file mode 100644 index 0000000..4ba3a86 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-wechat.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/icon-wxpay.vue b/fastmovie-vue/src/svg/icon/icon-wxpay.vue new file mode 100644 index 0000000..20aef2f --- /dev/null +++ b/fastmovie-vue/src/svg/icon/icon-wxpay.vue @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/language.vue b/fastmovie-vue/src/svg/icon/language.vue new file mode 100644 index 0000000..79ffa48 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/language.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/logo.vue b/fastmovie-vue/src/svg/icon/logo.vue new file mode 100644 index 0000000..9877a9b --- /dev/null +++ b/fastmovie-vue/src/svg/icon/logo.vue @@ -0,0 +1,61 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/upload.vue b/fastmovie-vue/src/svg/icon/upload.vue new file mode 100644 index 0000000..0bf4e0f --- /dev/null +++ b/fastmovie-vue/src/svg/icon/upload.vue @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/icon/video-file.vue b/fastmovie-vue/src/svg/icon/video-file.vue new file mode 100644 index 0000000..dd5b808 --- /dev/null +++ b/fastmovie-vue/src/svg/icon/video-file.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/actor-active.vue b/fastmovie-vue/src/svg/tabs/actor-active.vue new file mode 100644 index 0000000..fd1997d --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/actor-active.vue @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/actor.vue b/fastmovie-vue/src/svg/tabs/actor.vue new file mode 100644 index 0000000..40f204d --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/actor.vue @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/creative-active.vue b/fastmovie-vue/src/svg/tabs/creative-active.vue new file mode 100644 index 0000000..207c1ad --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/creative-active.vue @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/creative.vue b/fastmovie-vue/src/svg/tabs/creative.vue new file mode 100644 index 0000000..2cba1d5 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/creative.vue @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/home-active.vue b/fastmovie-vue/src/svg/tabs/home-active.vue new file mode 100644 index 0000000..249c022 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/home-active.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/home.vue b/fastmovie-vue/src/svg/tabs/home.vue new file mode 100644 index 0000000..7b61669 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/home.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/notice-active.vue b/fastmovie-vue/src/svg/tabs/notice-active.vue new file mode 100644 index 0000000..d2bb4d6 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/notice-active.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/notice.vue b/fastmovie-vue/src/svg/tabs/notice.vue new file mode 100644 index 0000000..0c1ac40 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/notice.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/square-active.vue b/fastmovie-vue/src/svg/tabs/square-active.vue new file mode 100644 index 0000000..4030a2a --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/square-active.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/square.vue b/fastmovie-vue/src/svg/tabs/square.vue new file mode 100644 index 0000000..a16cf6a --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/square.vue @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/user-active.vue b/fastmovie-vue/src/svg/tabs/user-active.vue new file mode 100644 index 0000000..17a6095 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/user-active.vue @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/user.vue b/fastmovie-vue/src/svg/tabs/user.vue new file mode 100644 index 0000000..3b18c99 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/user.vue @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/voice-active.vue b/fastmovie-vue/src/svg/tabs/voice-active.vue new file mode 100644 index 0000000..f7521ac --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/voice-active.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/voice.vue b/fastmovie-vue/src/svg/tabs/voice.vue new file mode 100644 index 0000000..2638ad3 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/voice.vue @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/works-active.vue b/fastmovie-vue/src/svg/tabs/works-active.vue new file mode 100644 index 0000000..c581415 --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/works-active.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/svg/tabs/works.vue b/fastmovie-vue/src/svg/tabs/works.vue new file mode 100644 index 0000000..f61508d --- /dev/null +++ b/fastmovie-vue/src/svg/tabs/works.vue @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/fastmovie-vue/src/workers/loadStream.ts b/fastmovie-vue/src/workers/loadStream.ts new file mode 100644 index 0000000..17433cb --- /dev/null +++ b/fastmovie-vue/src/workers/loadStream.ts @@ -0,0 +1,295 @@ +import { ImageDurationClip } from '@/common/av-cliper/ImageDurationClip'; +import { MP4Clip, ImgClip } from '@webav/av-cliper'; + +interface WorkerMessage { + type: 'init' | 'load' | 'play' | 'pause' | 'destroy' | 'stop' | 'switchClip' | 'resize'; + offscreen?: OffscreenCanvas; + TrackResource?: TrackResourceInterface[]; + index?: number; + time?: number; // 用于 seek + size?: { + width: number; + height: number; + }; +} + +let canvas: OffscreenCanvas; +let ctx: OffscreenCanvasRenderingContext2D; +let currentTrackResourceIndex = 0; +let currentTrackResource: TrackResourceInterface; +let TrackResource: TrackResourceInterface[] = []; +let Tracks: any[] = []; +let currentVideoClip: any; +let nextVideoClip: any; + +let timer: number | null = null; +let playStartTime = 0; +let isPlaying = false; +let ticking = false; +let currentTimeMs = 0; // 逻辑时间轴(关键) + + +/** 初始化 Worker */ +const initData = (data: WorkerMessage) => { + if (data.offscreen) { + canvas = data.offscreen; + ctx = canvas.getContext('2d')!; + } + TrackResource = data.TrackResource!; + for (let i = 0; i < Tracks.length; i++) { + Tracks[i]?.destroy?.(); + } + Tracks = []; + +}; +const resize = (data: WorkerMessage) => { + if (data.size) { + canvas.width = data.size.width; + canvas.height = data.size.height; + } + renderFirstFrame(); +} + +/** 停止视频 */ +const stop = () => { + isPlaying = false; + ticking = false; + currentTimeMs = 0; + if (timer !== null) { + clearTimeout(timer); + timer = null; + } + postMessage({ + type: 'stop', + }); +}; +const pause = () => { + if (!isPlaying) return; + stop(); // currentTimeMs 已经保存 +}; + + +/** 加载视频 clip */ +const loadClip = async (index: number) => { + if (Tracks[index]) { + return Tracks[index]; + } + if (!TrackResource[index]) return null; + const find = TrackResource[index]; + if (find.use_material_type === 'video' && find.video) { + const videoStream = (await fetch(find.video)).body!; + const clip = new MP4Clip(videoStream); + await clip.ready; + const [videoClip] = await clip.splitTrack(); + Tracks[index] = videoClip; + } else if (find.use_material_type === 'image' && find.image) { + const imageStream = await (await fetch(find.image)).blob(); + const imgClip = new ImgClip(await createImageBitmap(imageStream)); + await imgClip.ready; + const clip = new ImageDurationClip(imgClip, find.duration * 1000); + Tracks[index] = clip; + } else { + const offscreen = new OffscreenCanvas(canvas.width, canvas.height); + const offscreenCtx = offscreen.getContext('2d')!; + offscreenCtx.fillStyle = 'black'; + offscreenCtx.fillRect(0, 0, canvas.width, canvas.height); + const bitmap = offscreen.transferToImageBitmap(); + const imgClip = new ImgClip(bitmap); + await imgClip.ready; + const clip = new ImageDurationClip(imgClip, find.duration * 1000); + Tracks[index] = clip; + } + return Tracks[index]; +}; +// 渲染首帧 +const renderFirstFrame = async () => { + if (currentVideoClip) { + const { state, video } = await currentVideoClip.tick(0); + if (video && state === 'success') { + renderFrame(video); + } + } +} +const preloadNext = (index: number) => { + if (!TrackResource[index]) return; + + loadClip(index).then((clip) => { + nextVideoClip = clip; + }); +}; +const switchToNextClip = async () => { + currentTrackResourceIndex++; + if (nextVideoClip) { + currentVideoClip = nextVideoClip; + nextVideoClip = null; + postMessage({ + type: 'play', + index: currentTrackResourceIndex, + }); + currentTrackResource = TrackResource[currentTrackResourceIndex]; + } else { + stop(); + return false; + } + + // 预加载下下一个 + preloadNext(currentTrackResourceIndex + 1); + return true; +}; + +/** 播放视频 */ +const FPS = 60; +const FRAME_INTERVAL = Math.floor(1000 / FPS); +console.log('FPS', FPS, FRAME_INTERVAL + 'ms'); +let currentTime = 0; +const renderFrame = async (video: any) => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + const vw = video.displayWidth || video.codedWidth || video.width || canvas.width; + const vh = video.displayHeight || video.codedHeight || video.height || canvas.height; + + const scale = Math.min( + canvas.width / vw, + canvas.height / vh + ); + + const drawWidth = Math.round(vw * scale); + const drawHeight = Math.round(vh * scale); + + const x = Math.round((canvas.width - drawWidth) / 2); + const y = Math.round((canvas.height - drawHeight) / 2); + + ctx.drawImage(video, x, y, drawWidth, drawHeight); + const dialogue = currentTrackResource?.dialogues?.find((dialogue: any) => { + return currentTime >= dialogue.start_time && currentTime <= dialogue.end_time; + }); + const maxTextWidth = drawWidth - 20; + if (dialogue) { + /* 绘制字幕,如果字幕超出drawWidth,则换行 */ + ctx.fillStyle = 'white'; + ctx.font = '16px Arial'; + ctx.textAlign = 'center'; + ctx.strokeStyle = 'black'; + ctx.lineWidth = 2; + // ctx.textBaseline = 'middle'; + fillText(dialogue.content, canvas.width / 2, drawHeight - 130, maxTextWidth); + } + if (currentTrackResource?.narration) { + ctx.fillStyle = 'white'; + ctx.font = '16px Arial'; + ctx.textAlign = 'start'; + ctx.strokeStyle = 'black'; + ctx.lineWidth = 2; + fillText(currentTrackResource.narration, x + 10, 30, maxTextWidth); + } + video.close(); +} +const play = () => { + if (isPlaying || ticking) return; + ticking = true; + isPlaying = true; + playStartTime = performance.now() - currentTimeMs; + postMessage({ + type: 'play', + index: currentTrackResourceIndex, + }); + currentTrackResource = TrackResource[currentTrackResourceIndex]; + const loop = async () => { + if (!isPlaying) return; + const now = performance.now(); + currentTimeMs = Math.round((now - playStartTime) * 1000); + currentTime = currentTimeMs / 1000; + const { state, video } = await currentVideoClip!.tick(currentTimeMs); + // 广播当前播放时间 + postMessage({ + type: 'currentTime', + currentTime: currentTime, + }); + if (state === 'done') { + if (await switchToNextClip()) { + playStartTime = performance.now(); + currentTimeMs = 0; + schedule(); + } + return; + } + + if (video && state === 'success') { + renderFrame(video); + } + schedule(); + }; + + const schedule = () => { + timer = self.setTimeout(loop, FRAME_INTERVAL); + }; + + ticking = false; + schedule(); +}; +const fillText = (text: string, x: number, y: number, maxTextWidth: number) => { + const dialogueWidth = ctx.measureText(text).width; + if (dialogueWidth > maxTextWidth) { + const contentStrs = text.split(''); + let lines = []; + let line = ''; + for (let i = 0; i < contentStrs.length; i++) { + if (ctx.measureText(line + contentStrs[i]).width >= maxTextWidth) { + lines.push(line); + line = ''; + } + line += contentStrs[i]; + } + if (line != '') lines.push(line); + for (let i = lines.length - 1; i >= 0; i--) { + ctx.strokeText(lines[i], x, y + i * 24); + ctx.fillText(lines[i], x, y + i * 24); + } + } + else { + ctx.strokeText(text, x, y); + ctx.fillText(text, x, y); + } +} + +/** Worker 消息处理 */ +onmessage = async (e: MessageEvent) => { + const data = e.data; + switch (data.type) { + case 'load': + initData(data); + currentTrackResourceIndex = data.index ?? 0; + currentVideoClip = await loadClip(currentTrackResourceIndex); + renderFirstFrame(); + preloadNext(currentTrackResourceIndex + 1); + break; + case 'resize': + resize(data); + break; + case 'play': + if (currentTrackResourceIndex !== data.index) { + currentTrackResourceIndex = data.index ?? 0; + currentVideoClip = await loadClip(currentTrackResourceIndex); + renderFirstFrame(); + preloadNext(currentTrackResourceIndex + 1); + } + play(); + break; + case 'switchClip': + currentTrackResourceIndex = data.index ?? 0; + currentVideoClip = await loadClip(currentTrackResourceIndex); + renderFirstFrame(); + preloadNext(currentTrackResourceIndex + 1); + break; + case 'pause': + pause(); + break; + case 'stop': + stop(); + break; + case 'destroy': + stop(); + currentVideoClip = null; + nextVideoClip = null; + break; + } +}; diff --git a/fastmovie-vue/tsconfig.json b/fastmovie-vue/tsconfig.json new file mode 100644 index 0000000..ff6b247 --- /dev/null +++ b/fastmovie-vue/tsconfig.json @@ -0,0 +1,46 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": "./", + "paths": { + "@/*": [ + "src/*" + ] + }, + }, + "types": ["element-plus/global"], + "include": [ + "shared/**/*.d.ts", + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "auto-imports.d.ts", + "components.d.ts" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] + } \ No newline at end of file diff --git a/fastmovie-vue/tsconfig.node.json b/fastmovie-vue/tsconfig.node.json new file mode 100644 index 0000000..7ab9169 --- /dev/null +++ b/fastmovie-vue/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] + } + \ No newline at end of file diff --git a/fastmovie-vue/vite.config.ts b/fastmovie-vue/vite.config.ts new file mode 100644 index 0000000..a2e7e2f --- /dev/null +++ b/fastmovie-vue/vite.config.ts @@ -0,0 +1,90 @@ +import path from 'path' +import { ConfigEnv, defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import Icons from 'unplugin-icons/vite' +import IconsResolver from 'unplugin-icons/resolver' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import compression from 'vite-plugin-compression'; +import Inspect from 'vite-plugin-inspect' + +const pathSrc = path.resolve(__dirname, './src') +export default defineConfig((ConfigEnv: ConfigEnv) => { + const env = loadEnv(ConfigEnv.mode, process.cwd()) + return { + base: '/fastmovie/', + resolve: { + alias: { + '@': pathSrc, + }, + }, + build: { + assetsDir: 'assets', + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes('node_modules')) { + return 'vendor'; + } + } + } + }, + }, + plugins: [ + // bundleAnalyzer({ port: 8888, }), + compression({ + // 指定要压缩的文件类型,默认为 .js 和 .css + // ext: '.js', + // 开启Gzip压缩 + algorithm: 'gzip', + // 超过(n)k才打包 + threshold: 50 * 1024, + // 是否删除原始文件,默认为 false + deleteOriginFile: false + }), + vue(), + AutoImport({ + imports: ['vue'], + resolvers: [ + ElementPlusResolver(), + IconsResolver({ + prefix: 'Icon', + }), + ], + dts: path.resolve(__dirname, 'auto-imports.d.ts'), + eslintrc: { + enabled: true + }, + }), + Components({ + extensions: ['vue', 'md'], + include: [/\.vue$/, /\.vue\?vue/, /\.md$/], + resolvers: [ + IconsResolver({ + enabledCollections: ['ep'], + }), + ElementPlusResolver(), + ], + dts: path.resolve(__dirname, 'components.d.ts'), + }), + Icons({ + autoInstall: true, + }), + Inspect() + ], + server: { + open: true, + port: 36310, + allowedHosts: true, + // 接口代理(解决跨域) + proxy: { + "/local": { + target: env.VITE_REQUEST_BASE_URL, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/local/, ""), + } + }, + }, + } +})