From e78454540f1ebdf53700c57bc8b4650d23501af7 Mon Sep 17 00:00:00 2001
From: xuanchen <122383162@qq.com>
Date: Wed, 8 Mar 2023 09:16:04 +0800
Subject: [PATCH] first
---
.editorconfig | 15 +
.env.example | 48 +
.gitattributes | 5 +
.gitignore | 16 +
.styleci.yml | 13 +
README.md | 123 +
app/Admin/Controllers/AuthController.php | 10 +
app/Admin/Controllers/Dashboard.php | 183 +
app/Admin/Controllers/HomeController.php | 46 +
app/Admin/Controllers/ModuleController.php | 105 +
app/Admin/Extensions/CleanCache.php | 35 +
app/Admin/Extensions/FormQrCode.php | 21 +
app/Admin/Routes/modules.php | 12 +
app/Admin/Traits/WithUploads.php | 94 +
app/Admin/bootstrap.php | 51 +
app/Admin/routes.php | 20 +
app/Api/Controllers/Controller.php | 11 +
app/Api/Controllers/IndexController.php | 13 +
app/Api/Resources/BaseCollection.php | 19 +
app/Api/bootstrap.php | 1 +
app/Api/routes.php | 26 +
app/Console/Kernel.php | 52 +
app/Exceptions/Handler.php | 44 +
app/Http/Controllers/Controller.php | 28 +
app/Http/Kernel.php | 68 +
app/Http/Middleware/Authenticate.php | 22 +
app/Http/Middleware/EncryptCookies.php | 17 +
.../PreventRequestsDuringMaintenance.php | 17 +
.../Middleware/RedirectIfAuthenticated.php | 34 +
app/Http/Middleware/SetServerId.php | 29 +
app/Http/Middleware/TrimStrings.php | 19 +
app/Http/Middleware/TrustHosts.php | 20 +
app/Http/Middleware/TrustProxies.php | 28 +
app/Http/Middleware/VerifyCsrfToken.php | 17 +
app/Models/Google2FA.php | 45 +
app/Models/Model.php | 27 +
app/Models/Module.php | 49 +
app/Providers/AppServiceProvider.php | 28 +
app/Providers/AuthServiceProvider.php | 30 +
app/Providers/BroadcastServiceProvider.php | 21 +
app/Providers/EventServiceProvider.php | 32 +
app/Providers/RouteServiceProvider.php | 63 +
app/Rules/AdminG2FARule.php | 43 +
app/Rules/IdCardRule.php | 69 +
app/Scopes/OrderByIdDescScope.php | 23 +
app/Scopes/OrderByOrderAscScope.php | 23 +
app/Traits/HasClicks.php | 87 +
app/Traits/HasCovers.php | 86 +
app/Traits/HasStatus.php | 91 +
app/Traits/Macroable.php | 48 +
app/Traits/OrderByIdDesc.php | 21 +
app/Traits/OrderByOrderAsc.php | 22 +
app/Traits/WithGoogle2FA.php | 24 +
app/Traits/WithPosition.php | 67 +
artisan | 53 +
bootstrap/app.php | 55 +
bootstrap/cache/.gitignore | 2 +
composer.json | 75 +
composer.lock | 10220 ++++++
config/admin.php | 412 +
config/agent.php | 40 +
config/api.php | 48 +
config/app.php | 235 +
config/auth.php | 111 +
config/broadcasting.php | 64 +
config/cache.php | 106 +
config/cors.php | 34 +
config/database.php | 147 +
config/filesystems.php | 82 +
config/hashing.php | 52 +
config/image.php | 20 +
config/laravel-model-caching.php | 11 +
config/logging.php | 117 +
config/mail.php | 110 +
config/modules.php | 272 +
config/queue.php | 93 +
config/sanctum.php | 65 +
config/services.php | 33 +
config/session.php | 201 +
config/ueditor.php | 118 +
config/view.php | 36 +
database/.gitignore | 2 +
database/factories/UserFactory.php | 39 +
.../2016_01_04_173148_create_admin_tables.php | 119 +
..._08_19_000000_create_failed_jobs_table.php | 36 +
...01_create_personal_access_tokens_table.php | 36 +
.../2021_11_10_102248_create_jobs_table.php | 36 +
...2_11_30_204150_create_google2fas_table.php | 34 +
database/seeders/AdminPanelSeeder.php | 106 +
database/seeders/DatabaseSeeder.php | 18 +
docs/READMD.md | 2 +
docs/Trait doc.md | 117 +
init_composer.sh | 9 +
modules.json | 2 +
modules/.gitignore | 11 +
modules/Cms/.gitignore | 4 +
modules/Cms/Cms.php | 88 +
modules/Cms/Config/config.php | 10 +
...0000_create_cms_article_category_table.php | 33 +
...00_00_000000_create_cms_articles_table.php | 42 +
..._00_000000_create_cms_categories_table.php | 41 +
...00_00_00_000000_create_cms_pages_table.php | 42 +
...00_00_000000_create_cms_taggable_table.php | 33 +
...00_00_000000_create_cms_tags_table.php.php | 33 +
...09_29_142323_create_cms_storages_table.php | 34 +
...022_10_20_095341_create_cms_logs_table.php | 33 +
.../Controllers/Admin/ArticleController.php | 136 +
.../Controllers/Admin/CategoryController.php | 98 +
.../Http/Controllers/Admin/PageController.php | 124 +
.../Controllers/Admin/StorageController.php | 49 +
.../Http/Controllers/Admin/TagController.php | 48 +
.../Controllers/Admin/VersionController.php | 68 +
.../Controllers/Api/ArticleController.php | 113 +
.../Controllers/Api/CategoryController.php | 50 +
.../Http/Controllers/Api/PageController.php | 90 +
.../Http/Controllers/Api/TagController.php | 37 +
.../Http/Resources/ArticleBaseResource.php | 29 +
.../Cms/Http/Resources/ArticleCollection.php | 31 +
.../Cms/Http/Resources/ArticleResource.php | 36 +
.../Http/Resources/CategoryBaseResource.php | 19 +
.../Cms/Http/Resources/CategoryCollection.php | 33 +
.../Cms/Http/Resources/CategoryResource.php | 25 +
modules/Cms/Http/Resources/PageCollection.php | 42 +
modules/Cms/Http/Resources/PageResource.php | 28 +
modules/Cms/Http/Resources/TagCollection.php | 31 +
modules/Cms/Http/Resources/TagResource.php | 18 +
modules/Cms/Models/Article.php | 123 +
modules/Cms/Models/ArticleCategory.php | 14 +
modules/Cms/Models/Category.php | 69 +
modules/Cms/Models/Log.php | 15 +
modules/Cms/Models/Page.php | 55 +
modules/Cms/Models/Storage.php | 24 +
modules/Cms/Models/Tag.php | 34 +
modules/Cms/Models/Taggable.php | 14 +
modules/Cms/Providers/CmsServiceProvider.php | 63 +
.../Cms/Providers/RouteServiceProvider.php | 62 +
modules/Cms/README.md | 49 +
modules/Cms/Routes/admin.php | 17 +
modules/Cms/Routes/api.php | 34 +
modules/Cms/Traits/HasTags.php | 25 +
modules/Cms/composer.json | 34 +
modules/Cms/module.json | 15 +
modules/Configuration/.gitignore | 4 +
.../Configuration/Actions/UpdateSource.php | 44 +
modules/Configuration/Actions/UpdateType.php | 36 +
modules/Configuration/Configuration.php | 73 +
..._20_000000_create_configurations_table.php | 39 +
.../Http/Controllers/IndexController.php | 135 +
.../Configuration/Models/Configuration.php | 82 +
.../ConfigurationServiceProvider.php | 60 +
.../Providers/RouteServiceProvider.php | 52 +
modules/Configuration/README.md | 6 +
modules/Configuration/Routes/admin.php | 19 +
modules/Configuration/composer.json | 22 +
modules/Configuration/module.json | 18 +
modules/Linker/.gitignore | 4 +
...0_000000_create_linker_relations_table.php | 35 +
...0000_00_00_000000_create_linkers_table.php | 37 +
.../Http/Controllers/IndexController.php | 51 +
modules/Linker/Linker.php | 66 +
modules/Linker/Models/Linker.php | 102 +
modules/Linker/Models/LinkerRelation.php | 40 +
.../Providers/LinkerServiceProvider.php | 47 +
.../Linker/Providers/RouteServiceProvider.php | 51 +
modules/Linker/README.md | 22 +
modules/Linker/Routes/admin.php | 10 +
modules/Linker/Traits/HasLinker.php | 123 +
modules/Linker/Traits/WithLinker.php | 31 +
modules/Linker/composer.json | 28 +
modules/Linker/module.json | 15 +
modules/Mall/.gitignore | 4 +
modules/Mall/Config/config.php | 168 +
..._00_000000_create_mall_addresses_table.php | 40 +
...00_00_000000_create_mall_banners_table.php | 37 +
..._00_00_000000_create_mall_brands_table.php | 37 +
...0_00_00_000000_create_mall_carts_table.php | 35 +
...00_000000_create_mall_categories_table.php | 40 +
...00_000000_create_mall_deliveries_table.php | 35 +
...00000_create_mall_delivery_rules_table.php | 40 +
..._00_000000_create_mall_expresses_table.php | 43 +
...00_000000_create_mall_goods_skus_table.php | 41 +
...00_create_mall_goods_spec_values_table.php | 33 +
...0_000000_create_mall_goods_specs_table.php | 33 +
...0_00_00_000000_create_mall_goods_table.php | 52 +
..._00_000000_create_mall_goods_tag_table.php | 34 +
...00_00_00_000000_create_mall_jobs_table.php | 35 +
...0000_create_mall_order_expresses_table.php | 44 +
...0_000000_create_mall_order_items_table.php | 36 +
..._00_00_000000_create_mall_orders_table.php | 49 +
...00_00_000000_create_mall_reasons_table.php | 34 +
...000_create_mall_refund_expresses_table.php | 37 +
..._000000_create_mall_refund_items_table.php | 37 +
...0_000000_create_mall_refund_logs_table.php | 37 +
...00_00_000000_create_mall_refunds_table.php | 41 +
...00_00_000000_create_mall_regions_table.php | 39 +
..._000000_create_mall_shop_express_table.php | 34 +
..._000000_create_mall_shop_reasons_table.php | 33 +
...000000_create_mall_shop_staffers_table.php | 36 +
...0_00_00_000000_create_mall_shops_table.php | 46 +
...00_00_00_000000_create_mall_tags_table.php | 33 +
...27_110430_create_mall_activities_table.php | 36 +
...43557_add_channel_to_mall_orders_table.php | 32 +
..._person_to_mall_orders_expresses_table.php | 33 +
..._09_30_093509_create_mall_videos_table.php | 35 +
.../Mall/Database/Seeders/ExpressSeeder.php | 45 +
.../Mall/Database/Seeders/RegionSeeder.php | 3314 ++
modules/Mall/Events/OrderCanceled.php | 11 +
modules/Mall/Events/OrderClosed.php | 11 +
modules/Mall/Events/OrderCompleted.php | 11 +
modules/Mall/Events/OrderCreated.php | 11 +
modules/Mall/Events/OrderDelivered.php | 11 +
modules/Mall/Events/OrderEvent.php | 27 +
modules/Mall/Events/OrderPaid.php | 11 +
modules/Mall/Events/OrderSigned.php | 11 +
modules/Mall/Events/RefundAgreed.php | 23 +
modules/Mall/Events/RefundApplied.php | 27 +
modules/Mall/Events/RefundCompleted.php | 23 +
modules/Mall/Events/RefundProcessed.php | 23 +
modules/Mall/Events/RefundRefused.php | 23 +
modules/Mall/Facades/Item.php | 127 +
modules/Mall/Facades/Order.php | 386 +
modules/Mall/Facades/Refund.php | 250 +
modules/Mall/Facades/RefundItem.php | 68 +
modules/Mall/Facades/Workflow.php | 15 +
.../Controllers/Admin/Action/Order/Audit.php | 25 +
.../Admin/Action/Order/Delivered.php | 46 +
.../Controllers/Admin/Action/Order/Pay.php | 25 +
.../Admin/Action/Order/RefundAudit.php | 57 +
.../Admin/Action/Order/RefundReturns.php | 44 +
.../Admin/Action/Order/RefundSign.php | 42 +
.../Controllers/Admin/Action/Shop/Close.php | 32 +
.../Controllers/Admin/Action/Shop/Open.php | 32 +
.../Controllers/Admin/Action/Shop/Pass.php | 32 +
.../Controllers/Admin/Action/Shop/Reject.php | 33 +
.../Controllers/Admin/ActivityController.php | 48 +
.../Controllers/Admin/AddressController.php | 93 +
.../Http/Controllers/Admin/AjaxController.php | 70 +
.../Controllers/Admin/BannerController.php | 74 +
.../Controllers/Admin/BrandController.php | 56 +
.../Controllers/Admin/CategoryController.php | 101 +
.../Controllers/Admin/DashboardController.php | 67 +
.../Controllers/Admin/DeliveryController.php | 82 +
.../Admin/DeliveryRuleController.php | 106 +
.../Controllers/Admin/ExpressController.php | 70 +
.../Controllers/Admin/GoodsController.php | 228 +
.../Http/Controllers/Admin/JobController.php | 47 +
.../Controllers/Admin/OrderController.php | 222 +
.../Controllers/Admin/ReasonController.php | 48 +
.../Controllers/Admin/RefundController.php | 106 +
.../Controllers/Admin/RegionController.php | 27 +
.../Admin/Selectable/Expresses.php | 34 +
.../Controllers/Admin/Selectable/Reasons.php | 32 +
.../Http/Controllers/Admin/ShopController.php | 180 +
.../Http/Controllers/Admin/SkuController.php | 77 +
.../Http/Controllers/Admin/SpecController.php | 81 +
.../Controllers/Admin/SpecValueController.php | 70 +
.../Controllers/Admin/StafferController.php | 98 +
.../Admin/StockOrderBySystemController.php | 254 +
.../Admin/StockOrderController.php | 142 +
.../Http/Controllers/Admin/TagController.php | 48 +
.../Controllers/Admin/VersionController.php | 68 +
.../Controllers/Admin/VideoController.php | 64 +
.../Mall/Http/Controllers/Admin/WithShop.php | 22 +
.../Controllers/Agent/IndexController.php | 16 +
.../Http/Controllers/Agent/ShopController.php | 19 +
.../Controllers/Api/ActivityController.php | 34 +
.../Controllers/Api/AddressController.php | 160 +
.../Http/Controllers/Api/BannerController.php | 37 +
.../Http/Controllers/Api/CartController.php | 159 +
.../Controllers/Api/CategoryController.php | 37 +
.../Http/Controllers/Api/GoodsController.php | 87 +
.../Http/Controllers/Api/IndexController.php | 99 +
.../Controllers/Api/OrderBuyController.php | 298 +
.../Http/Controllers/Api/OrderController.php | 287 +
.../Http/Controllers/Api/PayController.php | 113 +
.../Http/Controllers/Api/RefundController.php | 124 +
.../Http/Controllers/Api/ShopController.php | 210 +
.../Controllers/Api/ShopExtendController.php | 87 +
.../Http/Controllers/Api/TagController.php | 36 +
modules/Mall/Http/Exporter/OrderExporter.php | 49 +
modules/Mall/Http/Exporter/OrderFromArray.php | 27 +
modules/Mall/Http/Middleware/Authenticate.php | 35 +
modules/Mall/Http/Middleware/ShopOwner.php | 24 +
.../Http/Requests/Address/AddressRequest.php | 58 +
.../Mall/Http/Requests/Cart/CartRequest.php | 24 +
.../Requests/OrderBuy/OrderBuyRequest.php | 30 +
.../Mall/Http/Requests/Shop/ShopRequest.php | 47 +
.../Api/Activity/ActivityBaseResource.php | 24 +
.../Api/Activity/ActivityCollection.php | 20 +
.../Api/Activity/ActivityResource.php | 25 +
.../Resources/Api/Address/AddressResource.php | 28 +
.../Resources/Api/Banner/BannerResource.php | 19 +
.../Api/Category/CategoryBaseResource.php | 19 +
.../Api/Category/CategoryResource.php | 22 +
.../Resources/Api/Goods/BrandResource.php | 18 +
.../Http/Resources/Api/Goods/CartResource.php | 24 +
.../Resources/Api/Goods/DetailResource.php | 24 +
.../Resources/Api/Goods/GoodsBaseResource.php | 33 +
.../Resources/Api/Goods/GoodsCollection.php | 20 +
.../Resources/Api/Goods/GoodsResource.php | 50 +
.../Resources/Api/Goods/SkuBaseResource.php | 19 +
.../Http/Resources/Api/Goods/SkuResource.php | 26 +
.../Http/Resources/Api/Goods/SpecResource.php | 19 +
.../Resources/Api/Goods/SpecValueResource.php | 18 +
.../Http/Resources/Api/Goods/TagResource.php | 18 +
.../Resources/Api/Order/LogisticResource.php | 21 +
.../Resources/Api/Order/OrderCollection.php | 34 +
.../Api/Order/OrderExpressResource.php | 22 +
.../Resources/Api/Order/OrderItemResource.php | 26 +
.../Api/Order/OrderLogisticResource.php | 22 +
.../Resources/Api/Order/OrderResource.php | 32 +
.../Resources/Api/Order/RefundCollection.php | 37 +
.../Api/Order/RefundItemsResource.php | 22 +
.../Resources/Api/Order/RefundLogResource.php | 22 +
.../Resources/Api/Order/RefundResource.php | 40 +
.../Resources/Api/Region/RegionResource.php | 18 +
.../Resources/Api/Shop/ExpressResource.php | 19 +
.../Api/Shop/ShopBaseInfoResource.php | 20 +
.../Resources/Api/Shop/ShopCollection.php | 27 +
.../Resources/Api/Shop/ShopEditResource.php | 35 +
.../Http/Resources/Api/Shop/ShopResource.php | 35 +
.../Resources/Api/Video/VideoResource.php | 20 +
modules/Mall/Jobs/CloseOrder.php | 67 +
.../Mall/Listeners/OrderCreatedListener.php | 24 +
modules/Mall/Listeners/OrderPaidListener.php | 42 +
.../Mall/Listeners/RefundAgreedListener.php | 39 +
.../Listeners/RefundCompletedListener.php | 29 +
modules/Mall/Mall.php | 151 +
modules/Mall/Models/Activity.php | 20 +
modules/Mall/Models/Address.php | 86 +
modules/Mall/Models/Banner.php | 33 +
modules/Mall/Models/Brand.php | 39 +
modules/Mall/Models/Cart.php | 29 +
modules/Mall/Models/Category.php | 56 +
modules/Mall/Models/Delivery.php | 39 +
modules/Mall/Models/DeliveryRule.php | 31 +
modules/Mall/Models/Express.php | 32 +
modules/Mall/Models/Goods.php | 267 +
modules/Mall/Models/GoodsSku.php | 62 +
modules/Mall/Models/GoodsSpec.php | 36 +
modules/Mall/Models/GoodsSpecValue.php | 24 +
modules/Mall/Models/Job.php | 19 +
modules/Mall/Models/Order.php | 301 +
modules/Mall/Models/OrderExpress.php | 48 +
modules/Mall/Models/OrderItem.php | 121 +
modules/Mall/Models/Reason.php | 30 +
modules/Mall/Models/Refund.php | 216 +
modules/Mall/Models/RefundExpress.php | 22 +
modules/Mall/Models/RefundItem.php | 35 +
modules/Mall/Models/RefundLog.php | 40 +
modules/Mall/Models/Region.php | 63 +
modules/Mall/Models/Shop.php | 187 +
modules/Mall/Models/ShopStaffer.php | 31 +
modules/Mall/Models/Tag.php | 29 +
modules/Mall/Models/Traits/BelongsToOrder.php | 22 +
.../Mall/Models/Traits/BelongsToRefund.php | 22 +
modules/Mall/Models/Traits/BelongsToShop.php | 49 +
modules/Mall/Models/Traits/GoodsAttribute.php | 211 +
modules/Mall/Models/Traits/HasRegion.php | 60 +
modules/Mall/Models/Traits/OrderActions.php | 188 +
modules/Mall/Models/Traits/OrderCando.php | 82 +
modules/Mall/Models/Traits/OrderScopes.php | 138 +
modules/Mall/Models/Traits/RefundActions.php | 318 +
modules/Mall/Models/Traits/ShopActions.php | 94 +
modules/Mall/Models/Video.php | 22 +
.../Mall/Providers/EventServiceProvider.php | 38 +
.../Mall/Providers/MallServiceProvider.php | 100 +
.../Mall/Providers/RouteServiceProvider.php | 80 +
modules/Mall/Providers/WorkflowRegistry.php | 135 +
modules/Mall/README.md | 37 +
.../views/admin/order/detail.blade.php | 271 +
.../views/admin/stock_order/detail.blade.php | 271 +
modules/Mall/Routes/admin.php | 85 +
modules/Mall/Routes/agent.php | 14 +
modules/Mall/Routes/api.php | 124 +
modules/Mall/Rules/CityRule.php | 38 +
modules/Mall/Rules/DistrictRule.php | 38 +
modules/Mall/Rules/ProvinceRule.php | 38 +
modules/Mall/Traits/HasAddresses.php | 22 +
modules/Mall/Traits/HasCart.php | 22 +
modules/Mall/Traits/HasOrders.php | 53 +
modules/Mall/Traits/HasShop.php | 22 +
modules/Mall/Traits/WithWorkflow.php | 48 +
modules/Mall/composer.json | 38 +
modules/Mall/docs/README.md | 25 +
modules/Mall/docs/商品接口.md | 0
modules/Mall/module.json | 20 +
modules/Notification/Channels/AppChannel.php | 45 +
modules/Notification/Config/config.php | 34 +
...00_create_notification_templates_table.php | 35 +
.../Controllers/Admin/IndexController.php | 47 +
.../Controllers/Admin/SettingController.php | 17 +
.../Http/Controllers/Api/IndexController.php | 141 +
.../Http/Resources/NotificationCollection.php | 20 +
.../Http/Resources/NotificationResource.php | 23 +
modules/Notification/Models/Template.php | 17 +
modules/Notification/Notification.php | 71 +
.../Providers/NotificationServiceProvider.php | 67 +
.../Providers/RouteServiceProvider.php | 65 +
modules/Notification/README.md | 154 +
modules/Notification/Routes/admin.php | 13 +
modules/Notification/Routes/api.php | 26 +
modules/Notification/composer.json | 23 +
modules/Notification/module.json | 17 +
modules/Payment/.gitignore | 4 +
modules/Payment/Config/config.php | 18 +
...00_000000_create_payment_alipays_table.php | 42 +
...0_00_000000_create_payment_bills_table.php | 34 +
...0_000000_create_payment_notifies_table.php | 33 +
...0_000000_create_payment_redpacks_table.php | 39 +
...00_000000_create_payment_refunds_table.php | 35 +
...0_000000_create_payment_settings_table.php | 37 +
..._000000_create_payment_transfers_table.php | 40 +
...00_000000_create_payment_wechats_table.php | 42 +
...000_00_00_000000_create_payments_table.php | 44 +
modules/Payment/Events/Paid.php | 39 +
modules/Payment/Facades/Pay.php | 31 +
.../Controllers/Admin/AlipayController.php | 73 +
.../Http/Controllers/Admin/BillController.php | 26 +
.../Controllers/Admin/IndexController.php | 60 +
.../Controllers/Admin/RedpackController.php | 50 +
.../Controllers/Admin/RefundController.php | 47 +
.../Controllers/Admin/SettingController.php | 73 +
.../Controllers/Admin/TransferController.php | 50 +
.../Controllers/Admin/WechatController.php | 96 +
.../Controllers/Api/GatewayController.php | 29 +
.../Http/Controllers/Api/NotifyController.php | 63 +
modules/Payment/Models/Alipay.php | 56 +
modules/Payment/Models/Bill.php | 12 +
modules/Payment/Models/Payment.php | 256 +
modules/Payment/Models/PaymentNotify.php | 27 +
modules/Payment/Models/Redpack.php | 25 +
modules/Payment/Models/Refund.php | 49 +
modules/Payment/Models/Setting.php | 83 +
modules/Payment/Models/Traits/WithConfig.php | 37 +
modules/Payment/Models/Transfer.php | 17 +
modules/Payment/Models/Wechat.php | 50 +
modules/Payment/Payment.php | 123 +
.../Providers/PaymentServiceProvider.php | 98 +
.../Providers/RouteServiceProvider.php | 63 +
modules/Payment/README.md | 46 +
modules/Payment/Routes/admin.php | 23 +
modules/Payment/Routes/api.php | 14 +
modules/Payment/Traits/WithPayments.php | 160 +
modules/Payment/composer.json | 23 +
modules/Payment/module.json | 15 +
modules/README.md | 81 +
modules/Storage/.gitignore | 4 +
modules/Storage/Config/config.php | 45 +
...0_00_000000_create_file_storages_table.php | 36 +
.../Http/Controllers/OssController.php | 133 +
.../Http/Controllers/StsController.php | 80 +
modules/Storage/Models/Storage.php | 12 +
.../Providers/RouteServiceProvider.php | 54 +
.../Providers/StorageServiceProvider.php | 73 +
modules/Storage/README.md | 70 +
modules/Storage/Routes/api.php | 12 +
modules/Storage/Storage.php | 34 +
modules/Storage/composer.json | 23 +
modules/Storage/module.json | 15 +
modules/Task/Config/config.php | 5 +
...00_000000_create_task_categories_table.php | 37 +
...00_00_00_000000_create_task_logs_table.php | 34 +
...0_00_00_000000_create_task_users_table.php | 36 +
.../0000_00_00_000000_create_tasks_table.php | 52 +
modules/Task/Facades/TaskFacade.php | 105 +
.../Controllers/Admin/CategoryController.php | 54 +
.../Http/Controllers/Admin/TaskController.php | 139 +
.../Http/Controllers/Admin/UserController.php | 50 +
.../Controllers/Api/CategoryController.php | 21 +
.../Http/Controllers/Api/TaskController.php | 39 +
.../Http/Controllers/Api/UserController.php | 76 +
.../Task/Http/Resources/CategoryResource.php | 21 +
.../Task/Http/Resources/TaskBaseResource.php | 27 +
modules/Task/Http/Resources/TaskResource.php | 36 +
modules/Task/Models/Category.php | 21 +
modules/Task/Models/Log.php | 19 +
modules/Task/Models/Task.php | 203 +
modules/Task/Models/Traits/BelongsToTask.php | 20 +
modules/Task/Models/Traits/TaskAttribute.php | 52 +
modules/Task/Models/User.php | 41 +
.../Task/Providers/RouteServiceProvider.php | 78 +
.../Task/Providers/TaskServiceProvider.php | 67 +
modules/Task/Routes/admin.php | 11 +
modules/Task/Routes/api.php | 21 +
modules/Task/Task.php | 81 +
modules/Task/Traits/HasTasks.php | 52 +
modules/Task/composer.json | 18 +
modules/Task/module.json | 15 +
modules/Task/package.json | 17 +
modules/User/.gitignore | 4 +
modules/User/.styleci.yml | 17 +
modules/User/Config/config.php | 98 +
modules/User/Config/identity.php | 19 +
.../Console/Commands/UserIdentityOver.php | 57 +
modules/User/Console/Kernel.php | 19 +
..._000000_create_user_account_logs_table.php | 41 +
...000000_create_user_account_rules_table.php | 39 +
...0_00_000000_create_user_accounts_table.php | 35 +
...00000_create_user_certifications_table.php | 37 +
...00_000000_create_user_identities_table.php | 53 +
...000000_create_user_identity_logs_table.php | 37 +
...0_00_000000_create_user_identity_table.php | 37 +
...0_00_00_000000_create_user_infos_table.php | 34 +
..._00_00_000000_create_user_orders_table.php | 42 +
..._00_000000_create_user_relations_table.php | 34 +
...000_create_user_service_identity_table.php | 33 +
...0_00_000000_create_user_services_table.php | 36 +
..._000000_create_user_sign_configs_table.php | 33 +
..._00_000000_create_user_sign_logs_table.php | 37 +
...0_00_00_000000_create_user_signs_table.php | 38 +
...0_000000_create_user_sms_configs_table.php | 38 +
..._000000_create_user_sms_gateways_table.php | 34 +
...000_00_00_000000_create_user_sms_table.php | 38 +
...00_create_user_socialite_configs_table.php | 34 +
...0_000000_create_user_wechat_apps_table.php | 33 +
..._000000_create_user_wechat_minis_table.php | 33 +
...000_create_user_wechat_officials_table.php | 36 +
...00_00_000000_create_user_wechats_table.php | 39 +
.../0000_00_00_000000_create_users_table.php | 35 +
...reate_user_certification_configs_table.php | 45 +
..._07_26_160901_create_user_stocks_table.php | 34 +
...02_143704_create_user_stock_logs_table.php | 35 +
...22_09_07_141907_create_user_logs_table.php | 34 +
..._170415_add_stock_to_user_orders_table.php | 32 +
...11009_add_scource_to_user_orders_table.php | 32 +
...28_142001_create_user_subscribes_table.php | 34 +
...01_create_user_wechat_subscribes_table.php | 34 +
.../UserCertificationConfigsSeeder.php | 23 +
.../User/Events/UserCertificationSuccess.php | 27 +
modules/User/Events/UserJoinIdentity.php | 38 +
modules/User/Events/UserLoginSuccess.php | 51 +
modules/User/Events/UserOrderPaid.php | 29 +
modules/User/Events/UserRemoveIdentity.php | 38 +
.../User/Events/UserSignReplenishSuccess.php | 25 +
modules/User/Events/UserSignSuccess.php | 25 +
modules/User/Events/UserUpdateIdentity.php | 42 +
modules/User/Facades/Calendar.php | 109 +
modules/User/Facades/Sms.php | 122 +
modules/User/Facades/UserSign.php | 308 +
.../Controllers/Admin/AccountController.php | 80 +
.../Admin/Actions/AddUserRemark.php | 32 +
.../Actions/Certification/ConfigPublish.php | 26 +
.../Admin/Actions/Certification/Replicate.php | 33 +
.../Admin/Actions/JoinIdentity.php | 69 +
.../Http/Controllers/Admin/Actions/Pay.php | 25 +
.../Http/Controllers/Admin/Actions/Refund.php | 25 +
.../Admin/Actions/RemoveIdentity.php | 48 +
.../Admin/Actions/UpdateRelation.php | 35 +
.../Admin/Actions/UserStatusInit.php | 27 +
.../Admin/Actions/UserStatusRefund.php | 27 +
.../Admin/CertificationConfigController.php | 68 +
.../Admin/CertificationController.php | 35 +
.../Controllers/Admin/GatewayController.php | 42 +
.../Admin/IdentitiesController.php | 226 +
.../Admin/IdentityLogController.php | 60 +
.../Controllers/Admin/IndexController.php | 156 +
.../Controllers/Admin/OrderController.php | 88 +
.../Http/Controllers/Admin/RuleController.php | 60 +
.../Admin/Selectable/Identities.php | 38 +
.../Controllers/Admin/ServiceController.php | 51 +
.../Admin/SignBannerController.php | 40 +
.../Admin/SignConfigController.php | 61 +
.../Controllers/Admin/SignTextController.php | 38 +
.../Controllers/Admin/SmsConfigController.php | 54 +
.../Http/Controllers/Admin/SmsController.php | 37 +
.../Controllers/Admin/StockController.php | 60 +
.../Controllers/Admin/StockLogController.php | 58 +
.../Controllers/Api/Account/LogController.php | 61 +
.../Controllers/Api/Auth/LoginController.php | 63 +
.../Api/Auth/RegisterController.php | 43 +
.../Controllers/Api/Auth/SmsController.php | 114 +
.../Controllers/Api/Auth/WechatController.php | 154 +
.../Api/Certification/IndexController.php | 90 +
.../Api/Favorite/IndexController.php | 45 +
.../Api/Identity/IndexController.php | 409 +
.../Http/Controllers/Api/IndexController.php | 200 +
.../Controllers/Api/Rank/IndexController.php | 131 +
.../Api/Relation/IndexController.php | 46 +
.../Api/Service/IndexController.php | 32 +
.../Api/Setting/IndexController.php | 96 +
.../Controllers/Api/Sign/IndexController.php | 363 +
.../Controllers/Api/Socialite/Controller.php | 14 +
.../Api/Socialite/UniCloudController.php | 93 +
.../Api/Socialite/WeChatController.php | 368 +
.../Controllers/Api/Stock/IndexController.php | 180 +
.../Http/Requests/CertificationRequest.php | 34 +
modules/User/Http/Requests/LoginRequest.php | 41 +
.../User/Http/Requests/LoginSmsRequest.php | 27 +
.../User/Http/Requests/RegisterRequest.php | 42 +
modules/User/Http/Requests/SmsRequest.php | 25 +
modules/User/Http/Requests/ThawOneRequest.php | 26 +
.../Http/Requests/UpdateUserInfoRequest.php | 26 +
.../User/Http/Requests/WechatMiniRequest.php | 28 +
.../Resources/Account/CrystalResource.php | 19 +
.../Account/UserAccountLogCollection.php | 20 +
.../Account/UserAccountLogResource.php | 30 +
.../Resources/Account/UserAccountResource.php | 22 +
.../Resources/Favorite/FavoriteCollection.php | 20 +
.../Resources/Favorite/FavoriteResource.php | 19 +
.../Http/Resources/IdentityMiddleResource.php | 27 +
.../User/Http/Resources/RelationResource.php | 24 +
.../Resources/Sign/SignBannerResource.php | 16 +
.../Http/Resources/Sign/SignTextResource.php | 18 +
.../Resources/UserCertificationResource.php | 28 +
.../Resources/UserIdentityBaseResource.php | 20 +
.../Http/Resources/UserIdentityResource.php | 103 +
.../Resources/UserIdentityRightsResource.php | 35 +
.../Http/Resources/UserInfoBaseResource.php | 26 +
.../User/Http/Resources/UserInfoResource.php | 60 +
.../Http/Resources/UserServiceResource.php | 20 +
.../User/Http/Resources/UserSignResource.php | 22 +
.../Http/Resources/UserWechatResource.php | 22 +
.../stock/UserStockLogCollection.php | 21 +
.../Resources/stock/UserStockLogResource.php | 22 +
.../User/Listeners/UserOrderPaidListeners.php | 51 +
.../User/Listeners/UserSignContinueDays.php | 32 +
modules/User/Models/Account.php | 175 +
modules/User/Models/AccountLog.php | 91 +
modules/User/Models/AccountRule.php | 43 +
modules/User/Models/Identity.php | 271 +
modules/User/Models/IdentityLog.php | 46 +
modules/User/Models/IdentityMiddle.php | 36 +
modules/User/Models/Order.php | 96 +
modules/User/Models/Relation.php | 75 +
modules/User/Models/Service.php | 34 +
modules/User/Models/Sign.php | 61 +
modules/User/Models/SignBanner.php | 16 +
modules/User/Models/SignConfig.php | 55 +
modules/User/Models/SignLog.php | 30 +
modules/User/Models/SignText.php | 13 +
modules/User/Models/Sms.php | 15 +
modules/User/Models/SmsConfig.php | 43 +
modules/User/Models/SmsGateway.php | 19 +
.../User/Models/Traits/CertificationTrait.php | 283 +
.../User/Models/Traits/HasIdentityScopes.php | 77 +
modules/User/Models/Traits/HasLog.php | 44 +
modules/User/Models/Traits/HasRelations.php | 192 +
modules/User/Models/Traits/HasSign.php | 77 +
modules/User/Models/Traits/HasStock.php | 153 +
modules/User/Models/Traits/HasVipOrders.php | 65 +
modules/User/Models/Traits/HasWechat.php | 56 +
modules/User/Models/Traits/JoinIdentity.php | 247 +
modules/User/Models/Traits/OrderActions.php | 107 +
.../User/Models/Traits/WechatAttribute.php | 77 +
modules/User/Models/User.php | 306 +
modules/User/Models/UserCertification.php | 27 +
.../User/Models/UserCertificationConfig.php | 48 +
modules/User/Models/UserInfo.php | 48 +
modules/User/Models/UserLog.php | 18 +
modules/User/Models/UserStock.php | 93 +
modules/User/Models/UserStockLog.php | 53 +
modules/User/Models/UserSubscribe.php | 11 +
modules/User/Models/UserWechat.php | 71 +
modules/User/Models/UserWechatApp.php | 16 +
modules/User/Models/UserWechatMini.php | 16 +
modules/User/Models/UserWechatOfficial.php | 19 +
modules/User/Models/UserWechatSubscribe.php | 10 +
.../User/Providers/EventServiceProvider.php | 33 +
.../User/Providers/RouteServiceProvider.php | 62 +
.../User/Providers/UserServiceProvider.php | 120 +
modules/User/README.md | 12 +
modules/User/Renderable/UserLog.php | 31 +
modules/User/Routes/admin.php | 33 +
modules/User/Routes/api.php | 188 +
modules/User/Rules/IdCardRule.php | 68 +
modules/User/Services/VerificationCode.php | 91 +
modules/User/Traits/BelongsToUser.php | 52 +
modules/User/Traits/RankDataTrait.php | 67 +
modules/User/Traits/WechatTrait.php | 180 +
modules/User/User.php | 192 +
modules/User/composer.json | 29 +
modules/User/docs/READMD.md | 13 +
modules/User/docs/用户鉴权.md | 2 +
modules/User/module.json | 18 +
modules/User/用户模块.apifox.json | 1 +
modules/pull-all.sh | 7 +
package.json | 18 +
parseComposer.php | 49 +
permission.sh | 9 +
public/.htaccess | 21 +
public/favicon.ico | 0
public/index.php | 55 +
public/robots.txt | 2 +
.../AdminLTE/bootstrap/css/bootstrap.min.css | 5 +
.../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes
.../fonts/glyphicons-halflings-regular.svg | 288 +
.../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes
.../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes
.../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes
.../AdminLTE/bootstrap/js/bootstrap.min.js | 7 +
.../AdminLTE/dist/css/AdminLTE.min.css | 7 +
.../dist/css/skins/_all-skins.min.css | 1 +
.../dist/css/skins/skin-black-light.min.css | 1 +
.../dist/css/skins/skin-black.min.css | 1 +
.../dist/css/skins/skin-blue-light.min.css | 1 +
.../AdminLTE/dist/css/skins/skin-blue.min.css | 1 +
.../dist/css/skins/skin-green-light.min.css | 1 +
.../dist/css/skins/skin-green.min.css | 1 +
.../dist/css/skins/skin-purple-light.min.css | 1 +
.../dist/css/skins/skin-purple.min.css | 1 +
.../dist/css/skins/skin-red-light.min.css | 1 +
.../AdminLTE/dist/css/skins/skin-red.min.css | 1 +
.../dist/css/skins/skin-yellow-light.min.css | 1 +
.../dist/css/skins/skin-yellow.min.css | 1 +
.../AdminLTE/dist/img/boxed-bg.jpg | Bin 0 -> 123770 bytes
.../AdminLTE/dist/img/boxed-bg.png | Bin 0 -> 43694 bytes
.../AdminLTE/dist/img/default-50x50.gif | Bin 0 -> 184 bytes
.../laravel-admin/AdminLTE/dist/img/icons.png | Bin 0 -> 1154 bytes
.../AdminLTE/dist/img/user2-160x160.jpg | Bin 0 -> 7070 bytes
.../laravel-admin/AdminLTE/dist/js/app.min.js | 13 +
.../bootstrap-slider/bootstrap-slider.js | 1167 +
.../plugins/bootstrap-slider/slider.css | 169 +
.../colorpicker/bootstrap-colorpicker.min.css | 9 +
.../colorpicker/bootstrap-colorpicker.min.js | 1 +
.../colorpicker/img/alpha-horizontal.png | Bin 0 -> 3635 bytes
.../plugins/colorpicker/img/alpha.png | Bin 0 -> 3271 bytes
.../colorpicker/img/hue-horizontal.png | Bin 0 -> 2837 bytes
.../AdminLTE/plugins/colorpicker/img/hue.png | Bin 0 -> 2972 bytes
.../plugins/colorpicker/img/saturation.png | Bin 0 -> 8817 bytes
.../AdminLTE/plugins/iCheck/all.css | 61 +
.../AdminLTE/plugins/iCheck/flat/_all.css | 560 +
.../AdminLTE/plugins/iCheck/flat/aero.css | 56 +
.../AdminLTE/plugins/iCheck/flat/aero.png | Bin 0 -> 1520 bytes
.../AdminLTE/plugins/iCheck/flat/aero@2x.png | Bin 0 -> 3218 bytes
.../AdminLTE/plugins/iCheck/flat/blue.css | 56 +
.../AdminLTE/plugins/iCheck/flat/blue.png | Bin 0 -> 1518 bytes
.../AdminLTE/plugins/iCheck/flat/blue@2x.png | Bin 0 -> 3217 bytes
.../AdminLTE/plugins/iCheck/flat/flat.css | 56 +
.../AdminLTE/plugins/iCheck/flat/flat.png | Bin 0 -> 1515 bytes
.../AdminLTE/plugins/iCheck/flat/flat@2x.png | Bin 0 -> 3217 bytes
.../AdminLTE/plugins/iCheck/flat/green.css | 56 +
.../AdminLTE/plugins/iCheck/flat/green.png | Bin 0 -> 1444 bytes
.../AdminLTE/plugins/iCheck/flat/green@2x.png | Bin 0 -> 3117 bytes
.../AdminLTE/plugins/iCheck/flat/grey.css | 56 +
.../AdminLTE/plugins/iCheck/flat/grey.png | Bin 0 -> 1516 bytes
.../AdminLTE/plugins/iCheck/flat/grey@2x.png | Bin 0 -> 3217 bytes
.../AdminLTE/plugins/iCheck/flat/orange.css | 56 +
.../AdminLTE/plugins/iCheck/flat/orange.png | Bin 0 -> 1518 bytes
.../plugins/iCheck/flat/orange@2x.png | Bin 0 -> 3275 bytes
.../AdminLTE/plugins/iCheck/flat/pink.css | 56 +
.../AdminLTE/plugins/iCheck/flat/pink.png | Bin 0 -> 1522 bytes
.../AdminLTE/plugins/iCheck/flat/pink@2x.png | Bin 0 -> 3218 bytes
.../AdminLTE/plugins/iCheck/flat/purple.css | 56 +
.../AdminLTE/plugins/iCheck/flat/purple.png | Bin 0 -> 1519 bytes
.../plugins/iCheck/flat/purple@2x.png | Bin 0 -> 3218 bytes
.../AdminLTE/plugins/iCheck/flat/red.css | 56 +
.../AdminLTE/plugins/iCheck/flat/red.png | Bin 0 -> 1516 bytes
.../AdminLTE/plugins/iCheck/flat/red@2x.png | Bin 0 -> 3276 bytes
.../AdminLTE/plugins/iCheck/flat/yellow.css | 56 +
.../AdminLTE/plugins/iCheck/flat/yellow.png | Bin 0 -> 1516 bytes
.../plugins/iCheck/flat/yellow@2x.png | Bin 0 -> 3216 bytes
.../plugins/iCheck/futurico/futurico.css | 56 +
.../plugins/iCheck/futurico/futurico.png | Bin 0 -> 1734 bytes
.../plugins/iCheck/futurico/futurico@2x.png | Bin 0 -> 3446 bytes
.../AdminLTE/plugins/iCheck/icheck.min.js | 10 +
.../AdminLTE/plugins/iCheck/line/_all.css | 740 +
.../AdminLTE/plugins/iCheck/line/aero.css | 74 +
.../AdminLTE/plugins/iCheck/line/blue.css | 74 +
.../AdminLTE/plugins/iCheck/line/green.css | 74 +
.../AdminLTE/plugins/iCheck/line/grey.css | 74 +
.../AdminLTE/plugins/iCheck/line/line.css | 74 +
.../AdminLTE/plugins/iCheck/line/line.png | Bin 0 -> 588 bytes
.../AdminLTE/plugins/iCheck/line/line@2x.png | Bin 0 -> 1073 bytes
.../AdminLTE/plugins/iCheck/line/orange.css | 74 +
.../AdminLTE/plugins/iCheck/line/pink.css | 74 +
.../AdminLTE/plugins/iCheck/line/purple.css | 74 +
.../AdminLTE/plugins/iCheck/line/red.css | 74 +
.../AdminLTE/plugins/iCheck/line/yellow.css | 74 +
.../AdminLTE/plugins/iCheck/minimal/_all.css | 557 +
.../AdminLTE/plugins/iCheck/minimal/aero.css | 62 +
.../AdminLTE/plugins/iCheck/minimal/aero.png | Bin 0 -> 1151 bytes
.../plugins/iCheck/minimal/aero@2x.png | Bin 0 -> 1409 bytes
.../AdminLTE/plugins/iCheck/minimal/blue.css | 62 +
.../AdminLTE/plugins/iCheck/minimal/blue.png | Bin 0 -> 1132 bytes
.../plugins/iCheck/minimal/blue@2x.png | Bin 0 -> 1410 bytes
.../AdminLTE/plugins/iCheck/minimal/green.css | 62 +
.../AdminLTE/plugins/iCheck/minimal/green.png | Bin 0 -> 1143 bytes
.../plugins/iCheck/minimal/green@2x.png | Bin 0 -> 1408 bytes
.../AdminLTE/plugins/iCheck/minimal/grey.css | 62 +
.../AdminLTE/plugins/iCheck/minimal/grey.png | Bin 0 -> 1142 bytes
.../plugins/iCheck/minimal/grey@2x.png | Bin 0 -> 1407 bytes
.../plugins/iCheck/minimal/minimal.css | 62 +
.../plugins/iCheck/minimal/minimal.png | Bin 0 -> 1114 bytes
.../plugins/iCheck/minimal/minimal@2x.png | Bin 0 -> 1410 bytes
.../plugins/iCheck/minimal/orange.css | 62 +
.../plugins/iCheck/minimal/orange.png | Bin 0 -> 1139 bytes
.../plugins/iCheck/minimal/orange@2x.png | Bin 0 -> 1407 bytes
.../AdminLTE/plugins/iCheck/minimal/pink.css | 62 +
.../AdminLTE/plugins/iCheck/minimal/pink.png | Bin 0 -> 1150 bytes
.../plugins/iCheck/minimal/pink@2x.png | Bin 0 -> 1409 bytes
.../plugins/iCheck/minimal/purple.css | 62 +
.../plugins/iCheck/minimal/purple.png | Bin 0 -> 1132 bytes
.../plugins/iCheck/minimal/purple@2x.png | Bin 0 -> 1409 bytes
.../AdminLTE/plugins/iCheck/minimal/red.css | 62 +
.../AdminLTE/plugins/iCheck/minimal/red.png | Bin 0 -> 1130 bytes
.../plugins/iCheck/minimal/red@2x.png | Bin 0 -> 1410 bytes
.../plugins/iCheck/minimal/yellow.css | 62 +
.../plugins/iCheck/minimal/yellow.png | Bin 0 -> 1135 bytes
.../plugins/iCheck/minimal/yellow@2x.png | Bin 0 -> 1406 bytes
.../plugins/iCheck/polaris/polaris.css | 62 +
.../plugins/iCheck/polaris/polaris.png | Bin 0 -> 6401 bytes
.../plugins/iCheck/polaris/polaris@2x.png | Bin 0 -> 16760 bytes
.../AdminLTE/plugins/iCheck/square/_all.css | 620 +
.../AdminLTE/plugins/iCheck/square/aero.css | 62 +
.../AdminLTE/plugins/iCheck/square/aero.png | Bin 0 -> 2167 bytes
.../plugins/iCheck/square/aero@2x.png | Bin 0 -> 4455 bytes
.../AdminLTE/plugins/iCheck/square/blue.css | 62 +
.../AdminLTE/plugins/iCheck/square/blue.png | Bin 0 -> 2185 bytes
.../plugins/iCheck/square/blue@2x.png | Bin 0 -> 4485 bytes
.../AdminLTE/plugins/iCheck/square/green.css | 62 +
.../AdminLTE/plugins/iCheck/square/green.png | Bin 0 -> 2193 bytes
.../plugins/iCheck/square/green@2x.png | Bin 0 -> 4498 bytes
.../AdminLTE/plugins/iCheck/square/grey.css | 62 +
.../AdminLTE/plugins/iCheck/square/grey.png | Bin 0 -> 2186 bytes
.../plugins/iCheck/square/grey@2x.png | Bin 0 -> 4483 bytes
.../AdminLTE/plugins/iCheck/square/orange.css | 62 +
.../AdminLTE/plugins/iCheck/square/orange.png | Bin 0 -> 2181 bytes
.../plugins/iCheck/square/orange@2x.png | Bin 0 -> 4474 bytes
.../AdminLTE/plugins/iCheck/square/pink.css | 62 +
.../AdminLTE/plugins/iCheck/square/pink.png | Bin 0 -> 2189 bytes
.../plugins/iCheck/square/pink@2x.png | Bin 0 -> 4479 bytes
.../AdminLTE/plugins/iCheck/square/purple.css | 62 +
.../AdminLTE/plugins/iCheck/square/purple.png | Bin 0 -> 2188 bytes
.../plugins/iCheck/square/purple@2x.png | Bin 0 -> 4501 bytes
.../AdminLTE/plugins/iCheck/square/red.css | 62 +
.../AdminLTE/plugins/iCheck/square/red.png | Bin 0 -> 2190 bytes
.../AdminLTE/plugins/iCheck/square/red@2x.png | Bin 0 -> 4490 bytes
.../AdminLTE/plugins/iCheck/square/square.css | 62 +
.../AdminLTE/plugins/iCheck/square/square.png | Bin 0 -> 2175 bytes
.../plugins/iCheck/square/square@2x.png | Bin 0 -> 4478 bytes
.../AdminLTE/plugins/iCheck/square/yellow.css | 62 +
.../AdminLTE/plugins/iCheck/square/yellow.png | Bin 0 -> 2131 bytes
.../plugins/iCheck/square/yellow@2x.png | Bin 0 -> 4385 bytes
.../input-mask/jquery.inputmask.bundle.min.js | 10 +
.../input-mask/phone-codes/phone-be.json | 45 +
.../input-mask/phone-codes/phone-codes.json | 294 +
.../plugins/input-mask/phone-codes/readme.txt | 1 +
.../ionslider/img/sprite-skin-flat.png | Bin 0 -> 352 bytes
.../ionslider/img/sprite-skin-nice.png | Bin 0 -> 1022 bytes
.../plugins/ionslider/ion.rangeSlider.css | 126 +
.../plugins/ionslider/ion.rangeSlider.min.js | 22 +
.../ionslider/ion.rangeSlider.skinFlat.css | 89 +
.../ionslider/ion.rangeSlider.skinNice.css | 85 +
.../plugins/jQuery/jQuery-2.1.4.min.js | 4 +
.../AdminLTE/plugins/select2/i18n/ar.js | 3 +
.../AdminLTE/plugins/select2/i18n/az.js | 3 +
.../AdminLTE/plugins/select2/i18n/bg.js | 3 +
.../AdminLTE/plugins/select2/i18n/ca.js | 3 +
.../AdminLTE/plugins/select2/i18n/cs.js | 3 +
.../AdminLTE/plugins/select2/i18n/da.js | 3 +
.../AdminLTE/plugins/select2/i18n/de.js | 3 +
.../AdminLTE/plugins/select2/i18n/el.js | 3 +
.../AdminLTE/plugins/select2/i18n/en.js | 3 +
.../AdminLTE/plugins/select2/i18n/es.js | 3 +
.../AdminLTE/plugins/select2/i18n/et.js | 3 +
.../AdminLTE/plugins/select2/i18n/eu.js | 3 +
.../AdminLTE/plugins/select2/i18n/fa.js | 3 +
.../AdminLTE/plugins/select2/i18n/fi.js | 3 +
.../AdminLTE/plugins/select2/i18n/fr.js | 3 +
.../AdminLTE/plugins/select2/i18n/gl.js | 3 +
.../AdminLTE/plugins/select2/i18n/he.js | 3 +
.../AdminLTE/plugins/select2/i18n/hi.js | 3 +
.../AdminLTE/plugins/select2/i18n/hr.js | 3 +
.../AdminLTE/plugins/select2/i18n/hu.js | 3 +
.../AdminLTE/plugins/select2/i18n/id.js | 3 +
.../AdminLTE/plugins/select2/i18n/is.js | 3 +
.../AdminLTE/plugins/select2/i18n/it.js | 3 +
.../AdminLTE/plugins/select2/i18n/ja.js | 3 +
.../AdminLTE/plugins/select2/i18n/km.js | 3 +
.../AdminLTE/plugins/select2/i18n/ko.js | 3 +
.../AdminLTE/plugins/select2/i18n/lt.js | 3 +
.../AdminLTE/plugins/select2/i18n/lv.js | 3 +
.../AdminLTE/plugins/select2/i18n/mk.js | 3 +
.../AdminLTE/plugins/select2/i18n/ms.js | 3 +
.../AdminLTE/plugins/select2/i18n/nb.js | 3 +
.../AdminLTE/plugins/select2/i18n/nl.js | 3 +
.../AdminLTE/plugins/select2/i18n/pl.js | 3 +
.../AdminLTE/plugins/select2/i18n/pt-BR.js | 3 +
.../AdminLTE/plugins/select2/i18n/pt.js | 3 +
.../AdminLTE/plugins/select2/i18n/ro.js | 3 +
.../AdminLTE/plugins/select2/i18n/ru.js | 3 +
.../AdminLTE/plugins/select2/i18n/sk.js | 3 +
.../AdminLTE/plugins/select2/i18n/sr-Cyrl.js | 3 +
.../AdminLTE/plugins/select2/i18n/sr.js | 3 +
.../AdminLTE/plugins/select2/i18n/sv.js | 3 +
.../AdminLTE/plugins/select2/i18n/th.js | 3 +
.../AdminLTE/plugins/select2/i18n/tr.js | 3 +
.../AdminLTE/plugins/select2/i18n/uk.js | 3 +
.../AdminLTE/plugins/select2/i18n/vi.js | 3 +
.../AdminLTE/plugins/select2/i18n/zh-CN.js | 3 +
.../AdminLTE/plugins/select2/i18n/zh-TW.js | 3 +
.../plugins/select2/select2.full.min.js | 3 +
.../AdminLTE/plugins/select2/select2.min.css | 1 +
.../slimScroll/jquery.slimscroll.min.js | 16 +
.../dist/bootstrap-duallistbox.min.css | 10 +
.../dist/jquery.bootstrap-duallistbox.min.js | 9 +
.../bootstrap-fileinput/css/fileinput.min.css | 12 +
.../bootstrap-fileinput/img/loading-sm.gif | Bin 0 -> 2670 bytes
.../bootstrap-fileinput/img/loading.gif | Bin 0 -> 847 bytes
.../bootstrap-fileinput/js/fileinput.min.js | 13 +
.../js/plugins/canvas-to-blob.js | 95 +
.../js/plugins/canvas-to-blob.min.js | 1 +
.../bootstrap-fileinput/js/plugins/piexif.js | 2471 ++
.../js/plugins/piexif.min.js | 1 +
.../bootstrap-fileinput/js/plugins/purify.js | 1009 +
.../js/plugins/purify.min.js | 1 +
.../js/plugins/sortable.js | 1590 +
.../js/plugins/sortable.min.js | 1 +
.../css/bootstrap3/bootstrap-switch.min.css | 22 +
.../dist/js/bootstrap-switch.min.js | 22 +
.../css/bootstrap-editable.css | 663 +
.../bootstrap3-editable/img/clear.png | Bin 0 -> 509 bytes
.../bootstrap3-editable/img/loading.gif | Bin 0 -> 1849 bytes
.../js/bootstrap-editable.min.js | 7 +
.../css/bootstrap-datetimepicker.min.css | 5 +
.../build/js/bootstrap-datetimepicker.min.js | 9 +
.../font-awesome/css/font-awesome.min.css | 4 +
.../font-awesome/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes
.../fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes
.../fonts/fontawesome-webfont.svg | 2671 ++
.../fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes
.../fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes
.../fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes
.../dist/css/fontawesome-iconpicker.min.css | 9 +
.../dist/js/fontawesome-iconpicker.min.js | 1 +
.../laravel-admin/google-fonts/fonts.css | 50 +
.../fonts/Source-Sans-Pro-Bold.ttf | Bin 0 -> 34908 bytes
.../fonts/Source-Sans-Pro-Bold.woff | Bin 0 -> 17264 bytes
.../fonts/Source-Sans-Pro-Bold.woff2 | Bin 0 -> 13892 bytes
.../fonts/Source-Sans-Pro-Italic.ttf | Bin 0 -> 33864 bytes
.../fonts/Source-Sans-Pro-Italic.woff | Bin 0 -> 17288 bytes
.../fonts/Source-Sans-Pro-Italic.woff2 | Bin 0 -> 13980 bytes
.../fonts/Source-Sans-Pro-Light-Italic.ttf | Bin 0 -> 33996 bytes
.../fonts/Source-Sans-Pro-Light-Italic.woff | Bin 0 -> 17304 bytes
.../fonts/Source-Sans-Pro-Light-Italic.woff2 | Bin 0 -> 13772 bytes
.../fonts/Source-Sans-Pro-Light.ttf | Bin 0 -> 35368 bytes
.../fonts/Source-Sans-Pro-Light.woff | Bin 0 -> 17540 bytes
.../fonts/Source-Sans-Pro-Light.woff2 | Bin 0 -> 14308 bytes
.../fonts/Source-Sans-Pro-Semibold-Italic.ttf | Bin 0 -> 33592 bytes
.../Source-Sans-Pro-Semibold-Italic.woff | Bin 0 -> 17168 bytes
.../Source-Sans-Pro-Semibold-Italic.woff2 | Bin 0 -> 13716 bytes
.../fonts/Source-Sans-Pro-Semibold.ttf | Bin 0 -> 34808 bytes
.../fonts/Source-Sans-Pro-Semibold.woff | Bin 0 -> 17380 bytes
.../fonts/Source-Sans-Pro-Semibold.woff2 | Bin 0 -> 13992 bytes
.../google-fonts/fonts/Source-Sans-Pro.eot | Bin 0 -> 16229 bytes
.../google-fonts/fonts/Source-Sans-Pro.svg | 345 +
.../google-fonts/fonts/Source-Sans-Pro.ttf | Bin 0 -> 35064 bytes
.../google-fonts/fonts/Source-Sans-Pro.woff | Bin 0 -> 17500 bytes
.../google-fonts/fonts/Source-Sans-Pro.woff2 | Bin 0 -> 14308 bytes
.../laravel-admin/jquery-pjax/jquery.pjax.js | 950 +
.../laravel-admin/laravel-admin.css | 85 +
.../laravel-admin/laravel-admin.js | 198 +
.../moment/min/moment-with-locales.min.js | 80 +
.../laravel-admin/nestable/jquery.nestable.js | 484 +
.../laravel-admin/nestable/nestable.css | 34 +
.../laravel-admin/nprogress/nprogress.css | 74 +
.../laravel-admin/nprogress/nprogress.js | 476 +
.../number-input/bootstrap-number-input.js | 88 +
.../sweetalert2/dist/sweetalert2.css | 1145 +
.../sweetalert2/dist/sweetalert2.min.js | 1 +
.../laravel-admin/toastr/build/toastr.min.css | 1 +
.../laravel-admin/toastr/build/toastr.min.js | 1 +
.../vendor/ueditor/dialogs/anchor/anchor.html | 40 +
.../ueditor/dialogs/attachment/attachment.css | 681 +
.../dialogs/attachment/attachment.html | 60 +
.../ueditor/dialogs/attachment/attachment.js | 754 +
.../attachment/fileTypeImages/icon_chm.gif | Bin 0 -> 923 bytes
.../fileTypeImages/icon_default.png | Bin 0 -> 841 bytes
.../attachment/fileTypeImages/icon_doc.gif | Bin 0 -> 1012 bytes
.../attachment/fileTypeImages/icon_exe.gif | Bin 0 -> 949 bytes
.../attachment/fileTypeImages/icon_jpg.gif | Bin 0 -> 950 bytes
.../attachment/fileTypeImages/icon_mp3.gif | Bin 0 -> 986 bytes
.../attachment/fileTypeImages/icon_mv.gif | Bin 0 -> 1001 bytes
.../attachment/fileTypeImages/icon_pdf.gif | Bin 0 -> 996 bytes
.../attachment/fileTypeImages/icon_ppt.gif | Bin 0 -> 1001 bytes
.../attachment/fileTypeImages/icon_psd.gif | Bin 0 -> 1009 bytes
.../attachment/fileTypeImages/icon_rar.gif | Bin 0 -> 1007 bytes
.../attachment/fileTypeImages/icon_txt.gif | Bin 0 -> 970 bytes
.../attachment/fileTypeImages/icon_xls.gif | Bin 0 -> 1005 bytes
.../dialogs/attachment/images/alignicon.gif | Bin 0 -> 1051 bytes
.../dialogs/attachment/images/alignicon.png | Bin 0 -> 3714 bytes
.../ueditor/dialogs/attachment/images/bg.png | Bin 0 -> 2810 bytes
.../dialogs/attachment/images/file-icons.gif | Bin 0 -> 20097 bytes
.../dialogs/attachment/images/file-icons.png | Bin 0 -> 44070 bytes
.../dialogs/attachment/images/icons.gif | Bin 0 -> 453 bytes
.../dialogs/attachment/images/icons.png | Bin 0 -> 2678 bytes
.../dialogs/attachment/images/image.png | Bin 0 -> 1672 bytes
.../dialogs/attachment/images/progress.png | Bin 0 -> 1269 bytes
.../dialogs/attachment/images/success.gif | Bin 0 -> 445 bytes
.../dialogs/attachment/images/success.png | Bin 0 -> 1621 bytes
.../ueditor/dialogs/background/background.css | 94 +
.../dialogs/background/background.html | 56 +
.../ueditor/dialogs/background/background.js | 376 +
.../ueditor/dialogs/background/images/bg.png | Bin 0 -> 2810 bytes
.../dialogs/background/images/success.png | Bin 0 -> 1621 bytes
.../ueditor/dialogs/charts/chart.config.js | 65 +
.../vendor/ueditor/dialogs/charts/charts.css | 165 +
.../vendor/ueditor/dialogs/charts/charts.html | 89 +
.../vendor/ueditor/dialogs/charts/charts.js | 519 +
.../ueditor/dialogs/charts/images/charts0.png | Bin 0 -> 25306 bytes
.../ueditor/dialogs/charts/images/charts1.png | Bin 0 -> 19263 bytes
.../ueditor/dialogs/charts/images/charts2.png | Bin 0 -> 23016 bytes
.../ueditor/dialogs/charts/images/charts3.png | Bin 0 -> 7823 bytes
.../ueditor/dialogs/charts/images/charts4.png | Bin 0 -> 8344 bytes
.../ueditor/dialogs/charts/images/charts5.png | Bin 0 -> 47084 bytes
.../ueditor/dialogs/emotion/emotion.css | 43 +
.../ueditor/dialogs/emotion/emotion.html | 54 +
.../vendor/ueditor/dialogs/emotion/emotion.js | 186 +
.../ueditor/dialogs/emotion/images/0.gif | Bin 0 -> 43 bytes
.../ueditor/dialogs/emotion/images/bface.gif | Bin 0 -> 27167 bytes
.../ueditor/dialogs/emotion/images/cface.gif | Bin 0 -> 8603 bytes
.../ueditor/dialogs/emotion/images/fface.gif | Bin 0 -> 18479 bytes
.../dialogs/emotion/images/jxface2.gif | Bin 0 -> 40706 bytes
.../emotion/images/neweditor-tab-bg.png | Bin 0 -> 216 bytes
.../ueditor/dialogs/emotion/images/tface.gif | Bin 0 -> 19805 bytes
.../ueditor/dialogs/emotion/images/wface.gif | Bin 0 -> 49850 bytes
.../ueditor/dialogs/emotion/images/yface.gif | Bin 0 -> 28409 bytes
public/vendor/ueditor/dialogs/gmap/gmap.html | 89 +
public/vendor/ueditor/dialogs/help/help.css | 7 +
public/vendor/ueditor/dialogs/help/help.html | 82 +
public/vendor/ueditor/dialogs/help/help.js | 56 +
public/vendor/ueditor/dialogs/image/image.css | 894 +
.../vendor/ueditor/dialogs/image/image.html | 120 +
public/vendor/ueditor/dialogs/image/image.js | 1139 +
.../dialogs/image/images/alignicon.jpg | Bin 0 -> 16101 bytes
.../ueditor/dialogs/image/images/bg.png | Bin 0 -> 2810 bytes
.../ueditor/dialogs/image/images/icons.gif | Bin 0 -> 453 bytes
.../ueditor/dialogs/image/images/icons.png | Bin 0 -> 2678 bytes
.../ueditor/dialogs/image/images/image.png | Bin 0 -> 1672 bytes
.../ueditor/dialogs/image/images/progress.png | Bin 0 -> 1269 bytes
.../ueditor/dialogs/image/images/success.gif | Bin 0 -> 445 bytes
.../ueditor/dialogs/image/images/success.png | Bin 0 -> 1621 bytes
.../dialogs/insertframe/insertframe.html | 98 +
public/vendor/ueditor/dialogs/internal.js | 81 +
public/vendor/ueditor/dialogs/link/link.html | 126 +
public/vendor/ueditor/dialogs/map/map.html | 135 +
public/vendor/ueditor/dialogs/map/show.html | 118 +
public/vendor/ueditor/dialogs/music/music.css | 30 +
.../vendor/ueditor/dialogs/music/music.html | 32 +
public/vendor/ueditor/dialogs/music/music.js | 192 +
.../ueditor/dialogs/preview/preview.html | 40 +
.../ueditor/dialogs/scrawl/images/addimg.png | Bin 0 -> 628 bytes
.../ueditor/dialogs/scrawl/images/brush.png | Bin 0 -> 608 bytes
.../ueditor/dialogs/scrawl/images/delimg.png | Bin 0 -> 516 bytes
.../ueditor/dialogs/scrawl/images/delimgH.png | Bin 0 -> 578 bytes
.../ueditor/dialogs/scrawl/images/empty.png | Bin 0 -> 519 bytes
.../ueditor/dialogs/scrawl/images/emptyH.png | Bin 0 -> 657 bytes
.../ueditor/dialogs/scrawl/images/eraser.png | Bin 0 -> 43271 bytes
.../ueditor/dialogs/scrawl/images/redo.png | Bin 0 -> 454 bytes
.../ueditor/dialogs/scrawl/images/redoH.png | Bin 0 -> 536 bytes
.../ueditor/dialogs/scrawl/images/scale.png | Bin 0 -> 435 bytes
.../ueditor/dialogs/scrawl/images/scaleH.png | Bin 0 -> 330 bytes
.../ueditor/dialogs/scrawl/images/size.png | Bin 0 -> 775 bytes
.../ueditor/dialogs/scrawl/images/undo.png | Bin 0 -> 444 bytes
.../ueditor/dialogs/scrawl/images/undoH.png | Bin 0 -> 511 bytes
.../vendor/ueditor/dialogs/scrawl/scrawl.css | 72 +
.../vendor/ueditor/dialogs/scrawl/scrawl.html | 95 +
.../vendor/ueditor/dialogs/scrawl/scrawl.js | 671 +
.../dialogs/searchreplace/searchreplace.html | 102 +
.../dialogs/searchreplace/searchreplace.js | 164 +
.../dialogs/snapscreen/snapscreen.html | 58 +
.../ueditor/dialogs/spechars/spechars.html | 21 +
.../ueditor/dialogs/spechars/spechars.js | 57 +
.../vendor/ueditor/dialogs/table/dragicon.png | Bin 0 -> 304 bytes
.../ueditor/dialogs/table/edittable.css | 84 +
.../ueditor/dialogs/table/edittable.html | 64 +
.../vendor/ueditor/dialogs/table/edittable.js | 237 +
.../vendor/ueditor/dialogs/table/edittd.html | 61 +
.../vendor/ueditor/dialogs/table/edittip.html | 33 +
.../vendor/ueditor/dialogs/template/config.js | 42 +
.../ueditor/dialogs/template/images/bg.gif | Bin 0 -> 84 bytes
.../ueditor/dialogs/template/images/pre0.png | Bin 0 -> 250 bytes
.../ueditor/dialogs/template/images/pre1.png | Bin 0 -> 291 bytes
.../ueditor/dialogs/template/images/pre2.png | Bin 0 -> 394 bytes
.../ueditor/dialogs/template/images/pre3.png | Bin 0 -> 485 bytes
.../ueditor/dialogs/template/images/pre4.png | Bin 0 -> 393 bytes
.../ueditor/dialogs/template/template.css | 18 +
.../ueditor/dialogs/template/template.html | 26 +
.../ueditor/dialogs/template/template.js | 53 +
.../ueditor/dialogs/video/images/bg.png | Bin 0 -> 2810 bytes
.../dialogs/video/images/center_focus.jpg | Bin 0 -> 11795 bytes
.../dialogs/video/images/file-icons.gif | Bin 0 -> 20097 bytes
.../dialogs/video/images/file-icons.png | Bin 0 -> 44070 bytes
.../ueditor/dialogs/video/images/icons.gif | Bin 0 -> 453 bytes
.../ueditor/dialogs/video/images/icons.png | Bin 0 -> 2678 bytes
.../ueditor/dialogs/video/images/image.png | Bin 0 -> 1672 bytes
.../dialogs/video/images/left_focus.jpg | Bin 0 -> 11423 bytes
.../dialogs/video/images/none_focus.jpg | Bin 0 -> 11546 bytes
.../ueditor/dialogs/video/images/progress.png | Bin 0 -> 1269 bytes
.../dialogs/video/images/right_focus.jpg | Bin 0 -> 11334 bytes
.../ueditor/dialogs/video/images/success.gif | Bin 0 -> 445 bytes
.../ueditor/dialogs/video/images/success.png | Bin 0 -> 1621 bytes
public/vendor/ueditor/dialogs/video/video.css | 635 +
.../vendor/ueditor/dialogs/video/video.html | 86 +
public/vendor/ueditor/dialogs/video/video.js | 789 +
.../vendor/ueditor/dialogs/webapp/webapp.html | 53 +
.../dialogs/wordimage/fClipboard_ueditor.swf | Bin 0 -> 1908 bytes
.../dialogs/wordimage/imageUploader.swf | Bin 0 -> 62857 bytes
.../ueditor/dialogs/wordimage/tangram.js | 1495 +
.../ueditor/dialogs/wordimage/wordimage.html | 111 +
.../ueditor/dialogs/wordimage/wordimage.js | 157 +
public/vendor/ueditor/index.html | 175 +
public/vendor/ueditor/lang/en/en.js | 684 +
.../ueditor/lang/en/images/addimage.png | Bin 0 -> 3373 bytes
.../lang/en/images/alldeletebtnhoverskin.png | Bin 0 -> 743 bytes
.../lang/en/images/alldeletebtnupskin.png | Bin 0 -> 743 bytes
.../ueditor/lang/en/images/background.png | Bin 0 -> 3854 bytes
.../vendor/ueditor/lang/en/images/button.png | Bin 0 -> 4929 bytes
public/vendor/ueditor/lang/en/images/copy.png | Bin 0 -> 1222 bytes
.../ueditor/lang/en/images/deletedisable.png | Bin 0 -> 649 bytes
.../ueditor/lang/en/images/deleteenable.png | Bin 0 -> 664 bytes
.../ueditor/lang/en/images/listbackground.png | Bin 0 -> 3750 bytes
.../ueditor/lang/en/images/localimage.png | Bin 0 -> 3083 bytes
.../vendor/ueditor/lang/en/images/music.png | Bin 0 -> 91561 bytes
.../lang/en/images/rotateleftdisable.png | Bin 0 -> 719 bytes
.../lang/en/images/rotateleftenable.png | Bin 0 -> 952 bytes
.../lang/en/images/rotaterightdisable.png | Bin 0 -> 754 bytes
.../lang/en/images/rotaterightenable.png | Bin 0 -> 1007 bytes
.../vendor/ueditor/lang/en/images/upload.png | Bin 0 -> 3941 bytes
.../vendor/ueditor/lang/zh-cn/images/copy.png | Bin 0 -> 4319 bytes
.../ueditor/lang/zh-cn/images/localimage.png | Bin 0 -> 6979 bytes
.../ueditor/lang/zh-cn/images/music.png | Bin 0 -> 23106 bytes
.../ueditor/lang/zh-cn/images/upload.png | Bin 0 -> 6608 bytes
public/vendor/ueditor/lang/zh-cn/zh-cn.js | 669 +
.../ueditor/themes/default/css/ueditor.css | 1903 +
.../themes/default/css/ueditor.min.css | 8 +
.../ueditor/themes/default/dialogbase.css | 100 +
.../ueditor/themes/default/images/anchor.gif | Bin 0 -> 184 bytes
.../ueditor/themes/default/images/arrow.png | Bin 0 -> 1173 bytes
.../themes/default/images/arrow_down.png | Bin 0 -> 1610 bytes
.../themes/default/images/arrow_up.png | Bin 0 -> 1649 bytes
.../themes/default/images/button-bg.gif | Bin 0 -> 1114 bytes
.../themes/default/images/cancelbutton.gif | Bin 0 -> 1227 bytes
.../ueditor/themes/default/images/charts.png | Bin 0 -> 518 bytes
.../themes/default/images/cursor_h.gif | Bin 0 -> 253 bytes
.../themes/default/images/cursor_h.png | Bin 0 -> 175 bytes
.../themes/default/images/cursor_v.gif | Bin 0 -> 370 bytes
.../themes/default/images/cursor_v.png | Bin 0 -> 177 bytes
.../themes/default/images/dialog-title-bg.png | Bin 0 -> 938 bytes
.../themes/default/images/filescan.png | Bin 0 -> 4282 bytes
.../themes/default/images/highlighted.gif | Bin 0 -> 111 bytes
.../themes/default/images/icons-all.gif | Bin 0 -> 3750 bytes
.../ueditor/themes/default/images/icons.gif | Bin 0 -> 20947 bytes
.../ueditor/themes/default/images/icons.png | Bin 0 -> 19694 bytes
.../themes/default/images/loaderror.png | Bin 0 -> 3209 bytes
.../ueditor/themes/default/images/loading.gif | Bin 0 -> 734 bytes
.../ueditor/themes/default/images/lock.gif | Bin 0 -> 1062 bytes
.../default/images/neweditor-tab-bg.png | Bin 0 -> 216 bytes
.../themes/default/images/pagebreak.gif | Bin 0 -> 54 bytes
.../ueditor/themes/default/images/scale.png | Bin 0 -> 167 bytes
.../themes/default/images/sortable.png | Bin 0 -> 2851 bytes
.../ueditor/themes/default/images/spacer.gif | Bin 0 -> 43 bytes
.../themes/default/images/sparator_v.png | Bin 0 -> 122 bytes
.../default/images/table-cell-align.png | Bin 0 -> 1847 bytes
.../default/images/tangram-colorpicker.png | Bin 0 -> 17355 bytes
.../themes/default/images/toolbar_bg.png | Bin 0 -> 170 bytes
.../themes/default/images/unhighlighted.gif | Bin 0 -> 111 bytes
.../ueditor/themes/default/images/upload.png | Bin 0 -> 6608 bytes
.../themes/default/images/videologo.gif | Bin 0 -> 1604 bytes
.../ueditor/themes/default/images/word.gif | Bin 0 -> 1019 bytes
.../themes/default/images/wordpaste.png | Bin 0 -> 6467 bytes
public/vendor/ueditor/themes/iframe.css | 1 +
.../third-party/SyntaxHighlighter/shCore.js | 3655 ++
.../SyntaxHighlighter/shCoreDefault.css | 1 +
.../third-party/codemirror/codemirror.css | 104 +
.../third-party/codemirror/codemirror.js | 3581 ++
.../highcharts/adapters/mootools-adapter.js | 13 +
.../adapters/mootools-adapter.src.js | 313 +
.../highcharts/adapters/prototype-adapter.js | 15 +
.../adapters/prototype-adapter.src.js | 316 +
.../adapters/standalone-framework.js | 17 +
.../adapters/standalone-framework.src.js | 583 +
.../third-party/highcharts/highcharts-more.js | 50 +
.../highcharts/highcharts-more.src.js | 2430 ++
.../third-party/highcharts/highcharts.js | 283 +
.../third-party/highcharts/highcharts.src.js | 16974 +++++++++
.../highcharts/modules/annotations.js | 7 +
.../highcharts/modules/annotations.src.js | 401 +
.../highcharts/modules/canvas-tools.js | 133 +
.../highcharts/modules/canvas-tools.src.js | 3113 ++
.../third-party/highcharts/modules/data.js | 17 +
.../highcharts/modules/data.src.js | 582 +
.../highcharts/modules/drilldown.js | 11 +
.../highcharts/modules/drilldown.src.js | 447 +
.../highcharts/modules/exporting.js | 22 +
.../highcharts/modules/exporting.src.js | 709 +
.../third-party/highcharts/modules/funnel.js | 12 +
.../highcharts/modules/funnel.src.js | 289 +
.../third-party/highcharts/modules/heatmap.js | 1 +
.../highcharts/modules/heatmap.src.js | 53 +
.../third-party/highcharts/modules/map.js | 27 +
.../third-party/highcharts/modules/map.src.js | 1002 +
.../highcharts/modules/no-data-to-display.js | 12 +
.../modules/no-data-to-display.src.js | 128 +
.../highcharts/themes/dark-blue.js | 254 +
.../highcharts/themes/dark-green.js | 255 +
.../third-party/highcharts/themes/gray.js | 257 +
.../third-party/highcharts/themes/grid.js | 103 +
.../third-party/highcharts/themes/skies.js | 89 +
.../ueditor/third-party/jquery-1.10.2.js | 9789 +++++
.../ueditor/third-party/jquery-1.10.2.min.js | 6 +
.../ueditor/third-party/jquery-1.10.2.min.map | 1 +
.../snapscreen/UEditorSnapscreen.exe | Bin 0 -> 519936 bytes
.../ueditor/third-party/video-js/font/vjs.eot | Bin 0 -> 3536 bytes
.../ueditor/third-party/video-js/font/vjs.svg | 65 +
.../ueditor/third-party/video-js/font/vjs.ttf | Bin 0 -> 3372 bytes
.../third-party/video-js/font/vjs.woff | Bin 0 -> 4228 bytes
.../ueditor/third-party/video-js/video-js.css | 766 +
.../third-party/video-js/video-js.min.css | 5 +
.../ueditor/third-party/video-js/video-js.swf | Bin 0 -> 16236 bytes
.../ueditor/third-party/video-js/video.dev.js | 7108 ++++
.../ueditor/third-party/video-js/video.js | 129 +
.../third-party/webuploader/Uploader.swf | Bin 0 -> 49382 bytes
.../third-party/webuploader/webuploader.css | 28 +
.../webuploader/webuploader.custom.js | 5670 +++
.../webuploader/webuploader.custom.min.js | 2 +
.../webuploader/webuploader.flashonly.js | 4176 +++
.../webuploader/webuploader.flashonly.min.js | 2 +
.../webuploader/webuploader.html5only.js | 5559 +++
.../webuploader/webuploader.html5only.min.js | 2 +
.../third-party/webuploader/webuploader.js | 6733 ++++
.../webuploader/webuploader.min.js | 2 +
.../webuploader/webuploader.withoutimage.js | 4593 +++
.../webuploader.withoutimage.min.js | 2 +
.../zeroclipboard/ZeroClipboard.js | 1256 +
.../zeroclipboard/ZeroClipboard.min.js | 9 +
.../zeroclipboard/ZeroClipboard.swf | Bin 0 -> 3933 bytes
public/vendor/ueditor/ueditor.all.js | 29431 ++++++++++++++++
public/vendor/ueditor/ueditor.all.min.js | 709 +
public/vendor/ueditor/ueditor.config.js | 413 +
public/vendor/ueditor/ueditor.parse.js | 1022 +
public/vendor/ueditor/ueditor.parse.min.js | 28 +
resources/css/app.css | 0
resources/js/app.js | 1 +
resources/js/bootstrap.js | 28 +
resources/lang/ar/admin.php | 105 +
resources/lang/az/admin.php | 102 +
resources/lang/bn/admin.php | 107 +
resources/lang/de/admin.php | 107 +
resources/lang/en/admin.php | 107 +
resources/lang/en/auth.php | 20 +
resources/lang/en/pagination.php | 19 +
resources/lang/en/passwords.php | 22 +
resources/lang/en/validation.php | 152 +
resources/lang/es/admin.php | 90 +
resources/lang/fa/admin.php | 110 +
resources/lang/fr/admin.php | 102 +
resources/lang/he/admin.php | 79 +
resources/lang/id/admin.php | 106 +
resources/lang/ja/admin.php | 102 +
resources/lang/ko/admin.php | 104 +
resources/lang/ms/admin.php | 102 +
resources/lang/nl/admin.php | 107 +
resources/lang/pl/admin.php | 78 +
resources/lang/pt-BR/admin.php | 102 +
resources/lang/pt/admin.php | 102 +
resources/lang/ru.json | 618 +
resources/lang/ru/admin.php | 107 +
resources/lang/ru/auth.php | 18 +
resources/lang/ru/pagination.php | 17 +
resources/lang/ru/passwords.php | 20 +
resources/lang/ru/ru.json | 713 +
resources/lang/ru/validation-attributes.php | 36 +
resources/lang/ru/validation-inline.php | 131 +
resources/lang/ru/validation.php | 162 +
resources/lang/tr/admin.php | 107 +
resources/lang/uk/admin.php | 106 +
resources/lang/ur/admin.php | 107 +
resources/lang/vendor/ueditor/en/upload.php | 34 +
.../lang/vendor/ueditor/zh_CN/upload.php | 31 +
.../lang/vendor/ueditor/zh_TW/upload.php | 33 +
resources/lang/zh-CN/admin.php | 106 +
resources/lang/zh-TW/admin.php | 105 +
resources/lang/zh_CN.json | 618 +
resources/lang/zh_CN/admin.php | 124 +
resources/lang/zh_CN/auth.php | 18 +
resources/lang/zh_CN/pagination.php | 17 +
resources/lang/zh_CN/passwords.php | 20 +
.../lang/zh_CN/validation-attributes.php | 35 +
resources/lang/zh_CN/validation-inline.php | 131 +
resources/lang/zh_CN/validation.php | 161 +
resources/lang/zh_CN/zh_CN.json | 713 +
resources/lang/zh_TW.json | 618 +
resources/lang/zh_TW/admin.php | 112 +
resources/lang/zh_TW/auth.php | 18 +
resources/lang/zh_TW/pagination.php | 17 +
resources/lang/zh_TW/passwords.php | 20 +
.../lang/zh_TW/validation-attributes.php | 35 +
resources/lang/zh_TW/validation-inline.php | 131 +
resources/lang/zh_TW/validation.php | 161 +
resources/lang/zh_TW/zh_TW.json | 713 +
resources/views/admin/auth/login.blade.php | 153 +
.../admin/dashboard/dependencies.blade.php | 31 +
.../admin/dashboard/environment.blade.php | 28 +
.../admin/dashboard/extensions.blade.php | 62 +
.../views/admin/dashboard/title.blade.php | 34 +
resources/views/admin/form/qr_code.blade.php | 7 +
.../views/vendor/ueditor/assets.blade.php | 7 +
resources/views/welcome.blade.php | 132 +
routes/api.php | 1 +
routes/channels.php | 1 +
routes/web.php | 18 +
server.php | 21 +
storage/app/.gitignore | 3 +
storage/app/public/.gitignore | 2 +
storage/framework/.gitignore | 9 +
storage/framework/cache/.gitignore | 3 +
storage/framework/cache/data/.gitignore | 2 +
storage/framework/sessions/.gitignore | 2 +
storage/framework/testing/.gitignore | 2 +
storage/framework/views/.gitignore | 2 +
storage/logs/.gitignore | 2 +
tests/CreatesApplication.php | 22 +
tests/Feature/ExampleTest.php | 21 +
tests/TestCase.php | 10 +
tests/Unit/ExampleTest.php | 18 +
webpack.mix.js | 17 +
1318 files changed, 210569 insertions(+)
create mode 100644 .editorconfig
create mode 100644 .env.example
create mode 100644 .gitattributes
create mode 100644 .gitignore
create mode 100644 .styleci.yml
create mode 100644 README.md
create mode 100644 app/Admin/Controllers/AuthController.php
create mode 100644 app/Admin/Controllers/Dashboard.php
create mode 100644 app/Admin/Controllers/HomeController.php
create mode 100644 app/Admin/Controllers/ModuleController.php
create mode 100644 app/Admin/Extensions/CleanCache.php
create mode 100644 app/Admin/Extensions/FormQrCode.php
create mode 100644 app/Admin/Routes/modules.php
create mode 100644 app/Admin/Traits/WithUploads.php
create mode 100644 app/Admin/bootstrap.php
create mode 100644 app/Admin/routes.php
create mode 100644 app/Api/Controllers/Controller.php
create mode 100644 app/Api/Controllers/IndexController.php
create mode 100644 app/Api/Resources/BaseCollection.php
create mode 100644 app/Api/bootstrap.php
create mode 100644 app/Api/routes.php
create mode 100644 app/Console/Kernel.php
create mode 100644 app/Exceptions/Handler.php
create mode 100644 app/Http/Controllers/Controller.php
create mode 100644 app/Http/Kernel.php
create mode 100644 app/Http/Middleware/Authenticate.php
create mode 100644 app/Http/Middleware/EncryptCookies.php
create mode 100644 app/Http/Middleware/PreventRequestsDuringMaintenance.php
create mode 100644 app/Http/Middleware/RedirectIfAuthenticated.php
create mode 100644 app/Http/Middleware/SetServerId.php
create mode 100644 app/Http/Middleware/TrimStrings.php
create mode 100644 app/Http/Middleware/TrustHosts.php
create mode 100644 app/Http/Middleware/TrustProxies.php
create mode 100644 app/Http/Middleware/VerifyCsrfToken.php
create mode 100644 app/Models/Google2FA.php
create mode 100644 app/Models/Model.php
create mode 100644 app/Models/Module.php
create mode 100644 app/Providers/AppServiceProvider.php
create mode 100644 app/Providers/AuthServiceProvider.php
create mode 100644 app/Providers/BroadcastServiceProvider.php
create mode 100644 app/Providers/EventServiceProvider.php
create mode 100644 app/Providers/RouteServiceProvider.php
create mode 100644 app/Rules/AdminG2FARule.php
create mode 100644 app/Rules/IdCardRule.php
create mode 100644 app/Scopes/OrderByIdDescScope.php
create mode 100644 app/Scopes/OrderByOrderAscScope.php
create mode 100644 app/Traits/HasClicks.php
create mode 100644 app/Traits/HasCovers.php
create mode 100644 app/Traits/HasStatus.php
create mode 100644 app/Traits/Macroable.php
create mode 100644 app/Traits/OrderByIdDesc.php
create mode 100644 app/Traits/OrderByOrderAsc.php
create mode 100644 app/Traits/WithGoogle2FA.php
create mode 100644 app/Traits/WithPosition.php
create mode 100644 artisan
create mode 100644 bootstrap/app.php
create mode 100644 bootstrap/cache/.gitignore
create mode 100644 composer.json
create mode 100644 composer.lock
create mode 100644 config/admin.php
create mode 100644 config/agent.php
create mode 100644 config/api.php
create mode 100644 config/app.php
create mode 100644 config/auth.php
create mode 100644 config/broadcasting.php
create mode 100644 config/cache.php
create mode 100644 config/cors.php
create mode 100644 config/database.php
create mode 100644 config/filesystems.php
create mode 100644 config/hashing.php
create mode 100644 config/image.php
create mode 100644 config/laravel-model-caching.php
create mode 100644 config/logging.php
create mode 100644 config/mail.php
create mode 100644 config/modules.php
create mode 100644 config/queue.php
create mode 100644 config/sanctum.php
create mode 100644 config/services.php
create mode 100644 config/session.php
create mode 100644 config/ueditor.php
create mode 100644 config/view.php
create mode 100644 database/.gitignore
create mode 100644 database/factories/UserFactory.php
create mode 100644 database/migrations/2016_01_04_173148_create_admin_tables.php
create mode 100644 database/migrations/2019_08_19_000000_create_failed_jobs_table.php
create mode 100644 database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
create mode 100644 database/migrations/2021_11_10_102248_create_jobs_table.php
create mode 100644 database/migrations/2022_11_30_204150_create_google2fas_table.php
create mode 100644 database/seeders/AdminPanelSeeder.php
create mode 100644 database/seeders/DatabaseSeeder.php
create mode 100644 docs/READMD.md
create mode 100644 docs/Trait doc.md
create mode 100644 init_composer.sh
create mode 100644 modules.json
create mode 100644 modules/.gitignore
create mode 100644 modules/Cms/.gitignore
create mode 100644 modules/Cms/Cms.php
create mode 100644 modules/Cms/Config/config.php
create mode 100644 modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_article_category_table.php
create mode 100644 modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_articles_table.php
create mode 100644 modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_categories_table.php
create mode 100644 modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_pages_table.php
create mode 100644 modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_taggable_table.php
create mode 100644 modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_tags_table.php.php
create mode 100644 modules/Cms/Database/Migrations/2021_09_29_142323_create_cms_storages_table.php
create mode 100644 modules/Cms/Database/Migrations/2022_10_20_095341_create_cms_logs_table.php
create mode 100644 modules/Cms/Http/Controllers/Admin/ArticleController.php
create mode 100644 modules/Cms/Http/Controllers/Admin/CategoryController.php
create mode 100644 modules/Cms/Http/Controllers/Admin/PageController.php
create mode 100644 modules/Cms/Http/Controllers/Admin/StorageController.php
create mode 100644 modules/Cms/Http/Controllers/Admin/TagController.php
create mode 100644 modules/Cms/Http/Controllers/Admin/VersionController.php
create mode 100644 modules/Cms/Http/Controllers/Api/ArticleController.php
create mode 100644 modules/Cms/Http/Controllers/Api/CategoryController.php
create mode 100644 modules/Cms/Http/Controllers/Api/PageController.php
create mode 100644 modules/Cms/Http/Controllers/Api/TagController.php
create mode 100644 modules/Cms/Http/Resources/ArticleBaseResource.php
create mode 100644 modules/Cms/Http/Resources/ArticleCollection.php
create mode 100644 modules/Cms/Http/Resources/ArticleResource.php
create mode 100644 modules/Cms/Http/Resources/CategoryBaseResource.php
create mode 100644 modules/Cms/Http/Resources/CategoryCollection.php
create mode 100644 modules/Cms/Http/Resources/CategoryResource.php
create mode 100644 modules/Cms/Http/Resources/PageCollection.php
create mode 100644 modules/Cms/Http/Resources/PageResource.php
create mode 100644 modules/Cms/Http/Resources/TagCollection.php
create mode 100644 modules/Cms/Http/Resources/TagResource.php
create mode 100644 modules/Cms/Models/Article.php
create mode 100644 modules/Cms/Models/ArticleCategory.php
create mode 100644 modules/Cms/Models/Category.php
create mode 100644 modules/Cms/Models/Log.php
create mode 100644 modules/Cms/Models/Page.php
create mode 100644 modules/Cms/Models/Storage.php
create mode 100644 modules/Cms/Models/Tag.php
create mode 100644 modules/Cms/Models/Taggable.php
create mode 100644 modules/Cms/Providers/CmsServiceProvider.php
create mode 100644 modules/Cms/Providers/RouteServiceProvider.php
create mode 100644 modules/Cms/README.md
create mode 100644 modules/Cms/Routes/admin.php
create mode 100644 modules/Cms/Routes/api.php
create mode 100644 modules/Cms/Traits/HasTags.php
create mode 100644 modules/Cms/composer.json
create mode 100644 modules/Cms/module.json
create mode 100644 modules/Configuration/.gitignore
create mode 100644 modules/Configuration/Actions/UpdateSource.php
create mode 100644 modules/Configuration/Actions/UpdateType.php
create mode 100644 modules/Configuration/Configuration.php
create mode 100644 modules/Configuration/Database/Migrations/2021_10_20_000000_create_configurations_table.php
create mode 100644 modules/Configuration/Http/Controllers/IndexController.php
create mode 100644 modules/Configuration/Models/Configuration.php
create mode 100644 modules/Configuration/Providers/ConfigurationServiceProvider.php
create mode 100644 modules/Configuration/Providers/RouteServiceProvider.php
create mode 100644 modules/Configuration/README.md
create mode 100644 modules/Configuration/Routes/admin.php
create mode 100644 modules/Configuration/composer.json
create mode 100644 modules/Configuration/module.json
create mode 100644 modules/Linker/.gitignore
create mode 100644 modules/Linker/Database/Migrations/0000_00_00_000000_create_linker_relations_table.php
create mode 100644 modules/Linker/Database/Migrations/0000_00_00_000000_create_linkers_table.php
create mode 100644 modules/Linker/Http/Controllers/IndexController.php
create mode 100644 modules/Linker/Linker.php
create mode 100644 modules/Linker/Models/Linker.php
create mode 100644 modules/Linker/Models/LinkerRelation.php
create mode 100644 modules/Linker/Providers/LinkerServiceProvider.php
create mode 100644 modules/Linker/Providers/RouteServiceProvider.php
create mode 100644 modules/Linker/README.md
create mode 100644 modules/Linker/Routes/admin.php
create mode 100644 modules/Linker/Traits/HasLinker.php
create mode 100644 modules/Linker/Traits/WithLinker.php
create mode 100644 modules/Linker/composer.json
create mode 100644 modules/Linker/module.json
create mode 100644 modules/Mall/.gitignore
create mode 100644 modules/Mall/Config/config.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_addresses_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_banners_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_brands_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_carts_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_categories_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_deliveries_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_delivery_rules_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_expresses_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_skus_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_spec_values_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_specs_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_tag_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_jobs_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_expresses_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_items_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_orders_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_reasons_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_expresses_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_items_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_logs_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refunds_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_regions_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_express_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_reasons_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_staffers_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shops_table.php
create mode 100644 modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_tags_table.php
create mode 100644 modules/Mall/Database/Migrations/2022_07_27_110430_create_mall_activities_table.php
create mode 100644 modules/Mall/Database/Migrations/2022_09_08_143557_add_channel_to_mall_orders_table.php
create mode 100644 modules/Mall/Database/Migrations/2022_09_08_144917_add_type_and_person_to_mall_orders_expresses_table.php
create mode 100644 modules/Mall/Database/Migrations/2022_09_30_093509_create_mall_videos_table.php
create mode 100644 modules/Mall/Database/Seeders/ExpressSeeder.php
create mode 100644 modules/Mall/Database/Seeders/RegionSeeder.php
create mode 100644 modules/Mall/Events/OrderCanceled.php
create mode 100644 modules/Mall/Events/OrderClosed.php
create mode 100644 modules/Mall/Events/OrderCompleted.php
create mode 100644 modules/Mall/Events/OrderCreated.php
create mode 100644 modules/Mall/Events/OrderDelivered.php
create mode 100644 modules/Mall/Events/OrderEvent.php
create mode 100644 modules/Mall/Events/OrderPaid.php
create mode 100644 modules/Mall/Events/OrderSigned.php
create mode 100644 modules/Mall/Events/RefundAgreed.php
create mode 100644 modules/Mall/Events/RefundApplied.php
create mode 100644 modules/Mall/Events/RefundCompleted.php
create mode 100644 modules/Mall/Events/RefundProcessed.php
create mode 100644 modules/Mall/Events/RefundRefused.php
create mode 100644 modules/Mall/Facades/Item.php
create mode 100644 modules/Mall/Facades/Order.php
create mode 100644 modules/Mall/Facades/Refund.php
create mode 100644 modules/Mall/Facades/RefundItem.php
create mode 100644 modules/Mall/Facades/Workflow.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Order/Audit.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Order/Pay.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Order/RefundAudit.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Order/RefundReturns.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Order/RefundSign.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Shop/Close.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Shop/Open.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Shop/Pass.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Action/Shop/Reject.php
create mode 100644 modules/Mall/Http/Controllers/Admin/ActivityController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/AddressController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/AjaxController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/BannerController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/BrandController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/CategoryController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/DashboardController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/DeliveryController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/DeliveryRuleController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/ExpressController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/GoodsController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/JobController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/OrderController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/ReasonController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/RefundController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/RegionController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Selectable/Expresses.php
create mode 100644 modules/Mall/Http/Controllers/Admin/Selectable/Reasons.php
create mode 100644 modules/Mall/Http/Controllers/Admin/ShopController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/SkuController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/SpecController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/SpecValueController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/StafferController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/StockOrderBySystemController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/StockOrderController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/TagController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/VersionController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/VideoController.php
create mode 100644 modules/Mall/Http/Controllers/Admin/WithShop.php
create mode 100644 modules/Mall/Http/Controllers/Agent/IndexController.php
create mode 100644 modules/Mall/Http/Controllers/Agent/ShopController.php
create mode 100644 modules/Mall/Http/Controllers/Api/ActivityController.php
create mode 100644 modules/Mall/Http/Controllers/Api/AddressController.php
create mode 100644 modules/Mall/Http/Controllers/Api/BannerController.php
create mode 100644 modules/Mall/Http/Controllers/Api/CartController.php
create mode 100644 modules/Mall/Http/Controllers/Api/CategoryController.php
create mode 100644 modules/Mall/Http/Controllers/Api/GoodsController.php
create mode 100644 modules/Mall/Http/Controllers/Api/IndexController.php
create mode 100644 modules/Mall/Http/Controllers/Api/OrderBuyController.php
create mode 100644 modules/Mall/Http/Controllers/Api/OrderController.php
create mode 100644 modules/Mall/Http/Controllers/Api/PayController.php
create mode 100644 modules/Mall/Http/Controllers/Api/RefundController.php
create mode 100644 modules/Mall/Http/Controllers/Api/ShopController.php
create mode 100644 modules/Mall/Http/Controllers/Api/ShopExtendController.php
create mode 100644 modules/Mall/Http/Controllers/Api/TagController.php
create mode 100644 modules/Mall/Http/Exporter/OrderExporter.php
create mode 100644 modules/Mall/Http/Exporter/OrderFromArray.php
create mode 100644 modules/Mall/Http/Middleware/Authenticate.php
create mode 100644 modules/Mall/Http/Middleware/ShopOwner.php
create mode 100644 modules/Mall/Http/Requests/Address/AddressRequest.php
create mode 100644 modules/Mall/Http/Requests/Cart/CartRequest.php
create mode 100644 modules/Mall/Http/Requests/OrderBuy/OrderBuyRequest.php
create mode 100644 modules/Mall/Http/Requests/Shop/ShopRequest.php
create mode 100644 modules/Mall/Http/Resources/Api/Activity/ActivityBaseResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Activity/ActivityCollection.php
create mode 100644 modules/Mall/Http/Resources/Api/Activity/ActivityResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Address/AddressResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Banner/BannerResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Category/CategoryBaseResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Category/CategoryResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/BrandResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/CartResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/DetailResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/GoodsCollection.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/GoodsResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/SkuBaseResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/SkuResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/SpecResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/SpecValueResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Goods/TagResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/LogisticResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/OrderCollection.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/OrderItemResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/OrderLogisticResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/OrderResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/RefundCollection.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/RefundItemsResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/RefundLogResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Order/RefundResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Region/RegionResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Shop/ExpressResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Shop/ShopBaseInfoResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Shop/ShopCollection.php
create mode 100644 modules/Mall/Http/Resources/Api/Shop/ShopEditResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Shop/ShopResource.php
create mode 100644 modules/Mall/Http/Resources/Api/Video/VideoResource.php
create mode 100644 modules/Mall/Jobs/CloseOrder.php
create mode 100644 modules/Mall/Listeners/OrderCreatedListener.php
create mode 100644 modules/Mall/Listeners/OrderPaidListener.php
create mode 100644 modules/Mall/Listeners/RefundAgreedListener.php
create mode 100644 modules/Mall/Listeners/RefundCompletedListener.php
create mode 100644 modules/Mall/Mall.php
create mode 100644 modules/Mall/Models/Activity.php
create mode 100644 modules/Mall/Models/Address.php
create mode 100644 modules/Mall/Models/Banner.php
create mode 100644 modules/Mall/Models/Brand.php
create mode 100644 modules/Mall/Models/Cart.php
create mode 100644 modules/Mall/Models/Category.php
create mode 100644 modules/Mall/Models/Delivery.php
create mode 100644 modules/Mall/Models/DeliveryRule.php
create mode 100644 modules/Mall/Models/Express.php
create mode 100644 modules/Mall/Models/Goods.php
create mode 100644 modules/Mall/Models/GoodsSku.php
create mode 100644 modules/Mall/Models/GoodsSpec.php
create mode 100644 modules/Mall/Models/GoodsSpecValue.php
create mode 100644 modules/Mall/Models/Job.php
create mode 100644 modules/Mall/Models/Order.php
create mode 100644 modules/Mall/Models/OrderExpress.php
create mode 100644 modules/Mall/Models/OrderItem.php
create mode 100644 modules/Mall/Models/Reason.php
create mode 100644 modules/Mall/Models/Refund.php
create mode 100644 modules/Mall/Models/RefundExpress.php
create mode 100644 modules/Mall/Models/RefundItem.php
create mode 100644 modules/Mall/Models/RefundLog.php
create mode 100644 modules/Mall/Models/Region.php
create mode 100644 modules/Mall/Models/Shop.php
create mode 100644 modules/Mall/Models/ShopStaffer.php
create mode 100644 modules/Mall/Models/Tag.php
create mode 100644 modules/Mall/Models/Traits/BelongsToOrder.php
create mode 100644 modules/Mall/Models/Traits/BelongsToRefund.php
create mode 100644 modules/Mall/Models/Traits/BelongsToShop.php
create mode 100644 modules/Mall/Models/Traits/GoodsAttribute.php
create mode 100644 modules/Mall/Models/Traits/HasRegion.php
create mode 100644 modules/Mall/Models/Traits/OrderActions.php
create mode 100644 modules/Mall/Models/Traits/OrderCando.php
create mode 100644 modules/Mall/Models/Traits/OrderScopes.php
create mode 100644 modules/Mall/Models/Traits/RefundActions.php
create mode 100644 modules/Mall/Models/Traits/ShopActions.php
create mode 100644 modules/Mall/Models/Video.php
create mode 100644 modules/Mall/Providers/EventServiceProvider.php
create mode 100644 modules/Mall/Providers/MallServiceProvider.php
create mode 100644 modules/Mall/Providers/RouteServiceProvider.php
create mode 100644 modules/Mall/Providers/WorkflowRegistry.php
create mode 100644 modules/Mall/README.md
create mode 100644 modules/Mall/Resources/views/admin/order/detail.blade.php
create mode 100644 modules/Mall/Resources/views/admin/stock_order/detail.blade.php
create mode 100644 modules/Mall/Routes/admin.php
create mode 100644 modules/Mall/Routes/agent.php
create mode 100644 modules/Mall/Routes/api.php
create mode 100644 modules/Mall/Rules/CityRule.php
create mode 100644 modules/Mall/Rules/DistrictRule.php
create mode 100644 modules/Mall/Rules/ProvinceRule.php
create mode 100644 modules/Mall/Traits/HasAddresses.php
create mode 100644 modules/Mall/Traits/HasCart.php
create mode 100644 modules/Mall/Traits/HasOrders.php
create mode 100644 modules/Mall/Traits/HasShop.php
create mode 100644 modules/Mall/Traits/WithWorkflow.php
create mode 100644 modules/Mall/composer.json
create mode 100644 modules/Mall/docs/README.md
create mode 100644 modules/Mall/docs/商品接口.md
create mode 100644 modules/Mall/module.json
create mode 100644 modules/Notification/Channels/AppChannel.php
create mode 100644 modules/Notification/Config/config.php
create mode 100644 modules/Notification/Database/Migrations/0000_00_00_000000_create_notification_templates_table.php
create mode 100644 modules/Notification/Http/Controllers/Admin/IndexController.php
create mode 100644 modules/Notification/Http/Controllers/Admin/SettingController.php
create mode 100644 modules/Notification/Http/Controllers/Api/IndexController.php
create mode 100644 modules/Notification/Http/Resources/NotificationCollection.php
create mode 100644 modules/Notification/Http/Resources/NotificationResource.php
create mode 100644 modules/Notification/Models/Template.php
create mode 100644 modules/Notification/Notification.php
create mode 100644 modules/Notification/Providers/NotificationServiceProvider.php
create mode 100644 modules/Notification/Providers/RouteServiceProvider.php
create mode 100644 modules/Notification/README.md
create mode 100644 modules/Notification/Routes/admin.php
create mode 100644 modules/Notification/Routes/api.php
create mode 100644 modules/Notification/composer.json
create mode 100644 modules/Notification/module.json
create mode 100644 modules/Payment/.gitignore
create mode 100644 modules/Payment/Config/config.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_alipays_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_bills_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_notifies_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_redpacks_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_refunds_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_settings_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_transfers_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_wechats_table.php
create mode 100644 modules/Payment/Database/Migrations/0000_00_00_000000_create_payments_table.php
create mode 100644 modules/Payment/Events/Paid.php
create mode 100644 modules/Payment/Facades/Pay.php
create mode 100644 modules/Payment/Http/Controllers/Admin/AlipayController.php
create mode 100644 modules/Payment/Http/Controllers/Admin/BillController.php
create mode 100644 modules/Payment/Http/Controllers/Admin/IndexController.php
create mode 100644 modules/Payment/Http/Controllers/Admin/RedpackController.php
create mode 100644 modules/Payment/Http/Controllers/Admin/RefundController.php
create mode 100644 modules/Payment/Http/Controllers/Admin/SettingController.php
create mode 100644 modules/Payment/Http/Controllers/Admin/TransferController.php
create mode 100644 modules/Payment/Http/Controllers/Admin/WechatController.php
create mode 100644 modules/Payment/Http/Controllers/Api/GatewayController.php
create mode 100644 modules/Payment/Http/Controllers/Api/NotifyController.php
create mode 100644 modules/Payment/Models/Alipay.php
create mode 100644 modules/Payment/Models/Bill.php
create mode 100644 modules/Payment/Models/Payment.php
create mode 100644 modules/Payment/Models/PaymentNotify.php
create mode 100644 modules/Payment/Models/Redpack.php
create mode 100644 modules/Payment/Models/Refund.php
create mode 100644 modules/Payment/Models/Setting.php
create mode 100644 modules/Payment/Models/Traits/WithConfig.php
create mode 100644 modules/Payment/Models/Transfer.php
create mode 100644 modules/Payment/Models/Wechat.php
create mode 100644 modules/Payment/Payment.php
create mode 100644 modules/Payment/Providers/PaymentServiceProvider.php
create mode 100644 modules/Payment/Providers/RouteServiceProvider.php
create mode 100644 modules/Payment/README.md
create mode 100644 modules/Payment/Routes/admin.php
create mode 100644 modules/Payment/Routes/api.php
create mode 100644 modules/Payment/Traits/WithPayments.php
create mode 100644 modules/Payment/composer.json
create mode 100644 modules/Payment/module.json
create mode 100644 modules/README.md
create mode 100644 modules/Storage/.gitignore
create mode 100644 modules/Storage/Config/config.php
create mode 100644 modules/Storage/Database/Migrations/0000_00_00_000000_create_file_storages_table.php
create mode 100644 modules/Storage/Http/Controllers/OssController.php
create mode 100644 modules/Storage/Http/Controllers/StsController.php
create mode 100644 modules/Storage/Models/Storage.php
create mode 100644 modules/Storage/Providers/RouteServiceProvider.php
create mode 100644 modules/Storage/Providers/StorageServiceProvider.php
create mode 100644 modules/Storage/README.md
create mode 100644 modules/Storage/Routes/api.php
create mode 100644 modules/Storage/Storage.php
create mode 100644 modules/Storage/composer.json
create mode 100644 modules/Storage/module.json
create mode 100644 modules/Task/Config/config.php
create mode 100644 modules/Task/Database/Migrations/0000_00_00_000000_create_task_categories_table.php
create mode 100644 modules/Task/Database/Migrations/0000_00_00_000000_create_task_logs_table.php
create mode 100644 modules/Task/Database/Migrations/0000_00_00_000000_create_task_users_table.php
create mode 100644 modules/Task/Database/Migrations/0000_00_00_000000_create_tasks_table.php
create mode 100644 modules/Task/Facades/TaskFacade.php
create mode 100644 modules/Task/Http/Controllers/Admin/CategoryController.php
create mode 100644 modules/Task/Http/Controllers/Admin/TaskController.php
create mode 100644 modules/Task/Http/Controllers/Admin/UserController.php
create mode 100644 modules/Task/Http/Controllers/Api/CategoryController.php
create mode 100644 modules/Task/Http/Controllers/Api/TaskController.php
create mode 100644 modules/Task/Http/Controllers/Api/UserController.php
create mode 100644 modules/Task/Http/Resources/CategoryResource.php
create mode 100644 modules/Task/Http/Resources/TaskBaseResource.php
create mode 100644 modules/Task/Http/Resources/TaskResource.php
create mode 100644 modules/Task/Models/Category.php
create mode 100644 modules/Task/Models/Log.php
create mode 100644 modules/Task/Models/Task.php
create mode 100644 modules/Task/Models/Traits/BelongsToTask.php
create mode 100644 modules/Task/Models/Traits/TaskAttribute.php
create mode 100644 modules/Task/Models/User.php
create mode 100644 modules/Task/Providers/RouteServiceProvider.php
create mode 100644 modules/Task/Providers/TaskServiceProvider.php
create mode 100644 modules/Task/Routes/admin.php
create mode 100644 modules/Task/Routes/api.php
create mode 100644 modules/Task/Task.php
create mode 100644 modules/Task/Traits/HasTasks.php
create mode 100644 modules/Task/composer.json
create mode 100644 modules/Task/module.json
create mode 100644 modules/Task/package.json
create mode 100644 modules/User/.gitignore
create mode 100644 modules/User/.styleci.yml
create mode 100644 modules/User/Config/config.php
create mode 100644 modules/User/Config/identity.php
create mode 100644 modules/User/Console/Commands/UserIdentityOver.php
create mode 100644 modules/User/Console/Kernel.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_account_logs_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_account_rules_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_accounts_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_certifications_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_logs_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_infos_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_relations_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_service_identity_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_services_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_configs_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_logs_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_configs_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_gateways_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_socialite_configs_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_apps_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_minis_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_officials_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_user_wechats_table.php
create mode 100644 modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php
create mode 100644 modules/User/Database/Migrations/2021_12_07_101850_create_user_certification_configs_table.php
create mode 100644 modules/User/Database/Migrations/2022_07_26_160901_create_user_stocks_table.php
create mode 100644 modules/User/Database/Migrations/2022_08_02_143704_create_user_stock_logs_table.php
create mode 100644 modules/User/Database/Migrations/2022_09_07_141907_create_user_logs_table.php
create mode 100644 modules/User/Database/Migrations/2022_09_07_170415_add_stock_to_user_orders_table.php
create mode 100644 modules/User/Database/Migrations/2022_09_08_111009_add_scource_to_user_orders_table.php
create mode 100644 modules/User/Database/Migrations/2022_09_28_142001_create_user_subscribes_table.php
create mode 100644 modules/User/Database/Migrations/2022_09_28_142001_create_user_wechat_subscribes_table.php
create mode 100644 modules/User/Database/Seeders/UserCertificationConfigsSeeder.php
create mode 100644 modules/User/Events/UserCertificationSuccess.php
create mode 100644 modules/User/Events/UserJoinIdentity.php
create mode 100644 modules/User/Events/UserLoginSuccess.php
create mode 100644 modules/User/Events/UserOrderPaid.php
create mode 100644 modules/User/Events/UserRemoveIdentity.php
create mode 100644 modules/User/Events/UserSignReplenishSuccess.php
create mode 100644 modules/User/Events/UserSignSuccess.php
create mode 100644 modules/User/Events/UserUpdateIdentity.php
create mode 100644 modules/User/Facades/Calendar.php
create mode 100644 modules/User/Facades/Sms.php
create mode 100644 modules/User/Facades/UserSign.php
create mode 100644 modules/User/Http/Controllers/Admin/AccountController.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/AddUserRemark.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/Certification/ConfigPublish.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/Certification/Replicate.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/Pay.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/Refund.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/RemoveIdentity.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/UpdateRelation.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/UserStatusInit.php
create mode 100644 modules/User/Http/Controllers/Admin/Actions/UserStatusRefund.php
create mode 100644 modules/User/Http/Controllers/Admin/CertificationConfigController.php
create mode 100644 modules/User/Http/Controllers/Admin/CertificationController.php
create mode 100644 modules/User/Http/Controllers/Admin/GatewayController.php
create mode 100644 modules/User/Http/Controllers/Admin/IdentitiesController.php
create mode 100644 modules/User/Http/Controllers/Admin/IdentityLogController.php
create mode 100644 modules/User/Http/Controllers/Admin/IndexController.php
create mode 100644 modules/User/Http/Controllers/Admin/OrderController.php
create mode 100644 modules/User/Http/Controllers/Admin/RuleController.php
create mode 100644 modules/User/Http/Controllers/Admin/Selectable/Identities.php
create mode 100644 modules/User/Http/Controllers/Admin/ServiceController.php
create mode 100644 modules/User/Http/Controllers/Admin/SignBannerController.php
create mode 100644 modules/User/Http/Controllers/Admin/SignConfigController.php
create mode 100644 modules/User/Http/Controllers/Admin/SignTextController.php
create mode 100644 modules/User/Http/Controllers/Admin/SmsConfigController.php
create mode 100644 modules/User/Http/Controllers/Admin/SmsController.php
create mode 100644 modules/User/Http/Controllers/Admin/StockController.php
create mode 100644 modules/User/Http/Controllers/Admin/StockLogController.php
create mode 100644 modules/User/Http/Controllers/Api/Account/LogController.php
create mode 100644 modules/User/Http/Controllers/Api/Auth/LoginController.php
create mode 100644 modules/User/Http/Controllers/Api/Auth/RegisterController.php
create mode 100644 modules/User/Http/Controllers/Api/Auth/SmsController.php
create mode 100644 modules/User/Http/Controllers/Api/Auth/WechatController.php
create mode 100644 modules/User/Http/Controllers/Api/Certification/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Favorite/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Identity/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Rank/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Relation/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Service/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Setting/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Sign/IndexController.php
create mode 100644 modules/User/Http/Controllers/Api/Socialite/Controller.php
create mode 100644 modules/User/Http/Controllers/Api/Socialite/UniCloudController.php
create mode 100644 modules/User/Http/Controllers/Api/Socialite/WeChatController.php
create mode 100644 modules/User/Http/Controllers/Api/Stock/IndexController.php
create mode 100644 modules/User/Http/Requests/CertificationRequest.php
create mode 100644 modules/User/Http/Requests/LoginRequest.php
create mode 100644 modules/User/Http/Requests/LoginSmsRequest.php
create mode 100644 modules/User/Http/Requests/RegisterRequest.php
create mode 100644 modules/User/Http/Requests/SmsRequest.php
create mode 100644 modules/User/Http/Requests/ThawOneRequest.php
create mode 100644 modules/User/Http/Requests/UpdateUserInfoRequest.php
create mode 100644 modules/User/Http/Requests/WechatMiniRequest.php
create mode 100644 modules/User/Http/Resources/Account/CrystalResource.php
create mode 100644 modules/User/Http/Resources/Account/UserAccountLogCollection.php
create mode 100644 modules/User/Http/Resources/Account/UserAccountLogResource.php
create mode 100644 modules/User/Http/Resources/Account/UserAccountResource.php
create mode 100644 modules/User/Http/Resources/Favorite/FavoriteCollection.php
create mode 100644 modules/User/Http/Resources/Favorite/FavoriteResource.php
create mode 100644 modules/User/Http/Resources/IdentityMiddleResource.php
create mode 100644 modules/User/Http/Resources/RelationResource.php
create mode 100644 modules/User/Http/Resources/Sign/SignBannerResource.php
create mode 100644 modules/User/Http/Resources/Sign/SignTextResource.php
create mode 100644 modules/User/Http/Resources/UserCertificationResource.php
create mode 100644 modules/User/Http/Resources/UserIdentityBaseResource.php
create mode 100644 modules/User/Http/Resources/UserIdentityResource.php
create mode 100644 modules/User/Http/Resources/UserIdentityRightsResource.php
create mode 100644 modules/User/Http/Resources/UserInfoBaseResource.php
create mode 100644 modules/User/Http/Resources/UserInfoResource.php
create mode 100644 modules/User/Http/Resources/UserServiceResource.php
create mode 100644 modules/User/Http/Resources/UserSignResource.php
create mode 100644 modules/User/Http/Resources/UserWechatResource.php
create mode 100644 modules/User/Http/Resources/stock/UserStockLogCollection.php
create mode 100644 modules/User/Http/Resources/stock/UserStockLogResource.php
create mode 100644 modules/User/Listeners/UserOrderPaidListeners.php
create mode 100644 modules/User/Listeners/UserSignContinueDays.php
create mode 100644 modules/User/Models/Account.php
create mode 100644 modules/User/Models/AccountLog.php
create mode 100644 modules/User/Models/AccountRule.php
create mode 100644 modules/User/Models/Identity.php
create mode 100644 modules/User/Models/IdentityLog.php
create mode 100644 modules/User/Models/IdentityMiddle.php
create mode 100644 modules/User/Models/Order.php
create mode 100644 modules/User/Models/Relation.php
create mode 100644 modules/User/Models/Service.php
create mode 100644 modules/User/Models/Sign.php
create mode 100644 modules/User/Models/SignBanner.php
create mode 100644 modules/User/Models/SignConfig.php
create mode 100644 modules/User/Models/SignLog.php
create mode 100644 modules/User/Models/SignText.php
create mode 100644 modules/User/Models/Sms.php
create mode 100644 modules/User/Models/SmsConfig.php
create mode 100644 modules/User/Models/SmsGateway.php
create mode 100644 modules/User/Models/Traits/CertificationTrait.php
create mode 100644 modules/User/Models/Traits/HasIdentityScopes.php
create mode 100644 modules/User/Models/Traits/HasLog.php
create mode 100644 modules/User/Models/Traits/HasRelations.php
create mode 100644 modules/User/Models/Traits/HasSign.php
create mode 100644 modules/User/Models/Traits/HasStock.php
create mode 100644 modules/User/Models/Traits/HasVipOrders.php
create mode 100644 modules/User/Models/Traits/HasWechat.php
create mode 100644 modules/User/Models/Traits/JoinIdentity.php
create mode 100644 modules/User/Models/Traits/OrderActions.php
create mode 100644 modules/User/Models/Traits/WechatAttribute.php
create mode 100644 modules/User/Models/User.php
create mode 100644 modules/User/Models/UserCertification.php
create mode 100644 modules/User/Models/UserCertificationConfig.php
create mode 100644 modules/User/Models/UserInfo.php
create mode 100644 modules/User/Models/UserLog.php
create mode 100644 modules/User/Models/UserStock.php
create mode 100644 modules/User/Models/UserStockLog.php
create mode 100644 modules/User/Models/UserSubscribe.php
create mode 100644 modules/User/Models/UserWechat.php
create mode 100644 modules/User/Models/UserWechatApp.php
create mode 100644 modules/User/Models/UserWechatMini.php
create mode 100644 modules/User/Models/UserWechatOfficial.php
create mode 100644 modules/User/Models/UserWechatSubscribe.php
create mode 100644 modules/User/Providers/EventServiceProvider.php
create mode 100644 modules/User/Providers/RouteServiceProvider.php
create mode 100644 modules/User/Providers/UserServiceProvider.php
create mode 100644 modules/User/README.md
create mode 100644 modules/User/Renderable/UserLog.php
create mode 100644 modules/User/Routes/admin.php
create mode 100644 modules/User/Routes/api.php
create mode 100644 modules/User/Rules/IdCardRule.php
create mode 100644 modules/User/Services/VerificationCode.php
create mode 100644 modules/User/Traits/BelongsToUser.php
create mode 100644 modules/User/Traits/RankDataTrait.php
create mode 100644 modules/User/Traits/WechatTrait.php
create mode 100644 modules/User/User.php
create mode 100644 modules/User/composer.json
create mode 100644 modules/User/docs/READMD.md
create mode 100644 modules/User/docs/用户鉴权.md
create mode 100644 modules/User/module.json
create mode 100644 modules/User/用户模块.apifox.json
create mode 100644 modules/pull-all.sh
create mode 100644 package.json
create mode 100644 parseComposer.php
create mode 100644 permission.sh
create mode 100644 public/.htaccess
create mode 100644 public/favicon.ico
create mode 100644 public/index.php
create mode 100644 public/robots.txt
create mode 100644 public/vendor/laravel-admin/AdminLTE/bootstrap/css/bootstrap.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.eot
create mode 100644 public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.svg
create mode 100644 public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.ttf
create mode 100644 public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff
create mode 100644 public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.woff2
create mode 100644 public/vendor/laravel-admin/AdminLTE/bootstrap/js/bootstrap.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/AdminLTE.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/_all-skins.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black-light.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-black.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue-light.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-blue.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green-light.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-green.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple-light.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-purple.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red-light.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-red.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow-light.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/css/skins/skin-yellow.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.jpg
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/img/boxed-bg.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/img/default-50x50.gif
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/img/icons.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg
create mode 100644 public/vendor/laravel-admin/AdminLTE/dist/js/app.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/bootstrap-slider.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/bootstrap-slider/slider.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/bootstrap-colorpicker.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/img/alpha-horizontal.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/img/alpha.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/img/hue-horizontal.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/img/hue.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/colorpicker/img/saturation.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/all.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/_all.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/aero.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/aero.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/aero@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/blue.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/blue.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/blue@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/flat.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/flat.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/flat@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/green.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/green.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/green@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/grey.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/grey.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/grey@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/orange.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/orange.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/orange@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/pink.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/pink.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/pink@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/purple.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/purple.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/purple@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/red.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/red.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/red@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/yellow.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/yellow.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/flat/yellow@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/futurico/futurico.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/futurico/futurico.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/futurico/futurico@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/icheck.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/_all.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/aero.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/blue.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/green.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/grey.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/line.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/line.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/line@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/orange.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/pink.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/purple.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/red.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/line/yellow.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/_all.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/aero.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/aero.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/aero@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/blue.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/blue.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/blue@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/green.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/green.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/green@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/grey.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/grey.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/grey@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/minimal.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/minimal.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/minimal@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/orange.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/orange.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/orange@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/pink.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/pink.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/pink@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/purple.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/purple.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/purple@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/red.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/red.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/red@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/yellow.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/yellow.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/minimal/yellow@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/polaris/polaris.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/polaris/polaris.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/polaris/polaris@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/_all.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/aero.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/aero.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/aero@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/blue.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/blue.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/blue@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/green.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/green.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/green@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/grey.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/grey.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/grey@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/orange.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/orange.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/orange@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/pink.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/pink.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/pink@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/purple.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/purple.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/purple@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/red.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/red.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/red@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/square.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/square.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/square@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/yellow.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/yellow.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/iCheck/square/yellow@2x.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/input-mask/jquery.inputmask.bundle.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/input-mask/phone-codes/phone-be.json
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/input-mask/phone-codes/phone-codes.json
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/input-mask/phone-codes/readme.txt
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/ionslider/img/sprite-skin-flat.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/ionslider/img/sprite-skin-nice.png
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/ionslider/ion.rangeSlider.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/ionslider/ion.rangeSlider.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/ionslider/ion.rangeSlider.skinFlat.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/ionslider/ion.rangeSlider.skinNice.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/jQuery/jQuery-2.1.4.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/ar.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/az.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/bg.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/ca.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/cs.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/da.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/de.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/el.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/en.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/es.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/et.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/eu.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/fa.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/fi.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/fr.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/gl.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/he.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/hi.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/hr.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/hu.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/id.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/is.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/it.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/ja.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/km.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/ko.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/lt.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/lv.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/mk.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/ms.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/nb.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/nl.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/pl.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/pt-BR.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/pt.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/ro.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/ru.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/sk.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/sr-Cyrl.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/sr.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/sv.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/th.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/tr.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/uk.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/vi.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/zh-CN.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/i18n/zh-TW.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/select2.full.min.js
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/select2/select2.min.css
create mode 100644 public/vendor/laravel-admin/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap-duallistbox/dist/bootstrap-duallistbox.min.css
create mode 100644 public/vendor/laravel-admin/bootstrap-duallistbox/dist/jquery.bootstrap-duallistbox.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/css/fileinput.min.css
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/img/loading-sm.gif
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/img/loading.gif
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/fileinput.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/canvas-to-blob.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/canvas-to-blob.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/piexif.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/piexif.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/purify.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/purify.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/sortable.js
create mode 100644 public/vendor/laravel-admin/bootstrap-fileinput/js/plugins/sortable.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min.css
create mode 100644 public/vendor/laravel-admin/bootstrap-switch/dist/js/bootstrap-switch.min.js
create mode 100644 public/vendor/laravel-admin/bootstrap3-editable/css/bootstrap-editable.css
create mode 100644 public/vendor/laravel-admin/bootstrap3-editable/img/clear.png
create mode 100644 public/vendor/laravel-admin/bootstrap3-editable/img/loading.gif
create mode 100644 public/vendor/laravel-admin/bootstrap3-editable/js/bootstrap-editable.min.js
create mode 100644 public/vendor/laravel-admin/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css
create mode 100644 public/vendor/laravel-admin/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js
create mode 100644 public/vendor/laravel-admin/font-awesome/css/font-awesome.min.css
create mode 100644 public/vendor/laravel-admin/font-awesome/fonts/FontAwesome.otf
create mode 100644 public/vendor/laravel-admin/font-awesome/fonts/fontawesome-webfont.eot
create mode 100644 public/vendor/laravel-admin/font-awesome/fonts/fontawesome-webfont.svg
create mode 100644 public/vendor/laravel-admin/font-awesome/fonts/fontawesome-webfont.ttf
create mode 100644 public/vendor/laravel-admin/font-awesome/fonts/fontawesome-webfont.woff
create mode 100644 public/vendor/laravel-admin/font-awesome/fonts/fontawesome-webfont.woff2
create mode 100644 public/vendor/laravel-admin/fontawesome-iconpicker/dist/css/fontawesome-iconpicker.min.css
create mode 100644 public/vendor/laravel-admin/fontawesome-iconpicker/dist/js/fontawesome-iconpicker.min.js
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts.css
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Bold.ttf
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Bold.woff
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Bold.woff2
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Italic.ttf
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Italic.woff
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Italic.woff2
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Light-Italic.ttf
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Light-Italic.woff
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Light-Italic.woff2
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Light.ttf
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Light.woff
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Light.woff2
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Semibold-Italic.ttf
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Semibold-Italic.woff
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Semibold-Italic.woff2
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Semibold.ttf
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Semibold.woff
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro-Semibold.woff2
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro.eot
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro.svg
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro.ttf
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro.woff
create mode 100644 public/vendor/laravel-admin/google-fonts/fonts/Source-Sans-Pro.woff2
create mode 100644 public/vendor/laravel-admin/jquery-pjax/jquery.pjax.js
create mode 100644 public/vendor/laravel-admin/laravel-admin/laravel-admin.css
create mode 100644 public/vendor/laravel-admin/laravel-admin/laravel-admin.js
create mode 100644 public/vendor/laravel-admin/moment/min/moment-with-locales.min.js
create mode 100644 public/vendor/laravel-admin/nestable/jquery.nestable.js
create mode 100644 public/vendor/laravel-admin/nestable/nestable.css
create mode 100644 public/vendor/laravel-admin/nprogress/nprogress.css
create mode 100644 public/vendor/laravel-admin/nprogress/nprogress.js
create mode 100644 public/vendor/laravel-admin/number-input/bootstrap-number-input.js
create mode 100644 public/vendor/laravel-admin/sweetalert2/dist/sweetalert2.css
create mode 100644 public/vendor/laravel-admin/sweetalert2/dist/sweetalert2.min.js
create mode 100644 public/vendor/laravel-admin/toastr/build/toastr.min.css
create mode 100644 public/vendor/laravel-admin/toastr/build/toastr.min.js
create mode 100644 public/vendor/ueditor/dialogs/anchor/anchor.html
create mode 100644 public/vendor/ueditor/dialogs/attachment/attachment.css
create mode 100644 public/vendor/ueditor/dialogs/attachment/attachment.html
create mode 100644 public/vendor/ueditor/dialogs/attachment/attachment.js
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_chm.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_default.png
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_doc.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_exe.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_jpg.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_mp3.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_mv.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_pdf.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_ppt.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_psd.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_rar.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_txt.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/fileTypeImages/icon_xls.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/alignicon.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/alignicon.png
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/bg.png
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/file-icons.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/file-icons.png
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/icons.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/icons.png
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/image.png
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/progress.png
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/success.gif
create mode 100644 public/vendor/ueditor/dialogs/attachment/images/success.png
create mode 100644 public/vendor/ueditor/dialogs/background/background.css
create mode 100644 public/vendor/ueditor/dialogs/background/background.html
create mode 100644 public/vendor/ueditor/dialogs/background/background.js
create mode 100644 public/vendor/ueditor/dialogs/background/images/bg.png
create mode 100644 public/vendor/ueditor/dialogs/background/images/success.png
create mode 100644 public/vendor/ueditor/dialogs/charts/chart.config.js
create mode 100644 public/vendor/ueditor/dialogs/charts/charts.css
create mode 100644 public/vendor/ueditor/dialogs/charts/charts.html
create mode 100644 public/vendor/ueditor/dialogs/charts/charts.js
create mode 100644 public/vendor/ueditor/dialogs/charts/images/charts0.png
create mode 100644 public/vendor/ueditor/dialogs/charts/images/charts1.png
create mode 100644 public/vendor/ueditor/dialogs/charts/images/charts2.png
create mode 100644 public/vendor/ueditor/dialogs/charts/images/charts3.png
create mode 100644 public/vendor/ueditor/dialogs/charts/images/charts4.png
create mode 100644 public/vendor/ueditor/dialogs/charts/images/charts5.png
create mode 100644 public/vendor/ueditor/dialogs/emotion/emotion.css
create mode 100644 public/vendor/ueditor/dialogs/emotion/emotion.html
create mode 100644 public/vendor/ueditor/dialogs/emotion/emotion.js
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/0.gif
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/bface.gif
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/cface.gif
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/fface.gif
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/jxface2.gif
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/neweditor-tab-bg.png
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/tface.gif
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/wface.gif
create mode 100644 public/vendor/ueditor/dialogs/emotion/images/yface.gif
create mode 100644 public/vendor/ueditor/dialogs/gmap/gmap.html
create mode 100644 public/vendor/ueditor/dialogs/help/help.css
create mode 100644 public/vendor/ueditor/dialogs/help/help.html
create mode 100644 public/vendor/ueditor/dialogs/help/help.js
create mode 100644 public/vendor/ueditor/dialogs/image/image.css
create mode 100644 public/vendor/ueditor/dialogs/image/image.html
create mode 100644 public/vendor/ueditor/dialogs/image/image.js
create mode 100644 public/vendor/ueditor/dialogs/image/images/alignicon.jpg
create mode 100644 public/vendor/ueditor/dialogs/image/images/bg.png
create mode 100644 public/vendor/ueditor/dialogs/image/images/icons.gif
create mode 100644 public/vendor/ueditor/dialogs/image/images/icons.png
create mode 100644 public/vendor/ueditor/dialogs/image/images/image.png
create mode 100644 public/vendor/ueditor/dialogs/image/images/progress.png
create mode 100644 public/vendor/ueditor/dialogs/image/images/success.gif
create mode 100644 public/vendor/ueditor/dialogs/image/images/success.png
create mode 100644 public/vendor/ueditor/dialogs/insertframe/insertframe.html
create mode 100644 public/vendor/ueditor/dialogs/internal.js
create mode 100644 public/vendor/ueditor/dialogs/link/link.html
create mode 100644 public/vendor/ueditor/dialogs/map/map.html
create mode 100644 public/vendor/ueditor/dialogs/map/show.html
create mode 100644 public/vendor/ueditor/dialogs/music/music.css
create mode 100644 public/vendor/ueditor/dialogs/music/music.html
create mode 100644 public/vendor/ueditor/dialogs/music/music.js
create mode 100644 public/vendor/ueditor/dialogs/preview/preview.html
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/addimg.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/brush.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/delimg.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/delimgH.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/empty.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/emptyH.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/eraser.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/redo.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/redoH.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/scale.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/scaleH.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/size.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/undo.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/images/undoH.png
create mode 100644 public/vendor/ueditor/dialogs/scrawl/scrawl.css
create mode 100644 public/vendor/ueditor/dialogs/scrawl/scrawl.html
create mode 100644 public/vendor/ueditor/dialogs/scrawl/scrawl.js
create mode 100644 public/vendor/ueditor/dialogs/searchreplace/searchreplace.html
create mode 100644 public/vendor/ueditor/dialogs/searchreplace/searchreplace.js
create mode 100644 public/vendor/ueditor/dialogs/snapscreen/snapscreen.html
create mode 100644 public/vendor/ueditor/dialogs/spechars/spechars.html
create mode 100644 public/vendor/ueditor/dialogs/spechars/spechars.js
create mode 100644 public/vendor/ueditor/dialogs/table/dragicon.png
create mode 100644 public/vendor/ueditor/dialogs/table/edittable.css
create mode 100644 public/vendor/ueditor/dialogs/table/edittable.html
create mode 100644 public/vendor/ueditor/dialogs/table/edittable.js
create mode 100644 public/vendor/ueditor/dialogs/table/edittd.html
create mode 100644 public/vendor/ueditor/dialogs/table/edittip.html
create mode 100644 public/vendor/ueditor/dialogs/template/config.js
create mode 100644 public/vendor/ueditor/dialogs/template/images/bg.gif
create mode 100644 public/vendor/ueditor/dialogs/template/images/pre0.png
create mode 100644 public/vendor/ueditor/dialogs/template/images/pre1.png
create mode 100644 public/vendor/ueditor/dialogs/template/images/pre2.png
create mode 100644 public/vendor/ueditor/dialogs/template/images/pre3.png
create mode 100644 public/vendor/ueditor/dialogs/template/images/pre4.png
create mode 100644 public/vendor/ueditor/dialogs/template/template.css
create mode 100644 public/vendor/ueditor/dialogs/template/template.html
create mode 100644 public/vendor/ueditor/dialogs/template/template.js
create mode 100644 public/vendor/ueditor/dialogs/video/images/bg.png
create mode 100644 public/vendor/ueditor/dialogs/video/images/center_focus.jpg
create mode 100644 public/vendor/ueditor/dialogs/video/images/file-icons.gif
create mode 100644 public/vendor/ueditor/dialogs/video/images/file-icons.png
create mode 100644 public/vendor/ueditor/dialogs/video/images/icons.gif
create mode 100644 public/vendor/ueditor/dialogs/video/images/icons.png
create mode 100644 public/vendor/ueditor/dialogs/video/images/image.png
create mode 100644 public/vendor/ueditor/dialogs/video/images/left_focus.jpg
create mode 100644 public/vendor/ueditor/dialogs/video/images/none_focus.jpg
create mode 100644 public/vendor/ueditor/dialogs/video/images/progress.png
create mode 100644 public/vendor/ueditor/dialogs/video/images/right_focus.jpg
create mode 100644 public/vendor/ueditor/dialogs/video/images/success.gif
create mode 100644 public/vendor/ueditor/dialogs/video/images/success.png
create mode 100644 public/vendor/ueditor/dialogs/video/video.css
create mode 100644 public/vendor/ueditor/dialogs/video/video.html
create mode 100644 public/vendor/ueditor/dialogs/video/video.js
create mode 100644 public/vendor/ueditor/dialogs/webapp/webapp.html
create mode 100644 public/vendor/ueditor/dialogs/wordimage/fClipboard_ueditor.swf
create mode 100644 public/vendor/ueditor/dialogs/wordimage/imageUploader.swf
create mode 100644 public/vendor/ueditor/dialogs/wordimage/tangram.js
create mode 100644 public/vendor/ueditor/dialogs/wordimage/wordimage.html
create mode 100644 public/vendor/ueditor/dialogs/wordimage/wordimage.js
create mode 100644 public/vendor/ueditor/index.html
create mode 100644 public/vendor/ueditor/lang/en/en.js
create mode 100644 public/vendor/ueditor/lang/en/images/addimage.png
create mode 100644 public/vendor/ueditor/lang/en/images/alldeletebtnhoverskin.png
create mode 100644 public/vendor/ueditor/lang/en/images/alldeletebtnupskin.png
create mode 100644 public/vendor/ueditor/lang/en/images/background.png
create mode 100644 public/vendor/ueditor/lang/en/images/button.png
create mode 100644 public/vendor/ueditor/lang/en/images/copy.png
create mode 100644 public/vendor/ueditor/lang/en/images/deletedisable.png
create mode 100644 public/vendor/ueditor/lang/en/images/deleteenable.png
create mode 100644 public/vendor/ueditor/lang/en/images/listbackground.png
create mode 100644 public/vendor/ueditor/lang/en/images/localimage.png
create mode 100644 public/vendor/ueditor/lang/en/images/music.png
create mode 100644 public/vendor/ueditor/lang/en/images/rotateleftdisable.png
create mode 100644 public/vendor/ueditor/lang/en/images/rotateleftenable.png
create mode 100644 public/vendor/ueditor/lang/en/images/rotaterightdisable.png
create mode 100644 public/vendor/ueditor/lang/en/images/rotaterightenable.png
create mode 100644 public/vendor/ueditor/lang/en/images/upload.png
create mode 100644 public/vendor/ueditor/lang/zh-cn/images/copy.png
create mode 100644 public/vendor/ueditor/lang/zh-cn/images/localimage.png
create mode 100644 public/vendor/ueditor/lang/zh-cn/images/music.png
create mode 100644 public/vendor/ueditor/lang/zh-cn/images/upload.png
create mode 100644 public/vendor/ueditor/lang/zh-cn/zh-cn.js
create mode 100644 public/vendor/ueditor/themes/default/css/ueditor.css
create mode 100644 public/vendor/ueditor/themes/default/css/ueditor.min.css
create mode 100644 public/vendor/ueditor/themes/default/dialogbase.css
create mode 100644 public/vendor/ueditor/themes/default/images/anchor.gif
create mode 100644 public/vendor/ueditor/themes/default/images/arrow.png
create mode 100644 public/vendor/ueditor/themes/default/images/arrow_down.png
create mode 100644 public/vendor/ueditor/themes/default/images/arrow_up.png
create mode 100644 public/vendor/ueditor/themes/default/images/button-bg.gif
create mode 100644 public/vendor/ueditor/themes/default/images/cancelbutton.gif
create mode 100644 public/vendor/ueditor/themes/default/images/charts.png
create mode 100644 public/vendor/ueditor/themes/default/images/cursor_h.gif
create mode 100644 public/vendor/ueditor/themes/default/images/cursor_h.png
create mode 100644 public/vendor/ueditor/themes/default/images/cursor_v.gif
create mode 100644 public/vendor/ueditor/themes/default/images/cursor_v.png
create mode 100644 public/vendor/ueditor/themes/default/images/dialog-title-bg.png
create mode 100644 public/vendor/ueditor/themes/default/images/filescan.png
create mode 100644 public/vendor/ueditor/themes/default/images/highlighted.gif
create mode 100644 public/vendor/ueditor/themes/default/images/icons-all.gif
create mode 100644 public/vendor/ueditor/themes/default/images/icons.gif
create mode 100644 public/vendor/ueditor/themes/default/images/icons.png
create mode 100644 public/vendor/ueditor/themes/default/images/loaderror.png
create mode 100644 public/vendor/ueditor/themes/default/images/loading.gif
create mode 100644 public/vendor/ueditor/themes/default/images/lock.gif
create mode 100644 public/vendor/ueditor/themes/default/images/neweditor-tab-bg.png
create mode 100644 public/vendor/ueditor/themes/default/images/pagebreak.gif
create mode 100644 public/vendor/ueditor/themes/default/images/scale.png
create mode 100644 public/vendor/ueditor/themes/default/images/sortable.png
create mode 100644 public/vendor/ueditor/themes/default/images/spacer.gif
create mode 100644 public/vendor/ueditor/themes/default/images/sparator_v.png
create mode 100644 public/vendor/ueditor/themes/default/images/table-cell-align.png
create mode 100644 public/vendor/ueditor/themes/default/images/tangram-colorpicker.png
create mode 100644 public/vendor/ueditor/themes/default/images/toolbar_bg.png
create mode 100644 public/vendor/ueditor/themes/default/images/unhighlighted.gif
create mode 100644 public/vendor/ueditor/themes/default/images/upload.png
create mode 100644 public/vendor/ueditor/themes/default/images/videologo.gif
create mode 100644 public/vendor/ueditor/themes/default/images/word.gif
create mode 100644 public/vendor/ueditor/themes/default/images/wordpaste.png
create mode 100644 public/vendor/ueditor/themes/iframe.css
create mode 100644 public/vendor/ueditor/third-party/SyntaxHighlighter/shCore.js
create mode 100644 public/vendor/ueditor/third-party/SyntaxHighlighter/shCoreDefault.css
create mode 100644 public/vendor/ueditor/third-party/codemirror/codemirror.css
create mode 100644 public/vendor/ueditor/third-party/codemirror/codemirror.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/adapters/mootools-adapter.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/adapters/mootools-adapter.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/adapters/prototype-adapter.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/adapters/prototype-adapter.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/adapters/standalone-framework.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/adapters/standalone-framework.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/highcharts-more.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/highcharts-more.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/highcharts.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/highcharts.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/annotations.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/annotations.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/canvas-tools.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/canvas-tools.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/data.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/data.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/drilldown.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/drilldown.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/exporting.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/exporting.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/funnel.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/funnel.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/heatmap.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/heatmap.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/map.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/map.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/no-data-to-display.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/modules/no-data-to-display.src.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/themes/dark-blue.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/themes/dark-green.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/themes/gray.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/themes/grid.js
create mode 100644 public/vendor/ueditor/third-party/highcharts/themes/skies.js
create mode 100644 public/vendor/ueditor/third-party/jquery-1.10.2.js
create mode 100644 public/vendor/ueditor/third-party/jquery-1.10.2.min.js
create mode 100644 public/vendor/ueditor/third-party/jquery-1.10.2.min.map
create mode 100644 public/vendor/ueditor/third-party/snapscreen/UEditorSnapscreen.exe
create mode 100644 public/vendor/ueditor/third-party/video-js/font/vjs.eot
create mode 100644 public/vendor/ueditor/third-party/video-js/font/vjs.svg
create mode 100644 public/vendor/ueditor/third-party/video-js/font/vjs.ttf
create mode 100644 public/vendor/ueditor/third-party/video-js/font/vjs.woff
create mode 100644 public/vendor/ueditor/third-party/video-js/video-js.css
create mode 100644 public/vendor/ueditor/third-party/video-js/video-js.min.css
create mode 100644 public/vendor/ueditor/third-party/video-js/video-js.swf
create mode 100644 public/vendor/ueditor/third-party/video-js/video.dev.js
create mode 100644 public/vendor/ueditor/third-party/video-js/video.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/Uploader.swf
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.css
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.custom.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.custom.min.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.flashonly.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.flashonly.min.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.html5only.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.html5only.min.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.min.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.withoutimage.js
create mode 100644 public/vendor/ueditor/third-party/webuploader/webuploader.withoutimage.min.js
create mode 100644 public/vendor/ueditor/third-party/zeroclipboard/ZeroClipboard.js
create mode 100644 public/vendor/ueditor/third-party/zeroclipboard/ZeroClipboard.min.js
create mode 100644 public/vendor/ueditor/third-party/zeroclipboard/ZeroClipboard.swf
create mode 100644 public/vendor/ueditor/ueditor.all.js
create mode 100644 public/vendor/ueditor/ueditor.all.min.js
create mode 100644 public/vendor/ueditor/ueditor.config.js
create mode 100644 public/vendor/ueditor/ueditor.parse.js
create mode 100644 public/vendor/ueditor/ueditor.parse.min.js
create mode 100644 resources/css/app.css
create mode 100644 resources/js/app.js
create mode 100644 resources/js/bootstrap.js
create mode 100644 resources/lang/ar/admin.php
create mode 100644 resources/lang/az/admin.php
create mode 100644 resources/lang/bn/admin.php
create mode 100644 resources/lang/de/admin.php
create mode 100644 resources/lang/en/admin.php
create mode 100644 resources/lang/en/auth.php
create mode 100644 resources/lang/en/pagination.php
create mode 100644 resources/lang/en/passwords.php
create mode 100644 resources/lang/en/validation.php
create mode 100644 resources/lang/es/admin.php
create mode 100644 resources/lang/fa/admin.php
create mode 100644 resources/lang/fr/admin.php
create mode 100644 resources/lang/he/admin.php
create mode 100644 resources/lang/id/admin.php
create mode 100644 resources/lang/ja/admin.php
create mode 100644 resources/lang/ko/admin.php
create mode 100644 resources/lang/ms/admin.php
create mode 100644 resources/lang/nl/admin.php
create mode 100644 resources/lang/pl/admin.php
create mode 100644 resources/lang/pt-BR/admin.php
create mode 100644 resources/lang/pt/admin.php
create mode 100644 resources/lang/ru.json
create mode 100644 resources/lang/ru/admin.php
create mode 100644 resources/lang/ru/auth.php
create mode 100644 resources/lang/ru/pagination.php
create mode 100644 resources/lang/ru/passwords.php
create mode 100644 resources/lang/ru/ru.json
create mode 100644 resources/lang/ru/validation-attributes.php
create mode 100644 resources/lang/ru/validation-inline.php
create mode 100644 resources/lang/ru/validation.php
create mode 100644 resources/lang/tr/admin.php
create mode 100644 resources/lang/uk/admin.php
create mode 100644 resources/lang/ur/admin.php
create mode 100644 resources/lang/vendor/ueditor/en/upload.php
create mode 100644 resources/lang/vendor/ueditor/zh_CN/upload.php
create mode 100644 resources/lang/vendor/ueditor/zh_TW/upload.php
create mode 100644 resources/lang/zh-CN/admin.php
create mode 100644 resources/lang/zh-TW/admin.php
create mode 100644 resources/lang/zh_CN.json
create mode 100644 resources/lang/zh_CN/admin.php
create mode 100644 resources/lang/zh_CN/auth.php
create mode 100644 resources/lang/zh_CN/pagination.php
create mode 100644 resources/lang/zh_CN/passwords.php
create mode 100644 resources/lang/zh_CN/validation-attributes.php
create mode 100644 resources/lang/zh_CN/validation-inline.php
create mode 100644 resources/lang/zh_CN/validation.php
create mode 100644 resources/lang/zh_CN/zh_CN.json
create mode 100644 resources/lang/zh_TW.json
create mode 100644 resources/lang/zh_TW/admin.php
create mode 100644 resources/lang/zh_TW/auth.php
create mode 100644 resources/lang/zh_TW/pagination.php
create mode 100644 resources/lang/zh_TW/passwords.php
create mode 100644 resources/lang/zh_TW/validation-attributes.php
create mode 100644 resources/lang/zh_TW/validation-inline.php
create mode 100644 resources/lang/zh_TW/validation.php
create mode 100644 resources/lang/zh_TW/zh_TW.json
create mode 100644 resources/views/admin/auth/login.blade.php
create mode 100644 resources/views/admin/dashboard/dependencies.blade.php
create mode 100644 resources/views/admin/dashboard/environment.blade.php
create mode 100644 resources/views/admin/dashboard/extensions.blade.php
create mode 100644 resources/views/admin/dashboard/title.blade.php
create mode 100644 resources/views/admin/form/qr_code.blade.php
create mode 100644 resources/views/vendor/ueditor/assets.blade.php
create mode 100644 resources/views/welcome.blade.php
create mode 100644 routes/api.php
create mode 100644 routes/channels.php
create mode 100644 routes/web.php
create mode 100644 server.php
create mode 100644 storage/app/.gitignore
create mode 100644 storage/app/public/.gitignore
create mode 100644 storage/framework/.gitignore
create mode 100644 storage/framework/cache/.gitignore
create mode 100644 storage/framework/cache/data/.gitignore
create mode 100644 storage/framework/sessions/.gitignore
create mode 100644 storage/framework/testing/.gitignore
create mode 100644 storage/framework/views/.gitignore
create mode 100644 storage/logs/.gitignore
create mode 100644 tests/CreatesApplication.php
create mode 100644 tests/Feature/ExampleTest.php
create mode 100644 tests/TestCase.php
create mode 100644 tests/Unit/ExampleTest.php
create mode 100644 webpack.mix.js
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..6537ca4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{yml,yaml}]
+indent_size = 2
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..0490e8d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,48 @@
+APP_NAME=Laravel
+APP_ENV=local
+APP_KEY=
+APP_DEBUG=true
+APP_URL=http://localhost
+
+SERVER_ID=001
+
+RATE_LIMITER=300
+
+ADMIN_HTTPS=false
+ADMIN_NAME="Uz.Tech 后台系统"
+ADMIN_TITLE='Uz.Tech'
+ADMIN_LOGO="UZ Tech"
+ADMIN_LOGO_MINI="U.T"
+
+LOG_CHANNEL=stack
+LOG_DEPRECATIONS_CHANNEL=null
+LOG_LEVEL=debug
+
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=laravel
+DB_USERNAME=root
+DB_PASSWORD=
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+QUEUE_CONNECTION=database
+SESSION_DRIVER=file
+SESSION_LIFETIME=120
+
+MEMCACHED_HOST=127.0.0.1
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+REDIS_DB=1
+REDIS_CACHE_DB=1
+
+FILESYSTEM_DRIVER=public
+OSS_ACCESS_KEY=
+OSS_SECRET_KEY=
+OSS_ENDPOINT=
+OSS_BUCKET=
+OSS_IS_CNAME=false
+OSS_CDN_HOST=
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..967315d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+* text=auto
+*.css linguist-vendored
+*.scss linguist-vendored
+*.js linguist-vendored
+CHANGELOG.md export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c407190
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+/node_modules
+/public/hot
+/public/storage
+/storage/*.key
+/vendor
+.env
+.env.backup
+.phpunit.result.cache
+docker-compose.override.yml
+Homestead.json
+Homestead.yaml
+npm-debug.log
+yarn-error.log
+.idea
+.DS_Store
+.vscode/
\ No newline at end of file
diff --git a/.styleci.yml b/.styleci.yml
new file mode 100644
index 0000000..9231873
--- /dev/null
+++ b/.styleci.yml
@@ -0,0 +1,13 @@
+php:
+ preset: laravel
+ disabled:
+ - no_unused_imports
+ finder:
+ not-name:
+ - index.php
+ - server.php
+js:
+ finder:
+ not-name:
+ - webpack.mix.js
+css: true
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d9ab369
--- /dev/null
+++ b/README.md
@@ -0,0 +1,123 @@
+

+
+# UzTech.Laravel
+
+> Jason.Chen , 为了构建一个健壮的底层。
+
+[TOC]
+
+## 1.安装
+
+```shell
+git pull http://git.yuzhankeji.cn/UzTech/laravel.git
+```
+
+```shell
+# 生成 laravel 密钥
+php artisan key:generate --ansi
+
+# 数据库迁移
+php artisan migrate
+
+# 数据填充(初始化基础数据)
+php artisan db:seed --class=AdminPanelSeeder
+```
+
+手动导入 initial.sql 后台数据基础文件
+
+## 包含的基础组件
+
+> OSS文件管理包
+>
+> "jasonc/laravel-filesystem-oss": "^3.0"
+>
+> 模块化工具
+>
+> "nwidart/laravel-modules": "^8.2"
+>
+> 模块安装工具
+>
+> "joshbrw/laravel-module-installer": "^2.0"
+>
+> API 管理工具
+>
+> "jasonc/api": "^3.3"
+>
+> 后台模板
+>
+> "encore/laravel-admin": "^1.8"
+
+## 模块安装
+
+> 模块安装完毕之后,要执行 composer dump-autoload
+
+### 1.
+
+## 模块开发
+
+### 1. 创建新模块
+
+```shell
+php artisan module:make ModuleName
+```
+
+### 2. 模块目录的一些规范
+
+```
+modules/
+ ├── Blog/
+ ├── Config/ 配置目录,使用 Config::get('module_name.') 调用
+ ├── Console/ 控制台命令
+ ├── Kernel.php 执行定时任务
+ ├── Database/ 数据库
+ ├── Migrations/ 数据表单迁移
+ ├── Seeders/ 数据填充文件
+ ├── Events/ 事件目录
+ ├── Http/
+ ├── Controllers/ 控制器
+ ├── Admin/
+ ├── Api/
+ ├── Middleware/ 中间件目录
+ ├── Requests/ 请求验证
+ ├── Resources API接口资源
+ ├── Jobs/ 队列
+ ├── Listeners/ 监听器
+ ├── Models/ 模型
+ ├── Traits/ 模块内部模型使用的traits
+ ├── Providers/
+ ├── BlogServiceProvider.php
+ ├── RouteServiceProvider.php
+ ├── Resources/ 静态资源目录
+ ├── assets/
+ ├── js/
+ ├── app.js
+ ├── sass/
+ ├── app.scss
+ ├── lang/
+ ├── views/
+ ├── Routes/ 路由
+ ├── admin.php
+ ├── api.php
+ ├── Service/ 内部服务
+ ├── Traits/ 对外的traits
+ ├── composer.json
+ ├── module.json
+ ├── README.md
+```
+### 3. 定时任务相关说明
+
+```php
+// 定时任务命令在ServiceProvider中加载
+if ($this->app->runningInConsole()) {
+ $this->commands([]);
+}
+// 定时任务的执行
+// 在模型文件夹 Console 建立 Kernel 类,类中 runCommand 执行定时任务
+$schedule->command(**)->everyMinute();
+```
+
+### 4. 查看全部路由,优化组件
+
+```shell
+php artisan route:pretty
+```
diff --git a/app/Admin/Controllers/AuthController.php b/app/Admin/Controllers/AuthController.php
new file mode 100644
index 0000000..3488577
--- /dev/null
+++ b/app/Admin/Controllers/AuthController.php
@@ -0,0 +1,10 @@
+ 'PHP version', 'value' => 'PHP/'.PHP_VERSION],
+ ['name' => 'Laravel version', 'value' => app()->version()],
+ ['name' => 'CGI', 'value' => php_sapi_name()],
+ ['name' => 'Uname', 'value' => php_uname()],
+ ['name' => 'Server', 'value' => Arr::get($_SERVER, 'SERVER_SOFTWARE')],
+
+ ['name' => 'Cache driver', 'value' => config('cache.default')],
+ ['name' => 'Session driver', 'value' => config('session.driver')],
+ ['name' => 'Queue driver', 'value' => config('queue.default')],
+
+ ['name' => 'Timezone', 'value' => config('app.timezone')],
+ ['name' => 'Locale', 'value' => config('app.locale')],
+ ['name' => 'Env', 'value' => config('app.env')],
+ ['name' => 'URL', 'value' => config('app.url')],
+ ];
+
+ return view('admin.dashboard.environment', compact('envs'));
+ }
+
+ /**
+ * @return Factory|View
+ */
+ public static function extensions()
+ {
+ $extensions = [
+ 'user' => [
+ 'name' => '用户模块',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-user-module',
+ 'icon' => 'users',
+ ],
+ 'chain' => [
+ 'name' => '区块链管理',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-chain-module',
+ 'icon' => 'chain',
+ ],
+ 'lottery' => [
+ 'name' => '抽奖模块',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-lottery-module',
+ 'icon' => 'gavel',
+ ],
+ 'task' => [
+ 'name' => '任务模块',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-task-module',
+ 'icon' => 'hourglass-end',
+ ],
+ 'cms' => [
+ 'name' => '内容管理',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-cms-module',
+ 'icon' => 'book',
+ ],
+ 'appversion' => [
+ 'name' => 'App版本',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-appversion-module',
+ 'icon' => 'apple',
+ ],
+ 'mall' => [
+ 'name' => '多用户商城',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-mall-module',
+ 'icon' => 'shopping-cart',
+ ],
+ 'payment' => [
+ 'name' => '支付模块',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-payment-module',
+ 'icon' => 'paypal',
+ ],
+ 'company' => [
+ 'name' => '企业管理',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-company-module',
+ 'icon' => 'black-tie',
+ ],
+ 'coupon' => [
+ 'name' => '优惠券模块',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-coupon-module',
+ 'icon' => 'qrcode',
+ ],
+ 'settlement' => [
+ 'name' => '结算模块',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-settlement-module',
+ 'icon' => 'sliders',
+ ],
+ 'configuration' => [
+ 'name' => '参数配置',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-configuration-module',
+ 'icon' => 'cogs',
+ ],
+ 'withdraw' => [
+ 'name' => '提现管理',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-withdraw-module',
+ 'icon' => 'clock-o',
+ ],
+ 'notification' => [
+ 'name' => '消息中心',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-notification-module',
+ 'icon' => 'envelope',
+ ],
+ 'linker' => [
+ 'name' => '链接管理',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-linker-module',
+ 'icon' => 'link',
+ ],
+ 'storage' => [
+ 'name' => '文件存储',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-storage-module',
+ 'icon' => 'file',
+ ],
+ 'omniform' => [
+ 'name' => '万能表单',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-omni-form-module',
+ 'icon' => 'wpforms',
+ ],
+ 'tao' => [
+ 'name' => '淘宝客',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-tao-module',
+ 'icon' => 'simplybuilt',
+ ],
+ 'market' => [
+ 'name' => '交易市场',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-market-module',
+ 'icon' => 'asl-interpreting',
+ ],
+ 'tokenmall' => [
+ 'name' => '区块链商城',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-token-mall-module',
+ 'icon' => 'flask',
+ ],
+ 'acme' => [
+ 'name' => 'SSL免费证书',
+ 'link' => 'https://git.yuzhankeji.cn/UzTech/laravel-acme-module',
+ 'icon' => 'html5',
+ ],
+ ];
+
+ foreach ($extensions as $key => &$extension) {
+ $module = Module::find($key);
+
+ if ($module) {
+ $extension['installed'] = (int) $module->isEnabled();
+ } else {
+ $extension['installed'] = 2;
+ }
+ }
+
+ return view('admin.dashboard.extensions', compact('extensions'));
+ }
+
+ /**
+ * @return string
+ */
+ public static function dependencies(): string
+ {
+ $json = file_get_contents(base_path('composer.json'));
+
+ $dependencies = json_decode($json, true)['require'];
+
+ return Admin::component('admin.dashboard.dependencies', compact('dependencies'));
+ }
+}
diff --git a/app/Admin/Controllers/HomeController.php b/app/Admin/Controllers/HomeController.php
new file mode 100644
index 0000000..65b757d
--- /dev/null
+++ b/app/Admin/Controllers/HomeController.php
@@ -0,0 +1,46 @@
+
+ * @param Content $content
+ * @return Content
+ */
+ public function index(Content $content): Content
+ {
+ if (config('app.debug')) {
+ return $content
+ ->title(__('admin.menu_titles.dashboard'))
+ ->description('Description...')
+ ->row(Dashboard::title())
+ ->row(function (Row $row) {
+ $row->column(4, function (Column $column) {
+ $column->append(Dashboard::environment());
+ });
+
+ $row->column(4, function (Column $column) {
+ $column->append(Dashboard::dependencies());
+ });
+
+ $row->column(4, function (Column $column) {
+ $column->append(Dashboard::extensions());
+ });
+ });
+ } else {
+ return $content
+ ->title(__('admin.menu_titles.dashboard'))
+ ->description('Description...');
+ }
+ }
+}
diff --git a/app/Admin/Controllers/ModuleController.php b/app/Admin/Controllers/ModuleController.php
new file mode 100644
index 0000000..3986f85
--- /dev/null
+++ b/app/Admin/Controllers/ModuleController.php
@@ -0,0 +1,105 @@
+
+ * @return Grid
+ */
+ protected function grid(): Grid
+ {
+ $grid = new Grid(new Module());
+
+ $grid->disableBatchActions();
+ $grid->disableFilter();
+ $grid->disableCreateButton();
+ $grid->disableActions();
+
+ $grid->column('name', '模块名称');
+ $grid->column('alias', '别名');
+ $grid->column('version', '版本');
+ $grid->column('author', '作者');
+ $grid->column('description', '模块简介');
+ $grid->column('enabled', '状态')->bool();
+ $grid->column('id', '操作')->display(function () {
+ if ($this->enabled) {
+ return sprintf('%s', route('admin.module.disable', $this->name), '禁用');
+ } else {
+ return sprintf('%s', route('admin.module.enable', $this->name), '启用');
+ }
+ });
+
+ return $grid;
+ }
+
+ /**
+ * Notes : 禁用模块
+ *
+ * @Date : 2021/3/11 1:13 下午
+ * @Author :
+ * @param string $name
+ * @return RedirectResponse
+ */
+ public function disable(string $name): RedirectResponse
+ {
+ try {
+ $module = ModuleManager::find($name);
+
+ $module->disable();
+
+ $class = sprintf('\\%s\\%s\\%s', config('modules.namespace'), $module->getName(), $module->getName());
+
+ if (class_exists($class)) {
+ call_user_func([$class, 'uninstall']);
+ }
+
+ admin_success('Success', $name . '模块禁用成功');
+ } catch (\Exception $exception) {
+ admin_error('Error', $exception->getMessage());
+ }
+
+ return back();
+ }
+
+ /**
+ * Notes : 启用模块
+ *
+ * @Date : 2021/3/11 1:13 下午
+ * @Author :
+ * @param string $name
+ * @return RedirectResponse
+ */
+ public function enable(string $name): RedirectResponse
+ {
+ try {
+ $module = ModuleManager::find($name);
+
+ $module->enable();
+
+ $class = sprintf('\\%s\\%s\\%s', config('modules.namespace'), $module->getName(), $module->getName());
+
+ if (class_exists($class)) {
+ call_user_func([$class, 'install']);
+ }
+
+ admin_success('Success', $name . '模块启用成功');
+ } catch (\Exception $exception) {
+ admin_error('Error', $exception->getMessage());
+ }
+
+ return back();
+ }
+}
diff --git a/app/Admin/Extensions/CleanCache.php b/app/Admin/Extensions/CleanCache.php
new file mode 100644
index 0000000..2ec3bbb
--- /dev/null
+++ b/app/Admin/Extensions/CleanCache.php
@@ -0,0 +1,35 @@
+response()->success('清理完成')->refresh();
+ }
+
+ public function dialog()
+ {
+ $this->confirm('确认清除缓存');
+ }
+
+ public function html(): string
+ {
+ return <<
+
+
+
+
+HTML;
+ }
+}
diff --git a/app/Admin/Extensions/FormQrCode.php b/app/Admin/Extensions/FormQrCode.php
new file mode 100644
index 0000000..73a4c2d
--- /dev/null
+++ b/app/Admin/Extensions/FormQrCode.php
@@ -0,0 +1,21 @@
+value = $google2fa->getQRCodeUrl(
+ $this->data['username'],
+ $this->data['name'],
+ $this->data['g2fa_secret']
+ );
+ return parent::render();
+ }
+}
\ No newline at end of file
diff --git a/app/Admin/Routes/modules.php b/app/Admin/Routes/modules.php
new file mode 100644
index 0000000..92206a0
--- /dev/null
+++ b/app/Admin/Routes/modules.php
@@ -0,0 +1,12 @@
+ 'modules',
+], function (Router $router) {
+ $router->get('', 'ModuleController@index');
+ $router->get('{name}/disable', 'ModuleController@disable')->name('module.disable');
+ $router->get('{name}/enable', 'ModuleController@enable')->name('module.enable');
+});
diff --git a/app/Admin/Traits/WithUploads.php b/app/Admin/Traits/WithUploads.php
new file mode 100644
index 0000000..bd6d14d
--- /dev/null
+++ b/app/Admin/Traits/WithUploads.php
@@ -0,0 +1,94 @@
+
+ * @param Form $form
+ * @param string $filed
+ * @param string $label
+ * @return Image
+ */
+ public function cover(Renderable $form, string $filed = 'cover', string $label = '封面图片'): Image
+ {
+ $cover = $form->image($filed, $label)
+ ->move('images/'.date('Y/m/d'))
+ ->uniqueName()
+ ->removable()
+ ->retainable();
+
+ $waterConfig = config('admin.image_water');
+
+ if (! empty($waterConfig)) {
+ $cover->insert(...$waterConfig);
+ }
+
+ $coverThumb = config('admin.cover_thumb');
+
+ if (! empty($coverThumb)) {
+ $cover->thumbnail($coverThumb);
+ }
+ return $cover;
+ }
+
+ /**
+ * Notes : 统一的多图上传
+ *
+ * @Date : 2021/4/25 2:06 下午
+ * @Author :
+ * @param Form $form
+ * @param string $filed
+ * @param string $label
+ * @return MultipleImage
+ */
+ public function pictures(Renderable $form, string $filed = 'pictures', string $label = '多图轮播'): MultipleImage
+ {
+ $pictures = $form->multipleImage($filed, $label)
+ ->move('images/'.date('Y/m/d'))
+ ->uniqueName()
+ ->removable()
+ ->retainable();
+
+ // 多图如果开启排序的话,会报错,暂时没由解决办法 ->sortable()
+ $waterConfig = config('admin.image_water');
+
+ if (! empty($waterConfig)) {
+ $pictures->insert(...$waterConfig);
+ }
+ return $pictures;
+ }
+
+ /**
+ * Notes : 统一的附件上传
+ *
+ * @Date : 2021/4/25 3:03 下午
+ * @Author :
+ * @param Renderable $form
+ * @param string $filed
+ * @param string $label
+ * @return MultipleFile
+ */
+ public function attachments(
+ Renderable $form,
+ string $filed = 'attachments',
+ string $label = '内容附件'
+ ): MultipleFile {
+ return $form->multipleFile($filed, $label)
+ ->move('attachments/'.date('Y/m/d'))
+ ->uniqueName()
+ ->removable()
+ ->retainable()
+ ->sortable();
+ }
+}
diff --git a/app/Admin/bootstrap.php b/app/Admin/bootstrap.php
new file mode 100644
index 0000000..1061fc8
--- /dev/null
+++ b/app/Admin/bootstrap.php
@@ -0,0 +1,51 @@
+right(new CleanCache());
+ $navbar->right(new Navbar\Fullscreen());
+});
+
+Form::init(function (Form $form) {
+ $form->disableEditingCheck();
+ $form->disableCreatingCheck();
+ $form->disableViewCheck();
+
+ $form->tools(function (Form\Tools $tools) {
+ $tools->disableView();
+ // $tools->disableDelete();
+ // $tools->disableList();
+ });
+});
+
+Show::init(function (Show $show) {
+ $show->panel()
+ ->tools(function ($tools) {
+ // $tools->disableEdit();
+ // $tools->disableList();
+ $tools->disableDelete();
+ });;
+});
+
+Grid::init(function (Grid $grid) {
+ $grid->disableExport();
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableView();
+ });
+ $grid->disableBatchActions();
+ $grid->filter(function ($filter) {
+ $filter->disableIdFilter();
+ });
+ // $grid->expandFilter();
+});
+
+Form::extend('qrCode', FormQrCode::class);
diff --git a/app/Admin/routes.php b/app/Admin/routes.php
new file mode 100644
index 0000000..8bb201f
--- /dev/null
+++ b/app/Admin/routes.php
@@ -0,0 +1,20 @@
+ config('admin.route.prefix'),
+ 'namespace' => config('admin.route.namespace'),
+ 'middleware' => config('admin.route.middleware'),
+ 'as' => config('admin.route.as'),
+], function (Router $router) {
+ $router->get('/', 'HomeController@index')->name('home');
+
+ foreach (glob(admin_path('Routes') . '/*.php') as $routeFile) {
+ require $routeFile;
+ }
+});
diff --git a/app/Api/Controllers/Controller.php b/app/Api/Controllers/Controller.php
new file mode 100644
index 0000000..1f36c80
--- /dev/null
+++ b/app/Api/Controllers/Controller.php
@@ -0,0 +1,11 @@
+success('Json Api is ready');
+ }
+}
diff --git a/app/Api/Resources/BaseCollection.php b/app/Api/Resources/BaseCollection.php
new file mode 100644
index 0000000..ec7eb12
--- /dev/null
+++ b/app/Api/Resources/BaseCollection.php
@@ -0,0 +1,19 @@
+ $this->currentPage(),
+ 'total_page' => $this->lastPage(),
+ 'per_page' => $this->perPage(),
+ 'has_more' => $this->hasMorePages(),
+ 'total' => $this->total(),
+ ];
+ }
+}
diff --git a/app/Api/bootstrap.php b/app/Api/bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/app/Api/bootstrap.php
@@ -0,0 +1 @@
+name('home');
+
+/**
+ * 分组的路由示例
+ */
+Route::group([
+// 'as' => '',
+// 'domain' => '',
+// 'middleware' => '',
+// 'namespace' => '',
+// 'prefix' => '',
+], function (Router $router) {
+ $router->get('/', 'IndexController@index')->name('home');
+});
+
+/**
+ * 文件夹引入的示例
+ */
+//foreach (glob(app_path('Api/Routes') . '/*.php') as $routeFile) {
+// require $routeFile;
+//}
\ No newline at end of file
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
new file mode 100644
index 0000000..e538ff9
--- /dev/null
+++ b/app/Console/Kernel.php
@@ -0,0 +1,52 @@
+command('inspire')->hourly();
+ $this->modules($schedule);
+ }
+
+ /**
+ * 要执行任务的位置增加Console\Kernel类
+ * 类中 runCommand(Schedule $schedule)
+ * 模型中的command在模型的ServiceProvider自行注册
+ *
+ * @param Schedule $schedule
+ */
+ protected function modules(Schedule $schedule)
+ {
+ $data = ModuleManager::toCollection();
+ foreach ($data as $name => $module) {
+ $class = "\\Modules\\$name\\Console\\Kernel";
+ if (class_exists($class)) {
+ $runKernel = resolve($class);
+ $runKernel->runCommand($schedule);
+ }
+ }
+ }
+
+ /**
+ * Register the commands for the application.
+ *
+ * @return void
+ */
+ protected function commands()
+ {
+ // $this->load(__DIR__.'/Commands');
+ }
+
+}
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
new file mode 100644
index 0000000..39a19e0
--- /dev/null
+++ b/app/Exceptions/Handler.php
@@ -0,0 +1,44 @@
+>
+ */
+ protected $dontReport = [
+ //
+ ];
+
+ /**
+ * A list of the inputs that are never flashed for validation exceptions.
+ *
+ * @var array
+ */
+ protected $dontFlash = [
+ 'current_password',
+ 'password',
+ 'password_confirmation',
+ ];
+
+ /**
+ * Register the exception handling callbacks for the application.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->reportable(function (Throwable $e) {
+ //
+ });
+ }
+}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
new file mode 100644
index 0000000..0671c57
--- /dev/null
+++ b/app/Http/Controllers/Controller.php
@@ -0,0 +1,28 @@
+
+ * @param string $token
+ * @return array
+ */
+ protected function respondWithToken(string $token): array
+ {
+ return [
+ 'access_token' => $token,
+ 'token_type' => 'Bearer',
+ 'expires_in' => auth('api')->factory()->getTTL() * 60,
+ ];
+ }
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
new file mode 100644
index 0000000..974243a
--- /dev/null
+++ b/app/Http/Kernel.php
@@ -0,0 +1,68 @@
+
+ */
+ protected $middleware = [
+ // \App\Http\Middleware\TrustHosts::class,
+ \App\Http\Middleware\TrustProxies::class,
+ \Fruitcake\Cors\HandleCors::class,
+ \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
+ \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
+ \App\Http\Middleware\TrimStrings::class,
+ \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
+ \App\Http\Middleware\SetServerId::class,
+ ];
+
+ /**
+ * The application's route middleware groups.
+ *
+ * @var array>
+ */
+ protected $middlewareGroups = [
+ 'web' => [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ // \Illuminate\Session\Middleware\AuthenticateSession::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\VerifyCsrfToken::class,
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+
+ 'api' => [
+ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
+ 'throttle:api',
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ ];
+
+ /**
+ * The application's route middleware.
+ *
+ * These middleware may be assigned to groups or used individually.
+ *
+ * @var array
+ */
+ protected $routeMiddleware = [
+ 'auth' => \App\Http\Middleware\Authenticate::class,
+ 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+ 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+ 'can' => \Illuminate\Auth\Middleware\Authorize::class,
+ 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+ 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+ 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
+ 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+ 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+ ];
+}
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
new file mode 100644
index 0000000..954e38e
--- /dev/null
+++ b/app/Http/Middleware/Authenticate.php
@@ -0,0 +1,22 @@
+expectsJson()) {
+ return route('login');
+ }
+ }
+}
diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php
new file mode 100644
index 0000000..867695b
--- /dev/null
+++ b/app/Http/Middleware/EncryptCookies.php
@@ -0,0 +1,17 @@
+
+ */
+ protected $except = [
+ //
+ ];
+}
diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php
new file mode 100644
index 0000000..74cbd9a
--- /dev/null
+++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php
@@ -0,0 +1,17 @@
+
+ */
+ protected $except = [
+ //
+ ];
+}
diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php
new file mode 100644
index 0000000..45bb1b7
--- /dev/null
+++ b/app/Http/Middleware/RedirectIfAuthenticated.php
@@ -0,0 +1,34 @@
+check()) {
+ return redirect(RouteServiceProvider::HOME);
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/SetServerId.php b/app/Http/Middleware/SetServerId.php
new file mode 100644
index 0000000..ee67e8b
--- /dev/null
+++ b/app/Http/Middleware/SetServerId.php
@@ -0,0 +1,29 @@
+
+ * @param Request $request
+ * @param Closure $next
+ * @return mixed
+ */
+ public function handle(Request $request, Closure $next)
+ {
+ $response = $next($request);
+ $response->headers->add([
+ 'X-Server-Id' => env('SERVER_ID'),
+ ]);
+
+ return $response;
+ }
+
+}
diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php
new file mode 100644
index 0000000..88cadca
--- /dev/null
+++ b/app/Http/Middleware/TrimStrings.php
@@ -0,0 +1,19 @@
+
+ */
+ protected $except = [
+ 'current_password',
+ 'password',
+ 'password_confirmation',
+ ];
+}
diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php
new file mode 100644
index 0000000..c9c58bd
--- /dev/null
+++ b/app/Http/Middleware/TrustHosts.php
@@ -0,0 +1,20 @@
+
+ */
+ public function hosts(): array
+ {
+ return [
+ $this->allSubdomainsOfApplicationUrl(),
+ ];
+ }
+}
diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php
new file mode 100644
index 0000000..3391630
--- /dev/null
+++ b/app/Http/Middleware/TrustProxies.php
@@ -0,0 +1,28 @@
+|string|null
+ */
+ protected $proxies;
+
+ /**
+ * The headers that should be used to detect proxies.
+ *
+ * @var int
+ */
+ protected $headers =
+ Request::HEADER_X_FORWARDED_FOR |
+ Request::HEADER_X_FORWARDED_HOST |
+ Request::HEADER_X_FORWARDED_PORT |
+ Request::HEADER_X_FORWARDED_PROTO |
+ Request::HEADER_X_FORWARDED_AWS_ELB;
+}
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
new file mode 100644
index 0000000..9e86521
--- /dev/null
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -0,0 +1,17 @@
+
+ */
+ protected $except = [
+ //
+ ];
+}
diff --git a/app/Models/Google2FA.php b/app/Models/Google2FA.php
new file mode 100644
index 0000000..062db7c
--- /dev/null
+++ b/app/Models/Google2FA.php
@@ -0,0 +1,45 @@
+ 'boolean',
+ ];
+
+ public function subscriber(): MorphTo
+ {
+ return $this->morphTo();
+ }
+
+ /**
+ * Notes : 更新密钥
+ *
+ * @Date : 2022/11/30 20:51
+ * @Author :
+ */
+ public function upgrade()
+ {
+ $google2fa = app('pragmarx.google2fa');
+
+ $this->secret = $google2fa->generateSecretKey(32);
+ $this->save();
+ }
+
+ /**
+ * Notes : 验证
+ *
+ * @Date : 2022/11/30 20:49
+ * @Author :
+ * @param string $value
+ * @return bool
+ */
+ public function verify(string $value): bool
+ {
+ $google2fa = app('pragmarx.google2fa');
+ return $google2fa->verifyGoogle2FA($this->secret, $value);
+ }
+}
diff --git a/app/Models/Model.php b/app/Models/Model.php
new file mode 100644
index 0000000..052b614
--- /dev/null
+++ b/app/Models/Model.php
@@ -0,0 +1,27 @@
+
+ * @return LengthAwarePaginator
+ */
+ public function paginate(): LengthAwarePaginator
+ {
+ $perPage = Request::get('per_page', 20);
+ $page = Request::get('page', 1);
+
+ $data = ModuleManager::toCollection();
+
+ $chunk = $data->forPage($page, $perPage);
+
+ $modules = $chunk->map(function ($module) {
+ return [
+ 'id' => $module->getName(),
+ 'name' => $module->getName(),
+ 'alias' => $module->getAlias(),
+ 'description' => $module->getDescription(),
+ 'priority' => $module->getPriority(),
+ 'keywords' => $module->get('keywords'),
+ 'requires' => $module->getRequires(),
+ 'enabled' => $module->isEnabled(),
+ 'version' => $module->get('version'),
+ 'author' => $module->get('author'),
+ ];
+ });
+
+ $modules = static::hydrate($modules->toArray());
+ $paginator = new LengthAwarePaginator($modules, ModuleManager::count(), $perPage);
+ $paginator->setPath(url()->current());
+
+ return $paginator;
+ }
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
new file mode 100644
index 0000000..ee8ca5b
--- /dev/null
+++ b/app/Providers/AppServiceProvider.php
@@ -0,0 +1,28 @@
+
+ */
+ protected $policies = [
+ // 'App\Models\Model' => 'App\Policies\ModelPolicy',
+ ];
+
+ /**
+ * Register any authentication / authorization services.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ $this->registerPolicies();
+
+ //
+ }
+}
diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php
new file mode 100644
index 0000000..395c518
--- /dev/null
+++ b/app/Providers/BroadcastServiceProvider.php
@@ -0,0 +1,21 @@
+>
+ */
+ protected $listen = [
+ Registered::class => [
+ SendEmailVerificationNotification::class,
+ ],
+ ];
+
+ /**
+ * Register any events for your application.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ //
+ }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..8e5b91c
--- /dev/null
+++ b/app/Providers/RouteServiceProvider.php
@@ -0,0 +1,63 @@
+configureRateLimiting();
+
+ $this->routes(function () {
+ Route::prefix('api')
+ ->middleware('api')
+ ->namespace($this->namespace)
+ ->group(base_path('routes/api.php'));
+
+ Route::middleware('web')
+ ->namespace($this->namespace)
+ ->group(base_path('routes/web.php'));
+ });
+ }
+
+ /**
+ * Configure the rate limiters for the application.
+ *
+ * @return void
+ */
+ protected function configureRateLimiting()
+ {
+ RateLimiter::for('api', function (Request $request) {
+ return Limit::perMinute(env('RATE_LIMITER', 60))->by(optional($request->user())->id ?: $request->ip());
+ });
+ }
+}
diff --git a/app/Rules/AdminG2FARule.php b/app/Rules/AdminG2FARule.php
new file mode 100644
index 0000000..a0d2025
--- /dev/null
+++ b/app/Rules/AdminG2FARule.php
@@ -0,0 +1,43 @@
+formData = $data;
+ }
+
+ public function passes($attribute, $value): bool
+ {
+ $user = Administrator::where('username', $this->formData['username'])->first();
+
+ if ($user && $user->use_g2fa) {
+ if (blank($value)) {
+ $this->errorMessage = '身份校验码 必须填写';
+ return false;
+ }
+ if (strlen($value) != 6) {
+ $this->errorMessage = '身份校验码 必须是6位';
+ return false;
+ }
+
+ $google2fa = app('pragmarx.google2fa');
+ return $google2fa->verifyGoogle2FA($user->g2fa_secret, $value);
+ }
+ return true;
+ }
+
+ public function message(): string
+ {
+ return $this->errorMessage;
+ }
+}
\ No newline at end of file
diff --git a/app/Rules/IdCardRule.php b/app/Rules/IdCardRule.php
new file mode 100644
index 0000000..ac6f04e
--- /dev/null
+++ b/app/Rules/IdCardRule.php
@@ -0,0 +1,69 @@
+
+ * @param string $attribute
+ * @param mixed $value
+ * @return bool
+ */
+ public function passes($attribute, $value): bool
+ {
+ if (strlen($value) !== 18) {
+ $this->errorMessage = '请输入18位身份证号码';
+ return false;
+ }
+
+ return $this->isIdCard($value);
+ }
+
+ private function isIdCard($id): bool
+ {
+ $id = strtoupper($id);
+ $regx = "/(^\d{17}([0-9]|X)$)/";
+ $arr_split = [];
+ if (! preg_match($regx, $id)) {
+ return false;
+ }
+
+ $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/";
+ @preg_match($regx, $id, $arr_split);
+ $dtm_birth = $arr_split[2].'/'.$arr_split[3].'/'.$arr_split[4];
+ if (! strtotime($dtm_birth)) {
+ return false;
+ }
+
+ $arr_int = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
+ $arr_ch = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
+ $sign = 0;
+ for ($i = 0; $i < 17; $i++) {
+ $b = (int) $id[$i];
+ $w = $arr_int[$i];
+ $sign += $b * $w;
+ }
+ $n = $sign % 11;
+ $val_num = $arr_ch[$n];
+
+ return ! ($val_num !== substr($id, 17, 1));
+ }
+
+ /**
+ * 获取校验错误信息
+ *
+ * @return string
+ */
+ public function message(): string
+ {
+ return $this->errorMessage;
+ }
+}
diff --git a/app/Scopes/OrderByIdDescScope.php b/app/Scopes/OrderByIdDescScope.php
new file mode 100644
index 0000000..54f16cd
--- /dev/null
+++ b/app/Scopes/OrderByIdDescScope.php
@@ -0,0 +1,23 @@
+
+ * @param Builder $builder
+ * @param Model $model
+ */
+ public function apply(Builder $builder, Model $model): void
+ {
+ $builder->orderByDesc('id');
+ }
+}
diff --git a/app/Scopes/OrderByOrderAscScope.php b/app/Scopes/OrderByOrderAscScope.php
new file mode 100644
index 0000000..2836331
--- /dev/null
+++ b/app/Scopes/OrderByOrderAscScope.php
@@ -0,0 +1,23 @@
+
+ * @param Builder $builder
+ * @param Model $model
+ */
+ public function apply(Builder $builder, Model $model): void
+ {
+ $builder->orderBy('order')->orderByDesc('id');
+ }
+}
diff --git a/app/Traits/HasClicks.php b/app/Traits/HasClicks.php
new file mode 100644
index 0000000..09598b2
--- /dev/null
+++ b/app/Traits/HasClicks.php
@@ -0,0 +1,87 @@
+
+ * @return string
+ */
+ private function getClicksField(): string
+ {
+ return $this->clicks_filed ?? 'clicks';
+ }
+
+ /**
+ * Notes : 获取缓存前缀
+ *
+ * @Date : 2021/3/16 5:52 下午
+ * @Author :
+ * @return string
+ */
+ private function getClickCachePrefix(): string
+ {
+ return $this->cachePrefix ?? class_basename(__CLASS__);
+ }
+
+ /**
+ * Notes : 生成一个缓存KEY
+ *
+ * @Date : 2021/3/16 5:52 下午
+ * @Author :
+ * @param string|null $appends
+ * @return string
+ */
+ private function getCacheKey(string $appends = null): string
+ {
+ return $this->getClickCachePrefix().':'.$this->getKey().':'.$appends;
+ }
+
+ /**
+ * Notes : 增加点击量
+ *
+ * @Date : 2021/3/17 9:20 上午
+ * @Author :
+ * @param int $step
+ */
+ public function incrementClicks(int $step = 1): void
+ {
+ Cache::increment($this->getCacheKey('clicks'), $step);
+
+ if (rand(1, $this->saveRate) === 1) {
+ $this->update([$this->getClicksField() => $this->clicks]);
+ }
+ }
+
+ /**
+ * Notes : 获取缓存的浏览次数
+ *
+ * @Date : 2021/3/16 5:52 下午
+ * @Author :
+ * @return int
+ */
+ public function getClicksAttribute(): int
+ {
+ $clicks = Cache::get($this->getCacheKey('clicks'));
+
+ if (is_null($clicks)) {
+ return Cache::rememberForever($this->getCacheKey('clicks'), function () {
+ return $this->getAttributes()[$this->getClicksField()];
+ });
+ } else {
+ return $clicks;
+ }
+ }
+}
diff --git a/app/Traits/HasCovers.php b/app/Traits/HasCovers.php
new file mode 100644
index 0000000..542852c
--- /dev/null
+++ b/app/Traits/HasCovers.php
@@ -0,0 +1,86 @@
+
+ * @return string
+ */
+ public function getCoverField(): string
+ {
+ return $this->cover_field ?? 'cover';
+ }
+
+ /**
+ * Notes : 获取图片字段(多图)
+ *
+ * @Date : 2021/3/16 4:35 下午
+ * @Author :
+ * @return string
+ */
+ public function getPicturesField(): string
+ {
+ return $this->pictures_field ?? 'pictures';
+ }
+
+ /**
+ * Notes : 解析单图地址
+ *
+ * @Date : 2021/3/16 4:54 下午
+ * @Author :
+ * @return string
+ */
+ public function getCoverUrlAttribute(): string
+ {
+ $cover = $this->getAttribute($this->getCoverField());
+
+ return $this->parseImageUrl($cover);
+ }
+
+ /**
+ * Notes : 解析多图地址
+ *
+ * @Date : 2021/3/16 4:54 下午
+ * @Author :
+ * @return array
+ */
+ public function getPicturesUrlAttribute(): array
+ {
+ $pictures = $this->getAttribute($this->getPicturesField());
+
+ if (empty($pictures)) {
+ return [];
+ }
+
+ return collect($pictures)->map(function ($picture) {
+ return $this->parseImageUrl($picture);
+ })->toArray();
+ }
+
+ /**
+ * Notes : 解析图片文件的实际展示地址
+ *
+ * @Date : 2021/3/16 4:53 下午
+ * @Author :
+ * @param string|null $image
+ * @return string
+ */
+ protected function parseImageUrl(?string $image): string
+ {
+ if (empty($image)) {
+ return '';
+ } elseif (Str::startsWith($image, 'http')) {
+ return $image;
+ } else {
+ return Storage::url($image);
+ }
+ }
+}
diff --git a/app/Traits/HasStatus.php b/app/Traits/HasStatus.php
new file mode 100644
index 0000000..dbd5ce4
--- /dev/null
+++ b/app/Traits/HasStatus.php
@@ -0,0 +1,91 @@
+
+ * @return string
+ */
+ protected function getStatusField(): string
+ {
+ return $this->status_field ?? 'status';
+ }
+
+ /**
+ * Notes : 获取各状态的名称
+ *
+ * @Date : 2021/5/27 11:50 上午
+ * @Author :
+ * @return string[]
+ */
+ protected function getStatusMap(): array
+ {
+ return isset($this->status_map) && ! empty($this->status_map) ? $this->status_map : [
+ 0 => '待审核',
+ 1 => '正常',
+ 2 => '驳回',
+ 3 => '关闭',
+ ];
+ }
+
+ /**
+ * 正常显示的数据
+ *
+ * @Author:
+ * @Date :2021-04-09
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeShown(Builder $query): Builder
+ {
+ return $query->where($this->getStatusField(), 1);
+ }
+
+ /**
+ * 不显示的数据
+ *
+ * @Author :
+ * @Date :2021-04-09
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeHidden(Builder $query): Builder
+ {
+ return $query->where($this->getStatusField(), 0);
+ }
+
+ /**
+ * Notes : 状态查询
+ *
+ * @Date : 2021/6/28 10:25 上午
+ * @Author :
+ * @param Builder $query
+ * @param int $status
+ * @return Builder
+ */
+ public function scopeOfStatus(Builder $query, int $status): Builder
+ {
+ return $query->where($this->getStatusField(), $status);
+ }
+
+ /**
+ * Notes : 获取状态的文本信息
+ *
+ * @Date : 2021/4/25 2:10 下午
+ * @Author :
+ * @return string
+ */
+ public function getStatusTextAttribute(): string
+ {
+ $map = $this->getStatusMap();
+
+ return $map[$this->{$this->getStatusField()}] ?? '未知';
+ }
+}
diff --git a/app/Traits/Macroable.php b/app/Traits/Macroable.php
new file mode 100644
index 0000000..df9637f
--- /dev/null
+++ b/app/Traits/Macroable.php
@@ -0,0 +1,48 @@
+getRelationshipFromMethod($key);
+ }
+
+ return $relation;
+ }
+
+ /**
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ if (static::hasMacro($method)) {
+ return $this->macroCall($method, $parameters);
+ }
+
+ return parent::__call($method, $parameters);
+ }
+
+ /**
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public static function __callStatic($method, $parameters)
+ {
+ return parent::__callStatic($method, $parameters);
+ }
+}
diff --git a/app/Traits/OrderByIdDesc.php b/app/Traits/OrderByIdDesc.php
new file mode 100644
index 0000000..25fe399
--- /dev/null
+++ b/app/Traits/OrderByIdDesc.php
@@ -0,0 +1,21 @@
+
+ * @Date : 2020/1/19 1:42 下午
+ */
+ public static function bootOrderByIdDesc(): void
+ {
+ static::addGlobalScope(new OrderByIdDescScope);
+ }
+}
diff --git a/app/Traits/OrderByOrderAsc.php b/app/Traits/OrderByOrderAsc.php
new file mode 100644
index 0000000..8ffd668
--- /dev/null
+++ b/app/Traits/OrderByOrderAsc.php
@@ -0,0 +1,22 @@
+
+ * @Date : 2020/1/19 1:42 下午
+ */
+ public static function bootOrderByOrderAsc(): void
+ {
+ static::addGlobalScope(new OrderByOrderAscScope());
+ }
+}
diff --git a/app/Traits/WithGoogle2FA.php b/app/Traits/WithGoogle2FA.php
new file mode 100644
index 0000000..3c01f85
--- /dev/null
+++ b/app/Traits/WithGoogle2FA.php
@@ -0,0 +1,24 @@
+google2fa()->create([
+ 'secret' => $google2fa->generateSecretKey(32),
+ ]);
+ });
+ }
+
+ public function google2fa(): MorphOne
+ {
+ return $this->morphOne(Google2FA::class, 'subscriber');
+ }
+}
\ No newline at end of file
diff --git a/app/Traits/WithPosition.php b/app/Traits/WithPosition.php
new file mode 100644
index 0000000..cd9e6c1
--- /dev/null
+++ b/app/Traits/WithPosition.php
@@ -0,0 +1,67 @@
+
+ */
+ protected function getPositionMap(): array
+ {
+ return $this->position_map ?? [];
+ }
+
+ /**
+ * Notes: 定位查询作用域
+ *
+ * @Author: Mr.wang
+ * @Date : 2021/5/11 10:48
+ * @param Builder $query
+ * @param int $pos
+ * @return Builder
+ */
+ public function scopeOfPosition(Builder $query, int $pos): Builder
+ {
+ return $query->whereRaw('position & '.$pos);
+ }
+
+ /**
+ * Notes: 设置定位
+ *
+ * @Author: Mr.wang
+ * @Date : 2020/5/11 10:48
+ * @param array $value
+ */
+ protected function setPositionAttribute(array $value): void
+ {
+ if (! blank($value)) {
+ $this->attributes['position'] = array_sum($value);
+ }
+ }
+
+ /**
+ * Notes: 获取定位数据
+ *
+ * @Author: Mr.wang
+ * @Date : 2020/5/11 10:48
+ * @param int $value
+ * @return array
+ */
+ protected function getPositionAttribute(int $value): array
+ {
+ $position = [];
+ foreach ($this->getPositionMap() as $k => $v) {
+ if ($k & $value) {
+ $position[] = $k;
+ }
+ }
+
+ return $position;
+ }
+}
diff --git a/artisan b/artisan
new file mode 100644
index 0000000..5c23e2e
--- /dev/null
+++ b/artisan
@@ -0,0 +1,53 @@
+#!/usr/bin/env php
+make(Illuminate\Contracts\Console\Kernel::class);
+
+$status = $kernel->handle(
+ $input = new Symfony\Component\Console\Input\ArgvInput,
+ new Symfony\Component\Console\Output\ConsoleOutput
+);
+
+/*
+|--------------------------------------------------------------------------
+| Shutdown The Application
+|--------------------------------------------------------------------------
+|
+| Once Artisan has finished running, we will fire off the shutdown events
+| so that any final work may be done by the application before we shut
+| down the process. This is the last thing to happen to the request.
+|
+*/
+
+$kernel->terminate($input, $status);
+
+exit($status);
diff --git a/bootstrap/app.php b/bootstrap/app.php
new file mode 100644
index 0000000..037e17d
--- /dev/null
+++ b/bootstrap/app.php
@@ -0,0 +1,55 @@
+singleton(
+ Illuminate\Contracts\Http\Kernel::class,
+ App\Http\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Console\Kernel::class,
+ App\Console\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Debug\ExceptionHandler::class,
+ App\Exceptions\Handler::class
+);
+
+/*
+|--------------------------------------------------------------------------
+| Return The Application
+|--------------------------------------------------------------------------
+|
+| This script returns the application instance. The instance is given to
+| the calling script so we can separate the building of the instances
+| from the actual running of the application and sending responses.
+|
+*/
+
+return $app;
diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/bootstrap/cache/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..402eb18
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,75 @@
+{
+ "name": "uztech/laravel",
+ "type": "project",
+ "description": "The Laravel Framework.",
+ "keywords": [
+ "framework",
+ "laravel"
+ ],
+ "license": "MIT",
+ "require": {
+ "php": "^7.4|^8.0",
+ "ext-json": "*",
+ "encore/laravel-admin": "^1.8.16",
+ "fideloper/proxy": "^4.4.1",
+ "fruitcake/laravel-cors": "^2.0.4",
+ "genealabs/laravel-model-caching": "^0.11.3",
+ "guzzlehttp/guzzle": "^7.4.0",
+ "jasonc/api": "^5.0.4",
+ "laravel/framework": "^8.71.0",
+ "laravel/sanctum": "^2.12.2",
+ "nosun/ueditor": "^3.0.2",
+ "nwidart/laravel-modules": "^8.2.0",
+ "pragmarx/google2fa-laravel": "^2.0",
+ "simplesoftwareio/simple-qrcode": "^4.2"
+ },
+ "require-dev": {
+ "facade/ignition": "^2.16.0",
+ "fakerphp/faker": "^1.16.0",
+ "laravel/sail": "^1.12.4",
+ "mockery/mockery": "^1.4.4",
+ "nunomaduro/collision": "^5.10.0",
+ "phpunit/phpunit": "^9.5.10",
+ "wulfheart/pretty_routes": "^0.3.0"
+ },
+ "config": {
+ "optimize-autoloader": true,
+ "preferred-install": "dist",
+ "sort-packages": true
+ },
+ "extra": {
+ "laravel": {
+ "dont-discover": []
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "App\\": "app/",
+ "Modules\\": "modules/",
+ "Database\\Factories\\": "database/factories/",
+ "Database\\Seeders\\": "database/seeders/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "scripts": {
+ "post-autoload-dump": [
+ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
+ "@php artisan package:discover --ansi"
+ ],
+ "post-update-cmd": [
+ "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
+ ],
+ "post-root-package-install": [
+ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
+ ],
+ "post-create-project-cmd": [
+ "@php artisan key:generate --ansi"
+ ]
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..d6b2d73
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,10220 @@
+{
+ "_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": "8488287b99a0b6989664702075a20d61",
+ "packages": [
+ {
+ "name": "asm89/stack-cors",
+ "version": "v2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/asm89/stack-cors.git",
+ "reference": "9cb795bf30988e8c96dd3c40623c48a877bc6714"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/asm89/stack-cors/zipball/9cb795bf30988e8c96dd3c40623c48a877bc6714",
+ "reference": "9cb795bf30988e8c96dd3c40623c48a877bc6714",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.0|^8.0",
+ "symfony/http-foundation": "~2.7|~3.0|~4.0|~5.0",
+ "symfony/http-kernel": "~2.7|~3.0|~4.0|~5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6|^7|^8|^9",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Asm89\\Stack\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alexander",
+ "email": "iam.asm89@gmail.com"
+ }
+ ],
+ "description": "Cross-origin resource sharing library and stack middleware",
+ "homepage": "https://github.com/asm89/stack-cors",
+ "keywords": [
+ "cors",
+ "stack"
+ ],
+ "support": {
+ "issues": "https://github.com/asm89/stack-cors/issues",
+ "source": "https://github.com/asm89/stack-cors/tree/v2.0.3"
+ },
+ "time": "2021-03-11T06:42:03+00:00"
+ },
+ {
+ "name": "bacon/bacon-qr-code",
+ "version": "2.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Bacon/BaconQrCode.git",
+ "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/d70c840f68657ce49094b8d91f9ee0cc07fbf66c",
+ "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "dasprid/enum": "^1.0.3",
+ "ext-iconv": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "phly/keep-a-changelog": "^2.1",
+ "phpunit/phpunit": "^7 | ^8 | ^9",
+ "spatie/phpunit-snapshot-assertions": "^4.2.9",
+ "squizlabs/php_codesniffer": "^3.4"
+ },
+ "suggest": {
+ "ext-imagick": "to generate QR code images"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "BaconQrCode\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ben Scholzen 'DASPRiD'",
+ "email": "mail@dasprids.de",
+ "homepage": "https://dasprids.de/",
+ "role": "Developer"
+ }
+ ],
+ "description": "BaconQrCode is a QR code generator for PHP.",
+ "homepage": "https://github.com/Bacon/BaconQrCode",
+ "support": {
+ "issues": "https://github.com/Bacon/BaconQrCode/issues",
+ "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.7"
+ },
+ "time": "2022-03-14T02:02:36+00:00"
+ },
+ {
+ "name": "brick/math",
+ "version": "0.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brick/math.git",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
+ "vimeo/psalm": "4.9.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Brick\\Math\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Arbitrary-precision arithmetic library",
+ "keywords": [
+ "Arbitrary-precision",
+ "BigInteger",
+ "BigRational",
+ "arithmetic",
+ "bigdecimal",
+ "bignum",
+ "brick",
+ "math"
+ ],
+ "support": {
+ "issues": "https://github.com/brick/math/issues",
+ "source": "https://github.com/brick/math/tree/0.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/BenMorel",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/brick/math",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-15T20:50:18+00:00"
+ },
+ {
+ "name": "composer/package-versions-deprecated",
+ "version": "1.11.99.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/package-versions-deprecated.git",
+ "reference": "b174585d1fe49ceed21928a945138948cb394600"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600",
+ "reference": "b174585d1fe49ceed21928a945138948cb394600",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "composer-plugin-api": "^1.1.0 || ^2.0",
+ "php": "^7 || ^8"
+ },
+ "replace": {
+ "ocramius/package-versions": "1.11.99"
+ },
+ "require-dev": {
+ "composer/composer": "^1.9.3 || ^2.0@dev",
+ "ext-zip": "^1.13",
+ "phpunit/phpunit": "^6.5 || ^7"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PackageVersions\\Installer",
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PackageVersions\\": "src/PackageVersions"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be"
+ }
+ ],
+ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
+ "support": {
+ "issues": "https://github.com/composer/package-versions-deprecated/issues",
+ "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-09-13T08:41:34+00:00"
+ },
+ {
+ "name": "dasprid/enum",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/DASPRiD/Enum.git",
+ "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2",
+ "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7 | ^8 | ^9",
+ "squizlabs/php_codesniffer": "^3.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DASPRiD\\Enum\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ben Scholzen 'DASPRiD'",
+ "email": "mail@dasprids.de",
+ "homepage": "https://dasprids.de/",
+ "role": "Developer"
+ }
+ ],
+ "description": "PHP 7.1 enum implementation",
+ "keywords": [
+ "enum",
+ "map"
+ ],
+ "support": {
+ "issues": "https://github.com/DASPRiD/Enum/issues",
+ "source": "https://github.com/DASPRiD/Enum/tree/1.0.3"
+ },
+ "time": "2020-10-02T16:03:48+00:00"
+ },
+ {
+ "name": "dflydev/dot-access-data",
+ "version": "v3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dflydev/dflydev-dot-access-data.git",
+ "reference": "0992cc19268b259a39e86f296da5f0677841f42c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c",
+ "reference": "0992cc19268b259a39e86f296da5f0677841f42c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12.42",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
+ "scrutinizer/ocular": "1.6.0",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^3.14"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dflydev\\DotAccessData\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Dragonfly Development Inc.",
+ "email": "info@dflydev.com",
+ "homepage": "http://dflydev.com"
+ },
+ {
+ "name": "Beau Simensen",
+ "email": "beau@dflydev.com",
+ "homepage": "http://beausimensen.com"
+ },
+ {
+ "name": "Carlos Frutos",
+ "email": "carlos@kiwing.it",
+ "homepage": "https://github.com/cfrutos"
+ },
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com"
+ }
+ ],
+ "description": "Given a deep data structure, access data by dot notation.",
+ "homepage": "https://github.com/dflydev/dflydev-dot-access-data",
+ "keywords": [
+ "access",
+ "data",
+ "dot",
+ "notation"
+ ],
+ "support": {
+ "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
+ "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1"
+ },
+ "time": "2021-08-13T13:06:58+00:00"
+ },
+ {
+ "name": "doctrine/cache",
+ "version": "2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/cache.git",
+ "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce",
+ "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "~7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/common": ">2.2,<2.4"
+ },
+ "require-dev": {
+ "alcaeus/mongo-php-adapter": "^1.1",
+ "cache/integration-tests": "dev-master",
+ "doctrine/coding-standard": "^8.0",
+ "mongodb/mongodb": "^1.1",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+ "predis/predis": "~1.0",
+ "psr/cache": "^1.0 || ^2.0 || ^3.0",
+ "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev",
+ "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev"
+ },
+ "suggest": {
+ "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
+ }
+ },
+ "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 Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
+ "homepage": "https://www.doctrine-project.org/projects/cache.html",
+ "keywords": [
+ "abstraction",
+ "apcu",
+ "cache",
+ "caching",
+ "couchdb",
+ "memcached",
+ "php",
+ "redis",
+ "xcache"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/cache/issues",
+ "source": "https://github.com/doctrine/cache/tree/2.1.1"
+ },
+ "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%2Fcache",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-07-17T14:49:29+00:00"
+ },
+ {
+ "name": "doctrine/dbal",
+ "version": "3.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/dbal.git",
+ "reference": "821b4f01a36ce63ed36c090ea74767b72db367e9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/821b4f01a36ce63ed36c090ea74767b72db367e9",
+ "reference": "821b4f01a36ce63ed36c090ea74767b72db367e9",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "composer/package-versions-deprecated": "^1.11.99",
+ "doctrine/cache": "^1.0|^2.0",
+ "doctrine/deprecations": "^0.5.3",
+ "doctrine/event-manager": "^1.0",
+ "php": "^7.3 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "9.0.0",
+ "jetbrains/phpstorm-stubs": "2021.1",
+ "phpstan/phpstan": "1.1.1",
+ "phpstan/phpstan-strict-rules": "^1",
+ "phpunit/phpunit": "9.5.10",
+ "psalm/plugin-phpunit": "0.16.1",
+ "squizlabs/php_codesniffer": "3.6.1",
+ "symfony/cache": "^5.2|^6.0",
+ "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0",
+ "vimeo/psalm": "4.12.0"
+ },
+ "suggest": {
+ "symfony/console": "For helpful console commands such as SQL execution and import of files."
+ },
+ "bin": [
+ "bin/doctrine-dbal"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\DBAL\\": "src"
+ }
+ },
+ "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"
+ }
+ ],
+ "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
+ "homepage": "https://www.doctrine-project.org/projects/dbal.html",
+ "keywords": [
+ "abstraction",
+ "database",
+ "db2",
+ "dbal",
+ "mariadb",
+ "mssql",
+ "mysql",
+ "oci8",
+ "oracle",
+ "pdo",
+ "pgsql",
+ "postgresql",
+ "queryobject",
+ "sasql",
+ "sql",
+ "sqlite",
+ "sqlserver",
+ "sqlsrv"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/dbal/issues",
+ "source": "https://github.com/doctrine/dbal/tree/3.1.4"
+ },
+ "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%2Fdbal",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-11-15T16:44:33+00:00"
+ },
+ {
+ "name": "doctrine/deprecations",
+ "version": "v0.5.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/deprecations.git",
+ "reference": "9504165960a1f83cc1480e2be1dd0a0478561314"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314",
+ "reference": "9504165960a1f83cc1480e2be1dd0a0478561314",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0|^7.0|^8.0",
+ "phpunit/phpunit": "^7.0|^8.0|^9.0",
+ "psr/log": "^1.0"
+ },
+ "suggest": {
+ "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
+ "homepage": "https://www.doctrine-project.org/",
+ "support": {
+ "issues": "https://github.com/doctrine/deprecations/issues",
+ "source": "https://github.com/doctrine/deprecations/tree/v0.5.3"
+ },
+ "time": "2021-03-21T12:59:47+00:00"
+ },
+ {
+ "name": "doctrine/event-manager",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/event-manager.git",
+ "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f",
+ "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/common": "<2.9@dev"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "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"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
+ "homepage": "https://www.doctrine-project.org/projects/event-manager.html",
+ "keywords": [
+ "event",
+ "event dispatcher",
+ "event manager",
+ "event system",
+ "events"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/event-manager/issues",
+ "source": "https://github.com/doctrine/event-manager/tree/1.1.x"
+ },
+ "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%2Fevent-manager",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-05-29T18:28:51+00:00"
+ },
+ {
+ "name": "doctrine/inflector",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/inflector.git",
+ "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89",
+ "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89",
+ "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": "^8.2",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpstan/phpstan-strict-rules": "^0.12",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+ "vimeo/psalm": "^4.10"
+ },
+ "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.4"
+ },
+ "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": "2021-10-22T20:16:43+00:00"
+ },
+ {
+ "name": "doctrine/lexer",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/lexer.git",
+ "reference": "e864bbf5904cb8f5bb334f99209b48018522f042"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042",
+ "reference": "e864bbf5904cb8f5bb334f99209b48018522f042",
+ "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": "^6.0",
+ "phpstan/phpstan": "^0.11.8",
+ "phpunit/phpunit": "^8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
+ }
+ },
+ "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": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+ "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "lexer",
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/lexer/issues",
+ "source": "https://github.com/doctrine/lexer/tree/1.2.1"
+ },
+ "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%2Flexer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-05-25T17:44:05+00:00"
+ },
+ {
+ "name": "dragonmantank/cron-expression",
+ "version": "v3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dragonmantank/cron-expression.git",
+ "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c",
+ "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.2|^8.0",
+ "webmozart/assert": "^1.7.0"
+ },
+ "replace": {
+ "mtdowling/cron-expression": "^1.0"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-webmozart-assert": "^0.12.7",
+ "phpunit/phpunit": "^7.0|^8.0|^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Cron\\": "src/Cron/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Tankersley",
+ "email": "chris@ctankersley.com",
+ "homepage": "https://github.com/dragonmantank"
+ }
+ ],
+ "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+ "keywords": [
+ "cron",
+ "schedule"
+ ],
+ "support": {
+ "issues": "https://github.com/dragonmantank/cron-expression/issues",
+ "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/dragonmantank",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-24T19:55:57+00:00"
+ },
+ {
+ "name": "egulias/email-validator",
+ "version": "2.1.25",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/egulias/EmailValidator.git",
+ "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4",
+ "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "doctrine/lexer": "^1.0.1",
+ "php": ">=5.5",
+ "symfony/polyfill-intl-idn": "^1.10"
+ },
+ "require-dev": {
+ "dominicsayers/isemail": "^3.0.7",
+ "phpunit/phpunit": "^4.8.36|^7.5.15",
+ "satooshi/php-coveralls": "^1.0.1"
+ },
+ "suggest": {
+ "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Egulias\\EmailValidator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Eduardo Gulias Davis"
+ }
+ ],
+ "description": "A library for validating emails against several RFCs",
+ "homepage": "https://github.com/egulias/EmailValidator",
+ "keywords": [
+ "email",
+ "emailvalidation",
+ "emailvalidator",
+ "validation",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/egulias/EmailValidator/issues",
+ "source": "https://github.com/egulias/EmailValidator/tree/2.1.25"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/egulias",
+ "type": "github"
+ }
+ ],
+ "time": "2020-12-29T14:50:06+00:00"
+ },
+ {
+ "name": "encore/laravel-admin",
+ "version": "v1.8.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/z-song/laravel-admin.git",
+ "reference": "0f7bafbd2acd4cdb2118c2a6105a6b2aa9dacb13"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/z-song/laravel-admin/zipball/0f7bafbd2acd4cdb2118c2a6105a6b2aa9dacb13",
+ "reference": "0f7bafbd2acd4cdb2118c2a6105a6b2aa9dacb13",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "doctrine/dbal": "2.*|3.*",
+ "laravel/framework": ">=5.5",
+ "php": ">=7.0.0",
+ "symfony/dom-crawler": "~3.1|~4.0|~5.0"
+ },
+ "require-dev": {
+ "fzaninotto/faker": "~1.4",
+ "intervention/image": "~2.3",
+ "laravel/browser-kit-testing": "^6.0",
+ "laravel/laravel": ">=5.5",
+ "spatie/phpunit-watcher": "^1.22.0"
+ },
+ "suggest": {
+ "intervention/image": "Required to handling and manipulation upload images (~2.3).",
+ "spatie/eloquent-sortable": "Required to built orderable gird."
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Encore\\Admin\\AdminServiceProvider"
+ ],
+ "aliases": {
+ "Admin": "Encore\\Admin\\Facades\\Admin"
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Encore\\Admin\\": "src/"
+ },
+ "files": [
+ "src/helpers.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "zsong",
+ "email": "zosong@126.com"
+ }
+ ],
+ "description": "laravel admin",
+ "homepage": "https://github.com/z-song/laravel-admin",
+ "keywords": [
+ "admin",
+ "form",
+ "grid",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/z-song/laravel-admin/issues",
+ "source": "https://github.com/z-song/laravel-admin/tree/v1.8.16"
+ },
+ "time": "2021-11-02T07:52:31+00:00"
+ },
+ {
+ "name": "fideloper/proxy",
+ "version": "4.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fideloper/TrustedProxy.git",
+ "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/c073b2bd04d1c90e04dc1b787662b558dd65ade0",
+ "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "illuminate/http": "^5.0|^6.0|^7.0|^8.0|^9.0",
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Fideloper\\Proxy\\TrustedProxyServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Fideloper\\Proxy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Fidao",
+ "email": "fideloper@gmail.com"
+ }
+ ],
+ "description": "Set trusted proxies for Laravel",
+ "keywords": [
+ "load balancing",
+ "proxy",
+ "trusted proxy"
+ ],
+ "support": {
+ "issues": "https://github.com/fideloper/TrustedProxy/issues",
+ "source": "https://github.com/fideloper/TrustedProxy/tree/4.4.1"
+ },
+ "time": "2020-10-22T13:48:01+00:00"
+ },
+ {
+ "name": "fruitcake/laravel-cors",
+ "version": "v2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fruitcake/laravel-cors.git",
+ "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/a8ccedc7ca95189ead0e407c43b530dc17791d6a",
+ "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "asm89/stack-cors": "^2.0.1",
+ "illuminate/contracts": "^6|^7|^8|^9",
+ "illuminate/support": "^6|^7|^8|^9",
+ "php": ">=7.2",
+ "symfony/http-foundation": "^4|^5",
+ "symfony/http-kernel": "^4.3.4|^5"
+ },
+ "require-dev": {
+ "laravel/framework": "^6|^7|^8",
+ "orchestra/testbench-dusk": "^4|^5|^6|^7",
+ "phpunit/phpunit": "^6|^7|^8|^9",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Fruitcake\\Cors\\CorsServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Fruitcake\\Cors\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fruitcake",
+ "homepage": "https://fruitcake.nl"
+ },
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application",
+ "keywords": [
+ "api",
+ "cors",
+ "crossdomain",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/fruitcake/laravel-cors/issues",
+ "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/barryvdh",
+ "type": "github"
+ }
+ ],
+ "time": "2021-04-26T11:24:25+00:00"
+ },
+ {
+ "name": "genealabs/laravel-model-caching",
+ "version": "0.11.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GeneaLabs/laravel-model-caching.git",
+ "reference": "ff303d782cb63a9c44bcd76a28bde73a67040c7e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GeneaLabs/laravel-model-caching/zipball/ff303d782cb63a9c44bcd76a28bde73a67040c7e",
+ "reference": "ff303d782cb63a9c44bcd76a28bde73a67040c7e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "genealabs/laravel-pivot-events": "^8.0",
+ "illuminate/cache": "^8.0",
+ "illuminate/config": "^8.0",
+ "illuminate/console": "^8.0",
+ "illuminate/container": "^8.0",
+ "illuminate/database": "^8.0",
+ "illuminate/http": "^8.0",
+ "illuminate/support": "^8.0",
+ "php": "^7.3|^8.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.10",
+ "fakerphp/faker": "^1.11",
+ "laravel/legacy-factories": "^1.0",
+ "laravel/nova": "^3.9",
+ "orchestra/testbench": "^6.0",
+ "orchestra/testbench-browser-kit": "^6.0",
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpmd/phpmd": "^2.7",
+ "phpunit/phpunit": "^9.0",
+ "slevomat/coding-standard": "^6.4",
+ "squizlabs/php_codesniffer": "^3.4",
+ "symfony/thanks": "^1.2"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "GeneaLabs\\LaravelModelCaching\\Providers\\Service"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GeneaLabs\\LaravelModelCaching\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike Bronner",
+ "email": "hello@genealabs.com"
+ }
+ ],
+ "description": "Automatic caching for Eloquent models.",
+ "support": {
+ "issues": "https://github.com/GeneaLabs/laravel-model-caching/issues",
+ "source": "https://github.com/GeneaLabs/laravel-model-caching/tree/0.11.7"
+ },
+ "time": "2022-02-03T13:21:28+00:00"
+ },
+ {
+ "name": "genealabs/laravel-pivot-events",
+ "version": "8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GeneaLabs/laravel-pivot-events.git",
+ "reference": "aafc9d7f6a0b31e0d58bd2b31e38253fbd27c2a9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GeneaLabs/laravel-pivot-events/zipball/aafc9d7f6a0b31e0d58bd2b31e38253fbd27c2a9",
+ "reference": "aafc9d7f6a0b31e0d58bd2b31e38253fbd27c2a9",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "illuminate/database": "^8.0",
+ "illuminate/support": "^8.0"
+ },
+ "require-dev": {
+ "orchestra/testbench": "^6.0",
+ "symfony/thanks": "^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "GeneaLabs\\LaravelPivotEvents\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike Bronner",
+ "email": "hello@genealabs.com",
+ "homepage": "https://genealabs.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "This package introduces new eloquent events for sync(), attach(), detach() or updateExistingPivot() methods on BelongsToMany relation.",
+ "homepage": "https://github.com/fico7489/laravel-pivot",
+ "keywords": [
+ "eloquent events",
+ "eloquent extra events",
+ "laravel BelongsToMany events",
+ "laravel pivot events",
+ "laravel sync events"
+ ],
+ "support": {
+ "issues": "https://github.com/fico7489/laravel-pivot/issues",
+ "source": "https://github.com/fico7489/laravel-pivot"
+ },
+ "time": "2020-09-08T14:39:12+00:00"
+ },
+ {
+ "name": "graham-campbell/result-type",
+ "version": "v1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GrahamCampbell/Result-Type.git",
+ "reference": "296c015dc30ec4322168c5ad3ee5cc11dae827ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/296c015dc30ec4322168c5ad3ee5cc11dae827ac",
+ "reference": "296c015dc30ec4322168c5ad3ee5cc11dae827ac",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "phpoption/phpoption": "^1.8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8"
+ },
+ "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"
+ }
+ ],
+ "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.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-17T19:48:54+00:00"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "7.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/868b3571a039f0ebc11ac8f344f4080babe2cb94",
+ "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.5",
+ "guzzlehttp/psr7": "^1.8.3 || ^2.1",
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-client": "^1.0",
+ "symfony/deprecation-contracts": "^2.2"
+ },
+ "provide": {
+ "psr/http-client-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "ext-curl": "*",
+ "php-http/client-integration-tests": "^3.0",
+ "phpunit/phpunit": "^8.5.5 || ^9.3.5",
+ "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": {
+ "branch-alias": {
+ "dev-master": "7.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "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.4.0"
+ },
+ "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": "2021-10-18T09:52:00+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "1.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
+ "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^4.4 || ^5.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "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/1.5.1"
+ },
+ "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": "2021-10-22T20:56:57+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72",
+ "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72",
+ "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.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.4.1",
+ "http-interop/http-factory-tests": "^0.9",
+ "phpunit/phpunit": "^8.5.8 || ^9.3.10"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "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.1.0"
+ },
+ "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": "2021-10-06T17:43:30+00:00"
+ },
+ {
+ "name": "jasonc/api",
+ "version": "5.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cjango/laravel-api.git",
+ "reference": "3025a88c9ef0276426d7b56051eff697adadc48f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cjango/laravel-api/zipball/3025a88c9ef0276426d7b56051eff697adadc48f",
+ "reference": "3025a88c9ef0276426d7b56051eff697adadc48f",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "laravel/framework": "^8.56",
+ "laravel/sanctum": "^2.11",
+ "php": ">=7.4.0",
+ "yangjisen/laravel-cache-provider": "^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Jason\\Api\\ApiServiceProvider"
+ ],
+ "aliases": {
+ "Api": "Jason\\Api"
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Jason\\Api\\": "src/"
+ },
+ "files": []
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "C.Jason",
+ "email": "chenjxlg@163.com"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/cjango/laravel-api/issues",
+ "source": "https://github.com/cjango/laravel-api/tree/5.0.4"
+ },
+ "time": "2021-08-27T01:41:18+00:00"
+ },
+ {
+ "name": "laravel/framework",
+ "version": "v8.71.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/framework.git",
+ "reference": "d822426d75ecf8e8ccd3ccd404db7202d1a9875e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/d822426d75ecf8e8ccd3ccd404db7202d1a9875e",
+ "reference": "d822426d75ecf8e8ccd3ccd404db7202d1a9875e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "doctrine/inflector": "^1.4|^2.0",
+ "dragonmantank/cron-expression": "^3.0.2",
+ "egulias/email-validator": "^2.1.10",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-openssl": "*",
+ "laravel/serializable-closure": "^1.0",
+ "league/commonmark": "^1.3|^2.0.2",
+ "league/flysystem": "^1.1",
+ "monolog/monolog": "^2.0",
+ "nesbot/carbon": "^2.53.1",
+ "opis/closure": "^3.6",
+ "php": "^7.3|^8.0",
+ "psr/container": "^1.0",
+ "psr/log": "^1.0 || ^2.0",
+ "psr/simple-cache": "^1.0",
+ "ramsey/uuid": "^4.2.2",
+ "swiftmailer/swiftmailer": "^6.3",
+ "symfony/console": "^5.1.4",
+ "symfony/error-handler": "^5.1.4",
+ "symfony/finder": "^5.1.4",
+ "symfony/http-foundation": "^5.1.4",
+ "symfony/http-kernel": "^5.1.4",
+ "symfony/mime": "^5.1.4",
+ "symfony/process": "^5.1.4",
+ "symfony/routing": "^5.1.4",
+ "symfony/var-dumper": "^5.1.4",
+ "tijsverkoyen/css-to-inline-styles": "^2.2.2",
+ "vlucas/phpdotenv": "^5.2",
+ "voku/portable-ascii": "^1.4.8"
+ },
+ "conflict": {
+ "tightenco/collect": "<5.5.33"
+ },
+ "provide": {
+ "psr/container-implementation": "1.0",
+ "psr/simple-cache-implementation": "1.0"
+ },
+ "replace": {
+ "illuminate/auth": "self.version",
+ "illuminate/broadcasting": "self.version",
+ "illuminate/bus": "self.version",
+ "illuminate/cache": "self.version",
+ "illuminate/collections": "self.version",
+ "illuminate/config": "self.version",
+ "illuminate/console": "self.version",
+ "illuminate/container": "self.version",
+ "illuminate/contracts": "self.version",
+ "illuminate/cookie": "self.version",
+ "illuminate/database": "self.version",
+ "illuminate/encryption": "self.version",
+ "illuminate/events": "self.version",
+ "illuminate/filesystem": "self.version",
+ "illuminate/hashing": "self.version",
+ "illuminate/http": "self.version",
+ "illuminate/log": "self.version",
+ "illuminate/macroable": "self.version",
+ "illuminate/mail": "self.version",
+ "illuminate/notifications": "self.version",
+ "illuminate/pagination": "self.version",
+ "illuminate/pipeline": "self.version",
+ "illuminate/queue": "self.version",
+ "illuminate/redis": "self.version",
+ "illuminate/routing": "self.version",
+ "illuminate/session": "self.version",
+ "illuminate/support": "self.version",
+ "illuminate/testing": "self.version",
+ "illuminate/translation": "self.version",
+ "illuminate/validation": "self.version",
+ "illuminate/view": "self.version"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^3.198.1",
+ "doctrine/dbal": "^2.13.3|^3.1.4",
+ "filp/whoops": "^2.14.3",
+ "guzzlehttp/guzzle": "^6.5.5|^7.0.1",
+ "league/flysystem-cached-adapter": "^1.0",
+ "mockery/mockery": "^1.4.4",
+ "orchestra/testbench-core": "^6.27",
+ "pda/pheanstalk": "^4.0",
+ "phpunit/phpunit": "^8.5.19|^9.5.8",
+ "predis/predis": "^1.1.9",
+ "symfony/cache": "^5.1.4"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.198.1).",
+ "brianium/paratest": "Required to run tests in parallel (^6.0).",
+ "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).",
+ "ext-bcmath": "Required to use the multiple_of validation rule.",
+ "ext-ftp": "Required to use the Flysystem FTP driver.",
+ "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
+ "ext-memcached": "Required to use the memcache cache driver.",
+ "ext-pcntl": "Required to use all features of the queue worker.",
+ "ext-posix": "Required to use all features of the queue worker.",
+ "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).",
+ "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
+ "filp/whoops": "Required for friendly error pages in development (^2.14.3).",
+ "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).",
+ "laravel/tinker": "Required to use the tinker console command (^2.0).",
+ "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).",
+ "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).",
+ "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).",
+ "mockery/mockery": "Required to use mocking (^1.4.4).",
+ "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).",
+ "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).",
+ "phpunit/phpunit": "Required to use assertions and run tests (^8.5.19|^9.5.8).",
+ "predis/predis": "Required to use the predis connector (^1.1.9).",
+ "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
+ "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0|^7.0).",
+ "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).",
+ "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).",
+ "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).",
+ "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Illuminate/Collections/helpers.php",
+ "src/Illuminate/Events/functions.php",
+ "src/Illuminate/Foundation/helpers.php",
+ "src/Illuminate/Support/helpers.php"
+ ],
+ "psr-4": {
+ "Illuminate\\": "src/Illuminate/",
+ "Illuminate\\Support\\": [
+ "src/Illuminate/Macroable/",
+ "src/Illuminate/Collections/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Laravel Framework.",
+ "homepage": "https://laravel.com",
+ "keywords": [
+ "framework",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2021-11-16T15:00:50+00:00"
+ },
+ {
+ "name": "laravel/sanctum",
+ "version": "v2.12.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/sanctum.git",
+ "reference": "76b2d552c00477d520338889160f80a0cfb5fc55"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/76b2d552c00477d520338889160f80a0cfb5fc55",
+ "reference": "76b2d552c00477d520338889160f80a0cfb5fc55",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-json": "*",
+ "illuminate/contracts": "^6.9|^7.0|^8.0",
+ "illuminate/database": "^6.9|^7.0|^8.0",
+ "illuminate/support": "^6.9|^7.0|^8.0",
+ "php": "^7.2|^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "^4.0|^5.0|^6.0",
+ "phpunit/phpunit": "^8.0|^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Laravel\\Sanctum\\SanctumServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Sanctum\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
+ "keywords": [
+ "auth",
+ "laravel",
+ "sanctum"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/sanctum/issues",
+ "source": "https://github.com/laravel/sanctum"
+ },
+ "time": "2021-11-16T16:57:07+00:00"
+ },
+ {
+ "name": "laravel/serializable-closure",
+ "version": "v1.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/serializable-closure.git",
+ "reference": "8148e72e6c2c3af7f05640ada1b26c3bca970f8d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8148e72e6c2c3af7f05640ada1b26c3bca970f8d",
+ "reference": "8148e72e6c2c3af7f05640ada1b26c3bca970f8d",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.3|^8.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^1.18",
+ "phpstan/phpstan": "^0.12.98",
+ "symfony/var-dumper": "^5.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\SerializableClosure\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Nuno Maduro",
+ "email": "nuno@laravel.com"
+ }
+ ],
+ "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
+ "keywords": [
+ "closure",
+ "laravel",
+ "serializable"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/serializable-closure/issues",
+ "source": "https://github.com/laravel/serializable-closure"
+ },
+ "time": "2021-11-16T17:01:57+00:00"
+ },
+ {
+ "name": "league/commonmark",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/commonmark.git",
+ "reference": "2df87709f44b0dd733df86aef0830dce9b1f0f13"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/2df87709f44b0dd733df86aef0830dce9b1f0f13",
+ "reference": "2df87709f44b0dd733df86aef0830dce9b1f0f13",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "league/config": "^1.1.1",
+ "php": "^7.4 || ^8.0",
+ "psr/event-dispatcher": "^1.0",
+ "symfony/polyfill-php80": "^1.15"
+ },
+ "require-dev": {
+ "cebe/markdown": "^1.0",
+ "commonmark/cmark": "0.30.0",
+ "commonmark/commonmark.js": "0.30.0",
+ "composer/package-versions-deprecated": "^1.8",
+ "erusev/parsedown": "^1.0",
+ "ext-json": "*",
+ "github/gfm": "0.29.0",
+ "michelf/php-markdown": "^1.4",
+ "phpstan/phpstan": "^0.12.88",
+ "phpunit/phpunit": "^9.5.5",
+ "scrutinizer/ocular": "^1.8.1",
+ "symfony/finder": "^5.3",
+ "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0",
+ "unleashedtech/php-coding-standard": "^3.1",
+ "vimeo/psalm": "^4.7.3"
+ },
+ "suggest": {
+ "symfony/yaml": "v2.3+ required if using the Front Matter extension"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\CommonMark\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
+ "homepage": "https://commonmark.thephpleague.com",
+ "keywords": [
+ "commonmark",
+ "flavored",
+ "gfm",
+ "github",
+ "github-flavored",
+ "markdown",
+ "md",
+ "parser"
+ ],
+ "support": {
+ "docs": "https://commonmark.thephpleague.com/",
+ "forum": "https://github.com/thephpleague/commonmark/discussions",
+ "issues": "https://github.com/thephpleague/commonmark/issues",
+ "rss": "https://github.com/thephpleague/commonmark/releases.atom",
+ "source": "https://github.com/thephpleague/commonmark"
+ },
+ "funding": [
+ {
+ "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.colinodell.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.paypal.me/colinpodell/10.00",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/colinodell",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/colinodell",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/league/commonmark",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-14T14:06:04+00:00"
+ },
+ {
+ "name": "league/config",
+ "version": "v1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/config.git",
+ "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
+ "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "dflydev/dot-access-data": "^3.0.1",
+ "nette/schema": "^1.2",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12.90",
+ "phpunit/phpunit": "^9.5.5",
+ "scrutinizer/ocular": "^1.8.1",
+ "unleashedtech/php-coding-standard": "^3.1",
+ "vimeo/psalm": "^4.7.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Config\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Define configuration arrays with strict schemas and access values with dot notation",
+ "homepage": "https://config.thephpleague.com",
+ "keywords": [
+ "array",
+ "config",
+ "configuration",
+ "dot",
+ "dot-access",
+ "nested",
+ "schema"
+ ],
+ "support": {
+ "docs": "https://config.thephpleague.com/",
+ "issues": "https://github.com/thephpleague/config/issues",
+ "rss": "https://github.com/thephpleague/config/releases.atom",
+ "source": "https://github.com/thephpleague/config"
+ },
+ "funding": [
+ {
+ "url": "https://www.colinodell.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.paypal.me/colinpodell/10.00",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/colinodell",
+ "type": "github"
+ }
+ ],
+ "time": "2021-08-14T12:15:32+00:00"
+ },
+ {
+ "name": "league/flysystem",
+ "version": "1.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem.git",
+ "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/18634df356bfd4119fe3d6156bdb990c414c14ea",
+ "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "league/mime-type-detection": "^1.3",
+ "php": "^7.2.5 || ^8.0"
+ },
+ "conflict": {
+ "league/flysystem-sftp": "<1.0.6"
+ },
+ "require-dev": {
+ "phpspec/prophecy": "^1.11.1",
+ "phpunit/phpunit": "^8.5.8"
+ },
+ "suggest": {
+ "ext-ftp": "Allows you to use FTP server storage",
+ "ext-openssl": "Allows you to use FTPS server storage",
+ "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+ "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+ "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+ "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+ "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+ "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+ "league/flysystem-webdav": "Allows you to use WebDAV storage",
+ "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+ "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+ "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Filesystem abstraction: Many filesystems, one API.",
+ "keywords": [
+ "Cloud Files",
+ "WebDAV",
+ "abstraction",
+ "aws",
+ "cloud",
+ "copy.com",
+ "dropbox",
+ "file systems",
+ "files",
+ "filesystem",
+ "filesystems",
+ "ftp",
+ "rackspace",
+ "remote",
+ "s3",
+ "sftp",
+ "storage"
+ ],
+ "support": {
+ "issues": "https://github.com/thephpleague/flysystem/issues",
+ "source": "https://github.com/thephpleague/flysystem/tree/1.1.5"
+ },
+ "funding": [
+ {
+ "url": "https://offset.earth/frankdejonge",
+ "type": "other"
+ }
+ ],
+ "time": "2021-08-17T13:49:42+00:00"
+ },
+ {
+ "name": "league/mime-type-detection",
+ "version": "1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/mime-type-detection.git",
+ "reference": "b38b25d7b372e9fddb00335400467b223349fd7e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b38b25d7b372e9fddb00335400467b223349fd7e",
+ "reference": "b38b25d7b372e9fddb00335400467b223349fd7e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.18",
+ "phpstan/phpstan": "^0.12.68",
+ "phpunit/phpunit": "^8.5.8 || ^9.3"
+ },
+ "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.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/frankdejonge",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-09-25T08:23:19+00:00"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "2.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "fd4380d6fc37626e2f799f29d91195040137eba9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9",
+ "reference": "fd4380d6fc37626e2f799f29d91195040137eba9",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2",
+ "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^7",
+ "graylog2/gelf-php": "^1.4.2",
+ "mongodb/mongodb": "^1.8",
+ "php-amqplib/php-amqplib": "~2.4 || ^3",
+ "php-console/php-console": "^3.1.3",
+ "phpspec/prophecy": "^1.6.1",
+ "phpstan/phpstan": "^0.12.91",
+ "phpunit/phpunit": "^8.5",
+ "predis/predis": "^1.1",
+ "rollbar/rollbar": "^1.3",
+ "ruflin/elastica": ">=0.90@dev",
+ "swiftmailer/swiftmailer": "^5.3|^6.0"
+ },
+ "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",
+ "php-console/php-console": "Allow sending log messages to Google Chrome",
+ "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": "2.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/2.3.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-01T21:08:31+00:00"
+ },
+ {
+ "name": "nesbot/carbon",
+ "version": "2.54.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/briannesbitt/Carbon.git",
+ "reference": "eed83939f1aed3eee517d03a33f5ec587ac529b5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/eed83939f1aed3eee517d03a33f5ec587ac529b5",
+ "reference": "eed83939f1aed3eee517d03a33f5ec587ac529b5",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.1.8 || ^8.0",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/translation": "^3.4 || ^4.0 || ^5.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.0 || ^3.0",
+ "doctrine/orm": "^2.7",
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "kylekatarnls/multi-tester": "^2.0",
+ "phpmd/phpmd": "^2.9",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12.54",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.14",
+ "squizlabs/php_codesniffer": "^3.4"
+ },
+ "bin": [
+ "bin/carbon"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-3.x": "3.x-dev",
+ "dev-master": "2.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Carbon\\Laravel\\ServiceProvider"
+ ]
+ },
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ }
+ },
+ "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": {
+ "issues": "https://github.com/briannesbitt/Carbon/issues",
+ "source": "https://github.com/briannesbitt/Carbon"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/Carbon",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-11-01T21:22:20+00:00"
+ },
+ {
+ "name": "nette/schema",
+ "version": "v1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/schema.git",
+ "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df",
+ "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
+ "php": ">=7.1 <8.2"
+ },
+ "require-dev": {
+ "nette/tester": "^2.3 || ^2.4",
+ "phpstan/phpstan-nette": "^0.12",
+ "tracy/tracy": "^2.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "📐 Nette Schema: validating data structures against a given Schema.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "config",
+ "nette"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/schema/issues",
+ "source": "https://github.com/nette/schema/tree/v1.2.2"
+ },
+ "time": "2021-10-15T11:40:02+00:00"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v3.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/9cd80396ca58d7969ab44fc7afcf03624dfa526e",
+ "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2 <8.2"
+ },
+ "conflict": {
+ "nette/di": "<3.0.6"
+ },
+ "require-dev": {
+ "nette/tester": "~2.0",
+ "phpstan/phpstan": "^0.12",
+ "tracy/tracy": "^2.3"
+ },
+ "suggest": {
+ "ext-gd": "to use Image",
+ "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+ "ext-json": "to use Nette\\Utils\\Json",
+ "ext-mbstring": "to use Strings::lower() etc...",
+ "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
+ "ext-xml": "to use Strings::length() etc. when mbstring is not available"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "array",
+ "core",
+ "datetime",
+ "images",
+ "json",
+ "nette",
+ "paginator",
+ "password",
+ "slugify",
+ "string",
+ "unicode",
+ "utf-8",
+ "utility",
+ "validation"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/utils/issues",
+ "source": "https://github.com/nette/utils/tree/v3.2.5"
+ },
+ "time": "2021-09-20T10:50:11+00:00"
+ },
+ {
+ "name": "nosun/laravel-ueditor",
+ "version": "v2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nosun/laravel-ueditor.git",
+ "reference": "3f6c9b9f187863f7bdf48bae94b799883d5b64a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nosun/laravel-ueditor/zipball/3f6c9b9f187863f7bdf48bae94b799883d5b64a2",
+ "reference": "3f6c9b9f187863f7bdf48bae94b799883d5b64a2",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "laravel/framework": "~5.5|~6.0|~7.0|~8.0"
+ },
+ "require-dev": {
+ "fabpot/php-cs-fixer": "^1.10"
+ },
+ "suggest": {
+ "overtrue/laravel-filesystem-qiniu": "如果你想要使用七牛云存储,也许你需要安装它哦~"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Nosun\\LaravelUEditor\\UEditorServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Nosun\\LaravelUEditor\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "nosun",
+ "email": "nosun2008@126.com"
+ }
+ ],
+ "description": "UEditor integration for Laravel.",
+ "support": {
+ "source": "https://github.com/nosun/laravel-ueditor/tree/v2.0.3"
+ },
+ "time": "2020-11-06T09:17:38+00:00"
+ },
+ {
+ "name": "nosun/ueditor",
+ "version": "v3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nosun/UEditor.git",
+ "reference": "02c51e93598e171d52b592391e17ca87e59dda73"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nosun/UEditor/zipball/02c51e93598e171d52b592391e17ca87e59dda73",
+ "reference": "02c51e93598e171d52b592391e17ca87e59dda73",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "encore/laravel-admin": "~1.7",
+ "nosun/laravel-ueditor": "~2.0",
+ "php": ">=7.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~6.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Nosun\\Ueditor\\UeditorServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Nosun\\Ueditor\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "nosun",
+ "email": "nosun2008@126.com"
+ }
+ ],
+ "description": "UEditor extension for laravel-admin",
+ "homepage": "https://github.com/nosun/UEditor",
+ "keywords": [
+ "extension",
+ "laravel-admin",
+ "ueditor"
+ ],
+ "support": {
+ "source": "https://github.com/nosun/UEditor/tree/v3.0.2"
+ },
+ "time": "2020-11-06T09:18:48+00:00"
+ },
+ {
+ "name": "nwidart/laravel-modules",
+ "version": "8.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nWidart/laravel-modules.git",
+ "reference": "6ade5ec19e81a0e4807834886a2c47509d069cb7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/6ade5ec19e81a0e4807834886a2c47509d069cb7",
+ "reference": "6ade5ec19e81a0e4807834886a2c47509d069cb7",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-json": "*",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.16",
+ "laravel/framework": "^8.0",
+ "mockery/mockery": "~1.0",
+ "orchestra/testbench": "^6.2",
+ "phpstan/phpstan": "^0.12.14",
+ "phpunit/phpunit": "^8.5",
+ "spatie/phpunit-snapshot-assertions": "^2.1.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Nwidart\\Modules\\LaravelModulesServiceProvider"
+ ],
+ "aliases": {
+ "Module": "Nwidart\\Modules\\Facades\\Module"
+ }
+ },
+ "branch-alias": {
+ "dev-master": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Nwidart\\Modules\\": "src"
+ },
+ "files": [
+ "src/helpers.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Widart",
+ "email": "n.widart@gmail.com",
+ "homepage": "https://nicolaswidart.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "Laravel Module management",
+ "keywords": [
+ "laravel",
+ "module",
+ "modules",
+ "nwidart",
+ "rad"
+ ],
+ "support": {
+ "issues": "https://github.com/nWidart/laravel-modules/issues",
+ "source": "https://github.com/nWidart/laravel-modules/tree/8.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/nwidart",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-11T09:24:22+00:00"
+ },
+ {
+ "name": "opis/closure",
+ "version": "3.6.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/opis/closure.git",
+ "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6",
+ "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^5.4 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "jeremeamia/superclosure": "^2.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Opis\\Closure\\": "src/"
+ },
+ "files": [
+ "functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marius Sarca",
+ "email": "marius.sarca@gmail.com"
+ },
+ {
+ "name": "Sorin Sarca",
+ "email": "sarca_sorin@hotmail.com"
+ }
+ ],
+ "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+ "homepage": "https://opis.io/closure",
+ "keywords": [
+ "anonymous functions",
+ "closure",
+ "function",
+ "serializable",
+ "serialization",
+ "serialize"
+ ],
+ "support": {
+ "issues": "https://github.com/opis/closure/issues",
+ "source": "https://github.com/opis/closure/tree/3.6.2"
+ },
+ "time": "2021-04-09T13:42:10+00:00"
+ },
+ {
+ "name": "paragonie/constant_time_encoding",
+ "version": "v2.6.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/constant_time_encoding.git",
+ "reference": "58c3f47f650c94ec05a151692652a868995d2938"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938",
+ "reference": "58c3f47f650c94ec05a151692652a868995d2938",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7|^8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6|^7|^8|^9",
+ "vimeo/psalm": "^1|^2|^3|^4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "ParagonIE\\ConstantTime\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com",
+ "role": "Maintainer"
+ },
+ {
+ "name": "Steve 'Sc00bz' Thomas",
+ "email": "steve@tobtu.com",
+ "homepage": "https://www.tobtu.com",
+ "role": "Original Developer"
+ }
+ ],
+ "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
+ "keywords": [
+ "base16",
+ "base32",
+ "base32_decode",
+ "base32_encode",
+ "base64",
+ "base64_decode",
+ "base64_encode",
+ "bin2hex",
+ "encoding",
+ "hex",
+ "hex2bin",
+ "rfc4648"
+ ],
+ "support": {
+ "email": "info@paragonie.com",
+ "issues": "https://github.com/paragonie/constant_time_encoding/issues",
+ "source": "https://github.com/paragonie/constant_time_encoding"
+ },
+ "time": "2022-06-14T06:56:20+00:00"
+ },
+ {
+ "name": "phpoption/phpoption",
+ "version": "1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/schmittjoh/php-option.git",
+ "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/5455cb38aed4523f99977c4a12ef19da4bfe2a28",
+ "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "phpunit/phpunit": "^6.5.14 || ^7.0.20 || ^8.5.19 || ^9.5.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.8-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"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk"
+ }
+ ],
+ "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.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-28T21:27:29+00:00"
+ },
+ {
+ "name": "pragmarx/google2fa",
+ "version": "v8.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antonioribeiro/google2fa.git",
+ "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3",
+ "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "paragonie/constant_time_encoding": "^1.0|^2.0",
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12.18",
+ "phpunit/phpunit": "^7.5.15|^8.5|^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PragmaRX\\Google2FA\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Antonio Carlos Ribeiro",
+ "email": "acr@antoniocarlosribeiro.com",
+ "role": "Creator & Designer"
+ }
+ ],
+ "description": "A One Time Password Authentication package, compatible with Google Authenticator.",
+ "keywords": [
+ "2fa",
+ "Authentication",
+ "Two Factor Authentication",
+ "google2fa"
+ ],
+ "support": {
+ "issues": "https://github.com/antonioribeiro/google2fa/issues",
+ "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1"
+ },
+ "time": "2022-06-13T21:57:56+00:00"
+ },
+ {
+ "name": "pragmarx/google2fa-laravel",
+ "version": "v2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antonioribeiro/google2fa-laravel.git",
+ "reference": "d8243b8f812472f1112716c5462157e0ec128fce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/d8243b8f812472f1112716c5462157e0ec128fce",
+ "reference": "d8243b8f812472f1112716c5462157e0ec128fce",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "laravel/framework": ">=5.4.36|^8.0|^9.0",
+ "php": ">=7.0",
+ "pragmarx/google2fa-qrcode": "^1.0|^2.0|^3.0"
+ },
+ "require-dev": {
+ "bacon/bacon-qr-code": "^2.0",
+ "orchestra/testbench": "3.4.*|3.5.*|3.6.*|3.7.*|4.*|5.*|6.*",
+ "phpunit/phpunit": "~5|~6|~7|~8|~9"
+ },
+ "suggest": {
+ "bacon/bacon-qr-code": "Required to generate inline QR Codes.",
+ "pragmarx/recovery": "Generate recovery codes."
+ },
+ "type": "library",
+ "extra": {
+ "component": "package",
+ "frameworks": [
+ "Laravel"
+ ],
+ "branch-alias": {
+ "dev-master": "0.2-dev"
+ },
+ "laravel": {
+ "providers": [
+ "PragmaRX\\Google2FALaravel\\ServiceProvider"
+ ],
+ "aliases": {
+ "Google2FA": "PragmaRX\\Google2FALaravel\\Facade"
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PragmaRX\\Google2FALaravel\\": "src/",
+ "PragmaRX\\Google2FALaravel\\Tests\\": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Antonio Carlos Ribeiro",
+ "email": "acr@antoniocarlosribeiro.com",
+ "role": "Creator & Designer"
+ }
+ ],
+ "description": "A One Time Password Authentication package, compatible with Google Authenticator.",
+ "keywords": [
+ "Authentication",
+ "Two Factor Authentication",
+ "google2fa",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/antonioribeiro/google2fa-laravel/issues",
+ "source": "https://github.com/antonioribeiro/google2fa-laravel/tree/v2.0.2"
+ },
+ "time": "2022-03-08T18:03:04+00:00"
+ },
+ {
+ "name": "pragmarx/google2fa-qrcode",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antonioribeiro/google2fa-qrcode.git",
+ "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/ce4d8a729b6c93741c607cfb2217acfffb5bf76b",
+ "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1",
+ "pragmarx/google2fa": ">=4.0"
+ },
+ "require-dev": {
+ "bacon/bacon-qr-code": "^2.0",
+ "chillerlan/php-qrcode": "^1.0|^2.0|^3.0|^4.0",
+ "khanamiryan/qrcode-detector-decoder": "^1.0",
+ "phpunit/phpunit": "~4|~5|~6|~7|~8|~9"
+ },
+ "suggest": {
+ "bacon/bacon-qr-code": "For QR Code generation, requires imagick",
+ "chillerlan/php-qrcode": "For QR Code generation"
+ },
+ "type": "library",
+ "extra": {
+ "component": "package",
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PragmaRX\\Google2FAQRCode\\": "src/",
+ "PragmaRX\\Google2FAQRCode\\Tests\\": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Antonio Carlos Ribeiro",
+ "email": "acr@antoniocarlosribeiro.com",
+ "role": "Creator & Designer"
+ }
+ ],
+ "description": "QR Code package for Google2FA",
+ "keywords": [
+ "2fa",
+ "Authentication",
+ "Two Factor Authentication",
+ "google2fa",
+ "qr code",
+ "qrcode"
+ ],
+ "support": {
+ "issues": "https://github.com/antonioribeiro/google2fa-qrcode/issues",
+ "source": "https://github.com/antonioribeiro/google2fa-qrcode/tree/v3.0.0"
+ },
+ "time": "2021-08-15T12:53:48+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "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/1.1.2"
+ },
+ "time": "2021-11-05T16:50:12+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.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+ "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+ "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"
+ },
+ "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": "http://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/tree/master"
+ },
+ "time": "2020-06-29T06:28:15+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+ "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.0.0",
+ "psr/http-message": "^1.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": "http://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/master"
+ },
+ "time": "2019-04-30T12:38:16+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=5.3.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": "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/master"
+ },
+ "time": "2016-08-06T14:39:51+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "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/1.1.4"
+ },
+ "time": "2021-05-03T11:20:27+00:00"
+ },
+ {
+ "name": "psr/simple-cache",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/simple-cache.git",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://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/master"
+ },
+ "time": "2017-10-23T01:57:42+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": "ramsey/collection",
+ "version": "1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/collection.git",
+ "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a",
+ "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.3 || ^8",
+ "symfony/polyfill-php81": "^1.23"
+ },
+ "require-dev": {
+ "captainhook/captainhook": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+ "ergebnis/composer-normalize": "^2.6",
+ "fakerphp/faker": "^1.5",
+ "hamcrest/hamcrest-php": "^2",
+ "jangregor/phpstan-prophecy": "^0.8",
+ "mockery/mockery": "^1.3",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpstan/extension-installer": "^1",
+ "phpstan/phpstan": "^0.12.32",
+ "phpstan/phpstan-mockery": "^0.12.5",
+ "phpstan/phpstan-phpunit": "^0.12.11",
+ "phpunit/phpunit": "^8.5 || ^9",
+ "psy/psysh": "^0.10.4",
+ "slevomat/coding-standard": "^6.3",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^4.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Collection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ben Ramsey",
+ "email": "ben@benramsey.com",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "description": "A PHP library for representing and manipulating collections.",
+ "keywords": [
+ "array",
+ "collection",
+ "hash",
+ "map",
+ "queue",
+ "set"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/collection/issues",
+ "source": "https://github.com/ramsey/collection/tree/1.2.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ramsey",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-10T03:01:02+00:00"
+ },
+ {
+ "name": "ramsey/uuid",
+ "version": "4.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/uuid.git",
+ "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df",
+ "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "brick/math": "^0.8 || ^0.9",
+ "ext-json": "*",
+ "php": "^7.2 || ^8.0",
+ "ramsey/collection": "^1.0",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-php80": "^1.14"
+ },
+ "replace": {
+ "rhumsaa/uuid": "self.version"
+ },
+ "require-dev": {
+ "captainhook/captainhook": "^5.10",
+ "captainhook/plugin-composer": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+ "doctrine/annotations": "^1.8",
+ "ergebnis/composer-normalize": "^2.15",
+ "mockery/mockery": "^1.3",
+ "moontoast/math": "^1.1",
+ "paragonie/random-lib": "^2",
+ "php-mock/php-mock": "^2.2",
+ "php-mock/php-mock-mockery": "^1.3",
+ "php-parallel-lint/php-parallel-lint": "^1.1",
+ "phpbench/phpbench": "^1.0",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-mockery": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^8.5 || ^9",
+ "slevomat/coding-standard": "^7.0",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^4.9"
+ },
+ "suggest": {
+ "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+ "ext-ctype": "Enables faster processing of character classification using ctype functions.",
+ "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+ "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
+ "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.x-dev"
+ },
+ "captainhook": {
+ "force-install": true
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Uuid\\": "src/"
+ },
+ "files": [
+ "src/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+ "keywords": [
+ "guid",
+ "identifier",
+ "uuid"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/uuid/issues",
+ "source": "https://github.com/ramsey/uuid/tree/4.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ramsey",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-09-25T23:10:38+00:00"
+ },
+ {
+ "name": "simplesoftwareio/simple-qrcode",
+ "version": "4.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git",
+ "reference": "916db7948ca6772d54bb617259c768c9cdc8d537"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/916db7948ca6772d54bb617259c768c9cdc8d537",
+ "reference": "916db7948ca6772d54bb617259c768c9cdc8d537",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "bacon/bacon-qr-code": "^2.0",
+ "ext-gd": "*",
+ "php": ">=7.2|^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1",
+ "phpunit/phpunit": "~9"
+ },
+ "suggest": {
+ "ext-imagick": "Allows the generation of PNG QrCodes.",
+ "illuminate/support": "Allows for use within Laravel."
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "SimpleSoftwareIO\\QrCode\\QrCodeServiceProvider"
+ ],
+ "aliases": {
+ "QrCode": "SimpleSoftwareIO\\QrCode\\Facades\\QrCode"
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SimpleSoftwareIO\\QrCode\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Simple Software LLC",
+ "email": "support@simplesoftware.io"
+ }
+ ],
+ "description": "Simple QrCode is a QR code generator made for Laravel.",
+ "homepage": "https://www.simplesoftware.io/#/docs/simple-qrcode",
+ "keywords": [
+ "Simple",
+ "generator",
+ "laravel",
+ "qrcode",
+ "wrapper"
+ ],
+ "support": {
+ "issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues",
+ "source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/4.2.0"
+ },
+ "time": "2021-02-08T20:43:55+00:00"
+ },
+ {
+ "name": "swiftmailer/swiftmailer",
+ "version": "v6.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swiftmailer/swiftmailer.git",
+ "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c",
+ "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "egulias/email-validator": "^2.0|^3.1",
+ "php": ">=7.0.0",
+ "symfony/polyfill-iconv": "^1.0",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "symfony/phpunit-bridge": "^4.4|^5.4"
+ },
+ "suggest": {
+ "ext-intl": "Needed to support internationalized email addresses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.2-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "lib/swift_required.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Corbyn"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Swiftmailer, free feature-rich PHP mailer",
+ "homepage": "https://swiftmailer.symfony.com",
+ "keywords": [
+ "email",
+ "mail",
+ "mailer"
+ ],
+ "support": {
+ "issues": "https://github.com/swiftmailer/swiftmailer/issues",
+ "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-18T15:26:12+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v5.3.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3",
+ "reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php73": "^1.8",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/string": "^5.1"
+ },
+ "conflict": {
+ "psr/log": ">=3",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/dotenv": "<5.1",
+ "symfony/event-dispatcher": "<4.4",
+ "symfony/lock": "<4.4",
+ "symfony/process": "<4.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/lock": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
+ },
+ "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/v5.3.10"
+ },
+ "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": "2021-10-26T09:30:15+00:00"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v5.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "7fb120adc7f600a59027775b224c13a33530dd90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90",
+ "reference": "7fb120adc7f600a59027775b224c13a33530dd90",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Converts CSS selectors to XPath expressions",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/css-selector/tree/v5.3.4"
+ },
+ "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": "2021-07-21T12:38:00+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
+ "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "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/v2.4.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": "2021-03-23T23:28:01+00:00"
+ },
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v5.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
+ "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "conflict": {
+ "masterminds/html5": "<2.6"
+ },
+ "require-dev": {
+ "masterminds/html5": "^2.6",
+ "symfony/css-selector": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/css-selector": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "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 DOM navigation for HTML and XML documents",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/dom-crawler/tree/v5.3.7"
+ },
+ "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": "2021-08-29T19:32:13+00:00"
+ },
+ {
+ "name": "symfony/error-handler",
+ "version": "v5.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/error-handler.git",
+ "reference": "3bc60d0fba00ae8d1eaa9eb5ab11a2bbdd1fc321"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/3bc60d0fba00ae8d1eaa9eb5ab11a2bbdd1fc321",
+ "reference": "3bc60d0fba00ae8d1eaa9eb5ab11a2bbdd1fc321",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/log": "^1|^2|^3",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "require-dev": {
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/serializer": "^4.4|^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ErrorHandler\\": ""
+ },
+ "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 manage errors and ease debugging PHP code",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/error-handler/tree/v5.3.7"
+ },
+ "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": "2021-08-28T15:07:08+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v5.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "ce7b20d69c66a20939d8952b617506a44d102130"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ce7b20d69c66a20939d8952b617506a44d102130",
+ "reference": "ce7b20d69c66a20939d8952b617506a44d102130",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/event-dispatcher-contracts": "^2",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<4.4"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "2.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/error-handler": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/stopwatch": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "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 that allow your application components to communicate with each other by dispatching events and listening to them",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.7"
+ },
+ "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": "2021-08-04T21:20:46+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher-contracts",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+ "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11",
+ "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/event-dispatcher": "^1"
+ },
+ "suggest": {
+ "symfony/event-dispatcher-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "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 dispatching event",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.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": "2021-03-23T23:28:01+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v5.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/a10000ada1e600d109a6c7632e9ac42e8bf2fb93",
+ "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "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": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v5.3.7"
+ },
+ "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": "2021-08-04T21:20:46+00:00"
+ },
+ {
+ "name": "symfony/http-client-contracts",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client-contracts.git",
+ "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
+ "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5"
+ },
+ "suggest": {
+ "symfony/http-client-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\HttpClient\\": ""
+ }
+ },
+ "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 HTTP clients",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.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": "2021-04-11T23:07:08+00:00"
+ },
+ {
+ "name": "symfony/http-foundation",
+ "version": "v5.3.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-foundation.git",
+ "reference": "9f34f02e8a5fdc7a56bafe011cea1ce97300e54c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9f34f02e8a5fdc7a56bafe011cea1ce97300e54c",
+ "reference": "9f34f02e8a5fdc7a56bafe011cea1ce97300e54c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/polyfill-mbstring": "~1.1",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "require-dev": {
+ "predis/predis": "~1.0",
+ "symfony/cache": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/mime": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/mime": "To use the file extension guesser"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpFoundation\\": ""
+ },
+ "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": "Defines an object-oriented layer for the HTTP specification",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/http-foundation/tree/v5.3.10"
+ },
+ "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": "2021-10-11T15:41:55+00:00"
+ },
+ {
+ "name": "symfony/http-kernel",
+ "version": "v5.3.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-kernel.git",
+ "reference": "703e4079920468e9522b72cf47fd76ce8d795e86"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/703e4079920468e9522b72cf47fd76ce8d795e86",
+ "reference": "703e4079920468e9522b72cf47fd76ce8d795e86",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/log": "^1|^2",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/error-handler": "^4.4|^5.0",
+ "symfony/event-dispatcher": "^5.0",
+ "symfony/http-client-contracts": "^1.1|^2",
+ "symfony/http-foundation": "^5.3.7",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-php73": "^1.9",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "conflict": {
+ "symfony/browser-kit": "<4.4",
+ "symfony/cache": "<5.0",
+ "symfony/config": "<5.0",
+ "symfony/console": "<4.4",
+ "symfony/dependency-injection": "<5.3",
+ "symfony/doctrine-bridge": "<5.0",
+ "symfony/form": "<5.0",
+ "symfony/http-client": "<5.0",
+ "symfony/mailer": "<5.0",
+ "symfony/messenger": "<5.0",
+ "symfony/translation": "<5.0",
+ "symfony/twig-bridge": "<5.0",
+ "symfony/validator": "<5.0",
+ "twig/twig": "<2.13"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0"
+ },
+ "require-dev": {
+ "psr/cache": "^1.0|^2.0|^3.0",
+ "symfony/browser-kit": "^4.4|^5.0",
+ "symfony/config": "^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/css-selector": "^4.4|^5.0",
+ "symfony/dependency-injection": "^5.3",
+ "symfony/dom-crawler": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/routing": "^4.4|^5.0",
+ "symfony/stopwatch": "^4.4|^5.0",
+ "symfony/translation": "^4.4|^5.0",
+ "symfony/translation-contracts": "^1.1|^2",
+ "twig/twig": "^2.13|^3.0.4"
+ },
+ "suggest": {
+ "symfony/browser-kit": "",
+ "symfony/config": "",
+ "symfony/console": "",
+ "symfony/dependency-injection": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpKernel\\": ""
+ },
+ "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 a structured process for converting a Request into a Response",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/http-kernel/tree/v5.3.10"
+ },
+ "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": "2021-10-29T08:36:48+00:00"
+ },
+ {
+ "name": "symfony/mime",
+ "version": "v5.3.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mime.git",
+ "reference": "a756033d0a7e53db389618653ae991eba5a19a11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/a756033d0a7e53db389618653ae991eba5a19a11",
+ "reference": "a756033d0a7e53db389618653ae991eba5a19a11",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "conflict": {
+ "egulias/email-validator": "~3.0.0",
+ "phpdocumentor/reflection-docblock": "<3.2.2",
+ "phpdocumentor/type-resolver": "<1.4.0",
+ "symfony/mailer": "<4.4"
+ },
+ "require-dev": {
+ "egulias/email-validator": "^2.1.10|^3.1",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/property-access": "^4.4|^5.1",
+ "symfony/property-info": "^4.4|^5.1",
+ "symfony/serializer": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mime\\": ""
+ },
+ "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": "Allows manipulating MIME messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "mime",
+ "mime-type"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/mime/tree/v5.3.8"
+ },
+ "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": "2021-09-10T12:30:38+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "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.23.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": "2021-02-19T12:13:01+00:00"
+ },
+ {
+ "name": "symfony/polyfill-iconv",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-iconv.git",
+ "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933",
+ "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-iconv": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Iconv\\": ""
+ },
+ "files": [
+ "bootstrap.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": "Symfony polyfill for the Iconv extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "iconv",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.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": "2021-05-27T09:27:20+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.23.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "16880ba9c5ebe3642d1995ab866db29270b36535"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535",
+ "reference": "16880ba9c5ebe3642d1995ab866db29270b36535",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ },
+ "files": [
+ "bootstrap.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": "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.23.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": "2021-05-27T12:26:48+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65",
+ "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1",
+ "symfony/polyfill-intl-normalizer": "^1.10",
+ "symfony/polyfill-php72": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Trevor Rowbotham",
+ "email": "trevor.rowbotham@pm.me"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "idn",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.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": "2021-05-27T09:27:20+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
+ "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "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.23.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": "2021-02-19T12:13:01+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.23.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6",
+ "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.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": "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.23.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": "2021-05-27T12:26:48+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php72",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php72.git",
+ "reference": "9a142215a36a3888e30d0a9eeea9766764e96976"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976",
+ "reference": "9a142215a36a3888e30d0a9eeea9766764e96976",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php72\\": ""
+ },
+ "files": [
+ "bootstrap.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": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.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": "2021-05-27T09:17:38+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010",
+ "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "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 backporting some PHP 7.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.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": "2021-02-19T12:13:01+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.23.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be",
+ "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "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.23.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": "2021-07-28T13:41:28+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php81",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php81.git",
+ "reference": "e66119f3de95efc359483f810c4c3e6436279436"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436",
+ "reference": "e66119f3de95efc359483f810c4c3e6436279436",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php81\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "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 backporting some PHP 8.1+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.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": "2021-05-21T13:25:03+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v5.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/38f26c7d6ed535217ea393e05634cb0b244a1967",
+ "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "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/v5.3.7"
+ },
+ "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": "2021-08-04T21:20:46+00:00"
+ },
+ {
+ "name": "symfony/routing",
+ "version": "v5.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/routing.git",
+ "reference": "be865017746fe869007d94220ad3f5297951811b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/be865017746fe869007d94220ad3f5297951811b",
+ "reference": "be865017746fe869007d94220ad3f5297951811b",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "conflict": {
+ "doctrine/annotations": "<1.12",
+ "symfony/config": "<5.3",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/yaml": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^1.12",
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^5.3",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/config": "For using the all-in-one router or any loader",
+ "symfony/expression-language": "For using expression matching",
+ "symfony/http-foundation": "For using a Symfony Request object",
+ "symfony/yaml": "For using the YAML loader"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Routing\\": ""
+ },
+ "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": "Maps an HTTP request to a set of configuration variables",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "router",
+ "routing",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/routing/tree/v5.3.7"
+ },
+ "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": "2021-08-04T21:42:42+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
+ "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.1"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "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/v2.4.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": "2021-04-01T10:43:52+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v5.3.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c",
+ "reference": "d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php80": "~1.15"
+ },
+ "require-dev": {
+ "symfony/error-handler": "^4.4|^5.0",
+ "symfony/http-client": "^4.4|^5.0",
+ "symfony/translation-contracts": "^1.1|^2",
+ "symfony/var-exporter": "^4.4|^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "files": [
+ "Resources/functions.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 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/v5.3.10"
+ },
+ "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": "2021-10-27T18:21:46+00:00"
+ },
+ {
+ "name": "symfony/translation",
+ "version": "v5.3.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation.git",
+ "reference": "6ef197aea2ac8b9cd63e0da7522b3771714035aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/6ef197aea2ac8b9cd63e0da7522b3771714035aa",
+ "reference": "6ef197aea2ac8b9cd63e0da7522b3771714035aa",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/translation-contracts": "^2.3"
+ },
+ "conflict": {
+ "symfony/config": "<4.4",
+ "symfony/dependency-injection": "<5.0",
+ "symfony/http-kernel": "<5.0",
+ "symfony/twig-bundle": "<5.0",
+ "symfony/yaml": "<4.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "2.3"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/dependency-injection": "^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/http-kernel": "^5.0",
+ "symfony/intl": "^4.4|^5.0",
+ "symfony/polyfill-intl-icu": "^1.21",
+ "symfony/service-contracts": "^1.1.2|^2",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "psr/log-implementation": "To use logging capability in translator",
+ "symfony/config": "",
+ "symfony/yaml": ""
+ },
+ "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/v5.3.10"
+ },
+ "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": "2021-10-10T06:43:24+00:00"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "95c812666f3e91db75385749fe219c5e494c7f95"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95",
+ "reference": "95c812666f3e91db75385749fe219c5e494c7f95",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5"
+ },
+ "suggest": {
+ "symfony/translation-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ }
+ },
+ "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/v2.4.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": "2021-03-23T23:28:01+00:00"
+ },
+ {
+ "name": "symfony/var-dumper",
+ "version": "v5.3.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "875432adb5f5570fff21036fd22aee244636b7d1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/875432adb5f5570fff21036fd22aee244636b7d1",
+ "reference": "875432adb5f5570fff21036fd22aee244636b7d1",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<5.4.3",
+ "symfony/console": "<4.4"
+ },
+ "require-dev": {
+ "ext-iconv": "*",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "twig/twig": "^2.13|^3.0.4"
+ },
+ "suggest": {
+ "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+ "ext-intl": "To show region name in time zone dump",
+ "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+ },
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions/dump.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\VarDumper\\": ""
+ },
+ "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 mechanisms for walking through any arbitrary PHP variable",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "debug",
+ "dump"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/var-dumper/tree/v5.3.10"
+ },
+ "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": "2021-10-26T09:30:15+00:00"
+ },
+ {
+ "name": "tijsverkoyen/css-to-inline-styles",
+ "version": "2.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git",
+ "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/b43b05cf43c1b6d849478965062b6ef73e223bb5",
+ "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "php": "^5.5 || ^7.0 || ^8.0",
+ "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "TijsVerkoyen\\CssToInlineStyles\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Tijs Verkoyen",
+ "email": "css_to_inline_styles@verkoyen.eu",
+ "role": "Developer"
+ }
+ ],
+ "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
+ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
+ "support": {
+ "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues",
+ "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.3"
+ },
+ "time": "2020-07-13T06:12:54+00:00"
+ },
+ {
+ "name": "vlucas/phpdotenv",
+ "version": "v5.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vlucas/phpdotenv.git",
+ "reference": "d4394d044ed69a8f244f3445bcedf8a0d7fe2403"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/d4394d044ed69a8f244f3445bcedf8a0d7fe2403",
+ "reference": "d4394d044ed69a8f244f3445bcedf8a0d7fe2403",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-pcre": "*",
+ "graham-campbell/result-type": "^1.0.2",
+ "php": "^7.1.3 || ^8.0",
+ "phpoption/phpoption": "^1.8",
+ "symfony/polyfill-ctype": "^1.23",
+ "symfony/polyfill-mbstring": "^1.23.1",
+ "symfony/polyfill-php80": "^1.23.1"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "ext-filter": "*",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10"
+ },
+ "suggest": {
+ "ext-filter": "Required to use the boolean validator."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.4-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"
+ },
+ {
+ "name": "Vance Lucas",
+ "email": "vance@vancelucas.com"
+ }
+ ],
+ "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.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-11-10T01:08:39+00:00"
+ },
+ {
+ "name": "voku/portable-ascii",
+ "version": "1.5.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/voku/portable-ascii.git",
+ "reference": "80953678b19901e5165c56752d087fc11526017c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/voku/portable-ascii/zipball/80953678b19901e5165c56752d087fc11526017c",
+ "reference": "80953678b19901e5165c56752d087fc11526017c",
+ "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": "http://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/1.5.6"
+ },
+ "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": "2020-11-12T00:07:28+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
+ "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<0.12.20",
+ "vimeo/psalm": "<4.6.1 || 4.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.13"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.10-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "support": {
+ "issues": "https://github.com/webmozarts/assert/issues",
+ "source": "https://github.com/webmozarts/assert/tree/1.10.0"
+ },
+ "time": "2021-03-09T10:59:23+00:00"
+ },
+ {
+ "name": "yangjisen/laravel-cache-provider",
+ "version": "v3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yangjisen/LaravelCacheProvider.git",
+ "reference": "2c94bce77f642f200e073cd1bf1b6b5554f48471"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yangjisen/LaravelCacheProvider/zipball/2c94bce77f642f200e073cd1bf1b6b5554f48471",
+ "reference": "2c94bce77f642f200e073cd1bf1b6b5554f48471",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "laravel/framework": "^5.1||^6.0||^7.0||^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "YangJiSen\\CacheUserProvider\\ServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "YangJiSen\\CacheUserProvider\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Yang Ji Sen",
+ "email": "381722452@qq.com"
+ }
+ ],
+ "keywords": [
+ "auth",
+ "cache",
+ "guard",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/yangjisen/LaravelCacheProvider/issues",
+ "source": "https://github.com/yangjisen/LaravelCacheProvider/tree/v3.0.1"
+ },
+ "time": "2020-12-15T13:43:38+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^8.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/instantiator/issues",
+ "source": "https://github.com/doctrine/instantiator/tree/1.4.0"
+ },
+ "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%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-10T18:47:58+00:00"
+ },
+ {
+ "name": "facade/flare-client-php",
+ "version": "1.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/facade/flare-client-php.git",
+ "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/facade/flare-client-php/zipball/b2adf1512755637d0cef4f7d1b54301325ac78ed",
+ "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "facade/ignition-contracts": "~1.0",
+ "illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0",
+ "php": "^7.1|^8.0",
+ "symfony/http-foundation": "^3.3|^4.1|^5.0",
+ "symfony/mime": "^3.4|^4.0|^5.1",
+ "symfony/var-dumper": "^3.4|^4.0|^5.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.14",
+ "phpunit/phpunit": "^7.5.16",
+ "spatie/phpunit-snapshot-assertions": "^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Facade\\FlareClient\\": "src"
+ },
+ "files": [
+ "src/helpers.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Send PHP errors to Flare",
+ "homepage": "https://github.com/facade/flare-client-php",
+ "keywords": [
+ "exception",
+ "facade",
+ "flare",
+ "reporting"
+ ],
+ "support": {
+ "issues": "https://github.com/facade/flare-client-php/issues",
+ "source": "https://github.com/facade/flare-client-php/tree/1.9.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2021-09-13T12:16:46+00:00"
+ },
+ {
+ "name": "facade/ignition",
+ "version": "2.16.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/facade/ignition.git",
+ "reference": "23400e6cc565c9dcae2c53704b4de1c4870c0697"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/facade/ignition/zipball/23400e6cc565c9dcae2c53704b4de1c4870c0697",
+ "reference": "23400e6cc565c9dcae2c53704b4de1c4870c0697",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "facade/flare-client-php": "^1.9.1",
+ "facade/ignition-contracts": "^1.0.2",
+ "illuminate/support": "^7.0|^8.0",
+ "monolog/monolog": "^2.0",
+ "php": "^7.2.5|^8.0",
+ "symfony/console": "^5.0",
+ "symfony/var-dumper": "^5.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.14",
+ "mockery/mockery": "^1.3",
+ "orchestra/testbench": "^5.0|^6.0",
+ "psalm/plugin-laravel": "^1.2"
+ },
+ "suggest": {
+ "laravel/telescope": "^3.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Facade\\Ignition\\IgnitionServiceProvider"
+ ],
+ "aliases": {
+ "Flare": "Facade\\Ignition\\Facades\\Flare"
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Facade\\Ignition\\": "src"
+ },
+ "files": [
+ "src/helpers.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A beautiful error page for Laravel applications.",
+ "homepage": "https://github.com/facade/ignition",
+ "keywords": [
+ "error",
+ "flare",
+ "laravel",
+ "page"
+ ],
+ "support": {
+ "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction",
+ "forum": "https://twitter.com/flareappio",
+ "issues": "https://github.com/facade/ignition/issues",
+ "source": "https://github.com/facade/ignition"
+ },
+ "time": "2021-10-28T11:47:23+00:00"
+ },
+ {
+ "name": "facade/ignition-contracts",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/facade/ignition-contracts.git",
+ "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
+ "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.3|^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^v2.15.8",
+ "phpunit/phpunit": "^9.3.11",
+ "vimeo/psalm": "^3.17.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Facade\\IgnitionContracts\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://flareapp.io",
+ "role": "Developer"
+ }
+ ],
+ "description": "Solution contracts for Ignition",
+ "homepage": "https://github.com/facade/ignition-contracts",
+ "keywords": [
+ "contracts",
+ "flare",
+ "ignition"
+ ],
+ "support": {
+ "issues": "https://github.com/facade/ignition-contracts/issues",
+ "source": "https://github.com/facade/ignition-contracts/tree/1.0.2"
+ },
+ "time": "2020-10-16T08:27:54+00:00"
+ },
+ {
+ "name": "fakerphp/faker",
+ "version": "v1.16.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/FakerPHP/Faker.git",
+ "reference": "271d384d216e5e5c468a6b28feedf95d49f83b35"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/271d384d216e5e5c468a6b28feedf95d49f83b35",
+ "reference": "271d384d216e5e5c468a6b28feedf95d49f83b35",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.1 || ^8.0",
+ "psr/container": "^1.0 || ^2.0",
+ "symfony/deprecation-contracts": "^2.2"
+ },
+ "conflict": {
+ "fzaninotto/faker": "*"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "ext-intl": "*",
+ "symfony/phpunit-bridge": "^4.4 || ^5.2"
+ },
+ "suggest": {
+ "ext-curl": "Required by Faker\\Provider\\Image to download images.",
+ "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
+ "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
+ "ext-mbstring": "Required for multibyte Unicode string functionality."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "v1.16-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Faker\\": "src/Faker/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "François Zaninotto"
+ }
+ ],
+ "description": "Faker is a PHP library that generates fake data for you.",
+ "keywords": [
+ "data",
+ "faker",
+ "fixtures"
+ ],
+ "support": {
+ "issues": "https://github.com/FakerPHP/Faker/issues",
+ "source": "https://github.com/FakerPHP/Faker/tree/v1.16.0"
+ },
+ "time": "2021-09-06T14:53:37+00:00"
+ },
+ {
+ "name": "filp/whoops",
+ "version": "2.14.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/filp/whoops.git",
+ "reference": "f056f1fe935d9ed86e698905a957334029899895"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/f056f1fe935d9ed86e698905a957334029899895",
+ "reference": "f056f1fe935d9ed86e698905a957334029899895",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^5.5.9 || ^7.0 || ^8.0",
+ "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9 || ^1.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
+ "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
+ },
+ "suggest": {
+ "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
+ "whoops/soap": "Formats errors as SOAP responses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Whoops\\": "src/Whoops/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Filipe Dobreira",
+ "homepage": "https://github.com/filp",
+ "role": "Developer"
+ }
+ ],
+ "description": "php error handling for cool kids",
+ "homepage": "https://filp.github.io/whoops/",
+ "keywords": [
+ "error",
+ "exception",
+ "handling",
+ "library",
+ "throwable",
+ "whoops"
+ ],
+ "support": {
+ "issues": "https://github.com/filp/whoops/issues",
+ "source": "https://github.com/filp/whoops/tree/2.14.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/denis-sokolov",
+ "type": "github"
+ }
+ ],
+ "time": "2021-10-03T12:00:00+00:00"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^5.3|^7.0|^8.0"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "^1.4 || ^2.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "support": {
+ "issues": "https://github.com/hamcrest/hamcrest-php/issues",
+ "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1"
+ },
+ "time": "2020-07-09T08:09:16+00:00"
+ },
+ {
+ "name": "laravel/sail",
+ "version": "v1.12.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/sail.git",
+ "reference": "d1c31b2c3d226e70e0071e074da69e4801f0f47b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/sail/zipball/d1c31b2c3d226e70e0071e074da69e4801f0f47b",
+ "reference": "d1c31b2c3d226e70e0071e074da69e4801f0f47b",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "illuminate/console": "^8.0|^9.0",
+ "illuminate/contracts": "^8.0|^9.0",
+ "illuminate/support": "^8.0|^9.0",
+ "php": "^7.3|^8.0"
+ },
+ "bin": [
+ "bin/sail"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Laravel\\Sail\\SailServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Sail\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "Docker files for running a basic Laravel application.",
+ "keywords": [
+ "docker",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/sail/issues",
+ "source": "https://github.com/laravel/sail"
+ },
+ "time": "2021-11-16T16:55:18+00:00"
+ },
+ {
+ "name": "mockery/mockery",
+ "version": "1.4.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mockery/mockery.git",
+ "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/e01123a0e847d52d186c5eb4b9bf58b0c6d00346",
+ "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "hamcrest/hamcrest-php": "^2.0.1",
+ "lib-pcre": ">=7.0",
+ "php": "^7.3 || ^8.0"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5 || ^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Mockery": "library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "http://blog.astrumfutura.com"
+ },
+ {
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "http://davedevelopment.co.uk"
+ }
+ ],
+ "description": "Mockery is a simple yet flexible PHP mock object framework",
+ "homepage": "https://github.com/mockery/mockery",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/mockery/mockery/issues",
+ "source": "https://github.com/mockery/mockery/tree/1.4.4"
+ },
+ "time": "2021-09-13T15:28:59+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.10.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "replace": {
+ "myclabs/deep-copy": "self.version"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-13T09:40:50+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v4.13.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd",
+ "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1"
+ },
+ "time": "2021-11-03T20:52:16+00:00"
+ },
+ {
+ "name": "nunomaduro/collision",
+ "version": "v5.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nunomaduro/collision.git",
+ "reference": "3004cfa49c022183395eabc6d0e5207dfe498d00"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nunomaduro/collision/zipball/3004cfa49c022183395eabc6d0e5207dfe498d00",
+ "reference": "3004cfa49c022183395eabc6d0e5207dfe498d00",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "facade/ignition-contracts": "^1.0",
+ "filp/whoops": "^2.14.3",
+ "php": "^7.3 || ^8.0",
+ "symfony/console": "^5.0"
+ },
+ "require-dev": {
+ "brianium/paratest": "^6.1",
+ "fideloper/proxy": "^4.4.1",
+ "fruitcake/laravel-cors": "^2.0.3",
+ "laravel/framework": "8.x-dev",
+ "nunomaduro/larastan": "^0.6.2",
+ "nunomaduro/mock-final-classes": "^1.0",
+ "orchestra/testbench": "^6.0",
+ "phpstan/phpstan": "^0.12.64",
+ "phpunit/phpunit": "^9.5.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "NunoMaduro\\Collision\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ }
+ ],
+ "description": "Cli error handling for console/command-line PHP applications.",
+ "keywords": [
+ "artisan",
+ "cli",
+ "command-line",
+ "console",
+ "error",
+ "handling",
+ "laravel",
+ "laravel-zero",
+ "php",
+ "symfony"
+ ],
+ "support": {
+ "issues": "https://github.com/nunomaduro/collision/issues",
+ "source": "https://github.com/nunomaduro/collision"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/nunomaduro",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/nunomaduro",
+ "type": "patreon"
+ }
+ ],
+ "time": "2021-09-20T15:06:32+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+ },
+ "time": "2021-07-20T11:28:43+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "bae7c545bef187884426f042434e561ab1ddb182"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182",
+ "reference": "bae7c545bef187884426f042434e561ab1ddb182",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.1.0"
+ },
+ "time": "2021-02-23T14:00:09+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "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-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+ },
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.3",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.2",
+ "psalm/phar": "^4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "account@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
+ },
+ "time": "2021-10-19T17:43:47+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae",
+ "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*",
+ "psalm/phar": "^4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1"
+ },
+ "time": "2021-10-02T14:08:47+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "1.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
+ "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "doctrine/instantiator": "^1.2",
+ "php": "^7.2 || ~8.0, <8.2",
+ "phpdocumentor/reflection-docblock": "^5.2",
+ "sebastian/comparator": "^3.0 || ^4.0",
+ "sebastian/recursion-context": "^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^6.0 || ^7.0",
+ "phpunit/phpunit": "^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "support": {
+ "issues": "https://github.com/phpspec/prophecy/issues",
+ "source": "https://github.com/phpspec/prophecy/tree/1.14.0"
+ },
+ "time": "2021-09-10T09:02:12+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "9.2.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
+ "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.13.0",
+ "php": ">=7.3",
+ "phpunit/php-file-iterator": "^3.0.3",
+ "phpunit/php-text-template": "^2.0.2",
+ "sebastian/code-unit-reverse-lookup": "^2.0.2",
+ "sebastian/complexity": "^2.0",
+ "sebastian/environment": "^5.1.2",
+ "sebastian/lines-of-code": "^1.0.3",
+ "sebastian/version": "^3.0.1",
+ "theseer/tokenizer": "^1.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcov": "*",
+ "ext-xdebug": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-10-30T08:01:38+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "3.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8",
+ "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:57:25+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:58:55+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T05:33:50+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:16:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "9.5.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
+ "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "doctrine/instantiator": "^1.3.1",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.10.1",
+ "phar-io/manifest": "^2.0.3",
+ "phar-io/version": "^3.0.2",
+ "php": ">=7.3",
+ "phpspec/prophecy": "^1.12.1",
+ "phpunit/php-code-coverage": "^9.2.7",
+ "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-invoker": "^3.1.1",
+ "phpunit/php-text-template": "^2.0.3",
+ "phpunit/php-timer": "^5.0.2",
+ "sebastian/cli-parser": "^1.0.1",
+ "sebastian/code-unit": "^1.0.6",
+ "sebastian/comparator": "^4.0.5",
+ "sebastian/diff": "^4.0.3",
+ "sebastian/environment": "^5.1.3",
+ "sebastian/exporter": "^4.0.3",
+ "sebastian/global-state": "^5.0.1",
+ "sebastian/object-enumerator": "^4.0.3",
+ "sebastian/resource-operations": "^3.0.3",
+ "sebastian/type": "^2.3.4",
+ "sebastian/version": "^3.0.2"
+ },
+ "require-dev": {
+ "ext-pdo": "*",
+ "phpspec/prophecy-phpunit": "^2.0.1"
+ },
+ "suggest": {
+ "ext-soap": "*",
+ "ext-xdebug": "*"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.5-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ],
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/donate.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-09-25T07:38:51+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:08:49+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:08:54+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:30:19+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "4.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/diff": "^4.0",
+ "sebastian/exporter": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:49:45+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "nikic/php-parser": "^4.7",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:52:27+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:10:38+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "5.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:52:38+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-11-11T14:18:36+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49",
+ "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-06-11T13:31:12+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "nikic/php-parser": "^4.6",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-28T06:42:11+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:12:34+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:14:26+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:17:30+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:45:17+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "2.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914",
+ "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/2.3.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-06-15T12:49:02+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:39:44+00:00"
+ },
+ {
+ "name": "spatie/laravel-package-tools",
+ "version": "1.9.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-package-tools.git",
+ "reference": "f710fe196c126fb9e0aee67eb5af49ad8f13f528"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f710fe196c126fb9e0aee67eb5af49ad8f13f528",
+ "reference": "f710fe196c126fb9e0aee67eb5af49ad8f13f528",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "illuminate/contracts": "^7.0|^8.0",
+ "php": "^7.4|^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.4",
+ "orchestra/testbench": "^5.0|^6.0",
+ "phpunit/phpunit": "^9.3",
+ "spatie/test-time": "^1.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\LaravelPackageTools\\": "src",
+ "Spatie\\LaravelPackageTools\\Database\\Factories\\": "database/factories"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Tools for creating Laravel packages",
+ "homepage": "https://github.com/spatie/laravel-package-tools",
+ "keywords": [
+ "laravel-package-tools",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-package-tools/issues",
+ "source": "https://github.com/spatie/laravel-package-tools/tree/1.9.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2021-09-21T13:06:51+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2021-07-28T10:34:58+00:00"
+ },
+ {
+ "name": "wulfheart/pretty_routes",
+ "version": "0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Wulfheart/pretty-routes.git",
+ "reference": "e257fac400db2c696ddaec197e634b1fc7c40d22"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Wulfheart/pretty-routes/zipball/e257fac400db2c696ddaec197e634b1fc7c40d22",
+ "reference": "e257fac400db2c696ddaec197e634b1fc7c40d22",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "illuminate/contracts": "^8.0",
+ "php": "^7.4|^8.0",
+ "spatie/laravel-package-tools": "^1.4.3"
+ },
+ "require-dev": {
+ "brianium/paratest": "^6.2",
+ "nunomaduro/collision": "^5.3",
+ "orchestra/testbench": "^6.15",
+ "phpunit/phpunit": "^9.3",
+ "spatie/laravel-ray": "^1.9",
+ "spatie/phpunit-snapshot-assertions": "^4.2",
+ "vimeo/psalm": "^4.4"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Wulfheart\\PrettyRoutes\\PrettyRoutesServiceProvider"
+ ],
+ "aliases": {
+ "PrettyRoutes": "Wulfheart\\PrettyRoutes\\PrettyRoutesFacade"
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Wulfheart\\PrettyRoutes\\": "src",
+ "Wulfheart\\PrettyRoutes\\Database\\Factories\\": "database/factories"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alexander Wulf",
+ "email": "dev@alexfwulf.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Display your Laravel routes in the console, but make it pretty. 😎",
+ "homepage": "https://github.com/wulfheart/pretty_routes",
+ "keywords": [
+ "laravel",
+ "pretty_routes",
+ "wulfheart"
+ ],
+ "support": {
+ "issues": "https://github.com/Wulfheart/pretty-routes/issues",
+ "source": "https://github.com/Wulfheart/pretty-routes/tree/0.3.0"
+ },
+ "time": "2021-05-03T09:19:08+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": [],
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "^7.4|^8.0",
+ "ext-json": "*"
+ },
+ "platform-dev": [],
+ "plugin-api-version": "2.3.0"
+}
diff --git a/config/admin.php b/config/admin.php
new file mode 100644
index 0000000..494f5fc
--- /dev/null
+++ b/config/admin.php
@@ -0,0 +1,412 @@
+ [
+ // storage_path('app/water.png'),
+ // 'bottom-right',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | 上传图片是否开启缩略图
+ | 'small' => [100, 100],
+ | ...
+ |--------------------------------------------------------------------------
+ */
+ 'cover_thumb' => [],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin name
+ |--------------------------------------------------------------------------
+ |
+ | This value is the name of laravel-admin, This setting is displayed on the
+ | login page.
+ |
+ */
+ 'name' => env('ADMIN_NAME', 'Jason-admin'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin html title
+ |--------------------------------------------------------------------------
+ |
+ | Html title for all pages.
+ |
+ */
+ 'title' => env('ADMIN_TITLE', 'Jason Admin'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin logo
+ |--------------------------------------------------------------------------
+ |
+ | The logo of all admin pages. You can also set it as an image by using a
+ | `img` tag, eg '
'.
+ |
+ */
+ 'logo' => env('ADMIN_LOGO', 'Jason admin'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin mini logo
+ |--------------------------------------------------------------------------
+ |
+ | The logo of all admin pages when the sidebar menu is collapsed. You can
+ | also set it as an image by using a `img` tag, eg
+ | '
'.
+ |
+ */
+ 'logo-mini' => env('ADMIN_LOGO_MINI', 'JA'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin bootstrap setting
+ |--------------------------------------------------------------------------
+ |
+ | This value is the path of laravel-admin bootstrap file.
+ |
+ */
+ 'bootstrap' => app_path('Admin/bootstrap.php'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin route settings
+ |--------------------------------------------------------------------------
+ |
+ | The routing configuration of the admin page, including the path prefix,
+ | the controller namespace, and the default middleware. If you want to
+ | access through the root path, just set the prefix to empty string.
+ |
+ */
+ 'route' => [
+ 'prefix' => env('ADMIN_ROUTE_PREFIX', 'admin'),
+ 'namespace' => 'App\\Admin\\Controllers',
+ 'middleware' => ['web', 'admin'],
+ 'as' => 'admin.',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin install directory
+ |--------------------------------------------------------------------------
+ |
+ | The installation directory of the controller and routing configuration
+ | files of the administration page. The default is `app/Admin`, which must
+ | be set before running `artisan admin::install` to take effect.
+ |
+ */
+ 'directory' => app_path('Admin'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Access via `https`
+ |--------------------------------------------------------------------------
+ |
+ | If your page is going to be accessed via https, set it to `true`.
+ |
+ */
+ 'https' => env('ADMIN_HTTPS', false),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin auth setting
+ |--------------------------------------------------------------------------
+ |
+ | Authentication settings for all admin pages. Include an authentication
+ | guard and a user provider setting of authentication driver.
+ |
+ | You can specify a controller for `login` `logout` and other auth routes.
+ |
+ */
+ 'auth' => [
+ 'controller' => App\Admin\Controllers\AuthController::class,
+ 'guard' => 'admin',
+ 'guards' => [
+ 'admin' => [
+ 'driver' => 'session',
+ 'provider' => 'admin',
+ ],
+ ],
+ 'providers' => [
+ 'admin' => [
+ 'driver' => 'eloquent',
+ 'model' => Encore\Admin\Auth\Database\Administrator::class,
+ ],
+ ],
+ // Add "remember me" to login form
+ 'remember' => true,
+ // Redirect to the specified URI when user is not authorized.
+ 'redirect_to' => 'auth/login',
+ // The URIs that should be excluded from authorization.
+ 'excepts' => [
+ 'auth/login',
+ 'auth/logout',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin upload setting
+ |--------------------------------------------------------------------------
+ |
+ | File system configuration for form upload files and images, including
+ | disk and upload path.
+ |
+ */
+ 'upload' => [
+ // Disk in `config/filesystem.php`.
+ 'disk' => env('FILESYSTEM_DRIVER', 'local'),
+ // Image and file upload path under the disk above.
+ 'directory' => [
+ 'image' => 'images',
+ 'file' => 'files',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Laravel-admin database settings
+ |--------------------------------------------------------------------------
+ |
+ | Here are database settings for laravel-admin builtin model & tables.
+ |
+ */
+ 'database' => [
+ // Database connection for following tables.
+ 'connection' => '',
+ // User tables and model.
+ 'users_table' => 'admin_users',
+ 'users_model' => Encore\Admin\Auth\Database\Administrator::class,
+ // Role table and model.
+ 'roles_table' => 'admin_roles',
+ 'roles_model' => Encore\Admin\Auth\Database\Role::class,
+ // Permission table and model.
+ 'permissions_table' => 'admin_permissions',
+ 'permissions_model' => Encore\Admin\Auth\Database\Permission::class,
+ // Menu table and model.
+ 'menu_table' => 'admin_menu',
+ 'menu_model' => Encore\Admin\Auth\Database\Menu::class,
+ // Pivot table for table above.
+ 'operation_log_table' => 'admin_operation_log',
+ 'user_permissions_table' => 'admin_user_permissions',
+ 'role_users_table' => 'admin_role_users',
+ 'role_permissions_table' => 'admin_role_permissions',
+ 'role_menu_table' => 'admin_role_menu',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | User operation log setting
+ |--------------------------------------------------------------------------
+ |
+ | By setting this option to open or close operation log in laravel-admin.
+ |
+ */
+ 'operation_log' => [
+ 'enable' => true,
+ /*
+ * Only logging allowed methods in the list
+ */
+ 'allowed_methods' => ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'],
+ /*
+ * Routes that will not log to database.
+ *
+ * All method to path like: admin/auth/logs
+ * or specific method to path like: get:admin/auth/logs.
+ */
+ 'except' => [
+ 'admin/auth/logs*',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Indicates whether to check route permission.
+ |--------------------------------------------------------------------------
+ */
+ 'check_route_permission' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Indicates whether to check menu roles.
+ |--------------------------------------------------------------------------
+ */
+ 'check_menu_roles' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | User default avatar
+ |--------------------------------------------------------------------------
+ |
+ | Set a default avatar for newly created users.
+ |
+ */
+ 'default_avatar' => '/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Admin map field provider
+ |--------------------------------------------------------------------------
+ |
+ | Supported: "tencent", "google", "yandex".
+ |
+ */
+ 'map_provider' => 'google',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Skin
+ |--------------------------------------------------------------------------
+ |
+ | This value is the skin of admin pages.
+ | @see https://adminlte.io/docs/2.4/layout
+ |
+ | Supported:
+ | "skin-blue", "skin-blue-light", "skin-yellow", "skin-yellow-light",
+ | "skin-green", "skin-green-light", "skin-purple", "skin-purple-light",
+ | "skin-red", "skin-red-light", "skin-black", "skin-black-light".
+ |
+ */
+ 'skin' => 'skin-blue',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application layout
+ |--------------------------------------------------------------------------
+ |
+ | This value is the layout of admin pages.
+ | @see https://adminlte.io/docs/2.4/layout
+ |
+ | Supported: "fixed", "layout-boxed", "layout-top-nav", "sidebar-collapse",
+ | "sidebar-mini".
+ |
+ */
+ 'layout' => ['fixed', 'sidebar-mini'],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Login page background image
+ |--------------------------------------------------------------------------
+ |
+ | This value is used to set the background image of login page.
+ |
+ */
+ 'login_background_image' => '',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Show version at footer
+ |--------------------------------------------------------------------------
+ |
+ | Whether to display the version number of laravel-admin at the footer of
+ | each page
+ |
+ */
+ 'show_version' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Show environment at footer
+ |--------------------------------------------------------------------------
+ |
+ | Whether to display the environment at the footer of each page
+ |
+ */
+ 'show_environment' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Menu bind to permission
+ |--------------------------------------------------------------------------
+ |
+ | whether enable menu bind to a permission
+ */
+ 'menu_bind_permission' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Enable default breadcrumb
+ |--------------------------------------------------------------------------
+ |
+ | Whether enable default breadcrumb for every page content.
+ */
+ 'enable_default_breadcrumb' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Enable/Disable assets minify
+ |--------------------------------------------------------------------------
+ */
+ 'minify_assets' => [
+
+ // Assets will not be minified.
+ 'excepts' => [
+
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Enable/Disable sidebar menu search
+ |--------------------------------------------------------------------------
+ */
+ 'enable_menu_search' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Alert message that will displayed on top of the page.
+ |--------------------------------------------------------------------------
+ */
+ 'top_alert' => '',
+
+ /*
+ |--------------------------------------------------------------------------
+ | The global Grid action display class.
+ |--------------------------------------------------------------------------
+ */
+ 'grid_action_class' => \Encore\Admin\Grid\Displayers\DropdownActions::class,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Extension Directory
+ |--------------------------------------------------------------------------
+ |
+ | When you use command `php artisan admin:extend` to generate extensions,
+ | the extension files will be generated in this directory.
+ */
+ 'extension_dir' => app_path('Admin/Extensions'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Settings for extensions.
+ |--------------------------------------------------------------------------
+ |
+ | You can find all available extensions here
+ | https://github.com/laravel-admin-extensions.
+ |
+ */
+ 'extensions' => [
+ 'ueditor' => [
+ // 如果要关掉这个扩展,设置为false
+ 'enable' => true,
+ // 编辑器的前端配置 参考:http://fex.baidu.com/ueditor/#start-config
+ 'config' => [
+ 'initialFrameHeight' => 400, // 例如初始化高度
+ ],
+ 'field_type' => 'ueditor',
+ ],
+ ],
+];
diff --git a/config/agent.php b/config/agent.php
new file mode 100644
index 0000000..6c8951f
--- /dev/null
+++ b/config/agent.php
@@ -0,0 +1,40 @@
+ [
+ /**
+ * 可配置 API 独立域名
+ */
+ 'domain' => env('AGENT_ROUTE_DOMAIN', ''),
+ /**
+ * 不实用独立域名,API 地址前缀
+ */
+ 'prefix' => env('AGENT_ROUTE_PREFIX', 'agent'),
+ /**
+ * API 控制器命名空间
+ */
+ 'namespace' => 'App\\Agent\\Controllers',
+ /**
+ * API 路由命名前缀
+ */
+ 'as' => 'agent.',
+ /**
+ * API 默认中间件
+ */
+ 'middleware' => ['api', 'api.accept'],
+ /**
+ * 身份认证的中间件
+ */
+ 'middleware_auth' => ['api', 'api.accept', 'token.auth'],
+ /**
+ * 获取token,获取不到也不报错的中间件
+ */
+ 'middleware_guess' => ['api', 'api.accept', 'token.guess'],
+ ],
+
+ /**
+ * API 目录
+ */
+ 'directory' => app_path('Agent'),
+];
diff --git a/config/api.php b/config/api.php
new file mode 100644
index 0000000..b7cc95d
--- /dev/null
+++ b/config/api.php
@@ -0,0 +1,48 @@
+ env('TOKEN_AUTO_REVOKE', true),
+ /**
+ * token的名称
+ */
+ 'passport_token_name' => env('PASSPORT_TOKEN_NAME', ''),
+
+ 'route' => [
+ /**
+ * API 路由命名前缀
+ */
+ 'as' => 'api.',
+ /**
+ * 可配置 API 独立域名
+ */
+ 'domain' => env('API_ROUTE_DOMAIN', ''),
+ /**
+ * 不使用用独立域名,API 地址前缀
+ */
+ 'prefix' => env('API_ROUTE_PREFIX', 'api'),
+ /**
+ * API 控制器命名空间
+ */
+ 'namespace' => 'App\\Api\\Controllers',
+ /**
+ * 中间件
+ */
+ 'middleware' => ['api', 'api.accept'],
+ /**
+ * 身份认证的中间件
+ */
+ 'middleware_auth' => ['api', 'api.accept', 'token.auth'],
+ /**
+ * 获取token,获取不到也不报错的中间件
+ */
+ 'middleware_guess' => ['api', 'api.accept', 'token.guess'],
+ ],
+
+ /**
+ * 接口目录
+ */
+ 'directory' => app_path('Api'),
+];
diff --git a/config/app.php b/config/app.php
new file mode 100644
index 0000000..e621141
--- /dev/null
+++ b/config/app.php
@@ -0,0 +1,235 @@
+ env('APP_NAME', 'Laravel'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Environment
+ |--------------------------------------------------------------------------
+ |
+ | This value determines the "environment" your application is currently
+ | running in. This may determine how you prefer to configure various
+ | services the application utilizes. Set this in your ".env" file.
+ |
+ */
+
+ 'env' => env('APP_ENV', 'production'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Debug Mode
+ |--------------------------------------------------------------------------
+ |
+ | When your application is in debug mode, detailed error messages with
+ | stack traces will be shown on every error that occurs within your
+ | application. If disabled, a simple generic error page is shown.
+ |
+ */
+
+ 'debug' => (bool) env('APP_DEBUG', false),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application URL
+ |--------------------------------------------------------------------------
+ |
+ | This URL is used by the console to properly generate URLs when using
+ | the Artisan command line tool. You should set this to the root of
+ | your application so that it is used when running Artisan tasks.
+ |
+ */
+
+ 'url' => env('APP_URL', 'http://localhost'),
+
+ 'asset_url' => env('ASSET_URL', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Timezone
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the default timezone for your application, which
+ | will be used by the PHP date and date-time functions. We have gone
+ | ahead and set this to a sensible default for you out of the box.
+ |
+ */
+
+ 'timezone' => 'Asia/Shanghai',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Locale Configuration
+ |--------------------------------------------------------------------------
+ |
+ | The application locale determines the default locale that will be used
+ | by the translation service provider. You are free to set this value
+ | to any of the locales which will be supported by the application.
+ |
+ */
+
+ 'locale' => 'zh_CN',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Fallback Locale
+ |--------------------------------------------------------------------------
+ |
+ | The fallback locale determines the locale to use when the current one
+ | is not available. You may change the value to correspond to any of
+ | the language folders that are provided through your application.
+ |
+ */
+
+ 'fallback_locale' => 'en',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Faker Locale
+ |--------------------------------------------------------------------------
+ |
+ | This locale will be used by the Faker PHP library when generating fake
+ | data for your database seeds. For example, this will be used to get
+ | localized telephone numbers, street address information and more.
+ |
+ */
+
+ 'faker_locale' => 'en_US',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Encryption Key
+ |--------------------------------------------------------------------------
+ |
+ | This key is used by the Illuminate encrypter service and should be set
+ | to a random, 32 character string, otherwise these encrypted strings
+ | will not be safe. Please do this before deploying an application!
+ |
+ */
+
+ 'key' => env('APP_KEY'),
+
+ 'cipher' => 'AES-256-CBC',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Autoloaded Service Providers
+ |--------------------------------------------------------------------------
+ |
+ | The service providers listed here will be automatically loaded on the
+ | request to your application. Feel free to add your own services to
+ | this array to grant expanded functionality to your applications.
+ |
+ */
+
+ 'providers' => [
+
+ /*
+ * Laravel Framework Service Providers...
+ */
+ Illuminate\Auth\AuthServiceProvider::class,
+ Illuminate\Broadcasting\BroadcastServiceProvider::class,
+ Illuminate\Bus\BusServiceProvider::class,
+ Illuminate\Cache\CacheServiceProvider::class,
+ Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
+ Illuminate\Cookie\CookieServiceProvider::class,
+ Illuminate\Database\DatabaseServiceProvider::class,
+ Illuminate\Encryption\EncryptionServiceProvider::class,
+ Illuminate\Filesystem\FilesystemServiceProvider::class,
+ Illuminate\Foundation\Providers\FoundationServiceProvider::class,
+ Illuminate\Hashing\HashServiceProvider::class,
+ Illuminate\Mail\MailServiceProvider::class,
+ Illuminate\Notifications\NotificationServiceProvider::class,
+ Illuminate\Pagination\PaginationServiceProvider::class,
+ Illuminate\Pipeline\PipelineServiceProvider::class,
+ Illuminate\Queue\QueueServiceProvider::class,
+ Illuminate\Redis\RedisServiceProvider::class,
+ Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
+ Illuminate\Session\SessionServiceProvider::class,
+ Illuminate\Translation\TranslationServiceProvider::class,
+ Illuminate\Validation\ValidationServiceProvider::class,
+ Illuminate\View\ViewServiceProvider::class,
+
+ /*
+ * Package Service Providers...
+ */
+
+ /*
+ * Application Service Providers...
+ */
+ App\Providers\AppServiceProvider::class,
+ App\Providers\AuthServiceProvider::class,
+ // App\Providers\BroadcastServiceProvider::class,
+ App\Providers\EventServiceProvider::class,
+ App\Providers\RouteServiceProvider::class,
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Class Aliases
+ |--------------------------------------------------------------------------
+ |
+ | This array of class aliases will be registered when this application
+ | is started. However, feel free to register as many as you wish as
+ | the aliases are "lazy" loaded so they don't hinder performance.
+ |
+ */
+
+ 'aliases' => [
+
+ 'App' => Illuminate\Support\Facades\App::class,
+ 'Arr' => Illuminate\Support\Arr::class,
+ 'Artisan' => Illuminate\Support\Facades\Artisan::class,
+ 'Auth' => Illuminate\Support\Facades\Auth::class,
+ 'Blade' => Illuminate\Support\Facades\Blade::class,
+ 'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
+ 'Bus' => Illuminate\Support\Facades\Bus::class,
+ 'Cache' => Illuminate\Support\Facades\Cache::class,
+ 'Config' => Illuminate\Support\Facades\Config::class,
+ 'Cookie' => Illuminate\Support\Facades\Cookie::class,
+ 'Crypt' => Illuminate\Support\Facades\Crypt::class,
+ 'Date' => Illuminate\Support\Facades\Date::class,
+ 'DB' => Illuminate\Support\Facades\DB::class,
+ 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
+ 'Event' => Illuminate\Support\Facades\Event::class,
+ 'File' => Illuminate\Support\Facades\File::class,
+ 'Gate' => Illuminate\Support\Facades\Gate::class,
+ 'Hash' => Illuminate\Support\Facades\Hash::class,
+ 'Http' => Illuminate\Support\Facades\Http::class,
+ 'Js' => Illuminate\Support\Js::class,
+ 'Lang' => Illuminate\Support\Facades\Lang::class,
+ 'Log' => Illuminate\Support\Facades\Log::class,
+ 'Mail' => Illuminate\Support\Facades\Mail::class,
+ 'Notification' => Illuminate\Support\Facades\Notification::class,
+ 'Password' => Illuminate\Support\Facades\Password::class,
+ 'Queue' => Illuminate\Support\Facades\Queue::class,
+ 'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
+ 'Redirect' => Illuminate\Support\Facades\Redirect::class,
+ // 'Redis' => Illuminate\Support\Facades\Redis::class,
+ 'Request' => Illuminate\Support\Facades\Request::class,
+ 'Response' => Illuminate\Support\Facades\Response::class,
+ 'Route' => Illuminate\Support\Facades\Route::class,
+ 'Schema' => Illuminate\Support\Facades\Schema::class,
+ 'Session' => Illuminate\Support\Facades\Session::class,
+ 'Storage' => Illuminate\Support\Facades\Storage::class,
+ 'Str' => Illuminate\Support\Str::class,
+ 'URL' => Illuminate\Support\Facades\URL::class,
+ 'Validator' => Illuminate\Support\Facades\Validator::class,
+ 'View' => Illuminate\Support\Facades\View::class,
+
+ ],
+
+];
diff --git a/config/auth.php b/config/auth.php
new file mode 100644
index 0000000..0dc88b3
--- /dev/null
+++ b/config/auth.php
@@ -0,0 +1,111 @@
+ [
+ 'guard' => 'web',
+ 'passwords' => 'users',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Authentication Guards
+ |--------------------------------------------------------------------------
+ |
+ | Next, you may define every authentication guard for your application.
+ | Of course, a great default configuration has been defined for you
+ | here which uses session storage and the Eloquent user provider.
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | Supported: "session"
+ |
+ */
+
+ 'guards' => [
+ 'web' => [
+ 'driver' => 'session',
+ 'provider' => 'users',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | User Providers
+ |--------------------------------------------------------------------------
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | If you have multiple user tables or models you may configure multiple
+ | sources which represent each model / table. These sources may then
+ | be assigned to any extra authentication guards you have defined.
+ |
+ | Supported: "database", "eloquent"
+ |
+ */
+
+ 'providers' => [
+ 'users' => [
+ 'driver' => 'eloquent',
+ 'model' => App\Models\User::class,
+ ],
+
+ // 'users' => [
+ // 'driver' => 'database',
+ // 'table' => 'users',
+ // ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Resetting Passwords
+ |--------------------------------------------------------------------------
+ |
+ | You may specify multiple password reset configurations if you have more
+ | than one user table or model in the application and you want to have
+ | separate password reset settings based on the specific user types.
+ |
+ | The expire time is the number of minutes that the reset token should be
+ | considered valid. This security feature keeps tokens short-lived so
+ | they have less time to be guessed. You may change this as needed.
+ |
+ */
+
+ 'passwords' => [
+ 'users' => [
+ 'provider' => 'users',
+ 'table' => 'password_resets',
+ 'expire' => 60,
+ 'throttle' => 60,
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Password Confirmation Timeout
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define the amount of seconds before a password confirmation
+ | times out and the user is prompted to re-enter their password via the
+ | confirmation screen. By default, the timeout lasts for three hours.
+ |
+ */
+
+ 'password_timeout' => 10800,
+
+];
diff --git a/config/broadcasting.php b/config/broadcasting.php
new file mode 100644
index 0000000..59bccd5
--- /dev/null
+++ b/config/broadcasting.php
@@ -0,0 +1,64 @@
+ env('BROADCAST_DRIVER', 'null'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Broadcast Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the broadcast connections that will be used
+ | to broadcast events to other systems or over websockets. Samples of
+ | each available type of connection are provided inside this array.
+ |
+ */
+
+ 'connections' => [
+
+ 'pusher' => [
+ 'driver' => 'pusher',
+ 'key' => env('PUSHER_APP_KEY'),
+ 'secret' => env('PUSHER_APP_SECRET'),
+ 'app_id' => env('PUSHER_APP_ID'),
+ 'options' => [
+ 'cluster' => env('PUSHER_APP_CLUSTER'),
+ 'useTLS' => true,
+ ],
+ ],
+
+ 'ably' => [
+ 'driver' => 'ably',
+ 'key' => env('ABLY_KEY'),
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ ],
+
+ 'log' => [
+ 'driver' => 'log',
+ ],
+
+ 'null' => [
+ 'driver' => 'null',
+ ],
+
+ ],
+
+];
diff --git a/config/cache.php b/config/cache.php
new file mode 100644
index 0000000..70a2674
--- /dev/null
+++ b/config/cache.php
@@ -0,0 +1,106 @@
+ env('CACHE_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Stores
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the cache "stores" for your application as
+ | well as their drivers. You may even define multiple stores for the
+ | same cache driver to group types of items stored in your caches.
+ |
+ | Supported drivers: "apc", "array", "database", "file",
+ | "memcached", "redis", "dynamodb", "null"
+ |
+ */
+
+ 'stores' => [
+
+ 'apc' => [
+ 'driver' => 'apc',
+ ],
+
+ 'array' => [
+ 'driver' => 'array',
+ 'serialize' => false,
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'cache',
+ 'connection' => null,
+ 'lock_connection' => null,
+ ],
+
+ 'file' => [
+ 'driver' => 'file',
+ 'path' => storage_path('framework/cache/data'),
+ ],
+
+ 'memcached' => [
+ 'driver' => 'memcached',
+ 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
+ 'sasl' => [
+ env('MEMCACHED_USERNAME'),
+ env('MEMCACHED_PASSWORD'),
+ ],
+ 'options' => [
+ // Memcached::OPT_CONNECT_TIMEOUT => 2000,
+ ],
+ 'servers' => [
+ [
+ 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
+ 'port' => env('MEMCACHED_PORT', 11211),
+ 'weight' => 100,
+ ],
+ ],
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'cache',
+ 'lock_connection' => 'default',
+ ],
+
+ 'dynamodb' => [
+ 'driver' => 'dynamodb',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
+ 'endpoint' => env('DYNAMODB_ENDPOINT'),
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Key Prefix
+ |--------------------------------------------------------------------------
+ |
+ | When utilizing a RAM based store such as APC or Memcached, there might
+ | be other applications utilizing the same cache. So, we'll specify a
+ | value to get prefixed to all our keys so we can avoid collisions.
+ |
+ */
+
+ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
+
+];
diff --git a/config/cors.php b/config/cors.php
new file mode 100644
index 0000000..7655fd3
--- /dev/null
+++ b/config/cors.php
@@ -0,0 +1,34 @@
+ ['api/*'],
+
+ 'allowed_methods' => ['*'],
+
+ 'allowed_origins' => ['*'],
+
+ 'allowed_origins_patterns' => [],
+
+ 'allowed_headers' => ['*'],
+
+ 'exposed_headers' => ['Authorization', '*'],
+
+ 'max_age' => 0,
+
+ 'supports_credentials' => false,
+
+];
diff --git a/config/database.php b/config/database.php
new file mode 100644
index 0000000..b42d9b3
--- /dev/null
+++ b/config/database.php
@@ -0,0 +1,147 @@
+ env('DB_CONNECTION', 'mysql'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Database Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here are each of the database connections setup for your application.
+ | Of course, examples of configuring each database platform that is
+ | supported by Laravel is shown below to make development simple.
+ |
+ |
+ | All database work in Laravel is done through the PHP PDO facilities
+ | so make sure you have the driver for your particular database of
+ | choice installed on your machine before you begin development.
+ |
+ */
+
+ 'connections' => [
+
+ 'sqlite' => [
+ 'driver' => 'sqlite',
+ 'url' => env('DATABASE_URL'),
+ 'database' => env('DB_DATABASE', database_path('database.sqlite')),
+ 'prefix' => '',
+ 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
+ ],
+
+ 'mysql' => [
+ 'driver' => 'mysql',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '3306'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'unix_socket' => env('DB_SOCKET', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'strict' => true,
+ 'engine' => null,
+ 'options' => extension_loaded('pdo_mysql') ? array_filter([
+ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
+ ]) : [],
+ ],
+
+ 'pgsql' => [
+ 'driver' => 'pgsql',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '5432'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'schema' => 'public',
+ 'sslmode' => 'prefer',
+ ],
+
+ 'sqlsrv' => [
+ 'driver' => 'sqlsrv',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', 'localhost'),
+ 'port' => env('DB_PORT', '1433'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Migration Repository Table
+ |--------------------------------------------------------------------------
+ |
+ | This table keeps track of all the migrations that have already run for
+ | your application. Using this information, we can determine which of
+ | the migrations on disk haven't actually been run in the database.
+ |
+ */
+
+ 'migrations' => 'migrations',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Redis Databases
+ |--------------------------------------------------------------------------
+ |
+ | Redis is an open source, fast, and advanced key-value store that also
+ | provides a richer body of commands than a typical key-value system
+ | such as APC or Memcached. Laravel makes it easy to dig right in.
+ |
+ */
+
+ 'redis' => [
+
+ 'client' => env('REDIS_CLIENT', 'phpredis'),
+
+ 'options' => [
+ 'cluster' => env('REDIS_CLUSTER', 'redis'),
+ 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
+ ],
+
+ 'default' => [
+ 'url' => env('REDIS_URL'),
+ 'host' => env('REDIS_HOST', '127.0.0.1'),
+ 'password' => env('REDIS_PASSWORD', null),
+ 'port' => env('REDIS_PORT', '6379'),
+ 'database' => env('REDIS_DB', '0'),
+ ],
+
+ 'cache' => [
+ 'url' => env('REDIS_URL'),
+ 'host' => env('REDIS_HOST', '127.0.0.1'),
+ 'password' => env('REDIS_PASSWORD', null),
+ 'port' => env('REDIS_PORT', '6379'),
+ 'database' => env('REDIS_CACHE_DB', '1'),
+ ],
+
+ ],
+
+];
diff --git a/config/filesystems.php b/config/filesystems.php
new file mode 100644
index 0000000..5b2058f
--- /dev/null
+++ b/config/filesystems.php
@@ -0,0 +1,82 @@
+ env('FILESYSTEM_DRIVER', 'local'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Filesystem Disks
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure as many filesystem "disks" as you wish, and you
+ | may even configure multiple disks of the same driver. Defaults have
+ | been setup for each driver as an example of the required options.
+ |
+ | Supported Drivers: "local", "ftp", "sftp", "s3"
+ |
+ */
+
+ 'disks' => [
+
+ 'local' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app'),
+ ],
+
+ 'public' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/public'),
+ 'url' => env('APP_URL').'/storage',
+ 'visibility' => 'public',
+ ],
+
+ 's3' => [
+ 'driver' => 's3',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION'),
+ 'bucket' => env('AWS_BUCKET'),
+ 'url' => env('AWS_URL'),
+ 'endpoint' => env('AWS_ENDPOINT'),
+ ],
+
+ 'oss' => [
+ 'driver' => 'oss',
+ 'root' => '',
+ 'access_key' => env('OSS_ACCESS_KEY'),
+ 'secret_key' => env('OSS_SECRET_KEY'),
+ 'endpoint' => env('OSS_ENDPOINT'),
+ 'bucket' => env('OSS_BUCKET'),
+ 'isCName' => env('OSS_IS_CNAME', false),
+ 'cdnHost' => env('OSS_CDN_HOST'),
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Symbolic Links
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the symbolic links that will be created when the
+ | `storage:link` Artisan command is executed. The array keys should be
+ | the locations of the links and the values should be their targets.
+ |
+ */
+
+ 'links' => [
+ public_path('storage') => storage_path('app/public'),
+ ],
+
+];
diff --git a/config/hashing.php b/config/hashing.php
new file mode 100644
index 0000000..5b10c09
--- /dev/null
+++ b/config/hashing.php
@@ -0,0 +1,52 @@
+ 'bcrypt',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Bcrypt Options
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the configuration options that should be used when
+ | passwords are hashed using the Bcrypt algorithm. This will allow you
+ | to control the amount of time it takes to hash the given password.
+ |
+ */
+
+ 'bcrypt' => [
+ 'rounds' => env('BCRYPT_ROUNDS', 10),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Argon Options
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the configuration options that should be used when
+ | passwords are hashed using the Argon algorithm. These will allow you
+ | to control the amount of time it takes to hash the given password.
+ |
+ */
+
+ 'argon' => [
+ 'memory' => 1024,
+ 'threads' => 2,
+ 'time' => 2,
+ ],
+
+];
diff --git a/config/image.php b/config/image.php
new file mode 100644
index 0000000..2b1d2c3
--- /dev/null
+++ b/config/image.php
@@ -0,0 +1,20 @@
+ 'gd'
+
+];
diff --git a/config/laravel-model-caching.php b/config/laravel-model-caching.php
new file mode 100644
index 0000000..cda02fb
--- /dev/null
+++ b/config/laravel-model-caching.php
@@ -0,0 +1,11 @@
+ '',
+
+ 'enabled' => env('MODEL_CACHE_ENABLED', true),
+
+ 'use-database-keying' => env('MODEL_CACHE_USE_DATABASE_KEYING', true),
+
+ 'store' => env('MODEL_CACHE_STORE'),
+];
diff --git a/config/logging.php b/config/logging.php
new file mode 100644
index 0000000..8a5c7d7
--- /dev/null
+++ b/config/logging.php
@@ -0,0 +1,117 @@
+ env('LOG_CHANNEL', 'stack'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Deprecations Log Channel
+ |--------------------------------------------------------------------------
+ |
+ | This option controls the log channel that should be used to log warnings
+ | regarding deprecated PHP and library features. This allows you to get
+ | your application ready for upcoming major versions of dependencies.
+ |
+ */
+
+ 'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Log Channels
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the log channels for your application. Out of
+ | the box, Laravel uses the Monolog PHP logging library. This gives
+ | you a variety of powerful log handlers / formatters to utilize.
+ |
+ | Available Drivers: "single", "daily", "slack", "syslog",
+ | "errorlog", "monolog",
+ | "custom", "stack"
+ |
+ */
+
+ 'channels' => [
+ 'stack' => [
+ 'driver' => 'stack',
+ 'channels' => ['daily'],
+ 'ignore_exceptions' => false,
+ ],
+
+ 'single' => [
+ 'driver' => 'single',
+ 'path' => storage_path('logs/laravel.log'),
+ 'level' => env('LOG_LEVEL', 'debug'),
+ ],
+
+ 'daily' => [
+ 'driver' => 'daily',
+ 'path' => storage_path('logs/laravel.log'),
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'days' => 14,
+ ],
+
+ 'slack' => [
+ 'driver' => 'slack',
+ 'url' => env('LOG_SLACK_WEBHOOK_URL'),
+ 'username' => 'Laravel Log',
+ 'emoji' => ':boom:',
+ 'level' => env('LOG_LEVEL', 'critical'),
+ ],
+
+ 'papertrail' => [
+ 'driver' => 'monolog',
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'handler' => SyslogUdpHandler::class,
+ 'handler_with' => [
+ 'host' => env('PAPERTRAIL_URL'),
+ 'port' => env('PAPERTRAIL_PORT'),
+ ],
+ ],
+
+ 'stderr' => [
+ 'driver' => 'monolog',
+ 'handler' => StreamHandler::class,
+ 'formatter' => env('LOG_STDERR_FORMATTER'),
+ 'with' => [
+ 'stream' => 'php://stderr',
+ ],
+ ],
+
+ 'syslog' => [
+ 'driver' => 'syslog',
+ 'level' => env('LOG_LEVEL', 'debug'),
+ ],
+
+ 'errorlog' => [
+ 'driver' => 'errorlog',
+ 'level' => env('LOG_LEVEL', 'debug'),
+ ],
+
+ 'null' => [
+ 'driver' => 'monolog',
+ 'handler' => NullHandler::class,
+ ],
+
+ 'emergency' => [
+ 'path' => storage_path('logs/laravel.log'),
+ ],
+ ],
+
+];
diff --git a/config/mail.php b/config/mail.php
new file mode 100644
index 0000000..e4b7258
--- /dev/null
+++ b/config/mail.php
@@ -0,0 +1,110 @@
+ env('MAIL_MAILER', 'smtp'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Mailer Configurations
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure all of the mailers used by your application plus
+ | their respective settings. Several examples have been configured for
+ | you and you are free to add your own as your application requires.
+ |
+ | Laravel supports a variety of mail "transport" drivers to be used while
+ | sending an e-mail. You will specify which one you are using for your
+ | mailers below. You are free to add additional mailers as required.
+ |
+ | Supported: "smtp", "sendmail", "mailgun", "ses",
+ | "postmark", "log", "array"
+ |
+ */
+
+ 'mailers' => [
+ 'smtp' => [
+ 'transport' => 'smtp',
+ 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
+ 'port' => env('MAIL_PORT', 587),
+ 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
+ 'username' => env('MAIL_USERNAME'),
+ 'password' => env('MAIL_PASSWORD'),
+ 'timeout' => null,
+ 'auth_mode' => null,
+ ],
+
+ 'ses' => [
+ 'transport' => 'ses',
+ ],
+
+ 'mailgun' => [
+ 'transport' => 'mailgun',
+ ],
+
+ 'postmark' => [
+ 'transport' => 'postmark',
+ ],
+
+ 'sendmail' => [
+ 'transport' => 'sendmail',
+ 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -t -i'),
+ ],
+
+ 'log' => [
+ 'transport' => 'log',
+ 'channel' => env('MAIL_LOG_CHANNEL'),
+ ],
+
+ 'array' => [
+ 'transport' => 'array',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Global "From" Address
+ |--------------------------------------------------------------------------
+ |
+ | You may wish for all e-mails sent by your application to be sent from
+ | the same address. Here, you may specify a name and address that is
+ | used globally for all e-mails that are sent by your application.
+ |
+ */
+
+ 'from' => [
+ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
+ 'name' => env('MAIL_FROM_NAME', 'Example'),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Markdown Mail Settings
+ |--------------------------------------------------------------------------
+ |
+ | If you are using Markdown based email rendering, you may configure your
+ | theme and component paths here, allowing you to customize the design
+ | of the emails. Or, you may simply stick with the Laravel defaults!
+ |
+ */
+
+ 'markdown' => [
+ 'theme' => 'default',
+
+ 'paths' => [
+ resource_path('views/vendor/mail'),
+ ],
+ ],
+
+];
diff --git a/config/modules.php b/config/modules.php
new file mode 100644
index 0000000..b81ed11
--- /dev/null
+++ b/config/modules.php
@@ -0,0 +1,272 @@
+ 'Modules',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Module Stubs
+ |--------------------------------------------------------------------------
+ |
+ | Default module stubs.
+ |
+ */
+
+ 'stubs' => [
+ 'enabled' => false,
+ 'path' => base_path() . '/vendor/nwidart/laravel-modules/src/Commands/stubs',
+ 'files' => [
+ 'routes/web' => 'Routes/web.php',
+ 'routes/api' => 'Routes/api.php',
+ 'views/index' => 'Resources/views/index.blade.php',
+ 'views/master' => 'Resources/views/layouts/master.blade.php',
+ 'scaffold/config' => 'Config/config.php',
+ 'composer' => 'composer.json',
+ 'assets/js/app' => 'Resources/assets/js/app.js',
+ 'assets/sass/app' => 'Resources/assets/sass/app.scss',
+ 'webpack' => 'webpack.mix.js',
+ 'package' => 'package.json',
+ ],
+ 'replacements' => [
+ 'routes/web' => ['LOWER_NAME', 'STUDLY_NAME'],
+ 'routes/api' => ['LOWER_NAME'],
+ 'webpack' => ['LOWER_NAME'],
+ 'json' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'PROVIDER_NAMESPACE'],
+ 'views/index' => ['LOWER_NAME'],
+ 'views/master' => ['LOWER_NAME', 'STUDLY_NAME'],
+ 'scaffold/config' => ['STUDLY_NAME'],
+ 'composer' => [
+ 'LOWER_NAME',
+ 'STUDLY_NAME',
+ 'VENDOR',
+ 'AUTHOR_NAME',
+ 'AUTHOR_EMAIL',
+ 'MODULE_NAMESPACE',
+ 'PROVIDER_NAMESPACE',
+ ],
+ ],
+ 'gitkeep' => false,
+ ],
+ 'paths' => [
+ /*
+ |--------------------------------------------------------------------------
+ | Modules path
+ |--------------------------------------------------------------------------
+ |
+ | This path used for save the generated module. This path also will be added
+ | automatically to list of scanned folders.
+ |
+ */
+
+ 'modules' => base_path('modules'),
+ /*
+ |--------------------------------------------------------------------------
+ | Modules assets path
+ |--------------------------------------------------------------------------
+ |
+ | Here you may update the modules assets path.
+ |
+ */
+
+ 'assets' => public_path('modules'),
+ /*
+ |--------------------------------------------------------------------------
+ | The migrations path
+ |--------------------------------------------------------------------------
+ |
+ | Where you run 'module:publish-migration' command, where do you publish the
+ | the migration files?
+ |
+ */
+
+ 'migration' => base_path('database/migrations'),
+ /*
+ |--------------------------------------------------------------------------
+ | Generator path
+ |--------------------------------------------------------------------------
+ | Customise the paths where the folders will be generated.
+ | Set the generate key to false to not generate that folder
+ */
+ 'generator' => [
+ 'config' => ['path' => 'Config', 'generate' => true],
+ 'command' => ['path' => 'Console', 'generate' => true],
+ 'migration' => ['path' => 'Database/Migrations', 'generate' => true],
+ 'seeder' => ['path' => 'Database/Seeders', 'generate' => true],
+ 'factory' => ['path' => 'Database/factories', 'generate' => true],
+ 'model' => ['path' => 'Models', 'generate' => true],
+ 'routes' => ['path' => 'Routes', 'generate' => true],
+ 'controller' => ['path' => 'Http/Controllers', 'generate' => true],
+ 'filter' => ['path' => 'Http/Middleware', 'generate' => true],
+ 'request' => ['path' => 'Http/Requests', 'generate' => true],
+ 'provider' => ['path' => 'Providers', 'generate' => true],
+ 'assets' => ['path' => 'Resources/assets', 'generate' => false],
+ 'lang' => ['path' => 'Resources/lang', 'generate' => true],
+ 'views' => ['path' => 'Resources/views', 'generate' => false],
+ 'test' => ['path' => 'Tests/Unit', 'generate' => false],
+ 'test-feature' => ['path' => 'Tests/Feature', 'generate' => false],
+ 'repository' => ['path' => 'Http/Resources', 'generate' => true],
+ 'event' => ['path' => 'Events', 'generate' => true],
+ 'listener' => ['path' => 'Listeners', 'generate' => true],
+ 'policies' => ['path' => 'Policies', 'generate' => false],
+ 'rules' => ['path' => 'Rules', 'generate' => false],
+ 'jobs' => ['path' => 'Jobs', 'generate' => true],
+ 'emails' => ['path' => 'Emails', 'generate' => false],
+ 'notifications' => ['path' => 'Notifications', 'generate' => false],
+ 'resource' => ['path' => 'Transformers', 'generate' => false],
+ 'component-view' => ['path' => 'Resources/views/components', 'generate' => false],
+ 'component-class' => ['path' => 'View/Component', 'generate' => false],
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Package commands
+ |--------------------------------------------------------------------------
+ |
+ | Here you can define which commands will be visible and used in your
+ | application. If for example you don't use some of the commands provided
+ | you can simply comment them out.
+ |
+ */
+ 'commands' => [
+ Commands\CommandMakeCommand::class,
+ Commands\ControllerMakeCommand::class,
+ Commands\DisableCommand::class,
+ Commands\DumpCommand::class,
+ Commands\EnableCommand::class,
+ Commands\EventMakeCommand::class,
+ Commands\JobMakeCommand::class,
+ Commands\ListenerMakeCommand::class,
+ Commands\MailMakeCommand::class,
+ Commands\MiddlewareMakeCommand::class,
+ Commands\NotificationMakeCommand::class,
+ Commands\ProviderMakeCommand::class,
+ Commands\RouteProviderMakeCommand::class,
+ Commands\InstallCommand::class,
+ Commands\ListCommand::class,
+ Commands\ModuleDeleteCommand::class,
+ Commands\ModuleMakeCommand::class,
+ Commands\FactoryMakeCommand::class,
+ Commands\PolicyMakeCommand::class,
+ Commands\RequestMakeCommand::class,
+ Commands\RuleMakeCommand::class,
+ Commands\MigrateCommand::class,
+ Commands\MigrateRefreshCommand::class,
+ Commands\MigrateResetCommand::class,
+ Commands\MigrateRollbackCommand::class,
+ Commands\MigrateStatusCommand::class,
+ Commands\MigrationMakeCommand::class,
+ Commands\ModelMakeCommand::class,
+ Commands\PublishCommand::class,
+ Commands\PublishConfigurationCommand::class,
+ Commands\PublishMigrationCommand::class,
+ Commands\PublishTranslationCommand::class,
+ Commands\SeedCommand::class,
+ Commands\SeedMakeCommand::class,
+ Commands\SetupCommand::class,
+ Commands\UnUseCommand::class,
+ Commands\UpdateCommand::class,
+ Commands\UseCommand::class,
+ Commands\ResourceMakeCommand::class,
+ Commands\TestMakeCommand::class,
+ Commands\LaravelModulesV6Migrator::class,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Scan Path
+ |--------------------------------------------------------------------------
+ |
+ | Here you define which folder will be scanned. By default will scan vendor
+ | directory. This is useful if you host the package in packagist website.
+ |
+ */
+
+ 'scan' => [
+ 'enabled' => false,
+ 'paths' => [
+ base_path('vendor/*/*'),
+ ],
+ ],
+ /*
+ |--------------------------------------------------------------------------
+ | Composer File Template
+ |--------------------------------------------------------------------------
+ |
+ | Here is the config for composer.json file, generated by this package
+ |
+ */
+
+ 'composer' => [
+ 'vendor' => 'jasonc',
+ 'author' => [
+ 'name' => 'Jason.Chen',
+ 'email' => 'chenjxlg@163.com',
+ ],
+ ],
+
+ 'composer-output' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Caching
+ |--------------------------------------------------------------------------
+ |
+ | Here is the config for setting up caching feature.
+ |
+ */
+ 'cache' => [
+ 'enabled' => false,
+ 'key' => 'laravel-modules',
+ 'lifetime' => 60,
+ ],
+ /*
+ |--------------------------------------------------------------------------
+ | Choose what laravel-modules will register as custom namespaces.
+ | Setting one to false will require you to register that part
+ | in your own Service Provider class.
+ |--------------------------------------------------------------------------
+ */
+ 'register' => [
+ 'translations' => true,
+ /**
+ * load files on boot or register method
+ * Note: boot not compatible with asgardcms
+ * @example boot|register
+ */
+ 'files' => 'register',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Activators
+ |--------------------------------------------------------------------------
+ |
+ | You can define new types of activators here, file, database etc. The only
+ | required parameter is 'class'.
+ | The file activator will store the activation status in storage/installed_modules
+ */
+ 'activators' => [
+ 'file' => [
+ 'class' => FileActivator::class,
+ 'statuses-file' => base_path('modules.json'),
+ 'cache-key' => 'activator.installed',
+ 'cache-lifetime' => 604800,
+ ],
+ ],
+
+ 'activator' => 'file',
+];
diff --git a/config/queue.php b/config/queue.php
new file mode 100644
index 0000000..84f7438
--- /dev/null
+++ b/config/queue.php
@@ -0,0 +1,93 @@
+ env('QUEUE_CONNECTION', 'sync'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Queue Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the connection information for each server that
+ | is used by your application. A default configuration has been added
+ | for each back-end shipped with Laravel. You are free to add more.
+ |
+ | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
+ |
+ */
+
+ 'connections' => [
+
+ 'sync' => [
+ 'driver' => 'sync',
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'jobs',
+ 'queue' => 'default',
+ 'retry_after' => 90,
+ 'after_commit' => false,
+ ],
+
+ 'beanstalkd' => [
+ 'driver' => 'beanstalkd',
+ 'host' => 'localhost',
+ 'queue' => 'default',
+ 'retry_after' => 90,
+ 'block_for' => 0,
+ 'after_commit' => false,
+ ],
+
+ 'sqs' => [
+ 'driver' => 'sqs',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
+ 'queue' => env('SQS_QUEUE', 'default'),
+ 'suffix' => env('SQS_SUFFIX'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ 'after_commit' => false,
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ 'queue' => env('REDIS_QUEUE', 'default'),
+ 'retry_after' => 90,
+ 'block_for' => null,
+ 'after_commit' => false,
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Failed Queue Jobs
+ |--------------------------------------------------------------------------
+ |
+ | These options configure the behavior of failed queue job logging so you
+ | can control which database and table are used to store the jobs that
+ | have failed. You may change them to any database / table you wish.
+ |
+ */
+
+ 'failed' => [
+ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
+ 'database' => env('DB_CONNECTION', 'mysql'),
+ 'table' => 'failed_jobs',
+ ],
+
+];
diff --git a/config/sanctum.php b/config/sanctum.php
new file mode 100644
index 0000000..0fefc04
--- /dev/null
+++ b/config/sanctum.php
@@ -0,0 +1,65 @@
+ explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
+ '%s%s',
+ 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
+ env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
+ ))),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Sanctum Guards
+ |--------------------------------------------------------------------------
+ |
+ | This array contains the authentication guards that will be checked when
+ | Sanctum is trying to authenticate a request. If none of these guards
+ | are able to authenticate the request, Sanctum will use the bearer
+ | token that's present on an incoming request for authentication.
+ |
+ */
+
+ 'guard' => ['web'],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Expiration Minutes
+ |--------------------------------------------------------------------------
+ |
+ | This value controls the number of minutes until an issued token will be
+ | considered expired. If this value is null, personal access tokens do
+ | not expire. This won't tweak the lifetime of first-party sessions.
+ |
+ */
+
+ 'expiration' => null,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Sanctum Middleware
+ |--------------------------------------------------------------------------
+ |
+ | When authenticating your first-party SPA with Sanctum you may need to
+ | customize some of the middleware Sanctum uses while processing the
+ | request. You may change the middleware listed below as required.
+ |
+ */
+
+ 'middleware' => [
+ 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
+ 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
+ ],
+
+];
\ No newline at end of file
diff --git a/config/services.php b/config/services.php
new file mode 100644
index 0000000..3215b42
--- /dev/null
+++ b/config/services.php
@@ -0,0 +1,33 @@
+ [
+ 'domain' => env('MAILGUN_DOMAIN'),
+ 'secret' => env('MAILGUN_SECRET'),
+ 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
+ ],
+
+ 'postmark' => [
+ 'token' => env('POSTMARK_TOKEN'),
+ ],
+
+ 'ses' => [
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ ],
+
+];
diff --git a/config/session.php b/config/session.php
new file mode 100644
index 0000000..c0e798b
--- /dev/null
+++ b/config/session.php
@@ -0,0 +1,201 @@
+ env('SESSION_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Lifetime
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the number of minutes that you wish the session
+ | to be allowed to remain idle before it expires. If you want them
+ | to immediately expire on the browser closing, set that option.
+ |
+ */
+
+ 'lifetime' => env('SESSION_LIFETIME', 120),
+
+ 'expire_on_close' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Encryption
+ |--------------------------------------------------------------------------
+ |
+ | This option allows you to easily specify that all of your session data
+ | should be encrypted before it is stored. All encryption will be run
+ | automatically by Laravel and you can use the Session like normal.
+ |
+ */
+
+ 'encrypt' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session File Location
+ |--------------------------------------------------------------------------
+ |
+ | When using the native session driver, we need a location where session
+ | files may be stored. A default has been set for you but a different
+ | location may be specified. This is only needed for file sessions.
+ |
+ */
+
+ 'files' => storage_path('framework/sessions'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Connection
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" or "redis" session drivers, you may specify a
+ | connection that should be used to manage these sessions. This should
+ | correspond to a connection in your database configuration options.
+ |
+ */
+
+ 'connection' => env('SESSION_CONNECTION', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Table
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" session driver, you may specify the table we
+ | should use to manage the sessions. Of course, a sensible default is
+ | provided for you; however, you are free to change this as needed.
+ |
+ */
+
+ 'table' => 'sessions',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cache Store
+ |--------------------------------------------------------------------------
+ |
+ | While using one of the framework's cache driven session backends you may
+ | list a cache store that should be used for these sessions. This value
+ | must match with one of the application's configured cache "stores".
+ |
+ | Affects: "apc", "dynamodb", "memcached", "redis"
+ |
+ */
+
+ 'store' => env('SESSION_STORE', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Sweeping Lottery
+ |--------------------------------------------------------------------------
+ |
+ | Some session drivers must manually sweep their storage location to get
+ | rid of old sessions from storage. Here are the chances that it will
+ | happen on a given request. By default, the odds are 2 out of 100.
+ |
+ */
+
+ 'lottery' => [2, 100],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Name
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the name of the cookie used to identify a session
+ | instance by ID. The name specified here will get used every time a
+ | new session cookie is created by the framework for every driver.
+ |
+ */
+
+ 'cookie' => env(
+ 'SESSION_COOKIE',
+ Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'
+ ),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Path
+ |--------------------------------------------------------------------------
+ |
+ | The session cookie path determines the path for which the cookie will
+ | be regarded as available. Typically, this will be the root path of
+ | your application but you are free to change this when necessary.
+ |
+ */
+
+ 'path' => '/',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Domain
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the domain of the cookie used to identify a session
+ | in your application. This will determine which domains the cookie is
+ | available to in your application. A sensible default has been set.
+ |
+ */
+
+ 'domain' => env('SESSION_DOMAIN', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTPS Only Cookies
+ |--------------------------------------------------------------------------
+ |
+ | By setting this option to true, session cookies will only be sent back
+ | to the server if the browser has a HTTPS connection. This will keep
+ | the cookie from being sent to you if it can not be done securely.
+ |
+ */
+
+ 'secure' => env('SESSION_SECURE_COOKIE'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTP Access Only
+ |--------------------------------------------------------------------------
+ |
+ | Setting this value to true will prevent JavaScript from accessing the
+ | value of the cookie and the cookie will only be accessible through
+ | the HTTP protocol. You are free to modify this option if needed.
+ |
+ */
+
+ 'http_only' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Same-Site Cookies
+ |--------------------------------------------------------------------------
+ |
+ | This option determines how your cookies behave when cross-site requests
+ | take place, and can be used to mitigate CSRF attacks. By default, we
+ | will set this value to "lax" since this is a secure default value.
+ |
+ | Supported: "lax", "strict", "none", null
+ |
+ */
+
+ 'same_site' => 'lax',
+
+];
diff --git a/config/ueditor.php b/config/ueditor.php
new file mode 100644
index 0000000..acbc0d4
--- /dev/null
+++ b/config/ueditor.php
@@ -0,0 +1,118 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+return [
+ 'hash_filename' => true,
+ // 存储引擎: config/filesystem.php 中 disks, public 或 qiniu
+ 'disk' => env('FILESYSTEM_DRIVER', 'local'),
+ 'route' => [
+ 'name' => '/ueditor/server',
+ 'options' => [
+ // middleware => 'auth',
+ ],
+ ],
+
+ // 上传 配置
+ 'upload' => [
+ /* 前后端通信相关的配置,注释只允许使用多行方式 */
+ /* 上传图片配置项 */
+ 'imageActionName' => 'upload-image', /* 执行上传图片的action名称 */
+ 'imageFieldName' => 'upfile', /* 提交的图片表单名称 */
+ 'imageMaxSize' => 2 * 1024 * 1024, /* 上传大小限制,单位B */
+ 'imageAllowFiles' => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */
+ 'imageCompressEnable' => true, /* 是否压缩图片,默认是true */
+ 'imageCompressBorder' => 1600, /* 图片压缩最长边限制 */
+ 'imageInsertAlign' => 'none', /* 插入的图片浮动方式 */
+ 'imageUrlPrefix' => '', /* 图片访问路径前缀 */
+ 'imagePathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ /* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */
+ /* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */
+ /* {time} 会替换成时间戳 */
+ /* {yyyy} 会替换成四位年份 */
+ /* {yy} 会替换成两位年份 */
+ /* {mm} 会替换成两位月份 */
+ /* {dd} 会替换成两位日期 */
+ /* {hh} 会替换成两位小时 */
+ /* {ii} 会替换成两位分钟 */
+ /* {ss} 会替换成两位秒 */
+ /* 非法字符 \ => * ? " < > | */
+ /* 具请体看线上文档 => fex.baidu.com/assets/#use-format_upload_filename */
+
+ /* 涂鸦图片上传配置项 */
+ 'scrawlActionName' => 'upload-scrawl', /* 执行上传涂鸦的action名称 */
+ 'scrawlFieldName' => 'upfile', /* 提交的图片表单名称 */
+ 'scrawlPathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ 'scrawlMaxSize' => 2048000, /* 上传大小限制,单位B */
+ 'scrawlUrlPrefix' => '', /* 图片访问路径前缀 */
+ 'scrawlInsertAlign' => 'none',
+
+ /* 截图工具上传 */
+ 'snapscreenActionName' => 'upload-image', /* 执行上传截图的action名称 */
+ 'snapscreenPathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ 'snapscreenUrlPrefix' => '', /* 图片访问路径前缀 */
+ 'snapscreenInsertAlign' => 'none', /* 插入的图片浮动方式 */
+
+ /* 抓取远程图片配置 */
+ 'catcherLocalDomain' => ['127.0.0.1', 'localhost', 'img.baidu.com'],
+ 'catcherActionName' => 'catch-image', /* 执行抓取远程图片的action名称 */
+ 'catcherFieldName' => 'source', /* 提交的图片列表表单名称 */
+ 'catcherPathFormat' => '/uploads/images/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ 'catcherUrlPrefix' => '', /* 图片访问路径前缀 */
+ 'catcherMaxSize' => 2048000, /* 上传大小限制,单位B */
+ 'catcherAllowFiles' => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 抓取图片格式显示 */
+
+ /* 上传视频配置 */
+ 'videoActionName' => 'upload-video', /* 执行上传视频的action名称 */
+ 'videoFieldName' => 'upfile', /* 提交的视频表单名称 */
+ 'videoPathFormat' => '/uploads/videos/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ 'videoUrlPrefix' => '', /* 视频访问路径前缀 */
+ 'videoMaxSize' => 102400000, /* 上传大小限制,单位B,默认100MB */
+ 'videoAllowFiles' => [
+ '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg',
+ '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid',
+ ], /* 上传视频格式显示 */
+
+ /* 上传文件配置 */
+ 'fileActionName' => 'upload-file', /* controller里,执行上传视频的action名称 */
+ 'fileFieldName' => 'upfile', /* 提交的文件表单名称 */
+ 'filePathFormat' => '/uploads/files/{yyyy}/{mm}/{dd}/', /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ 'fileUrlPrefix' => '', /* 文件访问路径前缀 */
+ 'fileMaxSize' => 51200000, /* 上传大小限制,单位B,默认50MB */
+ 'fileAllowFiles' => [
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp',
+ '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg',
+ '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid',
+ '.rar', '.zip', '.tar', '.gz', '.7z', '.bz2', '.cab', '.iso',
+ '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.md', '.xml',
+ ], /* 上传文件格式显示 */
+
+ /* 列出指定目录下的图片 */
+ 'imageManagerActionName' => 'list-image', /* 执行图片管理的action名称 */
+ 'imageManagerListPath' => '/uploads/images/', /* 指定要列出图片的目录 */
+ 'imageManagerListSize' => 20, /* 每次列出文件数量 */
+ 'imageManagerUrlPrefix' => '', /* 图片访问路径前缀 */
+ 'imageManagerInsertAlign' => 'none', /* 插入的图片浮动方式 */
+ 'imageManagerAllowFiles' => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 列出的文件类型 */
+
+ /* 列出指定目录下的文件 */
+ 'fileManagerActionName' => 'list-file', /* 执行文件管理的action名称 */
+ 'fileManagerListPath' => '/uploads/files/', /* 指定要列出文件的目录 */
+ 'fileManagerUrlPrefix' => '', /* 文件访问路径前缀 */
+ 'fileManagerListSize' => 20, /* 每次列出文件数量 */
+ 'fileManagerAllowFiles' => [
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp',
+ '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg',
+ '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid',
+ '.rar', '.zip', '.tar', '.gz', '.7z', '.bz2', '.cab', '.iso',
+ '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.md', '.xml',
+ ], /* 列出的文件类型 */
+ ],
+];
diff --git a/config/view.php b/config/view.php
new file mode 100644
index 0000000..22b8a18
--- /dev/null
+++ b/config/view.php
@@ -0,0 +1,36 @@
+ [
+ resource_path('views'),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Compiled View Path
+ |--------------------------------------------------------------------------
+ |
+ | This option determines where all the compiled Blade templates will be
+ | stored for your application. Typically, this is within the storage
+ | directory. However, as usual, you are free to change this value.
+ |
+ */
+
+ 'compiled' => env(
+ 'VIEW_COMPILED_PATH',
+ realpath(storage_path('framework/views'))
+ ),
+
+];
diff --git a/database/.gitignore b/database/.gitignore
new file mode 100644
index 0000000..97fc976
--- /dev/null
+++ b/database/.gitignore
@@ -0,0 +1,2 @@
+*.sqlite
+*.sqlite-journal
diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php
new file mode 100644
index 0000000..98604f8
--- /dev/null
+++ b/database/factories/UserFactory.php
@@ -0,0 +1,39 @@
+ $this->faker->name,
+ 'email' => $this->faker->unique()->safeEmail,
+ 'email_verified_at' => now(),
+ 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
+ 'remember_token' => Str::random(10),
+ ];
+ }
+
+ /**
+ * Indicate that the model's email address should be unverified.
+ *
+ * @return \Illuminate\Database\Eloquent\Factories\Factory
+ */
+ public function unverified()
+ {
+ return $this->state(function (array $attributes) {
+ return [
+ 'email_verified_at' => null,
+ ];
+ });
+ }
+}
diff --git a/database/migrations/2016_01_04_173148_create_admin_tables.php b/database/migrations/2016_01_04_173148_create_admin_tables.php
new file mode 100644
index 0000000..450847e
--- /dev/null
+++ b/database/migrations/2016_01_04_173148_create_admin_tables.php
@@ -0,0 +1,119 @@
+increments('id');
+ $table->string('username', 190)->unique();
+ $table->string('password', 60);
+ $table->string('name');
+ $table->string('avatar')->nullable();
+ $table->string('remember_token', 100)->nullable();
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.roles_table'), function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name', 50)->unique();
+ $table->string('slug', 50)->unique();
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.permissions_table'), function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name', 50)->unique();
+ $table->string('slug', 50)->unique();
+ $table->string('http_method')->nullable();
+ $table->text('http_path')->nullable();
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.menu_table'), function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('parent_id')->default(0);
+ $table->integer('order')->default(0);
+ $table->string('title', 50);
+ $table->string('icon', 50);
+ $table->string('uri')->nullable();
+ $table->string('permission')->nullable();
+
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.role_users_table'), function (Blueprint $table) {
+ $table->integer('role_id');
+ $table->integer('user_id');
+ $table->index(['role_id', 'user_id']);
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.role_permissions_table'), function (Blueprint $table) {
+ $table->integer('role_id');
+ $table->integer('permission_id');
+ $table->index(['role_id', 'permission_id']);
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.user_permissions_table'), function (Blueprint $table) {
+ $table->integer('user_id');
+ $table->integer('permission_id');
+ $table->index(['user_id', 'permission_id']);
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.role_menu_table'), function (Blueprint $table) {
+ $table->integer('role_id');
+ $table->integer('menu_id');
+ $table->index(['role_id', 'menu_id']);
+ $table->timestamps();
+ });
+
+ Schema::create(config('admin.database.operation_log_table'), function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('user_id');
+ $table->string('path');
+ $table->string('method', 10);
+ $table->string('ip');
+ $table->text('input');
+ $table->index('user_id');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists(config('admin.database.users_table'));
+ Schema::dropIfExists(config('admin.database.roles_table'));
+ Schema::dropIfExists(config('admin.database.permissions_table'));
+ Schema::dropIfExists(config('admin.database.menu_table'));
+ Schema::dropIfExists(config('admin.database.user_permissions_table'));
+ Schema::dropIfExists(config('admin.database.role_users_table'));
+ Schema::dropIfExists(config('admin.database.role_permissions_table'));
+ Schema::dropIfExists(config('admin.database.role_menu_table'));
+ Schema::dropIfExists(config('admin.database.operation_log_table'));
+ }
+}
diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php
new file mode 100644
index 0000000..6aa6d74
--- /dev/null
+++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->string('uuid')->unique();
+ $table->text('connection');
+ $table->text('queue');
+ $table->longText('payload');
+ $table->longText('exception');
+ $table->timestamp('failed_at')->useCurrent();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('failed_jobs');
+ }
+}
diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
new file mode 100644
index 0000000..3ce0002
--- /dev/null
+++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
@@ -0,0 +1,36 @@
+bigIncrements('id');
+ $table->morphs('tokenable');
+ $table->string('name');
+ $table->string('token', 64)->unique();
+ $table->text('abilities')->nullable();
+ $table->timestamp('last_used_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('personal_access_tokens');
+ }
+}
diff --git a/database/migrations/2021_11_10_102248_create_jobs_table.php b/database/migrations/2021_11_10_102248_create_jobs_table.php
new file mode 100644
index 0000000..1be9e8a
--- /dev/null
+++ b/database/migrations/2021_11_10_102248_create_jobs_table.php
@@ -0,0 +1,36 @@
+bigIncrements('id');
+ $table->string('queue')->index();
+ $table->longText('payload');
+ $table->unsignedTinyInteger('attempts');
+ $table->unsignedInteger('reserved_at')->nullable();
+ $table->unsignedInteger('available_at');
+ $table->unsignedInteger('created_at');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('jobs');
+ }
+}
diff --git a/database/migrations/2022_11_30_204150_create_google2fas_table.php b/database/migrations/2022_11_30_204150_create_google2fas_table.php
new file mode 100644
index 0000000..642f8cf
--- /dev/null
+++ b/database/migrations/2022_11_30_204150_create_google2fas_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->morphs('subscriber');
+ $table->string('secret', 64)->nullable();
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('google2fas');
+ }
+}
diff --git a/database/seeders/AdminPanelSeeder.php b/database/seeders/AdminPanelSeeder.php
new file mode 100644
index 0000000..e97cd97
--- /dev/null
+++ b/database/seeders/AdminPanelSeeder.php
@@ -0,0 +1,106 @@
+ 'admin',
+ 'password' => bcrypt('admin'),
+ 'name' => 'Administrator',
+ ]);
+
+ Role::create([
+ 'name' => 'Administrator',
+ 'slug' => 'administrator',
+ ]);
+
+ Permission::create([
+ 'name' => 'All permission',
+ 'slug' => '*',
+ 'http_method' => '',
+ 'http_path' => '*',
+ ]);
+
+ Menu::create([
+ 'id' => 1,
+ 'parent_id' => 0,
+ 'order' => 0,
+ 'title' => 'Dashboard',
+ 'icon' => 'fa-bar-chart',
+ 'uri' => '/',
+ ]);
+
+ $menu = Menu::create([
+ 'id' => 2,
+ 'parent_id' => 0,
+ 'order' => 99,
+ 'title' => 'Admin',
+ 'icon' => 'fa-tasks',
+ ]);
+
+ $menu->children()->createMany([
+ [
+ 'parent_id' => 2,
+ 'order' => 1,
+ 'title' => '模块管理',
+ 'icon' => 'fa-windows',
+ 'uri' => 'modules',
+ ],
+ [
+ 'parent_id' => 2,
+ 'order' => 2,
+ 'title' => 'Users',
+ 'icon' => 'fa-users',
+ 'uri' => 'auth/users',
+ ],
+ [
+ 'parent_id' => 2,
+ 'order' => 3,
+ 'title' => 'Roles',
+ 'icon' => 'fa-user',
+ 'uri' => 'auth/roles',
+ ],
+ [
+ 'parent_id' => 2,
+ 'order' => 4,
+ 'title' => 'Permission',
+ 'icon' => 'fa-ban',
+ 'uri' => 'auth/permissions',
+ ],
+ [
+ 'parent_id' => 2,
+ 'order' => 1,
+ 'title' => 'Menu',
+ 'icon' => 'fa-bars',
+ 'uri' => 'auth/menu',
+ ],
+ [
+ 'parent_id' => 2,
+ 'order' => 1,
+ 'title' => 'Operation log',
+ 'icon' => 'fa-history',
+ 'uri' => 'auth/logs',
+ ],
+ ]);
+
+ DB::insert("INSERT INTO `admin_role_menu` VALUES (1, 2, NULL, NULL);");
+ DB::insert("INSERT INTO `admin_role_permissions` VALUES (1, 1, NULL, NULL);");
+ DB::insert("INSERT INTO `admin_role_users` VALUES (1, 1, NULL, NULL);");
+ }
+
+}
\ No newline at end of file
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
new file mode 100644
index 0000000..57b73b5
--- /dev/null
+++ b/database/seeders/DatabaseSeeder.php
@@ -0,0 +1,18 @@
+create();
+ }
+}
diff --git a/docs/READMD.md b/docs/READMD.md
new file mode 100644
index 0000000..c20ed6f
--- /dev/null
+++ b/docs/READMD.md
@@ -0,0 +1,2 @@
+# Laravel 开发文档
+
\ No newline at end of file
diff --git a/docs/Trait doc.md b/docs/Trait doc.md
new file mode 100644
index 0000000..173c310
--- /dev/null
+++ b/docs/Trait doc.md
@@ -0,0 +1,117 @@
+# Trait
+
+## 1. HasClicks
+
+> 缓存的浏览计数器
+
+```php
+use App\Traits\HasClicks;
+
+class Test extends Model
+{
+ use HasClicks;
+ protected string $clicks_filed = 'clicks';
+}
+
+$test = Test::first();
+
+// 增加点击数量
+$test->incrementClicks(1);
+// 获取点击量
+$test->clicks;
+```
+
+## 2. HasCovers
+
+> 封面图与轮播图的展示扩展,完整url
+
+```php
+use App\Traits\HasCovers;
+
+class Test extends Model {
+ use HasCovers;
+ protected string $cover_field = 'cover';
+ protected string $pictures_field = 'pictures';
+}
+
+$test = Test::first();
+
+$test->cover_url;
+$test->pictures_url;
+```
+
+## 3. HasStatus
+
+> 基础状态的显示与作用域查询
+
+```php
+use App\Traits\HasStatus;
+
+class Test extends Model {
+ use HasStatus;
+ protected string $status_field = 'status';
+ protected array $status_map = [
+ 1 => '正常',
+ 0 => '禁用'
+ ];
+}
+
+// 状态为 1 的
+$test = Test::shown()->first();
+// 状态为 0 的
+$test = Test::hidden()->first();
+// 查询特定状态
+$test = Test::ofStatus(3)->first();
+// 状态的文本显示
+$test->status_text;
+```
+
+## 4. Macroable
+
+> 这个主要用于,对模型的一些外部扩展使用、
+>
+> 因为模型无法动态的注入trait,有些时候在模块外部,需要写入关联模型等时候,可以使用。
+
+```php
+use App\Tratis\Macroable;
+
+class Test extends Model {
+ use Macroable;
+}
+// 对模型注入一个 address 的一对多关联
+Test::macro('address', function () {
+ return $this->hasMany(Address::class);
+});
+```
+
+## 5. OrderByIdDesc
+
+> 直接引入模型后,查询到的数据会默认以ID 倒序排列,
+>
+> 暂时没有找到怎么获取主键的方法,待升级
+
+## 6. OrderByOrderAsc
+
+> 已特定的 order 字段,按照升序排序
+
+## 7. WithPosition
+
+> 位运算来解决的多点定位,模型中需要有 position 字段
+
+```php
+
+use App\Traits\WithPosition;
+
+class Test extends Model
+{
+ use WithPosition;
+ protected array $position_map = [
+ 1 => 'A',
+ 2 => 'B',
+ 4 => 'C',
+ 8 => 'D',
+ ];
+}
+
+$test = Test::ofPosition(3)->first();
+```
\ No newline at end of file
diff --git a/init_composer.sh b/init_composer.sh
new file mode 100644
index 0000000..24231ce
--- /dev/null
+++ b/init_composer.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+packages=$(php -f 'parseComposer.php')
+
+if [ "$packages" != "" ];then
+ composer require $packages
+fi
+
+composer install --no-dev
\ No newline at end of file
diff --git a/modules.json b/modules.json
new file mode 100644
index 0000000..2c63c08
--- /dev/null
+++ b/modules.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/modules/.gitignore b/modules/.gitignore
new file mode 100644
index 0000000..2157116
--- /dev/null
+++ b/modules/.gitignore
@@ -0,0 +1,11 @@
+*
+!.gitignore
+!pull-all.sh
+!README.md
+!Cms
+!Configuration
+!Mall
+!Payment
+!Storage
+!Task
+!User
\ No newline at end of file
diff --git a/modules/Cms/.gitignore b/modules/Cms/.gitignore
new file mode 100644
index 0000000..4b6cb11
--- /dev/null
+++ b/modules/Cms/.gitignore
@@ -0,0 +1,4 @@
+.idea
+vendor
+.DS_Store
+composer.lock
\ No newline at end of file
diff --git a/modules/Cms/Cms.php b/modules/Cms/Cms.php
new file mode 100644
index 0000000..566d17b
--- /dev/null
+++ b/modules/Cms/Cms.php
@@ -0,0 +1,88 @@
+
+ */
+ public static function install()
+ {
+ Artisan::call('migrate', [
+ '--path' => 'modules/Cms/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ * @Date : 2021/3/12 11:35 上午
+ * @Author : < Jason.C >
+ */
+ public static function uninstall()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $mains = $menu::where('title', self::$mainTitle)->get();
+
+ foreach ($mains as $main) {
+ $main->delete();
+ }
+ }
+
+ protected static function createAdminMenu()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 15,
+ 'title' => self::$mainTitle,
+ 'icon' => 'fa-wordpress',
+ ]);
+
+ $main->children()->createMany([
+ [
+ 'order' => 1,
+ 'title' => '文章管理',
+ 'icon' => 'fa-bars',
+ 'uri' => 'cms/articles',
+ ],
+ [
+ 'order' => 2,
+ 'title' => '单页管理',
+ 'icon' => 'fa-newspaper-o',
+ 'uri' => 'cms/pages',
+ ],
+ [
+ 'order' => 3,
+ 'title' => '分类管理',
+ 'icon' => 'fa-indent',
+ 'uri' => 'cms/categories',
+ ],
+ [
+ 'order' => 4,
+ 'title' => '标签管理',
+ 'icon' => 'fa-tags',
+ 'uri' => 'cms/tags',
+ ],
+ [
+ 'order' => 5,
+ 'title' => '素材管理',
+ 'icon' => 'fa-adjust',
+ 'uri' => 'cms/storages',
+ ],
+ ]);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Config/config.php b/modules/Cms/Config/config.php
new file mode 100644
index 0000000..825c0e8
--- /dev/null
+++ b/modules/Cms/Config/config.php
@@ -0,0 +1,10 @@
+ 'Cms',
+];
diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_article_category_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_article_category_table.php
new file mode 100644
index 0000000..dbb07a2
--- /dev/null
+++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_article_category_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->unsignedBigInteger('article_id');
+ $table->unsignedBigInteger('category_id');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_article_category');
+ }
+
+}
diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_articles_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_articles_table.php
new file mode 100644
index 0000000..cffbcde
--- /dev/null
+++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_articles_table.php
@@ -0,0 +1,42 @@
+id();
+ $table->string('slug')->index()->nullable()->comment('唯一的别名');
+ $table->string('title');
+ $table->string('sub_title')->nullable()->comment('副标题');
+ $table->string('description')->nullable();
+ $table->string('cover')->nullable();
+ $table->json('pictures')->nullable();
+ $table->text('content');
+ $table->json('attachments')->nullable();
+ $table->boolean('status')->default(0);
+ $table->unsignedInteger('clicks')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_articles');
+ }
+
+}
diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_categories_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_categories_table.php
new file mode 100644
index 0000000..ba460cb
--- /dev/null
+++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_categories_table.php
@@ -0,0 +1,41 @@
+id();
+ $table->unsignedBigInteger('parent_id')->index()->default(0);
+ $table->string('title');
+ $table->string('slug')->nullable();
+ $table->string('description')->nullable();
+ $table->string('cover')->nullable();
+ $table->json('pictures')->nullable();
+ $table->text('content')->nullable();
+ $table->integer('order')->default(0);
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_categories');
+ }
+
+}
diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_pages_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_pages_table.php
new file mode 100644
index 0000000..ecfc764
--- /dev/null
+++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_pages_table.php
@@ -0,0 +1,42 @@
+id();
+ $table->string('slug')->index()->nullable()->comment('唯一的别名');
+ $table->string('title');
+ $table->string('sub_title')->nullable()->comment('副标题');
+ $table->string('description')->nullable();
+ $table->string('cover')->nullable();
+ $table->json('pictures')->nullable();
+ $table->text('content');
+ $table->json('attachments')->nullable();
+ $table->boolean('status')->default(0);
+ $table->unsignedInteger('clicks')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_pages');
+ }
+
+}
diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_taggable_table.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_taggable_table.php
new file mode 100644
index 0000000..628f455
--- /dev/null
+++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_taggable_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('tag_id')->index();
+ $table->morphs('taggable');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_taggable');
+ }
+
+}
diff --git a/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_tags_table.php.php b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_tags_table.php.php
new file mode 100644
index 0000000..38bc2f7
--- /dev/null
+++ b/modules/Cms/Database/Migrations/0000_00_00_000000_create_cms_tags_table.php.php
@@ -0,0 +1,33 @@
+id();
+ $table->string('name')->comment('定位名称');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_tags');
+ }
+
+}
diff --git a/modules/Cms/Database/Migrations/2021_09_29_142323_create_cms_storages_table.php b/modules/Cms/Database/Migrations/2021_09_29_142323_create_cms_storages_table.php
new file mode 100644
index 0000000..8320938
--- /dev/null
+++ b/modules/Cms/Database/Migrations/2021_09_29_142323_create_cms_storages_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('name');
+ $table->string('url')->nullable();
+ $table->string('cover');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_storages');
+ }
+
+}
diff --git a/modules/Cms/Database/Migrations/2022_10_20_095341_create_cms_logs_table.php b/modules/Cms/Database/Migrations/2022_10_20_095341_create_cms_logs_table.php
new file mode 100644
index 0000000..5551514
--- /dev/null
+++ b/modules/Cms/Database/Migrations/2022_10_20_095341_create_cms_logs_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->unsignedBigInteger('article_id')->index();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('cms_logs');
+ }
+}
diff --git a/modules/Cms/Http/Controllers/Admin/ArticleController.php b/modules/Cms/Http/Controllers/Admin/ArticleController.php
new file mode 100644
index 0000000..0fa6a79
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Admin/ArticleController.php
@@ -0,0 +1,136 @@
+model()->with('categories')->withCount('versions');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('title', '文章标题');
+ $filter->like('slug', 'SLUG');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('categories.title', '所属分类');
+ $filter->like('tags.name', '标签');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->select(Article::getStatusMap());
+ });
+ });
+
+ $grid->column('id', '#ID#');
+ $grid->column('categories', '所属分类')->display(function ($categories) {
+ return Arr::pluck($categories, 'title');
+ })->label();
+ // $grid->column('tags')->pluck('name')->label('warning');
+ $grid->column('title', '文章标题');
+ // $grid->column('slug', 'SLUG');
+ $grid->column('status', '状态')->bool();
+ $grid->column('clicks', '浏览量');
+ $grid->column('versions_count', '历史版本')->link(function () {
+ return route('admin.cms.versions', [
+ 'model' => get_class($this),
+ 'key' => $this->id,
+ ]);
+ }, '_self');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ /**
+ * Notes : 内容表单
+ * @Date : 2021/3/15 5:21 下午
+ * @Author : < Jason.C >
+ * @return \Encore\Admin\Form
+ */
+ public function form(): Form
+ {
+ $form = new Form(new Article());
+
+ $form->text('title', '文章标题')
+ ->rules([
+ 'required',
+ 'max:255',
+ ], [
+ 'max' => '标题最大长度不能超过 :max 个字符',
+ ])
+ ->required();
+// $form->text('sub_title', '副标题')
+// ->rules([
+// 'nullable',
+// 'max:255',
+// ], [
+// 'max' => '标题最大长度不能超过 :max 个字符',
+// ]);
+// $form->text('slug', '英文别名')
+// ->rules([
+// 'nullable',
+// 'alpha_dash',
+// ], [
+// 'alpha_dash' => '别名中包含非法字符',
+// ])
+// ->help('字母、数字、下划线组成,用来展示简短的URL');
+
+ $form->multipleSelect('categories', '所属分类')
+ ->options(Category::selectOptions(function ($model) {
+ return $model->where('status', 1);
+ }, '请选择文章分类'))
+ ->required();
+
+// $form->multipleSelect('tags', '内容标签')
+// ->options(function () {
+// return Tag::pluck('name', 'id');
+// });
+
+ $this->cover($form);
+
+// $this->pictures($form);
+
+ $form->textarea('description', '文章简介')
+ ->rules([
+ 'nullable',
+ 'max:255',
+ ], [
+ 'max' => '标题最大长度不能超过 :max 个字符',
+ ]);
+
+ $form->ueditor('content', '文章内容')
+ ->rules([
+ 'required',
+ ], [
+ 'required' => '文章内容不能为空',
+ ])
+ ->required();
+
+ $this->attachments($form);
+
+ $form->switch('status', '状态')
+ ->default(1);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Admin/CategoryController.php b/modules/Cms/Http/Controllers/Admin/CategoryController.php
new file mode 100644
index 0000000..4e3b667
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Admin/CategoryController.php
@@ -0,0 +1,98 @@
+column(6, $this->treeView());
+
+ $row->column(6, function (Column $column) {
+ $form = new WidgetsForm();
+ $this->formData($form, true);
+ $form->action(route('admin.cms.categories.store'));
+ $column->append((new Box('新增分类', $form))->style('success'));
+ });
+ };
+ }
+
+ protected function treeView(): Tree
+ {
+ return Category::tree(function (Tree $tree) {
+ $tree->disableCreate();
+ $tree->branch(function ($branch) {
+ if ($branch['status'] == 1) {
+ $payload = " ";
+ } else {
+ $payload = " ";
+ }
+
+ $payload .= " [ID:{$branch['id']}] - ";
+ $payload .= " {$branch['title']} ";
+
+ return $payload;
+ });
+ });
+ }
+
+ protected function form(): Form
+ {
+ $form = new Form(new Category);
+
+ $this->formData($form);
+
+ return $form;
+ }
+
+ private function formData($form, $hideContent = false)
+ {
+ $form->select('parent_id', '上级分类')
+ ->options(Category::selectOptions(function ($model) {
+ return $model->where('status', 1);
+ }, '一级分类'))
+ ->required();
+ $form->text('title', '分类名称')
+ ->rules('required');
+ $form->text('slug', '英文别名')
+ ->rules([
+ 'nullable',
+ 'alpha_dash',
+ ], [
+ 'alpha_dash' => '别名中包含非法字符',
+ ])
+ ->help('字母、数字、下划线组成,用来展示简短的URL');
+ $form->textarea('description', '分类简介')
+ ->rules('nullable');
+
+ $this->cover($form);
+
+ $this->pictures($form);
+
+ $form->number('order', '排序')->default(0);
+
+ if (!$hideContent) {
+ $form->ueditor('content', '分类内容');
+ }
+
+ $form->switch('status', '状态')->default(1);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Admin/PageController.php b/modules/Cms/Http/Controllers/Admin/PageController.php
new file mode 100644
index 0000000..3471604
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Admin/PageController.php
@@ -0,0 +1,124 @@
+model()->withCount('versions');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('title', '页面标题');
+ $filter->like('tags.name', '标签');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('slug', 'SLUG');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->select(Page::getStatusMap());
+ });
+ });
+
+ $grid->column('id', '#ID#');
+ $grid->column('title', '页面标题');
+ $grid->column('tags')->pluck('name')->label('warning');
+ $grid->column('slug', '英文别名');
+ $grid->column('status', '状态')->bool();
+ $grid->column('clicks', '浏览量');
+ $grid->column('versions_count', '历史版本')->link(function () {
+ return route('admin.cms.versions', [
+ 'model' => get_class($this),
+ 'key' => $this->id,
+ ]);
+ }, '_self');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ /**
+ * Notes : 内容表单
+ * @Date : 2021/3/15 5:21 下午
+ * @Author : < Jason.C >
+ * @return \Encore\Admin\Form
+ */
+ public function form(): Form
+ {
+ $form = new Form(new Page());
+
+ $form->text('title', '页面标题')
+ ->rules([
+ 'required',
+ 'max:255',
+ ], [
+ 'max' => '标题最大长度不能超过 :max 个字符',
+ ])
+ ->required();
+ $form->text('sub_title', '副标题')
+ ->rules([
+ 'nullable',
+ 'max:255',
+ ], [
+ 'max' => '标题最大长度不能超过 :max 个字符',
+ ]);
+ $form->text('slug', '英文别名')
+ ->rules([
+ 'nullable',
+ 'alpha_dash',
+ ], [
+ 'alpha_dash' => '别名中包含非法字符',
+ ])
+ ->help('字母、数字、下划线组成,用来展示简短的URL');
+
+ $form->multipleSelect('tags', '页面标签')
+ ->options(function () {
+ return Tag::pluck('name', 'id');
+ });
+
+ $this->cover($form);
+
+ $this->pictures($form);
+
+ $form->textarea('description', '页面简介')
+ ->rules([
+ 'nullable',
+ 'max:255',
+ ], [
+ 'max' => '标题最大长度不能超过 :max 个字符',
+ ]);
+
+ $form->ueditor('content', '页面内容')
+ ->rules([
+ 'required',
+ ], [
+ 'required' => '页面内容不能为空',
+ ])
+ ->required();
+
+ $this->attachments($form);
+
+ $form->switch('status', '状态')
+ ->default(1);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Admin/StorageController.php b/modules/Cms/Http/Controllers/Admin/StorageController.php
new file mode 100644
index 0000000..8f80ff3
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Admin/StorageController.php
@@ -0,0 +1,49 @@
+column('id', '#ID#');
+ $grid->column('name', '素材标题');
+ $grid->column('地址')->display(function () {
+ return $this->cover_text;
+ });
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Storage());
+
+ $form->text('name', '素材标题')
+ ->rules([
+ 'required',
+ 'max:255',
+ ], [
+ 'max' => '标题最大长度不能超过 :max 个字符',
+ ])
+ ->required();
+ $this->cover($form);
+ $form->url('url', '素材地址');
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Admin/TagController.php b/modules/Cms/Http/Controllers/Admin/TagController.php
new file mode 100644
index 0000000..eff5254
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Admin/TagController.php
@@ -0,0 +1,48 @@
+model()
+ ->withCount(['articles', 'pages'])
+ ->orderByDesc('id');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('name', '标签名称');
+ });
+ });
+
+ $grid->column('name', '标签名称');
+ $grid->column('articles_count');
+ $grid->column('pages_count');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Tag());
+
+ $form->text('name', '标签名称');
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Admin/VersionController.php b/modules/Cms/Http/Controllers/Admin/VersionController.php
new file mode 100644
index 0000000..5c4cb87
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Admin/VersionController.php
@@ -0,0 +1,68 @@
+header($model . ' => ' . $key)
+ ->description('数据操作日志')
+ ->body($this->grid($model, $key));
+ }
+
+ public function grid($model, $key): Grid
+ {
+ $grid = new Grid(new Version());
+
+ $grid->disableCreateButton();
+ $grid->disableActions();
+ $grid->disableTools();
+
+ $grid->model()->whereHasMorph(
+ 'versionable',
+ $model,
+ function (Builder $query) use ($key) {
+ $query->where('id', $key);
+ }
+ )->orderByDesc('id');
+
+ $grid->column('操作用户')->display(function () {
+ if ($this->user_id < config('mall.administrator_max_id')) {
+ config(['versionable.user_model' => Administrator::class]);
+
+ return '[Admin] ' . ($this->user->name ?? '--SYSTEM--');
+ } else {
+ return '[USER] ' . ($this->user->username ?? '--USER--');
+ }
+ });
+ $grid->column('versionable_type', '数据变动')->expand(function () {
+ $data = [];
+ foreach ($this->contents as $key => $item) {
+ $data[] = [
+ $key,
+ is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE) : $item,
+ ];
+ }
+
+ return new Table(['字段名称', '变更值'], $data);
+ });
+
+ $grid->column('操作时间')->display(function () {
+ return $this->created_at->toDateTimeString();
+ });
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Api/ArticleController.php b/modules/Cms/Http/Controllers/Api/ArticleController.php
new file mode 100644
index 0000000..c868d86
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Api/ArticleController.php
@@ -0,0 +1,113 @@
+
+ * @param Request $request
+ * @return mixed
+ */
+ public function index(Request $request)
+ {
+ $categoryId = $request->category_id;
+ $title = $request->title;
+ $subTitle = $request->sub_title;
+ $slug = $request->slug;
+ $tagId = $request->tag_id;
+
+ $result = Article::query()
+ ->when($title, function ($query) use ($title) {
+ $query->where('title', 'like', "%{$title}%");
+ })
+ ->when($subTitle, function ($query) use ($subTitle) {
+ $query->where('sub_title', 'like', "%{$subTitle}%");
+ })
+ ->when($slug, function ($query) use ($slug) {
+ $query->where('slug', 'like', "%{$slug}%");
+ })
+ ->when($categoryId, function ($query) use ($categoryId) {
+ $query->whereHas('categories', function ($cate) use ($categoryId) {
+ $cate->where('category_id', $categoryId);
+ });
+ })
+ ->when($tagId, function ($query) use ($tagId) {
+ $query->whereHas('tags', function ($tag) use ($tagId) {
+ $tag->where('tag_id', $tagId);
+ });
+ })
+ ->where('status', 1)
+ ->with(['categories', 'tags'])
+ ->paginate(10);
+
+ return $this->success(new ArticleCollection($result));
+ }
+
+ /**
+ * Notes : 文章详情
+ *
+ * @Date : 2021/4/16 10:45 上午
+ * @Author : < Jason.C >
+ * @param Article $article
+ * @return mixed
+ */
+ public function show(Article $article)
+ {
+ $article->read(Api::userId());
+
+ return $this->success(new ArticleResource($article));
+ }
+
+ /**
+ * Notes: 收藏/取消收藏
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/11 9:59
+ */
+ public function favorite(Article $article): JsonResponse
+ {
+ $user = Api::user();
+
+ $user->toggleFavorite($article);
+
+ return $this->success([
+ 'count' => $article->favorites()->count(),
+ 'favorite' => $user->hasFavorited($article),
+ ]);
+ }
+
+ /**
+ * Notes: 点赞/取消点赞
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/11 10:18
+ * @param Article $article
+ * @return JsonResponse
+ * @throws \Exception
+ */
+ public function subscribe(Article $article): JsonResponse
+ {
+ $user = Api::user();
+
+ $user->toggleSubscribe($article);
+
+ return $this->success([
+ 'count' => $article->subscribers()->count(),
+ 'subscribed' => $user->hasSubscribed($article),
+ ]);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Api/CategoryController.php b/modules/Cms/Http/Controllers/Api/CategoryController.php
new file mode 100644
index 0000000..58359a5
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Api/CategoryController.php
@@ -0,0 +1,50 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function index(Request $request)
+ {
+ $parentId = $request->get('parent_id', 0);
+
+ if (!is_integer($parentId)) {
+ return $this->failed('The param must be integer');
+ }
+
+ $result = Category::where('parent_id', $parentId)
+ ->where('status', 1)
+ ->orderBy('order', 'asc')
+ ->get();
+
+ return $this->success(new CategoryCollection($result));
+ }
+
+ /**
+ * Notes : 分类的详情
+ * @Date : 2021/4/19 9:29 上午
+ * @Author : < Jason.C >
+ * @param \Modules\Cms\Models\Category $category
+ * @return mixed
+ */
+ public function show(Category $category)
+ {
+
+ return $this->success(new CategoryResource($category));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Api/PageController.php b/modules/Cms/Http/Controllers/Api/PageController.php
new file mode 100644
index 0000000..691bb8a
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Api/PageController.php
@@ -0,0 +1,90 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function index(Request $request)
+ {
+ $title = $request->title;
+ $subTitle = $request->sub_title;
+ $slug = $request->slug;
+ $tagId = $request->tag_id;
+
+ $result = Page::when($title, function ($query) use ($title) {
+ $query->where('title', 'like', "%{$title}%");
+ })->when($subTitle, function ($query) use ($subTitle) {
+ $query->where('sub_title', 'like', "%{$subTitle}%");
+ })->when($slug, function ($query) use ($slug) {
+ $query->where('slug', 'like', "%{$slug}%");
+ })->when($tagId, function ($query) use ($tagId) {
+ $query->whereHas('tags', function ($tag) use ($tagId) {
+ $tag->where('tag_id', $tagId);
+ });
+ })->where('status', 1)->with(['tags'])->paginate();
+
+ $result = new PageCollection($result);
+
+ return $this->success($result);
+ }
+
+ /**
+ * Notes : 单页详情
+ *
+ * @Date : 2021/4/16 11:25 上午
+ * @Author : < Jason.C >
+ * @param \Modules\Cms\Models\Page $page
+ * @return mixed
+ */
+ public function show(Page $page)
+ {
+ $page->incrementClicks();
+
+ return $this->success(new PageResource($page));
+ }
+
+ /**
+ * Notes: 隐私条款
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/29 11:59
+ */
+ public function secret()
+ {
+ $info = Page::where('slug', 'secret')->first();
+ $info->incrementClicks();
+
+ return $this->success(new PageResource($info));
+ }
+
+ /**
+ * Notes: 服务协议
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/29 12:00
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function protocol()
+ {
+ $info = Page::where('slug', 'protocol')->first();
+ $info->incrementClicks();
+
+ return $this->success(new PageResource($info));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Controllers/Api/TagController.php b/modules/Cms/Http/Controllers/Api/TagController.php
new file mode 100644
index 0000000..a72f52a
--- /dev/null
+++ b/modules/Cms/Http/Controllers/Api/TagController.php
@@ -0,0 +1,37 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function index(Request $request)
+ {
+ $name = $request->name;
+
+ $tags = Tag::when($name, function ($query) use ($name) {
+ $query->where('name', 'like', "%{$name}%");
+ })->orderByDesc('id')->paginate();
+
+ return $this->success(new TagCollection($tags));
+ }
+
+ public function show(Tag $tag)
+ {
+ return $this->success(new TagResource($tag));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/ArticleBaseResource.php b/modules/Cms/Http/Resources/ArticleBaseResource.php
new file mode 100644
index 0000000..c6da0fa
--- /dev/null
+++ b/modules/Cms/Http/Resources/ArticleBaseResource.php
@@ -0,0 +1,29 @@
+ $this->id,
+ 'title' => $this->title,
+ // 'sub_title' => $this->sub_title,
+ 'description' => $this->description,
+ // 'slug' => $this->slug,
+ 'categories' => CategoryBaseResource::collection($this->categories),
+ 'tags' => TagResource::collection($this->tags),
+ 'cover' => $this->cover_url,
+ 'pictures' => $this->pictures_url,
+ 'clicks' => $this->clicks,
+ 'favorites' => $this->favorites()->count(),
+ 'subscribes' => $this->subscribers()->count(),
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/ArticleCollection.php b/modules/Cms/Http/Resources/ArticleCollection.php
new file mode 100644
index 0000000..19aae07
--- /dev/null
+++ b/modules/Cms/Http/Resources/ArticleCollection.php
@@ -0,0 +1,31 @@
+ $this->collection->map(function ($article) {
+ return new ArticleBaseResource($article);
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+ protected function page(): array
+ {
+ return [
+ 'current' => $this->currentPage(),
+ 'total_page' => $this->lastPage(),
+ 'per_page' => $this->perPage(),
+ 'has_more' => $this->hasMorePages(),
+ 'total' => $this->total(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/ArticleResource.php b/modules/Cms/Http/Resources/ArticleResource.php
new file mode 100644
index 0000000..796114e
--- /dev/null
+++ b/modules/Cms/Http/Resources/ArticleResource.php
@@ -0,0 +1,36 @@
+ $this->id,
+ 'title' => $this->title,
+ // 'sub_title' => $this->sub_title,
+ 'description' => $this->description,
+ // 'slug' => $this->slug,
+ 'categories' => CategoryBaseResource::collection($this->categories),
+ 'tags' => TagResource::collection($this->tags),
+ 'cover' => $this->cover_url,
+ 'pictures' => $this->pictures_url,
+ 'clicks' => $this->clicks,
+ 'favorites' => $this->favorites()->count(),
+ 'subscribes' => $this->subscribers()->count(),
+ 'isFavorite' => $user && $this->isFavoritedBy($user),
+ 'isSubscribed' => $user && $this->isSubscribedBy($user),
+ 'content' => $this->content,
+ 'attachments' => $this->attachments,
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/CategoryBaseResource.php b/modules/Cms/Http/Resources/CategoryBaseResource.php
new file mode 100644
index 0000000..eb6db15
--- /dev/null
+++ b/modules/Cms/Http/Resources/CategoryBaseResource.php
@@ -0,0 +1,19 @@
+ $this->id,
+ 'title' => $this->title,
+ 'slug' => $this->slug,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/CategoryCollection.php b/modules/Cms/Http/Resources/CategoryCollection.php
new file mode 100644
index 0000000..6a66dfc
--- /dev/null
+++ b/modules/Cms/Http/Resources/CategoryCollection.php
@@ -0,0 +1,33 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return array
+ */
+ public function toArray($request): array
+ {
+ return $this->collection->map(function ($category) {
+ return [
+ 'category_id' => $category->id,
+ 'title' => $category->title,
+ 'slug' => $category->slug,
+ 'description' => $category->description,
+ 'cover' => $category->cover_url,
+ 'pictures' => $category->pictures_url,
+ 'created_at' => (string) $category->created_at,
+ 'children' => new self($category->children),
+ ];
+ })->toArray();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/CategoryResource.php b/modules/Cms/Http/Resources/CategoryResource.php
new file mode 100644
index 0000000..066a874
--- /dev/null
+++ b/modules/Cms/Http/Resources/CategoryResource.php
@@ -0,0 +1,25 @@
+ $this->id,
+ 'parent' => new self($this->parent),
+ 'title' => $this->title,
+ 'slug' => $this->slug,
+ 'description' => $this->description,
+ 'cover' => $this->cover_url,
+ 'pictures' => $this->pictures_url,
+ 'content' => $this->content,
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/PageCollection.php b/modules/Cms/Http/Resources/PageCollection.php
new file mode 100644
index 0000000..1cf1f84
--- /dev/null
+++ b/modules/Cms/Http/Resources/PageCollection.php
@@ -0,0 +1,42 @@
+ $this->collection->map(function ($page) {
+ return [
+ 'page_id' => $page->id,
+ 'title' => $page->title,
+ 'sub_title' => $page->sub_title,
+ 'tags' => TagResource::collection($page->tags),
+ 'description' => $page->description,
+ 'slug' => $page->slug,
+ 'cover' => $page->cover_url,
+ 'pictures' => $page->pictures_url,
+ 'clicks' => $page->clicks,
+ 'created_at' => (string) $page->created_at,
+ ];
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+ protected function page(): array
+ {
+ return [
+ 'current' => $this->currentPage(),
+ 'total_page' => $this->lastPage(),
+ 'per_page' => $this->perPage(),
+ 'has_more' => $this->hasMorePages(),
+ 'total' => $this->total(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/PageResource.php b/modules/Cms/Http/Resources/PageResource.php
new file mode 100644
index 0000000..6dfc61e
--- /dev/null
+++ b/modules/Cms/Http/Resources/PageResource.php
@@ -0,0 +1,28 @@
+ $this->id,
+ 'title' => $this->title,
+ 'sub_title' => $this->sub_title,
+ 'tags' => TagResource::collection($this->tags),
+ 'description' => $this->description,
+ 'slug' => $this->slug,
+ 'cover' => $this->cover_url,
+ 'pictures' => $this->pictures_url,
+ 'clicks' => $this->clicks,
+ 'content' => $this->content,
+ 'attachments' => $this->attachments_url,
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/TagCollection.php b/modules/Cms/Http/Resources/TagCollection.php
new file mode 100644
index 0000000..ffd89da
--- /dev/null
+++ b/modules/Cms/Http/Resources/TagCollection.php
@@ -0,0 +1,31 @@
+ $this->collection->map(function ($tag) {
+ return new TagResource($tag);
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+ protected function page(): array
+ {
+ return [
+ 'current' => $this->currentPage(),
+ 'total_page' => $this->lastPage(),
+ 'per_page' => $this->perPage(),
+ 'has_more' => $this->hasMorePages(),
+ 'total' => $this->total(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Http/Resources/TagResource.php b/modules/Cms/Http/Resources/TagResource.php
new file mode 100644
index 0000000..b52185b
--- /dev/null
+++ b/modules/Cms/Http/Resources/TagResource.php
@@ -0,0 +1,18 @@
+ $this->id,
+ 'name' => $this->name,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Models/Article.php b/modules/Cms/Models/Article.php
new file mode 100644
index 0000000..5d1ce45
--- /dev/null
+++ b/modules/Cms/Models/Article.php
@@ -0,0 +1,123 @@
+ 'json',
+ 'attachments' => 'json',
+ ];
+
+ /**
+ * 不参与版本记录的字段
+ *
+ * @var array|string[]
+ */
+ protected array $dontVersionable = ['clicks', 'updated_at'];
+
+ /**
+ * Notes: 阅读记录
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/20 9:57
+ * @return HasMany
+ */
+ public function logs(): HasMany
+ {
+ return $this->hasMany(Log::class);
+ }
+
+ /**
+ * Notes: 阅读
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/20 9:57
+ * @param int $user_id
+ */
+ public function read($user_id = '')
+ {
+ $this->incrementClicks();
+ if ($user_id) {
+ $this->logs()->create([
+ 'user_id' => $user_id
+ ]);
+
+ if (Task::query()->shown()->where('key', 'tour_article')->first()) {
+ # todo 阅读得水滴
+ TaskFacade::do('tour_article', $user_id);
+ }
+
+ if (Task::query()->shown()->where('key',
+ 'read_article')->first() && $this->categories()->Health()->first()) {
+ # todo 阅读100次得水滴
+ TaskFacade::do('read_article', $user_id);
+ }
+
+
+ }
+ }
+
+ /**
+ * Notes : 文章所属分类
+ *
+ * @Date : 2021/4/15 12:44 下午
+ * @Author : < Jason.C >
+ * @return BelongsToMany
+ */
+ public function categories(): BelongsToMany
+ {
+ return $this->belongsToMany(Category::class, 'cms_article_category')
+ ->using(ArticleCategory::class)
+ ->withTimestamps();
+ }
+
+ /**
+ * Notes : 获取附件的下载地址
+ *
+ * @Date : 2021/4/16 12:01 下午
+ * @Author : < Jason.C >
+ * @return Collection
+ */
+ public function getAttachmentsUrlAttribute(): Collection
+ {
+ return collect($this->attachments)->map(function ($item) {
+ return Storage::url($item);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Models/ArticleCategory.php b/modules/Cms/Models/ArticleCategory.php
new file mode 100644
index 0000000..63e4bc2
--- /dev/null
+++ b/modules/Cms/Models/ArticleCategory.php
@@ -0,0 +1,14 @@
+ 'json',
+ ];
+
+ /**
+ * 不参与版本记录的字段
+ *
+ * @var array|string[]
+ */
+ protected array $dontVersionable = ['updated_at'];
+
+ public function children(): HasMany
+ {
+ return $this->hasMany(self::class, 'parent_id');
+ }
+
+ /**
+ * Notes : 分类下的文章
+ *
+ * @Date : 2021/4/15 12:47 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function articles(): BelongsToMany
+ {
+ return $this->belongsToMany(Article::class, (new ArticleCategory())->getTable())
+ ->using(ArticleCategory::class)
+ ->withTimestamps();
+ }
+
+ /**
+ * Notes: 健康文章
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/31 10:05
+ */
+ public function scopeHealth(Builder $query): Builder
+ {
+ return $query->where('slug', 'HEALTH');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Models/Log.php b/modules/Cms/Models/Log.php
new file mode 100644
index 0000000..7bb8ef4
--- /dev/null
+++ b/modules/Cms/Models/Log.php
@@ -0,0 +1,15 @@
+ 'json',
+ 'attachments' => 'json',
+ ];
+
+ /**
+ * 不参与版本记录的字段
+ * @var array|string[]
+ */
+ protected array $dontVersionable = ['updated_at'];
+
+ /**
+ * Notes : 获取附件的下载地址
+ * @Date : 2021/4/16 12:01 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Support\Collection
+ */
+ public function getAttachmentsUrlAttribute(): Collection
+ {
+ return collect($this->attachments)->map(function ($item) {
+ return Storage::url($item);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Models/Storage.php b/modules/Cms/Models/Storage.php
new file mode 100644
index 0000000..4bfedee
--- /dev/null
+++ b/modules/Cms/Models/Storage.php
@@ -0,0 +1,24 @@
+url) {
+ return $this->url . '/' . $this->cover;
+ } else {
+ return $this->cover_url;
+ }
+ }
+
+}
diff --git a/modules/Cms/Models/Tag.php b/modules/Cms/Models/Tag.php
new file mode 100644
index 0000000..3b4d1dc
--- /dev/null
+++ b/modules/Cms/Models/Tag.php
@@ -0,0 +1,34 @@
+morphedByMany(Article::class, 'taggable', 'cms_taggable');
+ }
+
+ public function pages(): MorphToMany
+ {
+ return $this->morphedByMany(Page::class, 'taggable', 'cms_taggable');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/Models/Taggable.php b/modules/Cms/Models/Taggable.php
new file mode 100644
index 0000000..ef814c5
--- /dev/null
+++ b/modules/Cms/Models/Taggable.php
@@ -0,0 +1,14 @@
+loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+ }
+
+ /**
+ * Register the service provider.
+ * @return void
+ */
+ public function register()
+ {
+ $this->registerConfig();
+
+ $this->app->register(RouteServiceProvider::class);
+ }
+
+ /**
+ * Register config.
+ * @return void
+ */
+ protected function registerConfig()
+ {
+ $this->publishes([
+ module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'),
+ ], 'config');
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
+ );
+ }
+
+ /**
+ * Get the services provided by the provider.
+ * @return array
+ */
+ public function provides(): array
+ {
+ return [];
+ }
+
+}
diff --git a/modules/Cms/Providers/RouteServiceProvider.php b/modules/Cms/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..22f31fe
--- /dev/null
+++ b/modules/Cms/Providers/RouteServiceProvider.php
@@ -0,0 +1,62 @@
+mapAdminRoutes();
+ $this->mapApiRoutes();
+ }
+
+ protected function mapApiRoutes()
+ {
+ Route::as(config('api.route.as'))
+ ->domain(config('api.route.domain'))
+ ->middleware(config('api.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('api.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/api.php'));
+ }
+
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+
+}
diff --git a/modules/Cms/README.md b/modules/Cms/README.md
new file mode 100644
index 0000000..1ce5ed5
--- /dev/null
+++ b/modules/Cms/README.md
@@ -0,0 +1,49 @@
+# CMS
+
+## 安装
+
+```shell
+composer require overtrue/laravel-versionable
+```
+
+## 接口文档
+
+### 1. 文章
+
+#### 1. 文章列表
+
+> GET: /api/cms/articles
+
+#### 2. 文章详情
+
+> GET: /api/cms/articles/{article_id}
+
+### 2. 单页
+
+#### 1. 单页列表
+
+> GET: /api/cms/pages
+
+#### 2. 单页详情
+
+> GET: /api/cms/pages/{page_id}
+
+### 3. 分类
+
+#### 1. 分类列表
+
+> GET: /api/cms/categories
+
+#### 2. 分类详情
+
+> GET: /api/cms/categories/{category_id}
+
+### 4. 标签
+
+#### 1. 标签列表
+
+> GET: /api/cms/tags
+
+#### 2. 标签详情
+
+> GET: /api/cms/tags/{tag_id}
diff --git a/modules/Cms/Routes/admin.php b/modules/Cms/Routes/admin.php
new file mode 100644
index 0000000..91a9013
--- /dev/null
+++ b/modules/Cms/Routes/admin.php
@@ -0,0 +1,17 @@
+ 'cms',
+ 'namespace' => 'Admin',
+ 'as' => 'cms.',
+], function (Router $router) {
+ $router->resource('articles', 'ArticleController');
+ $router->resource('pages', 'PageController');
+ $router->resource('categories', 'CategoryController');
+ $router->resource('tags', 'TagController');
+ $router->resource('storages', 'StorageController');
+ $router->get('versions/{model}/{key}', 'VersionController@index')->name('versions');
+});
diff --git a/modules/Cms/Routes/api.php b/modules/Cms/Routes/api.php
new file mode 100644
index 0000000..c2f994f
--- /dev/null
+++ b/modules/Cms/Routes/api.php
@@ -0,0 +1,34 @@
+ 'cms',
+ 'namespace' => 'Api',
+ 'middleware' => config('api.route.middleware_guess'),
+], function (Router $router) {
+ $router->get('articles', 'ArticleController@index');
+ $router->get('articles/{article}', 'ArticleController@show');
+
+ $router->get('pages', 'PageController@index');
+ $router->get('pages/secret', 'PageController@secret');
+ $router->get('pages/protocol', 'PageController@protocol');
+ $router->get('pages/{page}', 'PageController@show');
+
+ $router->get('categories', 'CategoryController@index');
+ $router->get('categories/{category}', 'CategoryController@show');
+
+ $router->get('tags', 'TagController@index');
+ $router->get('tags/{tag}', 'TagController@show');
+});
+
+Route::group([
+ 'prefix' => 'cms',
+ 'namespace' => 'Api',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('articles/favorite/{article}', 'ArticleController@favorite');//收藏、取消收藏
+ $router->get('articles/subscribe/{article}', 'ArticleController@subscribe');//点赞、取消点赞
+
+});
\ No newline at end of file
diff --git a/modules/Cms/Traits/HasTags.php b/modules/Cms/Traits/HasTags.php
new file mode 100644
index 0000000..88a99e2
--- /dev/null
+++ b/modules/Cms/Traits/HasTags.php
@@ -0,0 +1,25 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
+ */
+ public function tags(): MorphToMany
+ {
+ return $this->morphToMany(Tag::class, 'taggable', 'cms_taggable')
+ ->using(Taggable::class)
+ ->withTimestamps();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Cms/composer.json b/modules/Cms/composer.json
new file mode 100644
index 0000000..9867aa9
--- /dev/null
+++ b/modules/Cms/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "uztech/cms-module",
+ "description": "内容模块",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Jason.Chen",
+ "email": "chenjxlg@163.com"
+ }
+ ],
+ "require": {
+ "php": "^7.3|^8.0",
+ "encore/laravel-admin": "^1.8",
+ "genealabs/laravel-model-caching": "^0.11.3",
+ "jasonc/api": "^5.0.0",
+ "joshbrw/laravel-module-installer": "^2.0",
+ "laravel/framework": "^8.5",
+ "nwidart/laravel-modules": "^8.2",
+ "overtrue/laravel-versionable": "^2.6"
+ },
+ "extra": {
+ "module-dir": "modules",
+ "laravel": {
+ "providers": [],
+ "aliases": {
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Cms\\": ""
+ }
+ }
+}
diff --git a/modules/Cms/module.json b/modules/Cms/module.json
new file mode 100644
index 0000000..6dcf928
--- /dev/null
+++ b/modules/Cms/module.json
@@ -0,0 +1,15 @@
+{
+ "name": "Cms",
+ "alias": "cms",
+ "description": "内容管理模块",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\Cms\\Providers\\CmsServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [],
+ "version": "1.0.0",
+ "author": "Jason.Chen"
+}
diff --git a/modules/Configuration/.gitignore b/modules/Configuration/.gitignore
new file mode 100644
index 0000000..4b6cb11
--- /dev/null
+++ b/modules/Configuration/.gitignore
@@ -0,0 +1,4 @@
+.idea
+vendor
+.DS_Store
+composer.lock
\ No newline at end of file
diff --git a/modules/Configuration/Actions/UpdateSource.php b/modules/Configuration/Actions/UpdateSource.php
new file mode 100644
index 0000000..bbbf405
--- /dev/null
+++ b/modules/Configuration/Actions/UpdateSource.php
@@ -0,0 +1,44 @@
+source;
+ $array = explode("\r\n", $source);
+ $data = [];
+ foreach ($array as $value) {
+ $key = explode("|", $value)[0];
+ $params = explode("|", $value)[1];
+ $data[$key] = $params;
+ }
+ $model->source = $data;
+ $model->paramsValue = '';
+ $model->save();
+
+ return $this->response()->success('更改成功')->refresh();
+ } catch (Exception $e) {
+ return $this->response()->error($e->getMessage())->refresh();
+ }
+ }
+
+ public function form()
+ {
+ $this->textarea('source', '配置项')->required();
+ $this->textarea('示例')->disable()->value("key|value\r\nkey|value\r\n非必要请不要随意更改");
+ $this->confirm('确认改变当前配置项?(删除值)');
+ }
+
+}
diff --git a/modules/Configuration/Actions/UpdateType.php b/modules/Configuration/Actions/UpdateType.php
new file mode 100644
index 0000000..f719821
--- /dev/null
+++ b/modules/Configuration/Actions/UpdateType.php
@@ -0,0 +1,36 @@
+type = $request->type;
+ $model->source = [];
+ $model->paramsValue = '';
+ $model->save();
+
+ return $this->response()->success('更改成功')->refresh();
+ } catch (Exception $e) {
+ return $this->response()->error($e->getMessage())->refresh();
+ }
+ }
+
+ public function form()
+ {
+ $this->select('type', '配置类型')->options(Configuration::TYPES);
+ $this->confirm('确认改变当前类型?(删除配置项与值)');
+ }
+
+}
diff --git a/modules/Configuration/Configuration.php b/modules/Configuration/Configuration.php
new file mode 100644
index 0000000..541caa2
--- /dev/null
+++ b/modules/Configuration/Configuration.php
@@ -0,0 +1,73 @@
+ 'modules/Configuration/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ */
+ public static function uninstall()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::where('title', self::$mainTitle)->first();
+
+ $main->delete();
+ }
+
+ /**
+ * 根据模块配置情况创建是否需要配置菜单
+ */
+ protected static function createAdminMenu()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 50,
+ 'title' => self::$mainTitle,
+ 'icon' => 'fa-bars',
+ ]);
+ $modules = ModuleManager::toCollection();
+ $data = [];
+ $i = 1;
+ foreach ($modules as $module) {
+ $need = $module->get('config', false);
+ if ($need) {
+ $name = $module->get('configName', '');
+ if (! $name) {
+ $name = $module->getName();
+ }
+ $alias = $module->getAlias();
+ $data[] = [
+ 'order' => $i,
+ 'title' => $name.'配置',
+ 'icon' => 'fa-bars',
+ 'uri' => 'configurations/'.$alias,
+ ];
+ $i++;
+ }
+ }
+ if (count($data) > 0) {
+ $main->children()->createMany($data);
+ }
+ }
+}
diff --git a/modules/Configuration/Database/Migrations/2021_10_20_000000_create_configurations_table.php b/modules/Configuration/Database/Migrations/2021_10_20_000000_create_configurations_table.php
new file mode 100644
index 0000000..c5b664a
--- /dev/null
+++ b/modules/Configuration/Database/Migrations/2021_10_20_000000_create_configurations_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->string('module', 100)->comment('所属模块');
+ $table->string('title', 100)->comment('配置项名称');
+ $table->string('keyValue', 100)->comment('配置键值');
+ $table->string('paramsValue', 100)->nullable()->comment('值');
+ $table->string('type', 100)->comment('类型');
+ $table->json('source')->nullable()->comment('其他数据源');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('configurations');
+ }
+
+}
diff --git a/modules/Configuration/Http/Controllers/IndexController.php b/modules/Configuration/Http/Controllers/IndexController.php
new file mode 100644
index 0000000..9777123
--- /dev/null
+++ b/modules/Configuration/Http/Controllers/IndexController.php
@@ -0,0 +1,135 @@
+findByAlias($module);
+ $content = new Content();
+ return $content
+ ->title($moduleName->get('configName', $moduleName->getName()).'参数配置')
+ ->body($this->grid($module));
+ }
+
+ public function grid(string $module): Grid
+ {
+ $grid = new Grid(new Configuration());
+ $grid->model()->where('module', $module);
+
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableDelete();
+ $actions->disableView();
+ $actions->add(new UpdateType());
+ if (in_array($actions->row->type, [
+ Configuration::TYPE_SELECT,
+ Configuration::TYPE_CHECKBOX,
+ ])) {
+ $actions->add(new UpdateSource());
+ }
+ });
+ $grid->column('keyValue', '关键字');
+ $grid->column('title', '配置项名称');
+ $grid->column('type', '类型')->using(Configuration::TYPES);
+ $grid->column('值')->display(function () {
+ return $this->params_text;
+ });
+ $grid->column('source', '参数')->display(function () {
+ $str = '';
+ foreach ($this->source ?? [] as $key => $value) {
+ $str .= $key.' = '.$value.'
';
+ }
+
+ return $str;
+ });
+
+ return $grid;
+ }
+
+ public function create(string $module): Content
+ {
+ $moduleName = app('modules')->findByAlias($module);
+ $content = new Content();
+ return $content
+ ->title($moduleName->get('configName', $moduleName->getName()).'参数新增')
+ ->body($this->form($module));
+ }
+
+ public function store(string $module)
+ {
+ return $this->form($module)->store();
+ }
+
+ public function edit(string $module, $id): Content
+ {
+ $moduleName = app('modules')->findByAlias($module);
+
+ $content = new Content();
+ return $content
+ ->title($moduleName->get('configName', $moduleName->getName()).'参数修改')
+ ->body($this->form($module, $id)->edit($id));
+ }
+
+ public function update(string $module, $id)
+ {
+ return $this->form($module, $id)->update($id);
+ }
+
+ public function form(string $module, $id = ''): Form
+ {
+ $form = new Form(new Configuration());
+ $form->hidden('module')->default($module);
+
+ $form->tools(function (Form\Tools $tools) {
+ $tools->disableDelete();
+ $tools->disableView();
+ });
+
+ if (! $id) {
+ $form->text('keyValue', '关键字')->required();
+ $form->text('title', '配置项名称')->required();
+ $form->select('type', '类型')->options(Configuration::TYPES)->required();
+ } else {
+ $form->text('keyValue', '关键字')->disable();
+ $form->text('title', '配置项名称')->disable();
+ $config = Configuration::find($id);
+ switch ($config->type) {
+ case Configuration::TYPE_SELECT:
+ $form->select('paramsValue', '值')
+ ->options($config->source)
+ ->required();
+ break;
+ case Configuration::TYPE_CHECKBOX:
+ $form->checkbox('paramsValue', '值')
+ ->options($config->source)
+ ->required();
+ break;
+ case Configuration::TYPE_NUMBER:
+ $form->number('paramsValue', '值')
+ ->required();
+ break;
+ case Configuration::TYPE_RATE:
+ $form->rate('paramsValue', '值')
+ ->required();
+ break;
+ case Configuration::TYPE_TEXT:
+ default:
+ $form->text('paramsValue', '值')
+ ->required();
+ break;
+ }
+ }
+ return $form;
+ }
+
+}
diff --git a/modules/Configuration/Models/Configuration.php b/modules/Configuration/Models/Configuration.php
new file mode 100644
index 0000000..f3f51b5
--- /dev/null
+++ b/modules/Configuration/Models/Configuration.php
@@ -0,0 +1,82 @@
+ '文本',
+ self::TYPE_IMG => '图片',
+ self::TYPE_SELECT => '下拉列表',
+ self::TYPE_CHECKBOX => '多选框',
+ self::TYPE_NUMBER => '数值',
+ self::TYPE_RATE => '比例',
+ ];
+ protected $casts = [
+ 'source' => 'json',
+ ];
+
+ /**
+ * Notes : 获取当前模块下的参数配置
+ *
+ * @Date : 2021/12/1 13:18
+ * @Author : Mr.wang
+ * @param $module
+ * @return mixed
+ */
+ public static function getModule($module)
+ {
+ return self::where('module', $module)
+ ->get()
+ ->pluck('paramsValue', 'keyValue');
+ }
+
+ /**
+ * Notes : 获取参数值
+ *
+ * @Date : 2021/12/1 11:43
+ * @Author : Mr.wang
+ * @param string $key
+ * @param string $default
+ * @return string
+ */
+ public static function getValue(string $key = '', $default = ''): string
+ {
+ $config = self::where('keyValue', $key)->first();
+ if (! $config) {
+ return $default;
+ }
+
+ return $config->paramsValue ?? $default;
+
+ }
+
+ /**
+ * Notes : 获取参数
+ *
+ * @Date : 2021/12/1 11:42
+ * @Author : Mr.wang
+ * @return string
+ */
+ public function getParamsTextAttribute(): string
+ {
+ if (in_array($this->type, [
+ 'select', 'checkbox', 'radio',
+ ])) {
+ return $this->source[$this->paramsValue] ?? '';
+ } else {
+ return $this->paramsValue ?? '';
+ }
+ }
+}
diff --git a/modules/Configuration/Providers/ConfigurationServiceProvider.php b/modules/Configuration/Providers/ConfigurationServiceProvider.php
new file mode 100644
index 0000000..ef17983
--- /dev/null
+++ b/modules/Configuration/Providers/ConfigurationServiceProvider.php
@@ -0,0 +1,60 @@
+loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+
+ $modules = ModuleManager::toCollection();
+ foreach ($modules as $module) {
+ if ($module->get('config', false)) {
+ $alias = $module->getAlias();
+ $this->app->bind('Conf_'.$alias, function () use ($alias) {
+ return Configuration::getModule($alias);
+ });
+ }
+ }
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->app->register(RouteServiceProvider::class);
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides(): array
+ {
+ return [];
+ }
+}
diff --git a/modules/Configuration/Providers/RouteServiceProvider.php b/modules/Configuration/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..da54e67
--- /dev/null
+++ b/modules/Configuration/Providers/RouteServiceProvider.php
@@ -0,0 +1,52 @@
+mapAdminRoutes();
+ }
+
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+}
diff --git a/modules/Configuration/README.md b/modules/Configuration/README.md
new file mode 100644
index 0000000..4654ce5
--- /dev/null
+++ b/modules/Configuration/README.md
@@ -0,0 +1,6 @@
+# laravel-configuration-module
+
+# 参数配置模块
+
+## 需要config的模块配置
+
diff --git a/modules/Configuration/Routes/admin.php b/modules/Configuration/Routes/admin.php
new file mode 100644
index 0000000..a4106c2
--- /dev/null
+++ b/modules/Configuration/Routes/admin.php
@@ -0,0 +1,19 @@
+ 'configurations.',
+], function (Router $router) {
+ $router->get('configurations/{module}', 'IndexController@index')
+ ->where(['module' => '[a-z-_]+']);
+ $router->post('configurations/{module}', 'IndexController@store')
+ ->where(['module' => '[a-z-_]+']);
+ $router->get('configurations/{module}/create', 'IndexController@create')
+ ->where(['module' => '[a-z-_]+']);
+ $router->get('configurations/{module}/{id}/edit', 'IndexController@edit')
+ ->where(['module' => '[a-z-_]+', 'id' => '[0-9]+']);
+ $router->put('configurations/{module}/{id}', 'IndexController@update')
+ ->where(['module' => '[a-z-_]+', 'id' => '[0-9]+']);
+});
diff --git a/modules/Configuration/composer.json b/modules/Configuration/composer.json
new file mode 100644
index 0000000..4da3e40
--- /dev/null
+++ b/modules/Configuration/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "uztech/configuration-module",
+ "description": "",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Yz-Leady",
+ "email": "149307205@qq.com"
+ }
+ ],
+ "require": {
+ "genealabs/laravel-model-caching": "^0.11.3"
+ },
+ "extra": {
+ "module-dir": "modules"
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Configuration\\": ""
+ }
+ }
+}
diff --git a/modules/Configuration/module.json b/modules/Configuration/module.json
new file mode 100644
index 0000000..c6a2d1e
--- /dev/null
+++ b/modules/Configuration/module.json
@@ -0,0 +1,18 @@
+{
+ "name": "Configuration",
+ "alias": "configuration",
+ "description": "模块参数配置管理",
+ "keywords": [
+ "Leady",
+ "参数配置"
+ ],
+ "priority": 0,
+ "providers": [
+ "Modules\\Configuration\\Providers\\ConfigurationServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [],
+ "version": "1.0.0",
+ "author": "Leady"
+}
diff --git a/modules/Linker/.gitignore b/modules/Linker/.gitignore
new file mode 100644
index 0000000..4b6cb11
--- /dev/null
+++ b/modules/Linker/.gitignore
@@ -0,0 +1,4 @@
+.idea
+vendor
+.DS_Store
+composer.lock
\ No newline at end of file
diff --git a/modules/Linker/Database/Migrations/0000_00_00_000000_create_linker_relations_table.php b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linker_relations_table.php
new file mode 100644
index 0000000..d7aaeaf
--- /dev/null
+++ b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linker_relations_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->unsignedBigInteger('linker_id')->index();
+ $table->morphs('model');
+ $table->enum('mode', ['replace', 'append', 'override'])->default('override');
+ $table->json('params')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('linker_relations');
+ }
+
+}
diff --git a/modules/Linker/Database/Migrations/0000_00_00_000000_create_linkers_table.php b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linkers_table.php
new file mode 100644
index 0000000..a02bb1b
--- /dev/null
+++ b/modules/Linker/Database/Migrations/0000_00_00_000000_create_linkers_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->string('title')->comment('链接标题');
+ $table->string('type', 50)->comment('链接类型');
+ $table->string('url')->comment('链接地址');
+ $table->json('params')->nullable()->comment('默认参数');
+ $table->boolean('status')->default(0)->comment('状态');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('linkers');
+ }
+
+}
diff --git a/modules/Linker/Http/Controllers/IndexController.php b/modules/Linker/Http/Controllers/IndexController.php
new file mode 100644
index 0000000..20ecd38
--- /dev/null
+++ b/modules/Linker/Http/Controllers/IndexController.php
@@ -0,0 +1,51 @@
+ ['value' => 1, 'text' => '启用', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '禁用', 'color' => 'danger'],
+ ];
+
+ public function grid(): Grid
+ {
+ $grid = new Grid(new Linker());
+
+ $grid->column('title', '标题');
+ $grid->column('type', '类型')->using(Linker::TYPES);
+ $grid->column('url', '目标地址');
+ $grid->column('params', '附加参数');
+ $grid->column('status', '状态')->switch($this->status);
+ $grid->column('created_at', '创建时间');
+ $grid->column('updated_at', '更新时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Linker());
+ $form->text('title', '链接标题')->required();
+ $form->select('type', '链接类型')
+ ->options(Linker::TYPES)
+ ->required();
+ $form->text('url', '目标地址')->required();
+ $form->keyValue('params', '附加参数');
+ $form->switch('status', '状态')
+ ->states($this->status)
+ ->default(1);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Linker/Linker.php b/modules/Linker/Linker.php
new file mode 100644
index 0000000..d503ad6
--- /dev/null
+++ b/modules/Linker/Linker.php
@@ -0,0 +1,66 @@
+
+ */
+ public static function install()
+ {
+ Artisan::call('migrate', [
+ '--path' => 'modules/Linker/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ * @Date : 2021/3/12 11:35 上午
+ * @Author : < Jason.C >
+ */
+ public static function uninstall()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::where('title', self::$mainTitle)->first();
+
+ $main->delete();
+ }
+
+ /**
+ * Notes : 创建后台菜单
+ * @Date : 2021/3/17 9:48 上午
+ * @Author : < Jason.C >
+ */
+ protected static function createAdminMenu()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 45,
+ 'title' => self::$mainTitle,
+ 'icon' => 'fa-bars',
+ ]);
+
+ $main->children()->createMany([
+ [
+ 'order' => 0,
+ 'title' => '链接管理',
+ 'icon' => 'fa-bars',
+ 'uri' => 'linkers',
+ ],
+ ]);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Linker/Models/Linker.php b/modules/Linker/Models/Linker.php
new file mode 100644
index 0000000..ac99ef2
--- /dev/null
+++ b/modules/Linker/Models/Linker.php
@@ -0,0 +1,102 @@
+ 'json',
+ ];
+
+ const TYPE_WEB = 'web';
+ const TYPE_MINI = 'mini';
+ const TYPE_APP_NAVIGATE_TO = 'navigateTo';
+ const TYPE_APP_REDIRECT_TO = 'redirectTo';
+ const TYPE_APP_RE_LAUNCH = 'reLaunch';
+ const TYPE_APP_SWITCH_TAB = 'switchTab';
+ const TYPE_APP_NAVIGATE_BACK = 'navigateBack';
+
+ const TYPES = [
+ self::TYPE_WEB => '网页连接(web)',
+ self::TYPE_MINI => '小程序中跳转',
+ self::TYPE_APP_NAVIGATE_TO => 'APP内部页面转跳',
+ self::TYPE_APP_REDIRECT_TO => '跳转内部页面(关闭当前页)',
+ self::TYPE_APP_RE_LAUNCH => '跳转内部页面(关闭所有页面)',
+ self::TYPE_APP_SWITCH_TAB => 'TAB跳转(关闭所有非TAB页面)',
+ self::TYPE_APP_NAVIGATE_BACK => 'APP页面返回',
+ ];
+
+ const MODE_APPEND = 'append';
+ const MODE_REPLACE = 'replace';
+ const MODE_OVERRIDE = 'override';
+
+ const MODES = [
+ self::MODE_APPEND => '追加',
+ self::MODE_REPLACE => '替换',
+ self::MODE_OVERRIDE => '覆盖',
+ ];
+
+ /**
+ * Notes : 转换链接到实际输出
+ * @Date : 2021/6/21 4:03 下午
+ * @Author : < Jason.C >
+ * @param \Modules\Linker\Models\LinkerRelation $relation
+ * @return array
+ */
+ public function getResource(LinkerRelation $relation): array
+ {
+ $params = '';
+
+ if (is_array($this->params)) {
+ if ($relation->params) {
+ switch ($relation->mode) {
+ case self::MODE_APPEND:
+ $this->params = array_merge($this->params, $relation->params);
+ break;
+ case self::MODE_REPLACE:
+ $this->params = array_replace($this->params, $relation->params);
+ break;
+ case self::MODE_OVERRIDE;
+ $this->params = $relation->params;
+ break;
+ }
+ }
+ $params = http_build_query($this->params);
+ }
+
+ $data = [
+ 'path' => $this->url,
+ 'openType' => $this->type,
+ 'url' => $this->url,
+ 'params' => $params,
+ ];
+ switch ($data['openType']) {
+ case self::TYPE_WEB:
+ $data['path'] = '';
+ $data['url'] .= '?' . $data['params'];
+ $data['params'] = '';
+ break;
+ case self::TYPE_APP_NAVIGATE_BACK:
+ $data['path'] = '';
+ $data['url'] = '';
+ $data['params'] = '';
+ break;
+ default:
+ $data['url'] = '';
+ break;
+ }
+
+ return $data;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Linker/Models/LinkerRelation.php b/modules/Linker/Models/LinkerRelation.php
new file mode 100644
index 0000000..e10ede9
--- /dev/null
+++ b/modules/Linker/Models/LinkerRelation.php
@@ -0,0 +1,40 @@
+ 'json',
+ ];
+
+ /**
+ * Notes : 隶属链接
+ * @Date : 2021/6/18 4:47 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function linker(): BelongsTo
+ {
+ return $this->belongsTo(Linker::class);
+ }
+
+ /**
+ * Notes : 链接对应的模型
+ * @Date : 2021/6/22 2:11 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\MorphTo
+ */
+ public function model(): MorphTo
+ {
+ return $this->morphTo();
+ }
+
+}
diff --git a/modules/Linker/Providers/LinkerServiceProvider.php b/modules/Linker/Providers/LinkerServiceProvider.php
new file mode 100644
index 0000000..75db0e8
--- /dev/null
+++ b/modules/Linker/Providers/LinkerServiceProvider.php
@@ -0,0 +1,47 @@
+loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+ }
+
+ /**
+ * Register the service provider.
+ * @return void
+ */
+ public function register()
+ {
+ $this->app->register(RouteServiceProvider::class);
+ }
+
+ /**
+ * Get the services provided by the provider.
+ * @return array
+ */
+ public function provides(): array
+ {
+ return [];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Linker/Providers/RouteServiceProvider.php b/modules/Linker/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..b8a54a5
--- /dev/null
+++ b/modules/Linker/Providers/RouteServiceProvider.php
@@ -0,0 +1,51 @@
+mapAdminRoutes();
+ }
+
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Linker/README.md b/modules/Linker/README.md
new file mode 100644
index 0000000..9b75460
--- /dev/null
+++ b/modules/Linker/README.md
@@ -0,0 +1,22 @@
+# 链接模块
+
+## 1. 使用
+
+### 1. 模型 Trait
+
+```php
+use Modules\Linker\Traits\HasLinker;
+```
+
+### 2. laravel-admin
+
+自动增加三个字段,
+1. 选择链接地址
+2. 替换链接地址里的参数
+3. 参数替换模式
+
+```php
+use Modules\Linker\Traits\WithLinker;
+
+$this->withUrl($form,'字段名称');
+```
diff --git a/modules/Linker/Routes/admin.php b/modules/Linker/Routes/admin.php
new file mode 100644
index 0000000..f26e11a
--- /dev/null
+++ b/modules/Linker/Routes/admin.php
@@ -0,0 +1,10 @@
+ 'linker.',
+], function (Router $router) {
+ $router->resource('linkers', 'IndexController');
+});
diff --git a/modules/Linker/Traits/HasLinker.php b/modules/Linker/Traits/HasLinker.php
new file mode 100644
index 0000000..915149e
--- /dev/null
+++ b/modules/Linker/Traits/HasLinker.php
@@ -0,0 +1,123 @@
+attributes['linker_id']);
+ unset($model->attributes['linker_params']);
+ unset($model->attributes['linker_mode']);
+ });
+ self::saved(function ($model) {
+ $model->saveWithUrl();
+ });
+ // 这里要注意,如果模型使用了缓存 (use GeneaLabs\LaravelModelCaching\Traits\Cachable)
+ // 在表单实例化模型的时候 要使用 (new Model())->disableModelCaching()
+ self::retrieved(function ($model) {
+ if ($relation = $model->hasLinker()) {
+ $model->attributes['linker_id'] = $relation->linker_id;
+ $model->attributes['linker_params'] = $relation->params;
+ $model->attributes['linker_mode'] = $relation->mode;
+ }
+ });
+ }
+
+ /**
+ * Notes : 处理当前模型多余的三个字段
+ * @Date : 2021/6/22 2:10 下午
+ * @Author : < Jason.C >
+ * @param $value
+ */
+ public function setLinkerIdAttribute($value)
+ {
+ $this->linker_id = $value;
+ }
+
+ public function setLinkerParamsAttribute($value)
+ {
+ $this->linker_params = $value;
+ }
+
+ public function setLinkerModeAttribute($value)
+ {
+ $this->linker_mode = $value;
+ }
+
+ /**
+ * 外链入库
+ * 在BOOT中触发
+ */
+ public function saveWithUrl(): void
+ {
+ if ($this->linker_id) {
+ LinkerRelation::updateOrCreate([
+ 'model_type' => get_class($this),
+ 'model_id' => $this->getKey(),
+ ], [
+ 'linker_id' => $this->linker_id,
+ 'mode' => $this->linker_mode,
+ 'params' => $this->linker_params,
+ ]);
+ } elseif (!$this->linker_id && $this->hasLinker()) {
+ $this->hasLinker()->delete();
+ }
+ }
+
+ /**
+ * 返回当前模型的链接配置
+ * @return null|LinkerRelation
+ */
+ public function hasLinker(): ?LinkerRelation
+ {
+ return LinkerRelation::whereHasMorph(
+ 'model',
+ [get_class($this)],
+ function (Builder $query) {
+ $query->where($this->getKeyName(), $this->getKey());
+ }
+ )->first();
+ }
+
+ /**
+ * Notes : 返回该模型的链接数组
+ * @Date : 2021/6/22 2:08 下午
+ * @Author : < Jason.C >
+ * @return array|null
+ */
+ public function getLinkerAttribute(): ?array
+ {
+ $relation = $this->hasLinker();
+ if ($relation && $relation->linker && $relation->linker->status) {
+ return $relation->linker->getResource($relation);
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Linker/Traits/WithLinker.php b/modules/Linker/Traits/WithLinker.php
new file mode 100644
index 0000000..f121b0b
--- /dev/null
+++ b/modules/Linker/Traits/WithLinker.php
@@ -0,0 +1,31 @@
+ '无链接'];
+ }
+
+ $form->divider('链接设置');
+ $form->select('linker_id', $label)
+ ->options($links);
+ $form->select('linker_mode', '参数模式')
+ ->options(Linker::MODES)
+ ->default(Linker::MODE_OVERRIDE);
+ $form->keyValue('linker_params', '替换参数');
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Linker/composer.json b/modules/Linker/composer.json
new file mode 100644
index 0000000..bc15ec9
--- /dev/null
+++ b/modules/Linker/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "uztech/linker-module",
+ "description": "",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Yz-Leady",
+ "email": "149307205@qq.com"
+ }
+ ],
+ "require": {
+ "php": "^7.3|^8.0",
+ "encore/laravel-admin": "^1.8",
+ "jasonc/api": "^5.0.0",
+ "genealabs/laravel-model-caching": "^0.11.3",
+ "joshbrw/laravel-module-installer": "^2.0",
+ "laravel/framework": "^8.5",
+ "nwidart/laravel-modules": "^8.2"
+ },
+ "extra": {
+ "module-dir": "modules"
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Linker\\": ""
+ }
+ }
+}
diff --git a/modules/Linker/module.json b/modules/Linker/module.json
new file mode 100644
index 0000000..430ff4d
--- /dev/null
+++ b/modules/Linker/module.json
@@ -0,0 +1,15 @@
+{
+ "name": "Linker",
+ "alias": "linker",
+ "description": "链接管理模块(重构版本)",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\Linker\\Providers\\LinkerServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [],
+ "version": "1.0.0",
+ "author": "Leady"
+}
diff --git a/modules/Mall/.gitignore b/modules/Mall/.gitignore
new file mode 100644
index 0000000..4b6cb11
--- /dev/null
+++ b/modules/Mall/.gitignore
@@ -0,0 +1,4 @@
+.idea
+vendor
+.DS_Store
+composer.lock
\ No newline at end of file
diff --git a/modules/Mall/Config/config.php b/modules/Mall/Config/config.php
new file mode 100644
index 0000000..ffa2ae1
--- /dev/null
+++ b/modules/Mall/Config/config.php
@@ -0,0 +1,168 @@
+ 'Mall',
+
+ /*
+ |--------------------------------------------------------------------------
+ | 订单编号计数器位数,取值范围 6 - 16
+ |--------------------------------------------------------------------------
+ */
+ 'order_no_counter_length' => 8,
+
+ /*
+ |--------------------------------------------------------------------------
+ | 退款单编号前缀
+ |--------------------------------------------------------------------------
+ */
+ 'refund_no_counter_prefix' => 'RF',
+
+ /*
+ |--------------------------------------------------------------------------
+ | 退款单编号计数器位数,取值范围 6 - 16 - 退款编号前缀位数
+ |--------------------------------------------------------------------------
+ */
+ 'refund_no_counter_length' => 6,
+
+ /*
+ |--------------------------------------------------------------------------
+ | 商品规格的扩展属性
+ |--------------------------------------------------------------------------
+ */
+ 'sku_extends_attributes' => [
+ 'market_fee' => '平台手续费',
+ ],
+
+ 'administrator_max_id' => 10000,
+
+ 'workflow' => [
+ 'orders' => [
+ 'type' => 'state_machine',
+ 'marking_store' => [
+ 'type' => 'single_state',
+ 'property' => 'currentState',
+ ],
+ 'supports' => [Modules\Mall\Models\Order::class],
+ 'places' => [
+ 'INIT',
+ 'CANCEL',
+ 'PAID',
+ 'DELIVERED',
+ 'SIGNED',
+ 'COMPLETED',
+ 'REFUND_APPLY',
+ 'REFUND_AGREE',
+ 'REFUND_REFUSE',
+ 'REFUND_DELIVER',
+ 'REFUND_DELIVERD',
+ 'REFUND_PROCESS',
+ 'REFUND_COMPLETED',
+ ],
+ 'transitions' => [
+ 'cancel' => [
+ 'from' => ['INIT', 'PAID'],
+ 'to' => 'CANCEL',
+ ],
+ 'pay' => [
+ 'from' => 'INIT',
+ 'to' => 'PAID',
+ ],
+ 'deliver' => [
+ 'from' => 'PAID',
+ 'to' => 'DELIVERED',
+ ],
+ 'sign' => [
+ 'from' => 'DELIVERED',
+ 'to' => 'SIGNED',
+ ],
+ 'complete' => [
+ 'from' => 'SIGNED',
+ 'to' => 'COMPLETED',
+ ],
+ 'refund' => [
+ 'from' => ['PAID', 'SIGNED'],
+ 'to' => 'REFUND_APPLY',
+ ],
+ 'agree' => [
+ 'from' => ['PAID', 'SIGNED', 'REFUND_APPLY'],
+ 'to' => 'REFUND_AGREE',
+ ],
+ 'refuse' => [
+ 'from' => 'REFUND_APPLY',
+ 'to' => 'REFUND_REFUSE',
+ ],
+ 'user_deliver' => [
+ 'from' => 'REFUND_AGREE',
+ 'to' => 'REFUND_DELIVER',
+ ],
+ 'user_deliverd' => [
+ 'from' => 'REFUND_DELIVER',
+ 'to' => 'REFUND_DELIVERD',
+ ],
+ 'process' => [
+ 'from' => ['REFUND_AGREE', 'REFUND_DELIVERD'],
+ 'to' => 'REFUND_PROCESS',
+ ],
+ 'completed' => [
+ 'from' => ['PAID', 'DELIVERED', 'SIGNED', 'REFUND_PROCESS', 'REFUND_DELIVERD'],
+ 'to' => 'REFUND_COMPLETED',
+ ],
+ ],
+ ],
+ 'refunds' => [
+ 'type' => 'state_machine',
+ 'marking_store' => [
+ 'type' => 'single_state',
+ 'property' => 'currentState',
+ ],
+ 'supports' => [Modules\Mall\Models\Refund::class],
+ 'places' => [
+ 'REFUND_APPLY',
+ 'REFUND_AGREE',
+ 'REFUND_REFUSE',
+ 'REFUND_PROCESS',
+ 'REFUND_DELIVER',
+ 'REFUND_DELIVERED',
+ 'REFUND_SIGNED',
+ 'REFUND_COMPLETED',
+ ],
+ 'transitions' => [
+ 'agree' => [
+ 'from' => 'REFUND_APPLY',
+ 'to' => 'REFUND_AGREE',
+ ],
+ 'refuse' => [
+ 'from' => 'REFUND_APPLY',
+ 'to' => 'REFUND_REFUSE',
+ ],
+ 'deliver' => [
+ 'from' => 'REFUND_AGREE',
+ 'to' => 'REFUND_DELIVER',
+ ],
+ 'delivered' => [
+ 'from' => 'REFUND_DELIVER',
+ 'to' => 'REFUND_DELIVERED',
+ ],
+ 'sign' => [
+ 'from' => 'REFUND_DELIVERED',
+ 'to' => 'REFUND_SIGNED',
+ ],
+ 'process' => [
+ 'from' => ['REFUND_AGREE', 'REFUND_SIGNED'],
+ 'to' => 'REFUND_PROCESS',
+ ],
+ 'completed' => [
+ 'from' => 'REFUND_PROCESS',
+ 'to' => 'REFUND_COMPLETED',
+ ],
+ ],
+ ],
+ ],
+
+];
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_addresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_addresses_table.php
new file mode 100644
index 0000000..be53ae1
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_addresses_table.php
@@ -0,0 +1,40 @@
+id();
+ $table->unsignedInteger('user_id')->index();
+ $table->string('name', 32);
+ $table->string('mobile', 32);
+ $table->unsignedInteger('province_id');
+ $table->unsignedInteger('city_id');
+ $table->unsignedInteger('district_id');
+ $table->string('address');
+ $table->boolean('is_default')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_addresses');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_banners_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_banners_table.php
new file mode 100644
index 0000000..aee80c4
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_banners_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->unsignedBigInteger('shop_id')->default(0)->index()->comment('所属店铺');
+ $table->string('title');
+ $table->string('cover');
+ $table->integer('position')->unsigned();
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_banners');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_brands_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_brands_table.php
new file mode 100644
index 0000000..1ba6a26
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_brands_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->string('name');
+ $table->string('cover')->nullable();
+ $table->string('description')->nullable();
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_brands');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_carts_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_carts_table.php
new file mode 100644
index 0000000..b00b875
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_carts_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->unsignedBigInteger('user_id')->index();
+ $table->unsignedBigInteger('sku_id')->comment('加入购物车的商品条目');
+ $table->unsignedInteger('qty')->default(1)->comment('数量');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_carts');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_categories_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_categories_table.php
new file mode 100644
index 0000000..0d417bf
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_categories_table.php
@@ -0,0 +1,40 @@
+id();
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->unsignedBigInteger('parent_id')->default(0)->index()->comment('父级id');
+ $table->string('name');
+ $table->string('slug')->nullable();
+ $table->string('description')->nullable();
+ $table->string('cover')->nullable();
+ $table->integer('order')->default(0);
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_categories');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_deliveries_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_deliveries_table.php
new file mode 100644
index 0000000..2a40cfb
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_deliveries_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->string('name')->comment('模板名称');
+ $table->unsignedTinyInteger('type')->comment('计费方式:1按件数 2按重量');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_deliveries');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_delivery_rules_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_delivery_rules_table.php
new file mode 100644
index 0000000..ef8fb57
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_delivery_rules_table.php
@@ -0,0 +1,40 @@
+id();
+ $table->integer('delivery_id')->comment('配送模板id');
+ $table->json('regions')->nullable()->comment('可配送区域(城市id集)');
+ $table->decimal('first', 8, 2)->default(0)->comment('首件(个)/首重(Kg)');
+ $table->decimal('first_fee', 8, 2)->default(0)->comment('运费(元)');
+
+ $table->decimal('additional', 8, 2)->default(0)->comment('续件/续重');
+ $table->decimal('additional_fee', 8, 2)->default(0)->comment('续费(元)');
+
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_delivery_rules');
+ }
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_expresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_expresses_table.php
new file mode 100644
index 0000000..bb01a3c
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_expresses_table.php
@@ -0,0 +1,43 @@
+id();
+ $table->string('name');
+ $table->string('slug')->nullable()->comment('简称');
+ $table->string('cover')->nullable();
+ $table->string('description')->nullable();
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+
+ Artisan::call('db:seed', [
+ '--class' => ExpressSeeder::class,
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_expresses');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_skus_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_skus_table.php
new file mode 100644
index 0000000..ae661ba
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_skus_table.php
@@ -0,0 +1,41 @@
+id();
+ $table->unsignedBigInteger('goods_id')->index();
+ $table->string('unit')->nullable()->comment('商品单元编号');
+ $table->string('cover')->nullable();
+ $table->decimal('price');
+ $table->decimal('score')->comment('积分/原石');
+ $table->decimal('assets', 8, 2)->comment('资产');
+ $table->integer('stock')->default(0);
+ $table->decimal('weight')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_goods_skus');
+ Schema::dropIfExists('mall_goods_sku_spec');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_spec_values_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_spec_values_table.php
new file mode 100644
index 0000000..efa7351
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_spec_values_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->unsignedBigInteger('spec_id')->index();
+ $table->string('value');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_goods_spec_values');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_specs_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_specs_table.php
new file mode 100644
index 0000000..0fe20bd
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_specs_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->unsignedBigInteger('goods_id')->index();
+ $table->string('name');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_goods_specs');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_table.php
new file mode 100644
index 0000000..8435f46
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_table.php
@@ -0,0 +1,52 @@
+id();
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->unsignedBigInteger('category_id')->index()->comment('分类');
+ $table->unsignedBigInteger('brand_id')->nullable()->index()->comment('品牌ID');
+ $table->unsignedBigInteger('delivery_id')->nullable()->comment('物流模板');
+ $table->unsignedTinyInteger('type')->default(1)->comment('规格类型');
+ $table->unsignedTinyInteger('pay_type')->default(1)->comment('支付方式');
+ $table->unsignedTinyInteger('deduct_stock_type')->default(1)->comment('库存扣减方式');
+ $table->unsignedTinyInteger('channel')->default(1)->comment('商品类型');
+ $table->boolean('status')->default(0);
+ $table->boolean('is_post_sale')->default(1)->comment('是否可以申请售后');
+ $table->unsignedInteger('clicks')->default(0)->comment('浏览量');
+ $table->unsignedInteger('sales')->default(0)->comment('总销量');
+ $table->decimal('original_price', 10, 2)->default(0)->comment('商品显示的原价');
+ $table->string('name');
+ $table->string('cover')->nullable();
+ $table->json('pictures')->nullable();
+ $table->string('description')->nullable();
+ $table->text('content')->nullable();
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_goods');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_tag_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_tag_table.php
new file mode 100644
index 0000000..324d4c0
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_goods_tag_table.php
@@ -0,0 +1,34 @@
+unsignedBigInteger('goods_id')->index();
+ $table->unsignedBigInteger('tag_id')->index();
+ $table->timestamps();
+
+ $table->primary(['goods_id', 'tag_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_goods_tag');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_jobs_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_jobs_table.php
new file mode 100644
index 0000000..0481262
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_jobs_table.php
@@ -0,0 +1,35 @@
+id();
+
+ $table->unsignedBigInteger('shop_id')->index();
+ $table->string('name');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_jobs');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_expresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_expresses_table.php
new file mode 100644
index 0000000..1afe34a
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_expresses_table.php
@@ -0,0 +1,44 @@
+id();
+ $table->unsignedBigInteger('order_id')->index();
+
+ $table->string('name', 32)->nullable();
+ $table->string('mobile', 32)->nullable();
+ $table->unsignedInteger('province_id')->index()->nullable();
+ $table->unsignedInteger('city_id')->index()->nullable();
+ $table->unsignedInteger('district_id')->index()->nullable();
+ $table->string('address')->nullable();
+
+ $table->unsignedBigInteger('express_id')->nullable()->comment('物流公司');
+ $table->string('express_no', 32)->nullable()->comment('物流单号');
+ $table->dateTime('deliver_at')->nullable()->comment('发货时间');
+ $table->dateTime('receive_at')->nullable()->comment('签收时间');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_order_expresses');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_items_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_items_table.php
new file mode 100644
index 0000000..0c9cc8a
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_order_items_table.php
@@ -0,0 +1,36 @@
+bigIncrements('id');
+ $table->unsignedBigInteger('order_id')->index('order_id');
+ $table->morphs('item');
+ $table->unsignedInteger('qty')->comment('数量');
+ $table->unsignedDecimal('price', 20, 2)->comment('单价');
+ $table->json('source')->nullable();
+ $table->timestamp('created_at')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_order_items');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_orders_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_orders_table.php
new file mode 100644
index 0000000..17487eb
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_orders_table.php
@@ -0,0 +1,49 @@
+id();
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->string('order_no', 32)->unique()->comment('订单编号');
+ $table->unsignedInteger('user_id')->index()->comment('下单用户');
+
+ $table->unsignedDecimal('amount', 20, 2)->comment('订单金额');
+ $table->unsignedDecimal('freight', 10, 2)->default(0)->comment('运费');
+
+ $table->timestamp('expired_at')->nullable()->comment('订单过期时间');
+ $table->timestamp('paid_at')->nullable()->comment('支付时间');
+
+ $table->string('state', 16)->comment('状态')->nullable();
+ $table->boolean('type')->comment('订单类型:1 正常 2试用');
+ $table->string('remark')->nullable()->comment('用户留言');
+ $table->string('description')->nullable()->comment('备注');
+
+ $table->json('source')->comment('附加数据')->nullable();
+
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_orders');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_reasons_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_reasons_table.php
new file mode 100644
index 0000000..fb87003
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_reasons_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('title');
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('reasons');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_expresses_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_expresses_table.php
new file mode 100644
index 0000000..5c45ec5
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_expresses_table.php
@@ -0,0 +1,37 @@
+bigIncrements('id');
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->unsignedBigInteger('refund_id')->index()->comment('所属退款单');
+ $table->string('company')->nullable()->comment('退款物流');
+ $table->string('number')->nullable()->comment('退款单号');
+ $table->timestamp('deliver_at')->nullable()->comment('寄回时间');
+ $table->timestamp('receive_at')->nullable()->comment('收到时间');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_refund_expresses');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_items_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_items_table.php
new file mode 100644
index 0000000..db45917
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_items_table.php
@@ -0,0 +1,37 @@
+bigIncrements('id');
+ $table->unsignedBigInteger('refund_id')->index()->comment('所属退款单');
+ $table->unsignedBigInteger('order_id')->comment('所属订单');
+ $table->unsignedBigInteger('order_item_id')->comment('详情ID');
+ $table->unsignedInteger('qty')->comment('数量');
+ $table->unsignedDecimal('price', 20, 2)->comment('单价');
+ $table->json('source')->nullable();
+ $table->timestamp('created_at')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_refund_items');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_logs_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_logs_table.php
new file mode 100644
index 0000000..7fcd12b
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refund_logs_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->unsignedBigInteger('refund_id');
+ $table->morphs('userable');
+ $table->string('pictures')->nullable()->comment('图片');
+ $table->string('title')->nullable()->comment('原因');
+ $table->string('remark')->nullable()->comment('备注');
+ $table->string('state', 16)->comment('状态')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_tags');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refunds_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refunds_table.php
new file mode 100644
index 0000000..e7f291c
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_refunds_table.php
@@ -0,0 +1,41 @@
+bigIncrements('id');
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->unsignedBigInteger('order_id')->index()->comment('所属订单');
+ $table->string('refund_no', 32)->comment('退款单号');
+ $table->unsignedBigInteger('user_id')->comment('下单用户');
+ $table->unsignedDecimal('refund_total', 20, 2)->comment('申请退款金额');
+ $table->unsignedDecimal('actual_total', 20, 2)->comment('实退金额');
+ $table->string('state', 16)->comment('状态')->nullable();
+ $table->string('remark')->nullable()->comment('备注');
+ $table->timestamp('refunded_at')->nullable()->comment('退款时间');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_refunds');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_regions_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_regions_table.php
new file mode 100644
index 0000000..95e98cb
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_regions_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->unsignedBigInteger('parent_id')->index();
+ $table->string('name');
+ $table->boolean('depth');
+ });
+
+ Artisan::call('db:seed', [
+ '--class' => RegionSeeder::class,
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_regions');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_express_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_express_table.php
new file mode 100644
index 0000000..1544195
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_express_table.php
@@ -0,0 +1,34 @@
+unsignedBigInteger('shop_id')->index();
+ $table->unsignedBigInteger('express_id')->index();
+ $table->timestamps();
+
+ $table->primary(['shop_id', 'express_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_shop_express');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_reasons_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_reasons_table.php
new file mode 100644
index 0000000..a12527e
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_reasons_table.php
@@ -0,0 +1,33 @@
+unsignedBigInteger('shop_id')->index();
+ $table->unsignedBigInteger('reason_id')->index();
+ $table->timestamps();
+ $table->primary(['shop_id', 'reason_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_shop_reasons');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_staffers_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_staffers_table.php
new file mode 100644
index 0000000..b82caa1
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shop_staffers_table.php
@@ -0,0 +1,36 @@
+id();
+
+ $table->unsignedBigInteger('shop_id')->index();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->unsignedBigInteger('job_id')->index();
+
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_shop_staffers');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shops_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shops_table.php
new file mode 100644
index 0000000..a4fc1ef
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_shops_table.php
@@ -0,0 +1,46 @@
+id();
+ $table->unsignedInteger('user_id')->index()->default(0);
+ $table->string('name');
+ $table->boolean('is_self')->unsigned()->default(0)->comment('是否自营');
+ $table->string('description')->nullable();
+ $table->string('mobile')->nullable();
+ $table->string('cover')->nullable();
+ $table->boolean('status')->default(0);
+ $table->unsignedInteger('province_id')->index()->default(0);
+ $table->unsignedInteger('city_id')->index()->default(0);
+ $table->unsignedInteger('district_id')->index()->default(0);
+ $table->string('address')->nullable();
+ $table->json('configs')->nullable();
+ $table->integer('order')->default(9999)->comment('排序用的,后台控制');
+ $table->string('reject_reason')->comment('驳回原因')->nullable();
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_shops');
+ }
+
+}
diff --git a/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_tags_table.php b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_tags_table.php
new file mode 100644
index 0000000..35192e2
--- /dev/null
+++ b/modules/Mall/Database/Migrations/0000_00_00_000000_create_mall_tags_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->unsignedBigInteger('shop_id')->index()->comment('所属店铺');
+ $table->string('name');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_tags');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Database/Migrations/2022_07_27_110430_create_mall_activities_table.php b/modules/Mall/Database/Migrations/2022_07_27_110430_create_mall_activities_table.php
new file mode 100644
index 0000000..71ba5c4
--- /dev/null
+++ b/modules/Mall/Database/Migrations/2022_07_27_110430_create_mall_activities_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->string('title');
+ $table->string('description')->nullable();
+ $table->string('cover')->nullable();
+ $table->text('content')->nullable();
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_activities');
+ }
+}
diff --git a/modules/Mall/Database/Migrations/2022_09_08_143557_add_channel_to_mall_orders_table.php b/modules/Mall/Database/Migrations/2022_09_08_143557_add_channel_to_mall_orders_table.php
new file mode 100644
index 0000000..033a126
--- /dev/null
+++ b/modules/Mall/Database/Migrations/2022_09_08_143557_add_channel_to_mall_orders_table.php
@@ -0,0 +1,32 @@
+boolean('channel')->default(1)->comment('渠道')->after('type');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('mall_orders', function (Blueprint $table) {
+
+ });
+ }
+}
diff --git a/modules/Mall/Database/Migrations/2022_09_08_144917_add_type_and_person_to_mall_orders_expresses_table.php b/modules/Mall/Database/Migrations/2022_09_08_144917_add_type_and_person_to_mall_orders_expresses_table.php
new file mode 100644
index 0000000..c9e7412
--- /dev/null
+++ b/modules/Mall/Database/Migrations/2022_09_08_144917_add_type_and_person_to_mall_orders_expresses_table.php
@@ -0,0 +1,33 @@
+boolean('type')->default(1)->comment('方式')->after('receive_at');
+ $table->boolean('person')->nullable()->comment('经办人')->after('type');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('mall_orders_expresses', function (Blueprint $table) {
+
+ });
+ }
+}
diff --git a/modules/Mall/Database/Migrations/2022_09_30_093509_create_mall_videos_table.php b/modules/Mall/Database/Migrations/2022_09_30_093509_create_mall_videos_table.php
new file mode 100644
index 0000000..44e33d6
--- /dev/null
+++ b/modules/Mall/Database/Migrations/2022_09_30_093509_create_mall_videos_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->string('name');
+ $table->string('cover')->nullable();
+ $table->string('path');
+ $table->boolean('status')->default(1);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('mall_videos');
+ }
+}
diff --git a/modules/Mall/Database/Seeders/ExpressSeeder.php b/modules/Mall/Database/Seeders/ExpressSeeder.php
new file mode 100644
index 0000000..4cbdbf4
--- /dev/null
+++ b/modules/Mall/Database/Seeders/ExpressSeeder.php
@@ -0,0 +1,45 @@
+order = $order;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Events/OrderPaid.php b/modules/Mall/Events/OrderPaid.php
new file mode 100644
index 0000000..683c538
--- /dev/null
+++ b/modules/Mall/Events/OrderPaid.php
@@ -0,0 +1,11 @@
+refund = $refund;
+ }
+
+}
diff --git a/modules/Mall/Events/RefundApplied.php b/modules/Mall/Events/RefundApplied.php
new file mode 100644
index 0000000..716e9cd
--- /dev/null
+++ b/modules/Mall/Events/RefundApplied.php
@@ -0,0 +1,27 @@
+order = $order;
+ $this->refund = $refund;
+ }
+
+}
diff --git a/modules/Mall/Events/RefundCompleted.php b/modules/Mall/Events/RefundCompleted.php
new file mode 100644
index 0000000..0e36fb6
--- /dev/null
+++ b/modules/Mall/Events/RefundCompleted.php
@@ -0,0 +1,23 @@
+refund = $refund;
+ }
+
+}
diff --git a/modules/Mall/Events/RefundProcessed.php b/modules/Mall/Events/RefundProcessed.php
new file mode 100644
index 0000000..d03eacb
--- /dev/null
+++ b/modules/Mall/Events/RefundProcessed.php
@@ -0,0 +1,23 @@
+refund = $refund;
+ }
+
+}
diff --git a/modules/Mall/Events/RefundRefused.php b/modules/Mall/Events/RefundRefused.php
new file mode 100644
index 0000000..5c75f6d
--- /dev/null
+++ b/modules/Mall/Events/RefundRefused.php
@@ -0,0 +1,23 @@
+refund = $refund;
+ }
+
+}
diff --git a/modules/Mall/Facades/Item.php b/modules/Mall/Facades/Item.php
new file mode 100644
index 0000000..ca98fed
--- /dev/null
+++ b/modules/Mall/Facades/Item.php
@@ -0,0 +1,127 @@
+model = $item;
+ $this->sku_id = $item->id;
+ $this->shop_id = $item->getShopIdAttribute();
+ $this->orderby = $item->getShopTypeAttribute().'_'.$item->getShopIdAttribute();
+ $this->qty = $qty;
+ $this->price = $item->getItemPrice();
+ $this->address = $address;
+ }
+
+ /**
+ * Notes: 获取条目总价
+ *
+ * @Author:
+ * @Date : 2019/11/21 11:03 上午
+ * @return float
+ */
+ public function total(): float
+ {
+ return bcmul($this->price, $this->qty, 2);
+ }
+
+ /**
+ * Notes: 获取总总量
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 13:20
+ * @return float
+ */
+ public function weight(): float
+ {
+ return bcmul($this->model->getGoodsWeight(), $this->qty, 2);
+ }
+
+ /**
+ * Notes: 获取运费
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 13:34
+ */
+ public function freight(): int
+ {
+ return 0;
+ }
+
+ /**
+ * Notes: 获取shop
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 14:15
+ */
+ public function shop(): array
+ {
+ return [
+ 'shop_id' => $this->model->getShopIdAttribute(),
+ 'name' => $this->model->getShopNameAttribute(),
+ ];
+ }
+
+ /**
+ * Notes: 下单存入order_item表
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/18 8:37
+ */
+ public function getSource(): array
+ {
+ return $this->model->getSource();
+ }
+
+ /**
+ * Notes: 转换成数组
+ *
+ * @Author:
+ * @Date : 2020/9/23 4:00 下午
+ * @return array
+ */
+ public function toArray(): array
+ {
+ return [
+ 'goods_id' => $this->model->goods_id,
+ 'goods_sku_id' => $this->sku_id,
+ 'title' => $this->model->getGoodsName(),
+ 'value' => $this->model->getItemValue(),
+ 'cover' => $this->model->cover_url,
+ 'price' => $this->price,
+ 'qty' => $this->qty,
+ 'score' => $this->model->score,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Facades/Order.php b/modules/Mall/Facades/Order.php
new file mode 100644
index 0000000..8557194
--- /dev/null
+++ b/modules/Mall/Facades/Order.php
@@ -0,0 +1,386 @@
+user_id = $user->getAuthIdentifier();;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 设置类型
+ *
+ * @Author: 玄尘
+ * @Date : 2021/10/18 10:03
+ * @param $type
+ * @return $this
+ */
+ public function type($type): Order
+ {
+ $this->type = $type;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 订单渠道
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/8 15:51
+ */
+ public function channel($channel): self
+ {
+ $this->channel = $channel;
+ return $this;
+ }
+
+ /**
+ * Notes: 设置订单备注信息
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/19 10:40
+ * @param string|null $remark
+ * @return $this
+ */
+ public function remark(?string $remark = ''): Order
+ {
+ $this->remark = $remark;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 附加数据
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/19 10:40
+ * @param array $source
+ * @return $this
+ */
+ public function source(array $source): Order
+ {
+ $this->source = $source;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 设置商品数据
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 13:58
+ * @param $items
+ * @return $this
+ */
+ public function items($items): Order
+ {
+ $this->items = new Collection($items);
+
+ return $this;
+ }
+
+ /**
+ * Notes: 设置订单收货地址
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/19 10:41
+ * @param Address $address
+ * @return $this
+ */
+ public function address(Address $address): Order
+ {
+ $this->address = $address;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 创建订单
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/19 10:42
+ * @return Collection
+ * @throws \Exception
+ */
+ public function create(): Collection
+ {
+ if ($this->items->isEmpty()) {
+ throw new Exception('无法创建无内容的订单');
+ }
+
+ if (! is_numeric($this->user_id)) {
+ throw new Exception('必须先设置订单用户');
+ }
+ $splits = $this->splitOrderByShop();
+
+ DB::beginTransaction();
+
+ try {
+ $orders = [];
+ foreach ($splits as $split) {
+ $orders[] = $this->createOne($split['items']);
+ }
+
+ DB::commit();
+ $result = new Collection($orders);
+
+ $result->total = $this->total();
+
+ return $result;
+ } catch (Exception $exception) {
+ DB::rollBack();
+ throw new Exception($exception->getMessage());
+ }
+ }
+
+ /**
+ * Notes: 按照商户,对订单进行分组
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/19 10:43
+ * @return mixed
+ * @throws \Exception
+ */
+ public function splitOrderByShop()
+ {
+ if (empty($this->items)) {
+ throw new Exception('无法创建无内容的订单');
+ }
+
+ return $this->items->groupBy('orderby')->map(function ($items, $key) {
+ /**
+ * 计算分订单总价格
+ */
+ $items->amount = $items->reduce(function ($total, $item) {
+ return $total + $item->total();
+ });
+
+ /**
+ * 计算分订单总数量
+ */
+ $items->qty = $items->reduce(function ($qty, $item) {
+ return $qty + $item->qty;
+ });
+
+ /**
+ * 计算分订单运费
+ */
+ $items->freight = $items->reduce(function ($total, $item) {
+ return $total + $item->freight();
+ });
+
+ /**
+ * 回传店铺ID
+ */
+ $items->shop_id = $items->first()->shop_id;
+
+ return [
+ 'shop' => $items->first()->shop(),
+ 'items' => $items,
+ ];
+ });
+ }
+
+ /**
+ * Notes: 创建一条订单记录
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/21 10:24
+ * @param Collection $split
+ * @return Builder|Model
+ * @throws \Exception
+ */
+ protected function createOne(Collection $split)
+ {
+ /**
+ * 创建主订单
+ */
+ $order = OrderModel::query()
+ ->create([
+ 'shop_id' => $split->shop_id,
+ 'user_id' => $this->user_id,
+ 'amount' => $split->amount,
+ 'type' => $this->type,
+ 'channel' => $this->channel,
+ 'freight' => $split->freight,
+ 'remark' => $this->remark,
+ 'source' => $this->source ?? null,
+ ]);
+
+ /**
+ * 创建订单子条目
+ */
+ foreach ($split as $item) {
+ // 库存校验
+ if ($item->qty > $item->model->getGoodsStock()) {
+ throw new Exception(sprintf('[%s]库存不足', $item->model->getGoodsName()));
+ }
+
+ $order->items()
+ ->create([
+ 'item_type' => get_class($item->model),
+ 'item_id' => $item->sku_id,
+ 'qty' => $item->qty,
+ 'price' => $item->price,
+ 'source' => $item->getSource(),
+ ]);
+
+ if ($item->model->goods->deduct_stock_type == Goods::DEDUCT_STOCK_ORDER) {
+ // 扣减库存
+ $item->model->deductStock($item->qty, $order->user_id);
+ }
+
+ }
+
+ /**
+ * 自动更新地址
+ */
+ if ($this->address instanceof Address) {
+ $this->setOrderAddress($order, $this->address);
+ }
+
+ /**
+ * 订单自动审核
+ */
+ if (config('order.auto_audit')) {
+ $order->audit();
+ }
+
+ event(new OrderCreated($order));
+
+ return $order;
+ }
+
+ /**
+ * Notes: 计算订单总价格
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/21 10:26
+ * @return float|int|string
+ */
+ public function total()
+ {
+ $this->total = 0;
+ foreach ($this->items as $item) {
+ $this->total = bcadd($this->total, $item->total(), 2);
+ }
+
+ return $this->total;
+ }
+
+ /**
+ * Notes: 设置订单收货地址
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/21 10:26
+ * @param $order
+ * @param $address
+ */
+ protected function setOrderAddress($order, $address)
+ {
+ $order->express()
+ ->create([
+ 'name' => $address->name,
+ 'mobile' => $address->mobile,
+ 'province_id' => $address->province_id,
+ 'city_id' => $address->city_id,
+ 'district_id' => $address->district_id,
+ 'address' => $address->address,
+ ]);
+ }
+
+ /**
+ * Notes: 魔术方法,获取一些参数
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/21 10:27
+ * @param $attr
+ * @return float|int|string|null
+ */
+ public function __get($attr)
+ {
+ switch ($attr) {
+ case 'total':
+ return $this->total();
+ }
+
+ return null;
+ }
+
+ /**
+ * Notes: 获取订单详情
+ *
+ * @Author:
+ * @Date : 2019/11/22 1:51 下午
+ * @param string $order_no
+ * @return mixed
+ */
+ public function get(string $order_no)
+ {
+ return OrderModel::where('user_id', $this->user_id)
+ ->with(['user', 'items', 'express', 'seller', 'logs'])
+ ->where('order_no', $order_no)
+ ->first();
+ }
+
+}
diff --git a/modules/Mall/Facades/Refund.php b/modules/Mall/Facades/Refund.php
new file mode 100644
index 0000000..6dbdb51
--- /dev/null
+++ b/modules/Mall/Facades/Refund.php
@@ -0,0 +1,250 @@
+user = $user;
+ } else {
+ throw new Exception('非法用户');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Notes: 订单
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 8:46
+ * @param OrderModel $order
+ * @return $this
+ */
+ public function order(OrderModel $order): Refund
+ {
+ $this->order = $order;
+
+ return $this;
+ }
+
+ /**
+ * Notes:商品
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 8:46
+ * @param array $items
+ * @return Refund
+ */
+ public function items(array $items): Refund
+ {
+ $this->items = $items;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 退款金额
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 8:48
+ * @param float $total
+ * @return Refund
+ */
+ public function total(float $total): Refund
+ {
+ $this->total = $total;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 设置订单备注信息
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/21 10:31
+ * @param string $remark
+ * @return $this
+ */
+ public function remark(string $remark): Refund
+ {
+ $this->remark = $remark;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 退款日志
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 11:37
+ * @param $logs
+ * @return $this
+ */
+ public function logs($logs): Refund
+ {
+ if (! isset($logs['remark']) && ! empty($this->remark)) {
+ $logs['remark'] = $this->remark;
+ }
+
+ $this->logs = $logs;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 设置退款商品
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 8:51
+ */
+ public function setItems(): Refund
+ {
+ $this->items = $this->order->items->map(function ($item) {
+ return [
+ 'order_item_id' => $item->id,
+ 'qty' => $item->qty,
+ ];
+ });
+
+ return $this;
+ }
+
+ /**
+ * Notes: 创建退款单
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 8:49
+ * @return mixed
+ * @throws \Exception
+ */
+ public function create()
+ {
+
+ if (! $this->order) {
+ throw new Exception('未设置订单');
+ }
+
+ if (empty($this->items)) {
+ $this->setItems();
+ }
+
+ if (! $this->order->can('refund')) {
+ throw new Exception('订单状态不可退款');
+ }
+
+ $maxAmount = 0;
+ $refundItems = [];
+
+ //判断最大可退数量
+ foreach ($this->items as $item) {
+
+ $orderItem = $this->order->items()->find($item['order_item_id']);
+ if (! $orderItem) {
+ throw new Exception('未找到可退商品');
+ }
+
+ if (! $orderItem->canRefund()) {
+ throw new \Exception('退款/货失败,此商品不可退款/货');
+ }
+
+ if ($item['qty'] <= 0) {
+ throw new Exception('【'.$orderItem->source['goods_name'].'】退货数量必须大于0');
+ }
+
+ if ($item['qty'] > $orderItem->qty) {
+ throw new Exception('【'.$orderItem->source['goods_name'].'】超过最大可退数量');
+ }
+
+ $maxAmount += $orderItem->price * $item['qty'];
+
+ $refundItems[] = new RefundItem($orderItem, $item['qty']);
+ }
+
+ // 自动计算退款金额
+ if (is_null($this->total)) {
+ $this->total = $maxAmount;
+ } elseif ($this->total > $maxAmount) {
+ throw new Exception('超过最大可退金额');
+ }
+
+ DB::transaction(function () use ($refundItems) {
+ $this->refund = $this->order->refunds()
+ ->create([
+ 'refund_total' => $this->total,
+ 'actual_total' => 0,
+ 'user_id' => $this->user->id,
+ 'shop_id' => $this->order->shop_id,
+ 'state' => RefundModel::REFUND_APPLY,
+ 'remark' => $this->remark,
+ ]);
+
+ foreach ($refundItems as $item) {
+ $this->refund->items()->create($item->toArray());
+ }
+
+ if ($this->logs) {
+ $this->createLog();
+ }
+
+ event(new RefundApplied($this->order, $this->refund));
+
+ });
+
+ return $this->refund;
+
+ }
+
+ /**
+ * Notes: 添加日志
+ *
+ * @Author: 玄尘
+ * @Date : 2020/12/11 11:43
+ */
+ public function createLog()
+ {
+ $logs = $this->logs;
+
+ if ($this->user) {
+ $logs['userable_type'] = get_class($this->user);
+ $logs['userable_id'] = $this->user->id;
+ $logs['state'] = $this->refund->state;
+ }
+
+ $this->refund->logs()->create($logs);
+ }
+
+}
diff --git a/modules/Mall/Facades/RefundItem.php b/modules/Mall/Facades/RefundItem.php
new file mode 100644
index 0000000..8f1ba8b
--- /dev/null
+++ b/modules/Mall/Facades/RefundItem.php
@@ -0,0 +1,68 @@
+qty = $qty;
+ $this->price = $item->price;
+ $this->order_id = $item->order_id;
+ $this->order_item_id = $item->id;
+ $this->source = $item->source;
+ }
+
+ /**
+ * Notes: 获取条目总价
+ * @Author: 玄尘
+ * @Date : 2020/12/10 9:43
+ * @return float
+ */
+ public function total(): float
+ {
+ return bcmul($this->price, $this->qty, 2);
+ }
+
+ /**
+ * Notes: 返回数组
+ * @Author: 玄尘
+ * @Date : 2021/5/21 10:40
+ * @return array
+ */
+ public function toArray(): array
+ {
+ return [
+ 'qty' => $this->qty,
+ 'price' => $this->price,
+ 'order_id' => $this->order_id,
+ 'order_item_id' => $this->order_item_id,
+ 'source' => $this->source,
+ ];
+ }
+
+ public function toJson($options = 0)
+ {
+ return json_encode($this->toArray(), $options);
+ }
+
+}
diff --git a/modules/Mall/Facades/Workflow.php b/modules/Mall/Facades/Workflow.php
new file mode 100644
index 0000000..d697402
--- /dev/null
+++ b/modules/Mall/Facades/Workflow.php
@@ -0,0 +1,15 @@
+pay();
+
+ return $this->response()->success('审核通过')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php b/modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php
new file mode 100644
index 0000000..11d4323
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php
@@ -0,0 +1,46 @@
+type == OrderExpress::TYPE_EXPRESS && (empty($request->express_id) || empty($request->express_no))) {
+ return $this->response()->error('缺少快递公司或快递单号');
+ }
+
+ $result = $order->deliver($request->express_id, $request->express_no, $request->type, $request->person);
+ if ($result === true) {
+ return $this->response()->success('发货成功')->refresh();
+ } else {
+ return $this->response()->error('失败');
+ }
+ }
+
+ public function form()
+ {
+ $order = Order::find($this->getKey());
+ $expresses = $order->shop->expresses()->pluck('name', 'id');
+
+ $this->select('type', '方式')
+ ->options(OrderExpress::TYPE_MAP)
+ ->required();
+
+ $this->select('express_id', '物流')
+ ->options($expresses);
+ $this->text('express_no', '物流单号');
+ $this->text('person', '经办人');
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/Pay.php b/modules/Mall/Http/Controllers/Admin/Action/Order/Pay.php
new file mode 100644
index 0000000..3b9f1a7
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Order/Pay.php
@@ -0,0 +1,25 @@
+pay();
+
+ return $this->response()->success('支付状态调整成功')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/RefundAudit.php b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundAudit.php
new file mode 100644
index 0000000..a75382d
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundAudit.php
@@ -0,0 +1,57 @@
+state;
+ $remark = $request->remark;
+ $res = false;
+
+ if (!$model->can('agree')) {
+ return $this->response()->error('不可操作')->refresh();
+ }
+
+ if ($state == 'agree') {
+ $res = $model->setOperator($admin)->agree($remark);
+ }
+
+ if ($state == 'refuse') {
+ $res = $model->setOperator($admin)->refuse($remark);
+ }
+
+ if ($res === true) {
+ return $this->response()->success('操作成功')->refresh();
+ }
+
+ return $this->response()->error('操作失败')->refresh();
+ }
+
+ public function form(Model $model)
+ {
+ $this->select('state', '状态')
+ ->options([
+ 'agree' => '通过',
+ 'refuse' => '驳回',
+ ])
+ ->required();
+
+ $this->text('remark', '说明')
+ ->default($express->number ?? '')
+ ->required();
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/RefundReturns.php b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundReturns.php
new file mode 100644
index 0000000..eb4efd5
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundReturns.php
@@ -0,0 +1,44 @@
+can('completed')) {
+ return $this->response()->error('不可操作')->refresh();
+ }
+
+ $res = $refund->setOperator($admin)->returns();
+
+ if ($res === true) {
+ $refund->setOperator($admin)->complete();//设置完成
+
+ return $this->response()->success('操作成功')->refresh();
+ }
+
+ return $this->response()->error('操作失败')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+
+ }
+
+ public function dialog()
+ {
+ $this->confirm('您确定要打款吗');
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Order/RefundSign.php b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundSign.php
new file mode 100644
index 0000000..55ac747
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Order/RefundSign.php
@@ -0,0 +1,42 @@
+can('sign')) {
+ return $this->response()->error('不可操作')->refresh();
+ }
+
+ $res = $model->setOperator($admin)->receive();
+
+ if ($res === true) {
+ return $this->response()->success('操作成功')->refresh();
+ }
+
+ return $this->response()->error('操作失败')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+
+ }
+
+ public function dialog()
+ {
+ $this->confirm('您确定已经收到货了吗');
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Close.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Close.php
new file mode 100644
index 0000000..7bf31cb
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Close.php
@@ -0,0 +1,32 @@
+close()) {
+ return $this->response()->success('店铺关闭成功')->refresh();
+ } else {
+ return $this->response()->error('店铺关闭失败')->refresh();
+ }
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+ public function dialog()
+ {
+ $this->confirm('确定关闭店铺吗?');
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Open.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Open.php
new file mode 100644
index 0000000..0830826
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Open.php
@@ -0,0 +1,32 @@
+open()) {
+ return $this->response()->success('店铺开启成功')->refresh();
+ } else {
+ return $this->response()->error('店铺开启失败')->refresh();
+ }
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+ public function dialog()
+ {
+ $this->confirm('确定开启店铺吗?');
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Pass.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Pass.php
new file mode 100644
index 0000000..6a201ca
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Pass.php
@@ -0,0 +1,32 @@
+pass()) {
+ return $this->response()->success('审核通过成功')->refresh();
+ } else {
+ return $this->response()->error('审核通过失败')->refresh();
+ }
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+ public function dialog()
+ {
+ $this->confirm('确定审核通过?');
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Action/Shop/Reject.php b/modules/Mall/Http/Controllers/Admin/Action/Shop/Reject.php
new file mode 100644
index 0000000..efec0e4
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Action/Shop/Reject.php
@@ -0,0 +1,33 @@
+reject($request->reject_reason)) {
+ return $this->response()->success('驳回申请成功')->refresh();
+ } else {
+ return $this->response()->error('驳回申请失败')->refresh();
+ }
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+ public function form()
+ {
+ $this->textarea('reject_reason', '驳回原因')->rules('required');
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/ActivityController.php b/modules/Mall/Http/Controllers/Admin/ActivityController.php
new file mode 100644
index 0000000..65897a5
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/ActivityController.php
@@ -0,0 +1,48 @@
+disableFilter();
+
+
+ $grid->column('id', '#ID#');
+ $grid->column('title', '活动名称');
+ $grid->column('description', '简介');
+ $grid->column('status', '状态')->switch();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ protected function form(): Form
+ {
+ $form = new Form(new Activity());
+
+ $form->text('title', '活动名称');
+ $this->cover($form);
+ $form->textarea('description', '简介');
+ $form->ueditor('content', '详情')->required();
+
+ $form->switch('status', '状态')->default(1);
+
+ return $form;
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/AddressController.php b/modules/Mall/Http/Controllers/Admin/AddressController.php
new file mode 100644
index 0000000..98ff05f
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/AddressController.php
@@ -0,0 +1,93 @@
+filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('user_id', '所属用户')->select()->ajax(route('admin.user.users.ajax'));
+ });
+ });
+
+ $grid->model()->with(['user']);
+
+ $grid->column('id', '#ID#');
+ $grid->column('user.username', '所属用户');
+ $grid->column('name', '姓名');
+ $grid->column('mobile', '手机号');
+ $grid->column('province.name', '省');
+ $grid->column('city.name', '市');
+ $grid->column('district.name', '区');
+ $grid->column('address', '详细地址');
+ $grid->column('is_default', '默认?')->bool();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ protected function form(): Form
+ {
+ $form = new Form(new Address);
+
+ $form->select('user_id', '所属用户')
+ ->options(function ($userId) {
+ $user = User::find($userId);
+ if ($user) {
+ return [$user->id => $user->username.' ['.$user->info->nickname.']'];
+ }
+ })
+ ->value(request()->user_id ?? '')
+ ->ajax(route('admin.user.users.ajax'))
+ ->required();
+ $form->text('name', '姓名');
+ $form->text('mobile', '手机号');
+ $form->select('province_id', '省份')
+ ->options(Region::where('parent_id', 1)->pluck('name', 'id'))
+ ->load('city_id', route('admin.mall.regions.region'))
+ ->required();
+ $form->select('city_id', '城市')
+ ->options(function ($option) {
+ $parent = Region::where('id', $option)->value('parent_id');
+
+ return Region::where(['parent_id' => $parent])->pluck('name', 'id');
+ })
+ ->load('district_id', route('admin.mall.regions.region'))
+ ->required();
+ $form->select('district_id', '区/县')
+ ->options(function ($option) {
+ $parent = Region::where('id', $option)->value('parent_id');
+
+ return Region::where(['parent_id' => $parent])->pluck('name', 'id');
+ })
+ ->required();
+ $form->text('address', '详细地址')
+ ->required();
+
+ $form->switch('is_default', '默认?')->default(1);
+
+ return $form;
+ }
+}
diff --git a/modules/Mall/Http/Controllers/Admin/AjaxController.php b/modules/Mall/Http/Controllers/Admin/AjaxController.php
new file mode 100644
index 0000000..c8b6ee6
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/AjaxController.php
@@ -0,0 +1,70 @@
+get('q');
+
+ return Address::ByUserId($user_id)
+ ->oldest('is_default')
+ ->get()
+ ->map(function ($address) {
+ return [
+ 'id' => $address->id,
+ 'text' => $address->getFullAddress(),
+ ];
+ });
+
+ }
+
+ /**
+ * Notes: 获取商品
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 13:46
+ * @param Request $request
+ */
+ public function goods(Request $request)
+ {
+ $address_id = $request->q;
+ if (!$address_id){
+ return [];
+ }
+ $address = Address::find($address_id);
+
+ return GoodsSku::query()
+ ->whereHas('goods', function ($q) {
+ $q->where('channel', Goods::CHANNEL_FREE)->where('status', Goods::STATUS_UP);
+ })
+ ->get()
+ ->map(function ($sku) use ($address) {
+ $stockData = $address->user->getStockData();
+ return [
+ 'id' => $sku->id,
+ 'text' => $sku->goods->name."(库存:{$stockData['residue']})",
+ ];
+ });
+ }
+}
diff --git a/modules/Mall/Http/Controllers/Admin/BannerController.php b/modules/Mall/Http/Controllers/Admin/BannerController.php
new file mode 100644
index 0000000..e9fd62f
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/BannerController.php
@@ -0,0 +1,74 @@
+filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('title', '轮播标题');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->radio([
+ 0 => '禁用',
+ 1 => '正常',
+ ]);
+ });
+ });
+
+ $grid->model()->with(['shop']);
+
+ $grid->column('id', '#ID#');
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('cover', '封面图片')->image('', 100, 100);
+ $grid->column('展示位置')->display(function () {
+ $data = [];
+ foreach ($this->position as $position) {
+ $data[] = $this->position_map[$position];
+ }
+
+ return $data;
+ })->label();
+ $grid->column('title', '轮播标题');
+ $grid->column('status', '状态')->bool();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ protected function form(): Form
+ {
+ $form = new Form((new Banner)->disableModelCaching());
+
+ $this->shop($form)->help('不选择,默认为平台轮播');
+ $form->text('title', '标题')
+ ->required();
+ $form->multipleSelect('position', '展示位置')
+ ->options($form->model()->position_map)
+ ->required();
+ $this->cover($form);
+ $this->withUrl($form);
+ $form->switch('status', '显示')->default(1);
+
+ return $form;
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/BrandController.php b/modules/Mall/Http/Controllers/Admin/BrandController.php
new file mode 100644
index 0000000..fd163f0
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/BrandController.php
@@ -0,0 +1,56 @@
+model()->with(['shop']);
+
+ $grid->column('cover', 'LOGO')->image('', 80);
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('name', '品牌名称');
+ $grid->column('status', '状态')->bool();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Brand());
+
+ $this->shop($form)->required();
+ $form->text('name', '品牌名称')
+ ->required();
+ $this->cover($form);
+ $form->textarea('description', '品牌简介');
+ $form->switch('status', '状态')->default(1);
+
+ return $form;
+ }
+
+ public function ajax(Request $request)
+ {
+ $key = $request->q;
+
+ return Brand::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/CategoryController.php b/modules/Mall/Http/Controllers/Admin/CategoryController.php
new file mode 100644
index 0000000..5dadb37
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/CategoryController.php
@@ -0,0 +1,101 @@
+get('parent_id');
+ $grid->model()
+ ->with(['shop'])
+ ->withCount('goods')
+ ->when($parentId, function ($query) use ($parentId) {
+ $query->where('parent_id', $parentId);
+ });
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '分类名称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('slug', '分类简称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->select([
+ 0 => '禁用',
+ 1 => '正常',
+ ]);
+ });
+ });
+
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('parent.name', '上级分类')
+ ->link(function () {
+ return route('admin.mall.categories.index', ['parent_id' => $this->parent_id]);
+ }, '_self');;
+ $grid->column('name', '分类名称')
+ ->link(function () {
+ return route('admin.mall.categories.index', ['parent_id' => $this->id]);
+ }, '_self');
+ $grid->column('slug', '分类简称');
+ $grid->column('goods_count', '分类商品');
+ $grid->column('order', '排序');
+ $grid->column('status', '状态')->bool();
+
+ return $grid;
+ }
+
+ /**
+ * Notes : 编辑表单
+ * @Date : 2021/4/25 1:41 下午
+ * @Author : < Jason.C >
+ * @return \Encore\Admin\Form
+ */
+ protected function form(): Form
+ {
+ $form = new Form(new Category);
+
+ $this->shop($form)->required();
+ $form->select('parent_id', '上级分类')
+ ->options(Category::selectOptions(function ($model) {
+ return $model->where('status', 1);
+ }, '一级分类'))
+ ->required();
+ $form->text('name', '分类名称')
+ ->required()
+ ->rules('required');
+ $form->text('slug', '分类简称');
+ $form->textarea('description', '分类简介');
+ $this->cover($form, 'cover', '分类图片');
+ $form->number('order', '排序')->default(0);
+ $form->switch('status', '显示')->default(1);
+
+ return $form;
+ }
+
+ public function ajax(Request $request)
+ {
+ $key = $request->q;
+
+ return Category::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']);
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/DashboardController.php b/modules/Mall/Http/Controllers/Admin/DashboardController.php
new file mode 100644
index 0000000..e6ed33a
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/DashboardController.php
@@ -0,0 +1,67 @@
+header('商城看板')
+ ->description('商城数据概览')
+ ->row(function (Row $row) {
+ $row->column(3, function (Column $column) {
+ $column->append(new InfoBox('店铺数量',
+ 'anchor',
+ 'blue',
+ route('admin.mall.shops.index'),
+ Shop::count()
+ )
+ );
+ });
+
+ $row->column(3, function (Column $column) {
+ $column->append(new InfoBox('商品数量',
+ 'anchor',
+ 'blue',
+ route('admin.mall.goods.index'),
+ Goods::count()
+ )
+ );
+ });
+
+ $row->column(3, function (Column $column) {
+ $column->append(new InfoBox('订单总数',
+ 'anchor',
+ 'blue',
+ route('admin.mall.orders.index'),
+ Order::count()
+ )
+ );
+ });
+
+ $row->column(3, function (Column $column) {
+ $column->append(new InfoBox('退款订单',
+ 'anchor',
+ 'blue',
+ route('admin.mall.refunds.index'),
+ Refund::count()
+ )
+ );
+ });
+ });
+
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/DeliveryController.php b/modules/Mall/Http/Controllers/Admin/DeliveryController.php
new file mode 100644
index 0000000..24b2c27
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/DeliveryController.php
@@ -0,0 +1,82 @@
+model()
+ ->with(['shop'])
+ ->withCount('rules');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('shop.name', '所属店铺');
+ });
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '模板名称');
+ });
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('name', '计费方式')->select(Delivery::TYPE_MAP);
+ });
+ });
+
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('name', '模板名称');
+ $grid->column('type', '计费方式')->using(Delivery::TYPE_MAP);
+
+ $grid->column('rules_count', '模板规则')->link(function () {
+ return route('admin.mall.deliveries.rules.index', $this);
+ }, '_self');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Delivery());
+
+ $this->shop($form)->required();
+ $form->text('name', '模板名称')->required();
+ $form->radioButton('type', '计费方式')
+ ->options(Delivery::TYPE_MAP)
+ ->default(Delivery::TYPE_BY_COUNT)
+ ->required();
+
+ return $form;
+ }
+
+ /**
+ * Notes : 选择运费模板
+ * @Date : 2021/5/11 11:36 上午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function ajax(Request $request)
+ {
+ $key = $request->get('q');
+
+ return Delivery::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/DeliveryRuleController.php b/modules/Mall/Http/Controllers/Admin/DeliveryRuleController.php
new file mode 100644
index 0000000..8a79dae
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/DeliveryRuleController.php
@@ -0,0 +1,106 @@
+delivery);
+
+ return $content
+ ->header($delivery->name)
+ ->description('模板规则')
+ ->body($this->grid($delivery));
+ }
+
+ public function grid($delivery): Grid
+ {
+ $grid = new Grid(new DeliveryRule());
+
+ $grid->model()->where('delivery_id', $delivery->id);
+
+ if ($delivery->type === Delivery::TYPE_BY_COUNT) {
+ $firstTitle = '首件(个)';
+ $additionalTitle = '续件';
+ } else {
+ $firstTitle = '首重(Kg)';
+ $additionalTitle = '续重';
+ }
+
+ $grid->column('regions', '配送区域')
+ ->display(function () {
+ return Region::find($this->regions);
+ })
+ ->pluck('name')
+ ->label();
+ $grid->column('first', $firstTitle);
+ $grid->column('first_fee', '运费(元)');
+ $grid->column('additional', $additionalTitle);
+ $grid->column('additional_fee', '续费(元)');
+
+ return $grid;
+ }
+
+ public function edit($id, Content $content): Content
+ {
+ return $content
+ ->title($this->title())
+ ->description($this->description['edit'] ?? trans('admin.edit'))
+ ->body($this->form()->edit(request()->rule));
+ }
+
+ public function update($id)
+ {
+ return $this->form()->update(request()->rule);
+ }
+
+ public function form(): Form
+ {
+ $delivery = Delivery::find(request()->delivery);
+
+ if ($delivery->type === Delivery::TYPE_BY_COUNT) {
+ $firstTitle = '首件(个)';
+ $additionalTitle = '续件';
+ } else {
+ $firstTitle = '首重(Kg)';
+ $additionalTitle = '续重';
+ }
+
+ $form = new Form(new DeliveryRule());
+
+ $form->multipleSelect('regions', '配送区域')
+ ->options(function ($regions) {
+ if ($regions) {
+ return Region::find($regions)->pluck('name', 'id');
+ }
+ })
+ ->ajax(route('admin.mall.regions.ajax'));
+ $form->text('first', $firstTitle)
+ ->required();
+ $form->currency('first_fee')
+ ->symbol('¥')
+ ->required();
+ $form->text('additional', $additionalTitle)
+ ->required();
+ $form->currency('additional_fee')
+ ->required()
+ ->symbol('¥');
+
+ $form->hidden('delivery_id')->value($delivery->id);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/ExpressController.php b/modules/Mall/Http/Controllers/Admin/ExpressController.php
new file mode 100644
index 0000000..02f3249
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/ExpressController.php
@@ -0,0 +1,70 @@
+model()->withCount('shops');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '物流名称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('slug', '物流简称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->select([
+ 0 => '禁用',
+ 1 => '正常',
+ ]);
+ });
+ });
+
+ $grid->column('id');
+ $grid->column('cover', 'LOGO')->image('', 60);
+ $grid->column('name', '物流名称');
+ $grid->column('slug', '物流简称');
+ $grid->column('description', '物流简介');
+ $grid->column('status', '状态')->bool();
+ $grid->column('shops_count', '使用商户');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Express());
+
+ $form->text('name', '物流名称')
+ ->required();
+ $form->text('slug', '物流简称')
+ ->required();
+ $form->textarea('description', '物流简介');
+
+ $this->cover($form);
+
+ $form->switch('status', '状态')->default(1);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/GoodsController.php b/modules/Mall/Http/Controllers/Admin/GoodsController.php
new file mode 100644
index 0000000..e5e6918
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/GoodsController.php
@@ -0,0 +1,228 @@
+quickSearch('id')->placeholder('商品编号快速搜索');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '商品名称');
+ $filter->equal('channel', '类型')->select(Goods::Channels);
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('shop_id', '所属店铺')->select(function ($shopId) {
+ if ($shopId) {
+ return Shop::where('id', $shopId)->pluck('name', 'id');
+ }
+ })->ajax(route('admin.mall.shops.ajax'));
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('category_id', '分类')->select(Category::shown()->pluck('name', 'id'));
+ });
+ });
+
+ $grid->model()
+ ->with(['category', 'shop', 'delivery'])
+ ->withCount(['specs', 'skus'])
+ ->orderByDesc('updated_at');
+
+ $grid->column('id', '#ID#');
+ $grid->column('brand.name', '品牌名称');
+ $grid->column('category.name', '商品分类');
+// $grid->column('tags', '标签')
+// ->pluck('name')
+// ->label();
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('name', '商品名称');
+ $grid->column('type', '规格类型')->using(Goods::TYPE_MAP);
+ $grid->column('sales', '销量');
+ $grid->column('original_price', '原价');
+ $grid->column('channel', '类型')->using(Goods::Channels)->label();
+
+ $grid->column('规格属性')
+ ->display(function () {
+ if ($this->type === Goods::TYPE_MULTIPLE) {
+ return $this->specs_count;
+ }
+ })
+ ->link(function () {
+ return route('admin.mall.goods.specs.index', $this);
+ }, '_self');
+ $grid->column('价格条目')
+ ->display(function () {
+ return $this->skus_count;
+ })
+ ->link(function () {
+ return route('admin.mall.goods.skus.index', $this);
+ }, '_self');
+ $grid->column('delivery.name', '运费模板');
+ $grid->column('status', '状态')->switch([
+ 'on' => ['value' => 1, 'text' => '上架', 'color' => 'primary'],
+ 'off' => ['value' => 3, 'text' => '下架', 'color' => 'default'],
+ ]);
+ // $grid->column('status', '状态')->display(function () {
+ // return $this->status_text;
+ // });
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Goods());
+
+ $form->text('name', '商品名称')->required();
+ $form->textarea('description', '商品简介')->required();
+
+ $this->shop($form)->required();
+
+ $form->select('category_id', '商品分类')
+ ->options(function ($categoryId) {
+ $category = Category::find($categoryId);
+ if ($category) {
+ return [$category->id => $category->name];
+ }
+ })
+ ->ajax(route('admin.mall.categories.ajax'))
+ ->required();
+ $form->select('brand_id', '所属品牌')
+ ->options(function ($brandId) {
+ $brand = Brand::find($brandId);
+ if ($brand) {
+ return [$brand->id => $brand->name];
+ }
+ })
+ ->ajax(route('admin.mall.brands.ajax'))
+ ->required();
+// $form->multipleSelect('tags', '商品标签')
+// ->options(function ($tagIds) {
+// if ($tagIds) {
+// return Tag::find($tagIds)->pluck('name', 'id');
+// }
+// })
+// ->ajax(route('admin.mall.tags.ajax'));
+ $this->cover($form);
+ $this->pictures($form);
+ $states = [
+ 'on' => ['value' => 1, 'text' => '是', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '否', 'color' => 'danger'],
+ ];
+ $form->switch('is_post_sale', '是否允许售后')->states($states)->default(1);
+ $form->radioButton('deduct_stock_type', '库存扣减')
+ ->default(1)
+ ->options(Goods::DEDUCT_STOCK_MAP)
+ ->required();
+ $form->radioButton('pay_type', '支付方式')
+ ->default(1)
+ ->options(Goods::PAY_TYPE_MAP)
+ ->required();
+ $form->select('delivery_id', '运费模板')
+ ->options(function ($deliveryId) {
+ $delivery = Delivery::find($deliveryId);
+ if ($delivery) {
+ return [$delivery->id => $delivery->name];
+ }
+ })
+ ->ajax(route('admin.mall.deliveries.ajax'))
+ ->required();
+ $form->currency('original_price', '展示原价')
+ ->default(0)
+ ->help('仅作展示使用');
+ $form->radioButton('channel', '商品类型')
+ ->options(Goods::Channels)
+ ->required();
+ $form->radioButton('type', '规格类型')
+ ->options(Goods::TYPE_MAP)
+ ->default(Goods::CHANNEL_NORMAL)
+ ->required()
+ ->when(Goods::TYPE_SINGLE, function (Form $form) {
+ $form->hasMany('skus', '单规格', function (Form\NestedForm $form) {
+ $form->currency('price', '销售价格')
+ ->default(0)
+ ->required();
+ $form->hidden('score', '积分')
+ ->default(0)
+ ->required();
+ $form->hidden('assets', '资产')
+ ->default(0)
+ ->required();
+ $form->number('stock', '商品库存')
+ ->default(0)
+ ->required();
+ $form->text('weight', '商品重量')
+ ->default(0)
+ ->setWidth(2)
+ ->required();
+ });
+ })
+ ->when(Goods::TYPE_MULTIPLE, function ($form) {
+
+ })
+ ->default(function () use ($form) {
+ return $form->isCreating() ? Goods::TYPE_SINGLE : '';
+ });
+ $form->ueditor('content', '商品详情')->required();
+
+ $form->switch('status', '状态')
+ ->states([
+ 'on' => ['value' => 1, 'text' => '上架', 'color' => 'success'],
+ 'off' => ['value' => 3, 'text' => '下架', 'color' => 'danger'],
+ ])
+ ->default(1);
+
+ $form->ignore(['single']);
+
+ $form->saving(function (Form $form) {
+ if ($form->type === Goods::TYPE_SINGLE) {
+ $form->skus = [Arr::first($form->skus)];
+ }
+
+ if ($form->channel == Goods::CHANNEL_FREE && $form->isCreating()) {
+ $hasOne = Goods::where('channel', Goods::CHANNEL_FREE)->exists();
+ if ($hasOne) {
+ $error = new MessageBag([
+ 'title' => '错误',
+ 'message' => '免费产品只有一个',
+ ]);
+
+ return back()->withInput()->with(compact('error'));
+ }
+ }
+
+ $form->content = Str::of(request()->get('content'))->replace('style=""', '');
+ });
+
+
+ return $form;
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/JobController.php b/modules/Mall/Http/Controllers/Admin/JobController.php
new file mode 100644
index 0000000..685a0d4
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/JobController.php
@@ -0,0 +1,47 @@
+filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('shop.name', '所属店铺');
+ });
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('name', '职位名称');
+ });
+ });
+
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('name', '职位名称');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Job());
+
+ $this->shop($form)->required();
+ $form->text('name', '职位名称')->required();
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/OrderController.php b/modules/Mall/Http/Controllers/Admin/OrderController.php
new file mode 100644
index 0000000..6a0bb9d
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/OrderController.php
@@ -0,0 +1,222 @@
+model()->whereIn('type', [Order::TYPE_NORMAL, Order::TYPE_SCORE])->with('shop')->withCount('versions');
+
+ $grid->disableExport(false);
+ $grid->exporter(new OrderExporter);
+ $grid->disableCreateButton();
+ $grid->quickSearch('order_no')->placeholder('搜索订单编号');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('unPay', '未付款')->unPay();
+ $filter->scope('paid', '待发货')->paid();
+ $filter->scope('delivered', '已发货')->delivered();
+ $filter->scope('signed', '已签收')->signed();
+ $filter->scope('completed', '已完成')->completed();
+
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('order_no', '订单编号');
+ $filter->equal('shop_id', '所属店铺')->select(Shop::pluck('name', 'id'));
+ $filter->equal('user_id', '下单用户')->select()->ajax(route('admin.user.users.ajax'));
+ $filter->equal('type', '订单类型')->select(Order::TYPE_MAP);
+ });
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->between('created_at', '下单时间')->datetime();
+ $filter->between('paid_at', '付款时间')->datetime();
+ $filter->between('expired_at', '过期时间')->datetime();
+ });
+ });
+
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableEdit();
+ $actions->disableDelete();
+ $actions->disableView();
+ if ($actions->row->can('pay') && $this->row->type == Order::TYPE_NORMAL) {
+ $actions->add(new Pay());
+ }
+ if ($actions->row->can('deliver')) {
+ $actions->add(new Delivered);
+ }
+ });
+
+ $grid->model()->with(['shop', 'user.info']);
+
+ $grid->column('order_no', '订单编号')->display(function () {
+ return sprintf('%s', route('admin.mall.orders.show', $this), $this->order_no);
+ });
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('user.username', '下单用户');
+ $grid->column('amount', '商品金额');
+ $grid->column('freight', '运费');
+ $grid->column('qty', '发货数量')
+ ->display(function () {
+ return $this->items()->sum('qty');
+ });
+ $grid->column('订单金额')->display(function () {
+ return $this->total;
+ });
+ $grid->column('state', '订单状态')
+ ->display(function () {
+ return $this->state_text;
+ })
+ ->label();
+ $grid->column('type', '订单类型')->using(Order::TYPE_MAP)->label();
+ $grid->column('paid_at', '支付时间')->sortable();
+ $grid->column('expired_at', '过期时间')->sortable();
+ $grid->column('versions_count', '操作日志')->link(function () {
+ return route('admin.mall.versions', [
+ 'model' => get_class($this),
+ 'key' => $this->id,
+ ]);
+ }, '_self');
+ $grid->column('created_at', '下单时间')->sortable();
+
+ return $grid;
+ }
+
+ /**
+ * Notes: 添加订单
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 13:21
+ * @return Form
+ */
+ public function form(): Form
+ {
+ $form = new Form(new Order());
+
+ $form->select('user_id', '用户')
+ ->options(function ($userId) {
+ $user = User::find($userId);
+
+ if ($user) {
+ return [$user->id => $user->username.' ['.$user->info->nickname.']'];
+ }
+ })
+ ->ajax(route('admin.user.users.ajax'))
+ ->load('address_id', route('admin.mall.ajax.address'))
+ ->required();
+
+ $form->select('address_id', '收货地址')
+ ->load('goods_sku_id', route('admin.mall.ajax.goods'))
+ ->required();
+ $form->select('goods_sku_id', '商品')
+ ->required();
+ $form->number('qty', '数量')->default(1);
+
+ $form->select('express_id', '物流')
+ ->options(function () {
+ return Express::pluck('name', 'id');
+ })
+ ->rules('required', ['required' => '物流公司必须选择']);
+ $form->text('express_no', '物流单号')->rules('required');
+
+ return $form;
+ }
+
+ /**
+ * Notes: 获取提交数据
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 14:16
+ * @return mixed|void
+ * @throws \Exception
+ */
+ public function store()
+ {
+ $user_id = request('user_id', 0);
+ $remark = request('remark', '后台录入');
+ $address_id = request('address_id', 0);
+ $goods_sku_id = request('goods_sku_id', 0);
+ $qty = request('qty', 0);
+ $express_id = request('express_id');
+ $express_no = request('express_no');
+
+ $user = User::find($user_id);
+ $address = Address::find($address_id);
+ $goods_sku = GoodsSku::find($goods_sku_id);
+
+ if (! $goods_sku) {
+ $error = new MessageBag([
+ 'title' => '错误',
+ 'message' => '未找到商品',
+ ]);
+
+ return back()->withInput()->with(compact('error'));
+ }
+
+ $stockData = $user->getStockData();
+
+ if ($qty > $stockData['residue']) {
+ $error = new MessageBag([
+ 'title' => '错误',
+ 'message' => '用户库存不足',
+ ]);
+
+ return back()->withInput()->with(compact('error'));
+ } else {
+ $detail = collect();
+ $item = new Item($goods_sku, $address, $qty);
+ $detail->push($item);
+
+ $orders = (new OrderFacade)->user($user)
+ ->remark($remark)
+ ->type(Order::TYPE_SAMPLE)
+ ->items($detail)
+ ->address($address)
+ ->create();
+
+ foreach ($orders as $order) {
+ $order->pay();//设置已支付
+ $order->deliver($express_id, $express_no);//发货
+ }
+
+ admin_toastr('操作完成');
+ return redirect()->to('/admin/mall/orders?user_id='.$user->id);
+ }
+ }
+
+
+ /**
+ * Notes : 订单详情
+ *
+ * @Date : 2021/3/16 1:46 下午
+ * @Author : < Jason.C >
+ * @param string $orderNo 订单的 order_no
+ */
+ protected function detail(string $orderNo)
+ {
+ $order = Order::query()->firstWhere(['order_no' => $orderNo]);
+
+ return view('mall::admin.order.detail', compact('order'));
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/ReasonController.php b/modules/Mall/Http/Controllers/Admin/ReasonController.php
new file mode 100644
index 0000000..fb893c7
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/ReasonController.php
@@ -0,0 +1,48 @@
+model()->withCount('shops');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+ });
+
+ $grid->column('id');
+ $grid->column('title', '名称');
+ $grid->column('status', '状态')->bool();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Reason());
+
+ $form->text('title', '名称')->required();
+ $form->switch('status', '状态')->default(1);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/RefundController.php b/modules/Mall/Http/Controllers/Admin/RefundController.php
new file mode 100644
index 0000000..deaf506
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/RefundController.php
@@ -0,0 +1,106 @@
+disableCreateButton();
+
+ $grid->actions(function ($actions) {
+ $actions->disableDelete();
+ $actions->disableEdit();
+ $actions->disableView();
+
+ if ($actions->row->can('agree')) {
+ $actions->add(new RefundAudit());
+ }
+
+ if ($actions->row->can('sign')) {
+ $actions->add(new RefundSign());
+ }
+
+ if ($actions->row->can('completed')) {
+ $actions->add(new RefundReturns());
+ }
+
+ });
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->disableIdFilter();
+
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->where(function ($query) {
+ $query->whereHas('order', function ($query) {
+ $query->where('order_no', 'like', "%{$this->input}%");
+ });
+ }, '订单编号');
+
+ $filter->equal('state', '状态')->select(Refund::STATUS_MAP);
+
+ });
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->where(function ($query) {
+ $query->whereHas('user', function ($query) {
+ $query->whereHas('info', function ($query) {
+ $query->where('nickname', 'like', "%{$this->input}%");
+ });
+ });
+ }, '下单用户');
+ $filter->where(function ($query) {
+ $query->whereHas('user', function ($query) {
+ $query->where('username', $this->input);
+ });
+ }, '手机号');
+ });
+ });
+
+ $grid->model()
+ ->with(['shop', 'order', 'user.info'])
+ ->withCount('versions');
+
+ $grid->column('id');
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('order.order_no', '订单编号')->display(function () {
+ return sprintf('%s', route('admin.mall.orders.show', $this->order),
+ $this->order->order_no);
+ });
+
+ $grid->column('user.username', '下单用户');
+ $grid->column('refund_total', '退款金额');
+ $grid->column('actual_total', '实退金额');
+
+ $grid->column('状态')
+ ->display(function () {
+ return $this->status_text;
+ })
+ ->label();
+ $grid->column('remark', '备注');
+
+ $grid->column('versions_count', '操作日志')->link(function () {
+ return route('admin.mall.versions', [
+ 'model' => get_class($this),
+ 'key' => $this->id,
+ ]);
+ }, '_self');
+
+ $grid->column('created_at', '申请时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/RegionController.php b/modules/Mall/Http/Controllers/Admin/RegionController.php
new file mode 100644
index 0000000..a1257a3
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/RegionController.php
@@ -0,0 +1,27 @@
+q;
+
+ return Region::where('name', 'like', "%$q%")->paginate(null, ['id', 'name as text']);
+ }
+
+ public function region(Request $request)
+ {
+ $regionId = $request->get('q');
+
+ return Region::where('parent_id', $regionId)->get(['id', DB::raw('name as text')]);
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Selectable/Expresses.php b/modules/Mall/Http/Controllers/Admin/Selectable/Expresses.php
new file mode 100644
index 0000000..29b8c98
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Selectable/Expresses.php
@@ -0,0 +1,34 @@
+column('id', '#ID#');
+ $this->column('name', '物流名称');
+ $this->column('status', '状态')->bool();
+ $this->column('created_at', '时间');
+
+ $this->filter(function (Filter $filter) {
+ $filter->like('name', '物流名称');
+ });
+ }
+
+ public static function display()
+ {
+ return function ($value) {
+ if (is_array($value)) {
+ return implode(';', array_column($value, 'name'));
+ }
+ return optional($this->expresses)->name;
+ };
+ }
+}
diff --git a/modules/Mall/Http/Controllers/Admin/Selectable/Reasons.php b/modules/Mall/Http/Controllers/Admin/Selectable/Reasons.php
new file mode 100644
index 0000000..fe5ee64
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/Selectable/Reasons.php
@@ -0,0 +1,32 @@
+column('id', '#ID#');
+ $this->column('title', '名称');
+ $this->column('status', '状态')->bool();
+ $this->column('created_at', '时间');
+ }
+
+ public static function display()
+ {
+ return function ($value) {
+ if (is_array($value)) {
+ return implode(';', array_column($value, 'title'));
+ }
+
+ return optional($this->reaons)->name;
+ };
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/ShopController.php b/modules/Mall/Http/Controllers/Admin/ShopController.php
new file mode 100644
index 0000000..5a71e20
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/ShopController.php
@@ -0,0 +1,180 @@
+actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableView();
+ // $actions->disableEdit();
+ $actions->disableDelete();
+ if ($actions->row->status == Shop::STATUS_APPLYING) {
+ $actions->add(new Pass);
+ $actions->add(new Reject);
+ }
+ if ($actions->row->status == Shop::STATUS_NORMAL) {
+ $actions->add(new Close);
+ }
+
+ if ($actions->row->status == Shop::STATUS_CLOSE) {
+ $actions->add(new Open);
+ }
+ });
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '店铺名称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.info.nickname', '用户昵称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('mobile', '联系电话');
+ });
+ });
+
+ $grid->model()->withCount(['versions', 'staffers'])->with(['user.info']);
+
+ $grid->column('name', '店铺名称');
+ $grid->column('所属用户')->display(fn() => $this->user->username . '[' . $this->user->info->nickname . ']');
+ $grid->column('is_self', '类型')
+ ->using([
+ 0 => '合作',
+ 1 => '自营',
+ ])->label([
+ 0 => 'info',
+ 1 => 'danger',
+ ]);
+ $grid->column('mobile', '联系电话');
+ $grid->column('address', '地址')->display(fn() => $this->getFullAddress());
+ $grid->column('status', '状态')
+ ->using(Shop::STATUS_MAP)
+ ->label(Shop::LABEL_MAP);
+ $grid->column('order', '排序')->sortable()->editable();
+ $grid->column('versions_count', '操作记录')->link(fn() => route('admin.mall.versions', [
+ 'model' => get_class($this),
+ 'key' => $this->id,
+ ]), '_self');
+ $grid->column('staffers_count', '员工数量')->link(fn() => route('admin.mall.shops.staffers.index', $this), '_self');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Shop());
+ $form->select('user_id', '所属用户')
+ ->options(function ($userId) {
+ $user = User::find($userId);
+
+ if ($user) {
+ return [$user->id => $user->username . ' [' . $user->info->nickname . ']'];
+ }
+ })
+ ->ajax(route('admin.user.users.ajax'))
+ ->creationRules([
+ 'required',
+ "unique:mall_shops",
+ ], [
+ 'unique' => '用户已存在店铺',
+ ])
+ ->updateRules([
+ 'required',
+ "unique:mall_shops,user_id,{{id}}",
+ ], [
+ 'unique' => '用户已存在店铺',
+ ])
+ ->required();
+ $form->text('name', '店铺名称')
+ ->creationRules(['required', "unique:mall_shops"], ['unique' => '店铺名称已存在'])
+ ->updateRules(['required', "unique:mall_shops,name,{{id}}"], ['unique' => '店铺名称已存在'])
+ ->required();
+ $form->switch('is_self', '自营店铺');
+ $form->textarea('description', '店铺简介')
+ ->required();
+ $form->text('mobile', '联系电话')
+ ->rules([
+ 'size:11',
+ 'phone:CN',
+ ], [
+ 'size' => '手机号格式不正确',
+ 'phone' => '手机号格式不正确',
+ ])
+ ->creationRules(['required', "unique:mall_shops"], ['unique' => '店铺联系电话已存在'])
+ ->updateRules(['required', "unique:mall_shops,name,{{id}}"], ['unique' => '店铺联系电话已存在'])
+ ->required();
+ $form->divider('地址信息');
+ $form->select('province_id', '省份')
+ ->options(Region::where('parent_id', 1)->pluck('name', 'id'))
+ ->load('city_id', route('admin.mall.regions.region'))
+ ->required();
+ $form->select('city_id', '城市')
+ ->options(function ($option) {
+ $parent = Region::where('id', $option)->value('parent_id');
+
+ return Region::where(['parent_id' => $parent])->pluck('name', 'id');
+ })
+ ->load('district_id', route('admin.mall.regions.region'))
+ ->required();
+ $form->select('district_id', '区/县')
+ ->options(function ($option) {
+ $parent = Region::where('id', $option)->value('parent_id');
+
+ return Region::where(['parent_id' => $parent])->pluck('name', 'id');
+ })
+ ->required();
+ $form->text('address', '详细地址')
+ ->required();
+ $form->divider('其他信息');
+ $this->cover($form, 'cover', '店铺LOGO');
+ $form->radio('status', '店铺状态')
+ ->options(Shop::STATUS_MAP);
+ $form->number('order', '排序')
+ ->default('9999')
+ ->help('仅后台可见,用于店铺推荐序列');
+ $form->belongsToMany('expresses', Expresses::class, '发货物流');
+ $form->belongsToMany('reasons', Reasons::class, '退款原因');
+
+ return $form;
+ }
+
+ /**
+ * Notes : 获取店铺AJAX
+ * @Date : 2021/5/7 4:39 下午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function ajax(Request $request)
+ {
+ $key = $request->get('q');
+
+ return Shop::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']);
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/SkuController.php b/modules/Mall/Http/Controllers/Admin/SkuController.php
new file mode 100644
index 0000000..7695b6d
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/SkuController.php
@@ -0,0 +1,77 @@
+header($good->name)
+ ->description('价格表')
+ ->body($this->grid($good));
+ }
+
+ public function grid($goods): Grid
+ {
+ $grid = new Grid(new GoodsSku());
+
+ $grid->model()->where('goods_id', $goods->id);
+
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableView();
+ $actions->disableDelete();
+ });
+ $grid->disableCreateButton();
+
+ $grid->column('cover', '封面图')->image('', 80);
+ $grid->column('unit', '产品单元');
+ $grid->column('price', '售价');
+ $grid->column('score', '积分/原石');
+ $grid->column('assets', '资产');
+ $grid->column('stock', '库存');
+ $grid->column('weight', '重量');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function edit(Content $content, Goods $good, GoodsSku $sku)
+ {
+ return $content
+ ->header($good->name)
+ ->description('编辑价格')
+ ->body($this->form($good)->edit($sku->id));
+ }
+
+ public function form($good): Form
+ {
+ $form = new Form(new GoodsSku());
+
+ $this->cover($form);
+ $form->currency('price', '销售价格');
+ $form->currency('score', '积分/原石');
+ $form->currency('assets', '资产');
+ $form->number('stock', '库存');
+ $form->text('weight', '重量')->setWidth(2);
+
+ return $form;
+ }
+
+ public function update(Goods $good, GoodsSku $sku)
+ {
+ return $this->form($good)->update($sku->id);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/SpecController.php b/modules/Mall/Http/Controllers/Admin/SpecController.php
new file mode 100644
index 0000000..3b8699b
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/SpecController.php
@@ -0,0 +1,81 @@
+header($good->name)
+ ->description('属性列表')
+ ->body($this->grid($good));
+ }
+
+ public function grid($good): Grid
+ {
+ $grid = new Grid(new GoodsSpec());
+
+ $grid->model()
+ ->where('goods_id', $good->id)
+ ->withCount('values');
+
+ $grid->column('name', '属性名称');
+ $grid->column('values_count', '属性值数量')
+ ->link(function () use ($good) {
+ return route('admin.mall.goods.specs.values.index', [$good, $this]);
+ }, '_self');
+
+ return $grid;
+ }
+
+ public function create(Content $content, Goods $good): Content
+ {
+ return $content
+ ->header($good->name)
+ ->description('属性列表')
+ ->body($this->form($good));
+ }
+
+ public function form($good): Form
+ {
+ $form = new Form(new GoodsSpec());
+
+ $form->text('name', '属性名称');
+ $form->hidden('goods_id')->value($good->id);
+
+ $form->hasMany('values', '属性值列表', function (NestedForm $form) {
+ $form->text('value', '属性值');
+ });
+
+ return $form;
+ }
+
+ public function store(Goods $good)
+ {
+ return $this->form($good)->store();
+ }
+
+ public function edit(Content $content, Goods $good, GoodsSpec $spec): Content
+ {
+ return $content
+ ->header($good->name)
+ ->description('属性列表')
+ ->body($this->form($good)->edit($spec->id));
+ }
+
+ public function update(Goods $good, GoodsSpec $spec)
+ {
+ return $this->form($good)->update($spec->id);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/SpecValueController.php b/modules/Mall/Http/Controllers/Admin/SpecValueController.php
new file mode 100644
index 0000000..5017a37
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/SpecValueController.php
@@ -0,0 +1,70 @@
+header($spec->name)
+ ->description('属性值列表')
+ ->body($this->grid($spec));
+ }
+
+ public function grid($spec): Grid
+ {
+ $grid = new Grid(new GoodsSpecValue());
+
+ $grid->model()->where('spec_id', $spec->id);
+ $grid->column('value', '属性值');
+
+ return $grid;
+ }
+
+ public function create(Content $content, Goods $good, GoodsSpec $spec): Content
+ {
+ return $content
+ ->header($spec->name)
+ ->description('属性列表')
+ ->body($this->form($spec));
+ }
+
+ public function form($spec): Form
+ {
+ $form = new Form(new GoodsSpecValue());
+
+ $form->text('value', '属性值');
+ $form->hidden('spec_id')->value($spec->id);
+
+ return $form;
+ }
+
+ public function store(Goods $good, GoodsSpec $spec)
+ {
+ return $this->form($spec)->store();
+ }
+
+ public function edit(Content $content, Goods $good, GoodsSpec $spec, GoodsSpecValue $value): Content
+ {
+ return $content
+ ->header($spec->name)
+ ->description('属性列表')
+ ->body($this->form($spec)->edit($value->id));
+ }
+
+ public function update(Goods $good, GoodsSpec $spec, GoodsSpecValue $value)
+ {
+ return $this->form($spec)->update($value->id);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/StafferController.php b/modules/Mall/Http/Controllers/Admin/StafferController.php
new file mode 100644
index 0000000..4c48e45
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/StafferController.php
@@ -0,0 +1,98 @@
+header($shop->name)
+ ->description('员工管理')
+ ->body($this->grid($shop));
+ }
+
+ public function grid($shop): Grid
+ {
+ $grid = new Grid(new ShopStaffer());
+
+ $grid->model()->byShop($shop);
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.username', '用户名');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.info.nickname', '用户昵称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('job.name', '职位名称');
+ });
+ });
+
+ $grid->column('所属用户')->display(fn() => $this->user->username . '[' . $this->user->info->nickname . ']');
+ $grid->column('job.name', '职位名称');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function create(Content $content, Shop $shop): Content
+ {
+ return $content
+ ->header($shop->name)
+ ->description('新增员工')
+ ->body($this->form($shop));
+ }
+
+ public function store(Shop $shop)
+ {
+ return $this->form($shop)->store();
+ }
+
+ public function form(Shop $shop): Form
+ {
+ $form = new Form(new ShopStaffer());
+
+ $form->hidden('shop_id')->value($shop->getKey());
+
+ $form->select('user_id', '所属用户')
+ ->options(function ($userId) {
+ $user = User::find($userId);
+
+ if ($user) {
+ return [$user->id => $user->username . ' [' . $user->info->nickname . ']'];
+ }
+ })
+ ->ajax(route('admin.user.users.ajax'))
+ ->required();
+
+ $form->select('job_id', '职位')->options(fn() => Job::byShop($shop)->pluck('name', 'id'));
+
+ return $form;
+ }
+
+ public function edit(Content $content, Shop $shop, ShopStaffer $staffer): Content
+ {
+ return $content
+ ->header($shop->name)
+ ->description('编辑员工')
+ ->body($this->form($shop)->edit($staffer->getKey()));
+ }
+
+ public function update(Shop $shop, ShopStaffer $staffer)
+ {
+ return $this->form($shop)->update($staffer->getKey());
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/StockOrderBySystemController.php b/modules/Mall/Http/Controllers/Admin/StockOrderBySystemController.php
new file mode 100644
index 0000000..d1e5252
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/StockOrderBySystemController.php
@@ -0,0 +1,254 @@
+model()
+ ->where('type', Order::TYPE_SAMPLE)
+ ->where('channel', Order::CHANNEL_SYSTEM)
+ ->with(['shop', 'user', 'items'])
+ ->withCount('versions');
+
+
+ $grid->quickSearch('order_no')->placeholder('搜索订单编号');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('unPay', '未付款')->unPay();
+ $filter->scope('paid', '待发货')->paid();
+ $filter->scope('delivered', '已发货')->delivered();
+ $filter->scope('signed', '已签收')->signed();
+ $filter->scope('completed', '已完成')->completed();
+
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('order_no', '订单编号');
+// $filter->equal('shop_id', '所属店铺')->select(Shop::pluck('name', 'id'));
+ $filter->equal('user_id', '提货用户')->select()->ajax(route('admin.user.users.ajax'));
+// $filter->equal('type', '订单类型')->select(Order::TYPE_MAP);
+ });
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+// $filter->between('created_at', '下单时间')->datetime();
+// $filter->between('paid_at', '付款时间')->datetime();
+// $filter->between('expired_at', '过期时间')->datetime();
+ });
+ });
+
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableEdit();
+ $actions->disableDelete();
+ $actions->disableView();
+ if ($actions->row->can('pay') && $this->row->type == Order::TYPE_NORMAL) {
+ $actions->add(new Pay());
+ }
+ if ($actions->row->can('deliver')) {
+ $actions->add(new Delivered);
+ }
+ });
+
+ $grid->model()->with(['shop', 'user.info']);
+
+ $grid->column('order_no', '订单编号')->display(function () {
+ return sprintf('%s', route('admin.mall.stock_orders.show', $this), $this->order_no);
+ });
+// $grid->column('shop.name', '所属店铺');
+ $grid->column('user.username', '提货用户');
+// $grid->column('amount', '商品金额');
+// $grid->column('freight', '运费');
+ $grid->column('qty', '提货数量')
+ ->display(function () {
+ return $this->items()->sum('qty');
+ });
+// $grid->column('订单金额')->display(function () {
+// return $this->total;
+// });
+ $grid->column('state', '订单状态')
+ ->display(function () {
+ return $this->state_text;
+ })
+ ->label();
+ $grid->column('type', '订单类型')->using(Order::TYPE_MAP)->label();
+// $grid->column('paid_at', '支付时间')->sortable();
+// $grid->column('expired_at', '过期时间')->sortable();
+ $grid->column('versions_count', '操作日志')->link(function () {
+ return route('admin.mall.versions', [
+ 'model' => get_class($this),
+ 'key' => $this->id,
+ ]);
+ }, '_self');
+ $grid->column('created_at', '下单时间')->sortable();
+
+ $grid->disableExport(false);
+ $grid->export(function ($export) {
+ $export->column('order_no', function ($value, $original) {
+ return strip_tags($value)."\n";
+ });
+ $export->column('type', function ($value, $original) {
+ return strip_tags($value);
+ });
+ $export->column('state', function ($value, $original) {
+ return strip_tags($value);
+ });
+
+ $export->except(['versions_count', 'paid_at', 'expired_at']);
+
+ $export->filename($this->title.date("YmdHis"));
+ });
+ return $grid;
+ }
+
+ /**
+ * Notes: 添加订单
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 13:21
+ * @return Form
+ */
+ public function form(): Form
+ {
+ $form = new Form(new Order());
+
+ $form->select('user_id', '会员')
+ ->options(function ($userId) {
+ $user = User::find($userId);
+
+ if ($user) {
+ return [$user->id => $user->username.' ['.$user->info->nickname.']'];
+ }
+ })
+ ->ajax(route('admin.user.users.ajax'))
+ ->load('address_id', route('admin.mall.ajax.address'))
+ ->required();
+
+ $form->select('address_id', '收货地址')
+ ->load('goods_sku_id', route('admin.mall.ajax.goods'))
+ ->required();
+ $form->html("",
+ '');
+ $form->select('goods_sku_id', '剩余箱数')->required();
+
+ $form->number('qty', '发货箱数')->default(1);
+ $form->radioButton('type', '方式')
+ ->options(OrderExpress::TYPE_MAP)
+ ->required()
+ ->when(OrderExpress::TYPE_EXPRESS, function ($form) {
+ $form->select('express_id', '物流')
+ ->options(function () {
+ return Express::pluck('name', 'id');
+ })
+ ->rules('required', ['required' => '物流公司必须选择']);
+ $form->text('express_no', '物流单号')->rules('required');
+
+ })
+ ->when(OrderExpress::TYPE_LOGISTICS, function ($form) {
+ $form->text('person', '经办人');
+ });
+
+ return $form;
+ }
+
+ /**
+ * Notes: 获取提交数据
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 14:16
+ * @return mixed|void
+ * @throws \Exception
+ */
+ public function store()
+ {
+ $user_id = request('user_id', 0);
+ $remark = request('remark', '后台录入');
+ $address_id = request('address_id', 0);
+ $goods_sku_id = request('goods_sku_id', 0);
+ $qty = request('qty', 0);
+ $express_id = request('express_id');
+ $express_no = request('express_no');
+ $type = request('type');
+ $person = request('person');
+
+ $user = User::find($user_id);
+ $address = Address::find($address_id);
+ $goods_sku = GoodsSku::find($goods_sku_id);
+
+ if (! $goods_sku) {
+ $error = new MessageBag([
+ 'title' => '错误',
+ 'message' => '未找到商品',
+ ]);
+
+ return back()->withInput()->with(compact('error'));
+ }
+
+ $stockData = $user->getStockData();
+
+ if ($qty > $stockData['residue']) {
+ $error = new MessageBag([
+ 'title' => '错误',
+ 'message' => '用户库存不足',
+ ]);
+
+ return back()->withInput()->with(compact('error'));
+ } else {
+ $detail = collect();
+ $item = new Item($goods_sku, $address, $qty);
+ $detail->push($item);
+
+ $orders = (new OrderFacade)->user($user)
+ ->remark($remark)
+ ->channel(Order::CHANNEL_SYSTEM)
+ ->type(Order::TYPE_SAMPLE)
+ ->items($detail)
+ ->address($address)
+ ->create();
+
+ foreach ($orders as $order) {
+ $order->pay();//设置已支付
+ $order->deliver($express_id, $express_no, $type, $person);//发货
+ }
+
+ admin_toastr('操作完成');
+ return redirect()->to('/admin/mall/stock_orders_by_system?user_id='.$user->id);
+ }
+ }
+
+
+ /**
+ * Notes : 订单详情
+ *
+ * @Date : 2021/3/16 1:46 下午
+ * @Author : < Jason.C >
+ * @param string $orderNo 订单的 order_no
+ */
+ protected function detail(string $orderNo)
+ {
+ $order = Order::query()->firstWhere(['order_no' => $orderNo]);
+
+ return view('mall::admin.stock_order.detail', compact('order'));
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/StockOrderController.php b/modules/Mall/Http/Controllers/Admin/StockOrderController.php
new file mode 100644
index 0000000..0a30afe
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/StockOrderController.php
@@ -0,0 +1,142 @@
+model()
+ ->where('type', Order::TYPE_SAMPLE)
+ ->where('channel', Order::CHANNEL_USER)
+ ->with(['shop', 'user', 'items'])
+ ->withCount('versions');
+
+ $grid->disableCreateButton();
+ $grid->quickSearch('order_no')->placeholder('搜索订单编号');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('unPay', '未付款')->unPay();
+ $filter->scope('paid', '待发货')->paid();
+ $filter->scope('delivered', '已发货')->delivered();
+ $filter->scope('signed', '已签收')->signed();
+ $filter->scope('completed', '已完成')->completed();
+
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('order_no', '订单编号');
+// $filter->equal('shop_id', '所属店铺')->select(Shop::pluck('name', 'id'));
+ $filter->equal('user_id', '提货用户')->select()->ajax(route('admin.user.users.ajax'));
+ });
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->between('created_at', '下单时间')->datetime();
+ $filter->equal('state', '订单状态')->select([
+ Order::STATUS_CANCEL => '已取消',
+ Order::STATUS_PAID => '待发货',
+ Order::STATUS_DELIVERED => '已发货',
+ Order::STATUS_SIGNED => '已签收',
+ ]);
+
+// $filter->between('paid_at', '付款时间')->datetime();
+// $filter->between('expired_at', '过期时间')->datetime();
+ });
+ });
+
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableEdit();
+ $actions->disableDelete();
+ $actions->disableView();
+ if ($actions->row->can('pay') && $this->row->type == Order::TYPE_NORMAL) {
+ $actions->add(new Pay());
+ }
+ if ($actions->row->can('deliver')) {
+ $actions->add(new Delivered);
+ }
+ });
+
+ $grid->model()->with(['shop', 'user.info']);
+
+ $grid->column('order_no', '订单编号')->display(function () {
+ return sprintf('%s', route('admin.mall.stock_orders.show', $this), $this->order_no);
+ });
+// $grid->column('shop.name', '所属店铺');
+ $grid->column('user.username', '提货用户');
+// $grid->column('amount', '商品金额');
+// $grid->column('freight', '运费');
+ $grid->column('qty', '提货数量')
+ ->display(function () {
+ return $this->items()->sum('qty');
+ });
+// $grid->column('订单金额')->display(function () {
+// return $this->total;
+// });
+ $grid->column('state', '订单状态')
+ ->display(function () {
+ return $this->state_text;
+ })
+ ->label();
+// $grid->column('type', '订单类型')->using(Order::TYPE_MAP)->label();
+// $grid->column('paid_at', '支付时间')->sortable();
+// $grid->column('expired_at', '过期时间')->sortable();
+ $grid->column('versions_count', '操作日志')->link(function () {
+ return route('admin.mall.versions', [
+ 'model' => get_class($this),
+ 'key' => $this->id,
+ ]);
+ }, '_self');
+ $grid->column('created_at', '下单时间')->sortable();
+
+ $grid->disableExport(false);
+ $grid->export(function ($export) {
+ $export->column('order_no', function ($value, $original) {
+ return strip_tags($value)."\n";
+ });
+ $export->column('type', function ($value, $original) {
+ return strip_tags($value);
+ });
+ $export->column('state', function ($value, $original) {
+ return strip_tags($value);
+ });
+
+ $export->except(['versions_count', 'paid_at', 'expired_at']);
+
+ $export->filename($this->title.date("YmdHis"));
+ });
+ return $grid;
+ }
+
+ /**
+ * Notes : 订单详情
+ *
+ * @Date : 2021/3/16 1:46 下午
+ * @Author : < Jason.C >
+ * @param string $orderNo 订单的 order_no
+ */
+ protected function detail(string $orderNo)
+ {
+ $order = Order::query()->firstWhere(['order_no' => $orderNo]);
+
+ return view('mall::admin.stock_order.detail', compact('order'));
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/TagController.php b/modules/Mall/Http/Controllers/Admin/TagController.php
new file mode 100644
index 0000000..4a61488
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/TagController.php
@@ -0,0 +1,48 @@
+model()->with(['shop']);
+
+ $grid->column('shop.name', '所属店铺');
+ $grid->column('name', '标签名称');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Tag());
+
+ $this->shop($form)->required();
+ $form->text('name', '标签名称')
+ ->required();
+
+ return $form;
+ }
+
+ public function ajax(Request $request)
+ {
+ $key = $request->q;
+
+ return Tag::where('name', 'like', "%$key%")->paginate(null, ['id', 'name as text']);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/VersionController.php b/modules/Mall/Http/Controllers/Admin/VersionController.php
new file mode 100644
index 0000000..3647436
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/VersionController.php
@@ -0,0 +1,68 @@
+header($model . ' => ' . $key)
+ ->description('数据操作日志')
+ ->body($this->grid($model, $key));
+ }
+
+ public function grid($model, $key): Grid
+ {
+ $grid = new Grid(new Version());
+
+ $grid->disableCreateButton();
+ $grid->disableActions();
+ $grid->disableTools();
+
+ $grid->model()->whereHasMorph(
+ 'versionable',
+ $model,
+ function (Builder $query) use ($key) {
+ $query->where('id', $key);
+ }
+ )->orderByDesc('id');
+
+ $grid->column('操作用户')->display(function () {
+ if ($this->user_id < config('mall.administrator_max_id')) {
+ config(['versionable.user_model' => Administrator::class]);
+
+ return '[Admin] ' . ($this->user->name ?? '--SYSTEM--');
+ } else {
+ return '[USER] ' . ($this->user->username ?? '--USER--');
+ }
+ });
+ $grid->column('versionable_type', '数据变动')->expand(function () {
+ $data = [];
+ foreach ($this->contents as $key => $item) {
+ $data[] = [
+ $key,
+ is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE) : $item,
+ ];
+ }
+
+ return new Table(['字段名称', '变更值'], $data);
+ });
+
+ $grid->column('操作时间')->display(function () {
+ return $this->created_at->toDateTimeString();
+ });
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Admin/VideoController.php b/modules/Mall/Http/Controllers/Admin/VideoController.php
new file mode 100644
index 0000000..76ba035
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/VideoController.php
@@ -0,0 +1,64 @@
+filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '视频名称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->radio([
+ 0 => '禁用',
+ 1 => '正常',
+ ]);
+ });
+ });
+
+
+ $grid->column('id', '#ID#');
+ $grid->column('name', '视频名称');
+ $grid->column('cover', '封面地址')->image('', 60, 60);
+ $grid->column('path', '视频地址')
+ ->display(function () {
+ return $this->path_url;
+ });
+ $grid->column('status', '状态')->switch();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ protected function form(): Form
+ {
+ $form = new Form((new Video())->disableModelCaching());
+
+ $form->text('name', '视频标题')->required();
+ $this->cover($form);
+ $this->video($form);
+ $form->switch('status', '显示')->default(1);
+
+ return $form;
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Admin/WithShop.php b/modules/Mall/Http/Controllers/Admin/WithShop.php
new file mode 100644
index 0000000..23d5823
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Admin/WithShop.php
@@ -0,0 +1,22 @@
+select('shop_id', '所属店铺')
+ ->options(function ($shopId) {
+ $shop = Shop::find($shopId);
+ if ($shop) {
+ return [$shop->id => $shop->name];
+ }
+ })
+ ->ajax(route('admin.mall.shops.ajax'));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Agent/IndexController.php b/modules/Mall/Http/Controllers/Agent/IndexController.php
new file mode 100644
index 0000000..de8fa90
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Agent/IndexController.php
@@ -0,0 +1,16 @@
+success($request->shop);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Agent/ShopController.php b/modules/Mall/Http/Controllers/Agent/ShopController.php
new file mode 100644
index 0000000..0686d91
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Agent/ShopController.php
@@ -0,0 +1,19 @@
+success();
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Api/ActivityController.php b/modules/Mall/Http/Controllers/Api/ActivityController.php
new file mode 100644
index 0000000..22a57b9
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/ActivityController.php
@@ -0,0 +1,34 @@
+get();
+
+ return $this->success(ActivityBaseResource::collection($activities));
+ }
+
+ public function show(Activity $activity)
+ {
+ return $this->success(new ActivityResource($activity));
+ }
+
+
+}
diff --git a/modules/Mall/Http/Controllers/Api/AddressController.php b/modules/Mall/Http/Controllers/Api/AddressController.php
new file mode 100644
index 0000000..277be11
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/AddressController.php
@@ -0,0 +1,160 @@
+
+ * @Date : 2020/11/5 5:09 下午
+ * @return mixed
+ */
+ public function index()
+ {
+ $addresses = Address::byUser(Api::user())->orderBy('is_default', 'desc')->get();
+
+ return $this->success(AddressResource::collection($addresses));
+ }
+
+ /**
+ * Notes: 创建地址,展示地区联动数据
+ *
+ * @Author:
+ * @Date : 2020/11/5 5:10 下午
+ */
+ public function create(Request $request)
+ {
+ $parentId = $request->parent_id ?? 1;
+
+ $list = Region::where('parent_id', $parentId)->get();
+
+ return $this->success($list);
+ }
+
+ /**
+ * Notes: 保存地址
+ *
+ * @Author:
+ * @Date : 2020/11/5 5:13 下午
+ * @param \Modules\Mall\Http\Requests\Address\AddressRequest $request
+ * @return mixed
+ */
+ public function store(AddressRequest $request)
+ {
+ $result = Address::create([
+ 'user_id' => Api::userId(),
+ 'name' => $request->name,
+ 'mobile' => $request->mobile,
+ 'province_id' => $request->province_id,
+ 'city_id' => $request->city_id,
+ 'district_id' => $request->district_id,
+ 'address' => $request->address,
+ 'is_default' => $request->is_default,
+ ]);
+ if ($result) {
+ return $this->success('操作成功');
+ } else {
+ return $this->failed('失败');
+ }
+ }
+
+ /**
+ * Notes: 地址详情,如果不是自己的地址,不显示
+ *
+ * @Author:
+ * @Date : 2020/11/5 5:31 下午
+ * @param Address $address
+ * @return mixed
+ */
+ public function show(Address $address)
+ {
+ if ($address->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+
+ return $this->success(new AddressResource($address));
+ }
+
+ /**
+ * Notes: 设置默认地址
+ *
+ * @Author:
+ * @Date : 2020/11/5 5:34 下午
+ * @param Address $address
+ * @return mixed
+ */
+ public function setDefault(Address $address)
+ {
+ if ($address->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+ try {
+ $address->setDefault();
+
+ return $this->success('操作成功');
+ } catch (Exception $exception) {
+ return $this->failed('失败');
+ }
+ }
+
+ /**
+ * Notes: 更新地址
+ *
+ * @Author:
+ * @Date : 2020/11/5 5:40 下午
+ */
+ public function update(AddressRequest $request, Address $address)
+ {
+ if ($address->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+
+ $address->update([
+ 'name' => $request->name,
+ 'mobile' => $request->mobile,
+ 'province_id' => $request->province_id,
+ 'city_id' => $request->city_id,
+ 'district_id' => $request->district_id,
+ 'address' => $request->address,
+ 'is_default' => $request->is_default,
+ ]);
+
+ return $this->success('操作成功');
+ }
+
+ /**
+ * Notes: 删除地址
+ *
+ * @Author:
+ * @Date : 2020/11/5 5:52 下午
+ * @param Address $address
+ * @return mixed
+ */
+ public function destroy(Address $address)
+ {
+ if ($address->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+
+ try {
+ $address->delete();
+
+ return $this->success('操作成功');
+ } catch (Exception$exception) {
+ return $this->failed('失败');
+ }
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Api/BannerController.php b/modules/Mall/Http/Controllers/Api/BannerController.php
new file mode 100644
index 0000000..ae45195
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/BannerController.php
@@ -0,0 +1,37 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $position = $request->position;
+ $shopId = $request->shop_id;
+
+ // 店铺ID为null的是平台轮播
+ $banners = Banner::when($shopId, function (Builder $query) use ($shopId) {
+ $query->where('shop_id', $shopId);
+ })->when(is_numeric($position), function (Builder $query) use ($position) {
+ $query->ofPosition($position);
+ })->get();
+
+ return $this->success(BannerResource::collection($banners));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Api/CartController.php b/modules/Mall/Http/Controllers/Api/CartController.php
new file mode 100644
index 0000000..7739df4
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/CartController.php
@@ -0,0 +1,159 @@
+
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(): JsonResponse
+ {
+ $list = Cart::byUser(Api::user())->with('sku')->orderByDesc('updated_at')->get();
+
+ $collection = $list->groupBy('shop_id');
+
+ $items = [];
+
+ foreach ($collection as $shopId => $item) {
+ $items[] = [
+ 'shop' => new ShopBaseInfoResource(Shop::find($shopId)),
+ 'items' => CartResource::collection($item),
+ ];
+ }
+
+ return $this->success($items);
+ }
+
+ /**
+ * Notes : 加入购物车
+ * @Date : 2021/5/14 2:17 下午
+ * @Author :
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function store(Request $request)
+ {
+ $skuId = $request->sku_id;
+ $qty = $request->qty ?? 1;
+
+ $sku = GoodsSku::findOrFail($skuId);
+
+ if (!$sku->canBuy($qty)) {
+ return $this->failed('超过最大可购买数量');
+ }
+
+ $cart = Cart::where('user_id', Api::userId())->where('sku_id', $skuId)->first();
+
+ if ($cart) {
+ $cart->qty = $cart->qty + $qty;
+ $cart->save();
+ } else {
+ $cart = Cart::create([
+ 'user_id' => Api::userId(),
+ 'shop_id' => $sku->goods->shop_id,
+ 'sku_id' => $skuId,
+ 'qty' => $qty,
+ ]);
+ }
+
+ return $this->success($cart->qty);
+ }
+
+ /**
+ * Notes : 获取购物车商品数量
+ * @Date : 2021/5/14 1:47 下午
+ * @Author :
+ */
+ public function count(): JsonResponse
+ {
+ if (Api::userId()) {
+ $count = Cart::where('user_id', Api::userId())->count();
+ } else {
+ $count = 0;
+ }
+
+ return $this->success($count);
+ }
+
+ /**
+ * Notes : 更新购物车商品数量
+ * @Date : 2021/5/13 2:52 下午
+ * @Author :
+ * @param \Modules\Mall\Http\Requests\Cart\CartRequest $request
+ * @param \Modules\Mall\Models\Cart $cart
+ * @return mixed
+ */
+ public function update(CartRequest $request, Cart $cart)
+ {
+ $skuId = $request->sku_id;
+ $qty = $request->qty;
+
+ if ($cart->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+
+ if ($skuId && ($skuId != $cart->sku_id)) {
+ $sku = GoodsSku::find($skuId);
+
+ if (!$cart->sku->goods->is($sku->goods)) {
+ return $this->failed('错误的商品');
+ }
+
+ $cart->sku_id = $skuId;
+ } else {
+ $sku = $cart->sku;
+ }
+
+ if ($qty < 1 || $qty > $sku->stock) {
+ return $this->failed('库存不足');
+ }
+
+ $cart->qty = $qty;
+ $cart->save();
+
+ return $this->success($qty);
+ }
+
+ /**
+ * Notes : 删除购物车商品
+ * @Date : 2021/5/17 4:00 下午
+ * @Author :
+ * @param string $cart
+ * @return mixed|string
+ */
+ public function destroy(string $cart)
+ {
+ if (Str::contains($cart, ',')) {
+ $count = Cart::query()->where('user_id', Api::userId())->whereIn('id', explode(',', $cart))->delete();
+ if (!$count) {
+ return $this->failed('empty');
+ }
+ } else {
+ $cart = Cart::find($cart);
+ if ($cart->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+ $cart->delete();
+ }
+
+ return $this->success('删除成功');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Api/CategoryController.php b/modules/Mall/Http/Controllers/Api/CategoryController.php
new file mode 100644
index 0000000..43e045f
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/CategoryController.php
@@ -0,0 +1,37 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $shopId = $request->shop_id;
+
+ $list = Category::query()
+ ->where('id', '<>', 2)
+ ->when($shopId, function (Builder $query) use ($shopId) {
+ $query->where('shop_id', $shopId);
+ })->where('parent_id', 0)->get();
+
+ return $this->success(CategoryResource::collection($list));
+
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Api/GoodsController.php b/modules/Mall/Http/Controllers/Api/GoodsController.php
new file mode 100644
index 0000000..cf499dc
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/GoodsController.php
@@ -0,0 +1,87 @@
+get('name');
+ $shopId = $request->get('shop_id');
+ $categoryId = $request->get('category_id');
+ $brandId = $request->get('brand_id');
+ $payType = $request->get('pay_type');
+ $tagId = $request->get('tag_id');
+ $isSelf = $request->get('is_self');
+ $perPage = $request->get('per_page');
+ $orderBy = $request->get('order_by');
+
+ $goods = Goods::shown()
+ ->select(Goods::LIST_SELECT_FIELDS)
+ ->with(['tags', 'shop'])
+ ->when($name, function (Builder $query) use ($name) {
+ $query->where('name', 'like', "%$name%");
+ })
+ ->when($isSelf, function (Builder $query) {
+ $query->whereHas('shop', function (Builder $query) {
+ $query->where('is_self', 1);
+ });
+ })
+ ->when($shopId, function (Builder $query) use ($shopId) {
+ $query->where('shop_id', $shopId);
+ })
+ ->when($categoryId, function (Builder $query) use ($categoryId) {
+ $query->where('category_id', $categoryId);
+ })
+ ->when($brandId, function (Builder $query) use ($brandId) {
+ $query->where('brand_id', $brandId);
+ })
+ ->when($payType, function (Builder $query) use ($payType) {
+ $query->where('pay_type', $payType);
+ })
+ ->when($tagId, function (Builder $query) use ($tagId) {
+ $query->whereHas('tags', function (Builder $query) use ($tagId) {
+ $query->whereIn('id', explode(',', $tagId));
+ });
+ })
+ ->when($orderBy, function (Builder $query) use ($orderBy) {
+ switch ($orderBy) {
+ case 'sales':
+ $query->orderByDesc('sales');
+ break;
+ case 'price':
+ $query->orderByPrice();
+ break;
+ }
+ })
+ ->paginate($perPage);
+
+ return $this->success(new GoodsCollection($goods));
+ }
+
+ public function show(Goods $goods): JsonResponse
+ {
+ $goods->incrementClicks();
+
+ return $this->success(new GoodsResource($goods));
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Api/IndexController.php b/modules/Mall/Http/Controllers/Api/IndexController.php
new file mode 100644
index 0000000..e9b796f
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/IndexController.php
@@ -0,0 +1,99 @@
+
+ * @return JsonResponse
+ */
+ public function index(): JsonResponse
+ {
+ $banners = Banner::query()->ofPosition(Banner::POSITION_INDEX)->get();
+ $centers = Banner::query()->ofPosition(Banner::POSITION_INDEX_CENTER)->get();
+
+
+ $show_goods_id = app('Conf_mall')['show_goods_id'] ?? 0;
+ $show_goods = '';
+ if ($show_goods_id > 0) {
+ $show_goods = Goods::find($show_goods_id);
+ }
+
+ $goods = Goods::query()
+ ->where('id', '<>', $show_goods_id)
+ ->shown()
+ ->where('channel', Goods::CHANNEL_SCORE)
+ ->get();
+
+ $videos = Video::query()
+ ->shown()
+ ->get();
+
+ $memories = Article::query()
+ ->whereHas('categories', function ($q) {
+ $q->where('slug', 'MEMORY');
+ })
+ ->inRandomOrder()
+ ->take(10)
+ ->get();
+
+ $memoriesData = [];
+ $all = $memories->count() * 30;
+ foreach ($memories as $key => $memory) {
+ $id = ++$key;
+ $memoriesData[] = [
+ 'id' => $memory->id,
+ 'title' => $memory->title,
+ 'description' => $memory->description,
+ 'zIndex' => $id,
+ 'left' => (int) bcsub($all, bcmul($key, 30)),
+ 'image' => $memory->cover_url,
+ 'animation' => null,
+ ];
+ }
+
+ $healths = Article::query()
+ ->whereHas('categories', function ($q) {
+ $q->where('slug', 'HEALTH');
+ })
+ ->inRandomOrder()
+ ->take(3)
+ ->get();
+
+ $data = [
+ 'show_goods' => $show_goods ? new GoodsBaseResource($show_goods) : '',
+ 'videos' => VideoResource::collection($videos),
+ 'goods' => GoodsBaseResource::collection($goods),
+ 'banners' => BannerResource::collection($banners),
+ 'center' => BannerResource::collection($centers),
+ 'memories' => $memoriesData,
+ 'healths' => ArticleBaseResource::collection($healths),
+ 'water_mobile' => app('Conf_mall')['water_mobile'] ?? '',
+ 'categories' => [
+ 'memory' => Category::query()->where('slug', 'MEMORY')->value('id') ?? 0,
+ 'health' => Category::query()->where('slug', 'HEALTH')->value('id') ?? 0,
+ ],
+ ];
+
+ return $this->success($data);
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Api/OrderBuyController.php b/modules/Mall/Http/Controllers/Api/OrderBuyController.php
new file mode 100644
index 0000000..4526d97
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/OrderBuyController.php
@@ -0,0 +1,298 @@
+all(), [
+ 'goods_sku_id' => 'required|integer',
+ 'qty' => 'nullable|integer',
+ ], [
+ 'goods_sku_id.required' => '缺少商品规格id',
+ 'goods_sku_id.integer' => '商品规格id必须是数字',
+ 'qty.integer' => '数量必须是数字',
+ ]);
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first());
+ }
+
+ $goods_sku_id = $request->goods_sku_id;
+ $qty = $request->qty;
+ $address_id = $request->address_id;
+
+ $detail = collect();
+ $user = Api::user();
+
+ if ($address_id) {
+ $address = Address::find($address_id);
+ } else {
+ $address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->first();
+ }
+
+ $goods_sku = GoodsSku::find($goods_sku_id);
+
+ if (! $goods_sku) {
+ return $this->failed('未找到商品');
+ }
+
+ if (! $goods_sku->canBuy($qty)) {
+ return $this->failed($goods_sku->getGoodsName().'商品不可购买');
+ }
+
+ $item = new Item($goods_sku, $address, $qty);
+ $detail->push($item);
+
+ $freight = $item->freight();
+ $amount = $item->total();
+ $weight = $item->weight();
+
+ $detail = (new Order)->items($detail)->splitOrderByShop();
+ $items = [];
+ foreach ($detail as $item) {
+ $items[] = $item;
+ }
+
+ $data = [
+ 'address' => $address ? new AddressResource($address) : '',
+ 'detail' => $items,
+ 'amount' => floatval(bcadd($amount, $freight, 2)),
+ 'freight' => $freight,
+ 'weight' => $weight,
+ ];
+
+ return $this->success($data);
+
+ }
+
+ /**
+ * Notes: 商品下单
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 13:53
+ * @param \Modules\Mall\Http\Requests\OrderBuy\OrderBuyRequest $request
+ * @return mixed
+ * @throws \Exception
+ */
+ public function goodsBuy(OrderBuyRequest $request)
+ {
+ $validator = Validator::make($request->all(), [
+ 'goods_sku_id' => 'required|integer',
+ 'qty' => 'nullable|integer',
+ 'address_id' => 'required|integer',
+ ], [
+ 'goods_sku_id.required' => '缺少商品规格id',
+ 'goods_sku_id.integer' => '商品规格id必须是数字',
+ 'qty.integer' => '数量必须是数字',
+ 'address_id.required' => '缺少收货地址',
+ 'address_id.integer' => '收货地址必须是数字',
+ ]);
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first());
+ }
+
+ $user = Api::user();
+ $remark = $request->remark ?? '';
+ $address_id = $request->address_id ?? '';
+ $goods_sku_id = $request->goods_sku_id;
+ $qty = $request->qty;
+ $share_user_id = $request->share_user_id ?? 0;
+
+ $address = Address::where('user_id', $user->id)->where('id', $address_id)->first();
+ if (! $address) {
+ return $this->failed('未找到收货地址');
+ }
+
+ $detail = collect();
+
+ $goods_sku = GoodsSku::find($goods_sku_id);
+
+ if (! $goods_sku) {
+ return $this->failed('未找到商品');
+ }
+
+ if (! $goods_sku->canBuy($qty)) {
+ return $this->failed($goods_sku->getGoodsName().' 商品不可购买');
+ }
+
+ $item = new Item($goods_sku, $address, $qty);
+ $detail->push($item);
+
+ $type = OrderModel::TYPE_NORMAL;
+ if ($goods_sku->goods->channel == Goods::CHANNEL_FREE) {
+ $type = OrderModel::TYPE_SAMPLE;
+ }
+
+ if ($goods_sku->goods->channel == Goods::CHANNEL_SCORE) {
+ $type = OrderModel::TYPE_SCORE;
+ }
+
+ $orders = (new Order)->user($user)
+ ->remark($remark)
+ ->items($detail)
+ ->type($type)
+ ->address($address)
+ ->source(['share_user_id' => $share_user_id,])
+ ->create();
+
+ return $this->success([
+ 'order_no' => $orders->first()->order_no,
+ 'total' => $orders->first()->total,
+ 'score' => $user->account->score,
+ ]);
+ }
+
+ /**
+ * Notes: 购物车确认下单
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 10:14
+ */
+ public function cartsCreate(Request $request)
+ {
+ $user = Api::user();
+
+ $cart_ids = $request->cart_ids;//1,2,3
+ $cart_ids = explode(',', $cart_ids);
+ $carts = Cart::where('user_id', $user->id)->whereIn('id', $cart_ids)->get();
+ if ($carts->isEmpty()) {
+ return $this->failed('未找到购物车');
+ }
+
+ $detail = collect();
+
+ $all_address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->get();
+ $address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->first();
+
+ $amount = $weight = $freight = 0;
+ foreach ($carts as $cart) {
+ $goods_sku = $cart->sku;
+
+ if (! $goods_sku) {
+ return $this->failed('未找到商品');
+ }
+
+ if (! $goods_sku->canBuy($cart->qty)) {
+ return $this->failed($goods_sku->getGoodsName().' 不可购买');
+ }
+
+ $item = new Item($goods_sku, $address, $cart->qty);
+ $detail->push($item);
+
+ $freight += $item->freight();
+ $amount += $item->total();
+ $weight += $item->weight();
+ }
+
+ try {
+ $data = [
+ 'address' => $address ? new AddressResource($address) : '',
+ 'all_address' => AddressResource::collection($all_address),
+ 'detail' => (new Order)->items($detail)->splitOrderByShop(),
+ 'amount' => bcadd($amount, $freight, 2),
+ 'freight' => $freight,
+ 'weight' => $weight,
+ ];
+
+ return $this->success($data);
+
+ } catch (Exception $e) {
+ return $this->failed($e->getMessage());
+ }
+
+ }
+
+ /**
+ * Notes: 购物车购买
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 10:14
+ */
+ public function cartsBuy(Request $request)
+ {
+ $user = Api::user();
+
+ $cart_ids = $request->cart_ids; //1,2,3
+ $cart_ids = explode(',', $cart_ids);
+ $address_id = $request->address_id ?? 0;
+ $remark = $request->remark ?? '';
+ $share_user_id = $request->share_user_id ?? 0;
+
+ $carts = Cart::where('user_id', $user->id)->whereIn('id', $cart_ids)->get();
+ if ($carts->isEmpty()) {
+ return $this->failed('未找到购物车');
+ }
+
+ $address = Address::where('user_id', $user->id)->where('id', $address_id)->first();
+ if (! $address) {
+ return $this->failed('未找到收货地址');
+ }
+
+ $detail = collect();
+ foreach ($carts as $cart) {
+ $goods_sku = $cart->sku;
+
+ if (! $goods_sku) {
+ return $this->failed('未找到商品');
+ }
+
+ if (! $goods_sku->canBuy($cart->qty)) {
+ return $this->failed($goods_sku->getGoodsName().' 不可购买');
+ }
+
+ $item = new Item($goods_sku, $address, $cart->qty);
+ $detail->push($item);
+ }
+
+ try {
+ (new Order)->user($user)
+ ->remark($remark)
+ ->items($detail)
+ ->address($address)
+ ->source(['share_user_id' => $share_user_id])
+ ->create();
+ //删除购物车
+ Cart::whereIn('id', $cart_ids)->delete();
+
+ return $this->success('下单成功');
+ } catch (Exception $e) {
+ return $this->failed($e->getMessage());
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Api/OrderController.php b/modules/Mall/Http/Controllers/Api/OrderController.php
new file mode 100644
index 0000000..33cd43a
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/OrderController.php
@@ -0,0 +1,287 @@
+
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $state = $request->state ?? '';
+ $type = $request->type ?? '';// score sample
+
+ #todo 类别筛选
+ #todo 商品名称搜索
+ #todo 订单编号搜索
+
+ $list = Order::byUser(Api::user())
+ ->when($state, function ($query) use ($state) {
+ switch ($state) {
+ case 'unpay':
+ $query->unPay();
+ break;
+ case 'paid':
+ $query->paid();
+ break;
+ case 'delivered':
+ $query->Delivered();
+ break;
+ case 'signed':
+ $query->signed();
+ break;
+ case 'completed':
+ $query->completed();
+ break;
+ }
+ }, function ($query) {
+ $query->common();
+ })
+// ->when($type, function ($q) use ($type) {
+// if ($type == 'score') {
+// $q->TypeScore();
+// }
+//
+// if ($type == 'sample') {
+// $q->TypeSample();
+// }
+// })
+ ->paginate();
+
+ return $this->success(new OrderCollection($list));
+ }
+
+ /**
+ * Notes : 订单详情
+ *
+ * @Date : 2021/5/13 5:17 下午
+ * @Author :
+ * @param Order $order
+ * @return mixed
+ */
+ public function show(Order $order)
+ {
+ if ($order->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+
+ return $this->success(new OrderResource($order));
+ }
+
+ /**
+ * Notes : 查看物流
+ *
+ * @Date : 2021/5/14 16:52
+ * @Author : Mr.wang
+ * @param Order $order
+ * @return mixed
+ * @throws HttpException
+ */
+ public function logistic(Order $order)
+ {
+ if ($order->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+ if (! $order->is_logistic_show) {
+ return $this->failed('', 404);
+ }
+
+ $express = new Synquery();
+ $synQuery = $express->pollQuery($order->express->express->slug, $order->express->express_no);
+ if (isset($synQuery->result) && $synQuery->result === false) {
+ return $this->failed($synQuery->message);
+ }
+ $logistics = $synQuery->data;
+
+ return $this->success([
+ 'orderExpress' => new OrderLogisticResource($order->express),
+ 'logistics' => LogisticResource::collection($logistics),
+ ]);
+ }
+
+ /**
+ * Notes : 签收成功
+ *
+ * @Date : 2021/5/17 16:52
+ * @Author : Mr.wang
+ * @param Order $order
+ * @return mixed
+ * @throws \Exception
+ */
+ public function sign(Order $order)
+ {
+ if ($order->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+ if ($order->sign() === true) {
+ return $this->success('操作成功');
+ } else {
+ return $this->failed();
+ }
+ }
+
+ /**
+ * Notes : 取消订单
+ *
+ * @Date : 2021/5/19 11:35
+ * @Author : Mr.wang
+ * @param Order $order
+ * @return mixed
+ */
+ public function cancel(Order $order)
+ {
+ if ($order->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+ try {
+ $order->cancel();
+
+ return $this->success('操作成功');
+ } catch (Exception $e) {
+ return $this->failed($e->getMessage());
+ }
+ }
+
+ /**
+ * Notes : 删除订单(对订单进行软删除操作)
+ *
+ * @Date : 2021/5/19 11:35
+ * @Author : Mr.wang
+ * @param Order $order
+ * @return mixed
+ */
+ public function destroy(Order $order)
+ {
+ if ($order->user()->isNot(Api::user())) {
+ return $this->failed('', 404);
+ }
+ if ($order->canDelete() === false) {
+ return $this->failed('订单不允许删除');
+ }
+ try {
+ $order->delete();
+
+ return $this->success('操作成功');
+ } catch (Exception $e) {
+ return $this->failed($e->getMessage());
+ }
+ }
+
+ /**
+ * Notes: 订单商品
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/18 8:47
+ * @param Order $order
+ * @return mixed
+ */
+ public function goods(Order $order)
+ {
+ $user = Api::user();
+
+ if ($order->user()->isNot($user)) {
+ return $this->failed('', 404);
+ }
+ $data = [
+ 'order' => new OrderResource($order),
+ 'title' => Reason::query()->pluck('title'),
+ ];
+
+ return $this->success($data);
+ }
+
+ /**
+ * Notes: 退款/退货
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/19 9:11
+ * @param Order $order
+ * @param Request $request
+ * @return mixed
+ */
+ public function refund(Order $order, Request $request)
+ {
+ $user = Api::user();
+
+ if (! $order->type == Order::TYPE_SAMPLE) {
+ return $this->failed('操作失败,提货单不可退货');
+ }
+
+ if (! $order->can('refund')) {
+ return $this->failed('操作失败,此订单不可退货');
+ }
+
+ if ($order->user()->isNot($user)) {
+ return $this->failed('您没有权限进行此操作');
+ }
+
+ $remark = $request->remark ?? '';
+ $title = $request->title;//退款原因
+ $pictures = $request->pictures;//图片 many
+
+ if (! $order->isPay()) {
+ return $this->failed('退款失败,未找到支付信息');
+ }
+
+ try {
+ (new Refund)->user($user)
+ ->remark($remark)
+ ->order($order)
+ ->logs([
+ 'remark' => $remark,
+ 'title' => $title,
+ 'pictures' => $pictures,
+ ])
+ ->create();
+
+ return $this->success('提交成功,请等待审核。');
+ } catch (Exception $e) {
+ return $this->failed($e->getMessage());
+ }
+
+ }
+
+ /**
+ * Notes : 个人中心对订单的总数
+ *
+ * @Date : 2021/6/3 13:36
+ * @Author : Mr.wang
+ * @return mixed
+ */
+ public function counts()
+ {
+ $orderCount = Order::byUser(Api::user())->common()->count();
+ $refundCount = RefundModel::byUser(Api::user())->count();
+
+ return $this->success([
+ 'orderCount' => $orderCount,
+ 'refundCount' => $refundCount,
+ ]);
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Api/PayController.php b/modules/Mall/Http/Controllers/Api/PayController.php
new file mode 100644
index 0000000..c3514f0
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/PayController.php
@@ -0,0 +1,113 @@
+can('pay')) {
+ return $this->failed('支付失败,订单不可支付');
+ }
+
+ if ($order->user()->isNot($user)) {
+ return $this->failed('支付失败,您没有权限支付');
+ }
+
+ $payment = $order->createWechatPayment($user, $order->total, 'app');
+
+ $notify = $payment->getPaymentParams('商品下单', [
+ 'notify_url' => route('api.payment.notify.wechat'),
+ ]);
+
+ return $this->success($notify->getContent());
+ }
+
+ /**
+ * Notes: 支付宝支付
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/20 9:13
+ * @param \Modules\Mall\Models\Order $order
+ * @return mixed
+ * @throws \Exception
+ */
+ public function alipay(Order $order)
+ {
+ $user = Api::user();
+
+ if (! $order->can('pay')) {
+ return $this->failed('支付失败,订单不可支付');
+ }
+
+ if ($order->user()->isNot($user)) {
+ return $this->failed('支付失败,您没有权限支付');
+ }
+
+ $payment = $order->createAlipayPayment($user, $order->total, 'app');
+
+ $notify = $payment->getPaymentParams('商品下单', [
+ 'notify_url' => route('api.payment.notify.alipay'),
+ ]);
+
+ return $this->success($notify->getContent());
+
+ }
+
+ /**
+ * Notes: 水滴支付
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/5 15:51
+ * @param Order $order
+ * @return JsonResponse|mixed
+ * @throws \Exception
+ */
+ public function score(Order $order, Request $request)
+ {
+ $gateway = $request->gateway ?? 'web';
+ $user = Api::user();
+
+ if (! $order->can('pay')) {
+ return $this->failed('支付失败,订单不可支付');
+ }
+
+ if ($order->user()->isNot($user)) {
+ return $this->failed('支付失败,您没有权限支付');
+ }
+
+ if ($user->account->score < $order->total) {
+ return $this->failed('支付失败,您水滴积分不足');
+ }
+
+ $payment = $order->createScorePayment($user, $order->total, $gateway);
+
+ if ($payment) {
+ $payment->paid();
+ $payment->order->pay();
+ return $this->success('支付成功!');
+ } else {
+ return $this->failed('未知错误');
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Api/RefundController.php b/modules/Mall/Http/Controllers/Api/RefundController.php
new file mode 100644
index 0000000..f21ba68
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/RefundController.php
@@ -0,0 +1,124 @@
+state ?? 'refunding';//refunding refunded
+
+ $list = Refund::byUser(Api::user())
+ ->when($state, function ($query) use ($state) {
+ switch ($state) {
+ case 'refunding':
+ $query->whereIn('state', [
+ Refund::REFUND_APPLY,
+ Refund::REFUND_AGREE,
+ Refund::REFUND_PROCESS,
+ Refund::REFUND_DELIVER,
+ Refund::REFUND_DELIVERED,
+ Refund::REFUND_SIGNED,
+ ]);
+ break;
+ case 'refunded':
+ $query->whereIn('state', [
+ Refund::REFUND_REFUSE,
+ Refund::REFUND_COMPLETED,
+ ]);
+ break;
+ }
+ })
+ ->paginate();
+
+ return $this->success(new RefundCollection($list));
+ }
+
+ /**
+ * Notes: 退单详情
+ * @Author: 玄尘
+ * @Date : 2021/5/18 11:35
+ * @param \Modules\Mall\Models\Refund $refund
+ * @return mixed
+ */
+ public function show(Refund $refund)
+ {
+ if ($refund->user()->isNot(Api::user())) {
+ return $this->failed('您没有权限查看', 404);
+ }
+
+ return $this->success(new RefundResource($refund));
+ }
+
+ /**
+ * Notes: 退货
+ * @Author: 玄尘
+ * @Date : 2021/5/17 15:02
+ * @param \Modules\Mall\Models\Refund $refund
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ * @throws \Exception
+ */
+ public function deliver(Refund $refund, Request $request)
+ {
+ if ($refund->user()->isNot(Api::user())) {
+ return $this->failed('您没有权限操作');
+ }
+
+ $validator = Validator::make($request->all(), [
+ 'company' => 'required',
+ 'number' => 'required',
+ ], [
+ 'company.required' => '缺少物流公司',
+ 'number.required' => '缺少物流单号',
+ ]);
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first());
+ }
+ // $express = Express::find($request->company);
+ // if (!$express) {
+ // return $this->failed('未找到物流公司');
+ // }
+
+ $res = $refund->deliver($request->company, $request->number);
+ if ($res === true) {
+ return $this->success('退货成功,请等待退款');
+ }
+
+ return $this->failed('退货失败');
+ }
+
+ /**
+ * Notes: 退单日志
+ * @Author: 玄尘
+ * @Date : 2021/5/18 11:36
+ * @param \Modules\Mall\Models\Refund $refund
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function logs(Refund $refund): JsonResponse
+ {
+ $logs = $refund->logs()->latest()->get();
+
+ return $this->success(RefundLogResource::collection($logs));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Api/ShopController.php b/modules/Mall/Http/Controllers/Api/ShopController.php
new file mode 100644
index 0000000..a54d5a4
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/ShopController.php
@@ -0,0 +1,210 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $name = $request->name ?? '';
+
+ $list = Shop::where('status', Shop::STATUS_NORMAL)
+ ->when($name, function (Builder $query) use ($name) {
+ $query->where('name', 'like', "%{$name}%");
+ })
+ ->paginate();
+
+ return $this->success(new ShopCollection($list));
+ }
+
+ /**
+ * Notes : 店铺详情
+ * @Date : 2021/5/6 3:28 下午
+ * @Author :
+ */
+ public function show(Shop $shop): JsonResponse
+ {
+ if ($shop->status !== Shop::STATUS_NORMAL) {
+ return $this->failed('店铺不存在或已关闭');
+ }
+
+ return $this->success(new ShopResource($shop));
+ }
+
+ /**
+ * Notes : 申请的前置判断和基础数据
+ * @Date : 2021/5/6 3:29 下午
+ * @Author :
+ */
+ public function create()
+ {
+ $user = Api::user();
+ $checkShop = Shop::firstWhere('user_id', $user->id);
+ if ($checkShop) {
+ return $this->failed('用户已有店铺,不能重复申请');
+ }
+ $regions = Region::where('depth', 1)->get();
+
+ return $this->success(RegionResource::collection($regions));
+ }
+
+ /**
+ * Notes : 申请一个店铺
+ * @Date : 2021/5/6 3:29 下午
+ * @Author :
+ */
+ public function store(ShopRequest $request)
+ {
+ $user = Api::user();
+
+ if (Shop::firstWhere('user_id', $user->id)) {
+ return $this->failed('用户已有店铺,不能重复申请');
+ }
+ $result = Shop::create([
+ 'name' => $request->name,
+ 'user_id' => $user->id,
+ 'mobile' => $request->mobile,
+ 'description' => $request->description,
+ 'province_id' => $request->province_id,
+ 'city_id' => $request->city_id,
+ 'district_id' => $request->district_id,
+ 'cover' => $request->cover,
+ 'address' => $request->address,
+ 'status' => Shop::STATUS_APPLYING,
+ ]);
+ if ($result) {
+ return $this->success('操作成功');
+ } else {
+ return $this->failed('失败');
+ }
+ }
+
+ /**
+ * Notes : 编辑店铺需要的数据
+ * @Date : 2021/5/7 10:26 上午
+ * @Author :
+ */
+ public function edit()
+ {
+ $user = Api::user();
+ $shop = Shop::firstWhere('user_id', $user->id);
+ if (!$shop) {
+ return $this->failed('用户没有店铺,不能编辑');
+ }
+ if ($shop->status == Shop::STATUS_NORMAL) {
+ return $this->failed('用户已通过审核,不能编辑');
+ }
+
+ return $this->success(new ShopEditResource($shop));
+ }
+
+ /**
+ * Notes : 更新店铺数据
+ * @Date : 2021/5/7 10:27 上午
+ * @Author :
+ */
+ public function update(Request $request)
+ {
+ $user = Api::user();
+ $shop = Shop::firstWhere('user_id', $user->id);
+ if (!$shop) {
+ return $this->failed('用户没有店铺,不能编辑');
+ }
+ if ($shop->status == Shop::STATUS_NORMAL) {
+ return $this->failed('用户已通过审核,不能编辑');
+ }
+ $validator = Validator::make(
+ $request->all(),
+ [
+ 'name' => ['required', Rule::unique('mall_shops', 'name')->ignore($shop->id)],
+ 'mobile' => [
+ 'required', 'phone:CN', Rule::unique('mall_shops', 'mobile')->ignore($shop->id),
+ ],
+ 'description' => 'required',
+ 'province_id' => ['required', 'integer', new ProvinceRule()],
+ 'city_id' => ['required', 'integer', new CityRule()],
+ 'district_id' => ['required', 'integer', new DistrictRule()],
+ 'address' => 'required',
+ 'cover' => 'required',
+ ], [
+ 'name.required' => '店铺名称必须填写',
+ 'name.unique' => '店铺名称不能重复',
+ 'mobile.required' => '联系方式必须填写',
+ 'mobile.phone' => '联系方式格式不正确',
+ 'mobile.unique' => '联系方式不能重复',
+ 'description.required' => '店铺简介必须填写',
+ 'province_id.required' => '店铺所在省份必须选择',
+ 'province_id.integer' => '店铺所在省份格式不正确',
+ 'city_id.required' => '店铺所在城市必须选择',
+ 'city_id.integer' => '店铺所在城市格式不正确',
+ 'district_id.required' => '店铺所在地区必须选择',
+ 'district_id.integer' => '店铺所在地区格式不正确',
+ 'address.required' => '店铺详细地址必须填写',
+ 'cover.required' => '店铺封面图必须上传',
+ ]
+ );
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first(), 422);
+ }
+ $result = $shop->update([
+ 'name' => $request->name,
+ 'mobile' => $request->mobile,
+ 'description' => $request->description,
+ 'province_id' => $request->province_id,
+ 'city_id' => $request->city_id,
+ 'district_id' => $request->district_id,
+ 'cover' => $request->cover,
+ 'address' => $request->address,
+ 'status' => Shop::STATUS_APPLYING,
+ ]);
+ if ($result) {
+ return $this->success('操作成功');
+ } else {
+ return $this->failed('失败');
+ }
+ }
+
+ /**
+ * Notes : 关闭店铺
+ * @Date : 2021/5/6 3:29 下午
+ * @Author :
+ */
+ public function destroy(Shop $shop)
+ {
+ if ($shop->user()->isNot(Api::user())) {
+ return $this->failed('', '404');
+ }
+ if ($shop->close()) {
+ return $this->success('店铺关闭成功');
+ } else {
+ return $this->failed('失败');
+ }
+ }
+
+}
diff --git a/modules/Mall/Http/Controllers/Api/ShopExtendController.php b/modules/Mall/Http/Controllers/Api/ShopExtendController.php
new file mode 100644
index 0000000..b59f116
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/ShopExtendController.php
@@ -0,0 +1,87 @@
+
+ * @param \Modules\Mall\Models\Shop $shop
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function categories(Shop $shop): JsonResponse
+ {
+ $list = $shop->categories()->where('parent_id', 0)->get();
+
+ return $this->success(CategoryResource::collection($list));
+ }
+
+ /**
+ * Notes : 店铺标签
+ * @Date : 2021/5/13 11:35 上午
+ * @Author :
+ * @param \Modules\Mall\Models\Shop $shop
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function tags(Shop $shop): JsonResponse
+ {
+ $list = $shop->tags()->get();
+
+ return $this->success(TagResource::collection($list));
+ }
+
+ /**
+ * Notes : 店铺品牌
+ * @Date : 2021/5/13 11:35 上午
+ * @Author :
+ * @param \Modules\Mall\Models\Shop $shop
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function brands(Shop $shop): JsonResponse
+ {
+ $list = $shop->brands()->get();
+
+ return $this->success(BrandResource::collection($list));
+ }
+
+ /**
+ * Notes : 支持的物流
+ * @Date : 2021/5/13 11:36 上午
+ * @Author :
+ * @param \Modules\Mall\Models\Shop $shop
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function expresses(Shop $shop): JsonResponse
+ {
+ $list = $shop->expresses()->get();
+
+ return $this->success(ExpressResource::collection($list));
+ }
+
+ /**
+ * Notes : 轮播图
+ * @Date : 2021/5/13 11:36 上午
+ * @Author :
+ * @param \Modules\Mall\Models\Shop $shop
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function banners(Shop $shop): JsonResponse
+ {
+ $list = $shop->banners()->get();
+
+ return $this->success(BannerResource::collection($list));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Controllers/Api/TagController.php b/modules/Mall/Http/Controllers/Api/TagController.php
new file mode 100644
index 0000000..46584a0
--- /dev/null
+++ b/modules/Mall/Http/Controllers/Api/TagController.php
@@ -0,0 +1,36 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $shopId = $request->shop_id;
+ $limit = $request->limit;
+
+ $list = Tag::when($shopId, function (Builder $query) use ($shopId) {
+ $query->where('shop_id', $shopId);
+ })->when($limit, function (Builder $query) use ($limit) {
+ $query->take($limit);
+ })->get();
+
+ return $this->success(TagResource::collection($list));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Exporter/OrderExporter.php b/modules/Mall/Http/Exporter/OrderExporter.php
new file mode 100644
index 0000000..9f0f571
--- /dev/null
+++ b/modules/Mall/Http/Exporter/OrderExporter.php
@@ -0,0 +1,49 @@
+headings;
+
+ $data = $this->getData();
+ if (count($data)) {
+ foreach ($data as $v) {
+ $rows[] = [
+ $v['order_no'],
+ $v['shop']['name'],
+ $v['user']['username'],
+ $v['amount'],
+ $v['freight'],
+ $v['items'][0]['qty'],
+ $v['total'],
+ $v['state'],
+ $v['type'],
+ $v['paid_at'],
+ $v['created_at'],
+ ];
+ }
+ }
+
+ // 数据格式化
+ $export = new OrderFromArray($rows);
+
+ // 导出
+ return Excel::download($export, $this->fileName)->send();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Exporter/OrderFromArray.php b/modules/Mall/Http/Exporter/OrderFromArray.php
new file mode 100644
index 0000000..c86c6b9
--- /dev/null
+++ b/modules/Mall/Http/Exporter/OrderFromArray.php
@@ -0,0 +1,27 @@
+order = $order;
+ }
+
+ public function array(): array
+ {
+ return $this->order;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Middleware/Authenticate.php b/modules/Mall/Http/Middleware/Authenticate.php
new file mode 100644
index 0000000..e9c8b24
--- /dev/null
+++ b/modules/Mall/Http/Middleware/Authenticate.php
@@ -0,0 +1,35 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ * @return mixed
+ * @throws \Exception
+ */
+ public function handle(Request $request, Closure $next)
+ {
+ $shop = Shop::byUser(Api::user())->first();
+
+ if ($shop) {
+ $request->merge(['shop' => $shop]);
+ } else {
+ throw new \Exception('Do not have a shop');
+ }
+
+ return $next($request);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Middleware/ShopOwner.php b/modules/Mall/Http/Middleware/ShopOwner.php
new file mode 100644
index 0000000..1ea828f
--- /dev/null
+++ b/modules/Mall/Http/Middleware/ShopOwner.php
@@ -0,0 +1,24 @@
+
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ * @return mixed
+ */
+ public function handle(Request $request, Closure $next)
+ {
+ return $next($request);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Requests/Address/AddressRequest.php b/modules/Mall/Http/Requests/Address/AddressRequest.php
new file mode 100644
index 0000000..5189b75
--- /dev/null
+++ b/modules/Mall/Http/Requests/Address/AddressRequest.php
@@ -0,0 +1,58 @@
+
+ * @Date : 2020/11/5 5:55 下午
+ * @return array
+ */
+ public function rules(): array
+ {
+ return [
+ 'name' => 'required|min:2|max:16',
+ 'mobile' => ['required', 'numeric', 'regex:/^1[3-9]\d{9}$/'],
+ 'province_id' => ['required', 'numeric', new ProvinceRule()],
+ 'city_id' => ['required', 'numeric', new CityRule()],
+ 'district_id' => ['required', 'numeric', new DistrictRule()],
+ 'address' => 'required|min:2|max:255',
+ ];
+ }
+
+ /**
+ * Notes: 验证错误提示信息
+ * @Author:
+ * @Date : 2020/11/6 9:33 上午
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'name.required' => '收件人必须填写',
+ 'name.min' => '收件人姓名至少:min位字符',
+ 'name.max' => '收件人姓名最多:max位字符',
+ 'mobile.required' => '手机号码必须填写',
+ 'mobile.numeric' => '手机号码必须是数字',
+ 'mobile.regex' => '手机号码格式不正确',
+ 'province_id.required' => '所在省份必须填写',
+ 'province_id.numeric' => '所在省份格式不正确',
+ 'city_id.required' => '所在城市必须填写',
+ 'city_id.numeric' => '所在城市格式不正确',
+ 'district_id.required' => '所在区县必须填写',
+ 'district_id.numeric' => '所在区县格式不正确',
+ 'address.required' => '详细地址必须填写',
+ 'address.min' => '详细地址至少:min位字符',
+ 'address.max' => '详细地址最多:max位字符',
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Requests/Cart/CartRequest.php b/modules/Mall/Http/Requests/Cart/CartRequest.php
new file mode 100644
index 0000000..15553c5
--- /dev/null
+++ b/modules/Mall/Http/Requests/Cart/CartRequest.php
@@ -0,0 +1,24 @@
+ 'nullable|integer',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'qty.integer' => '商品数量必须是整数',
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Requests/OrderBuy/OrderBuyRequest.php b/modules/Mall/Http/Requests/OrderBuy/OrderBuyRequest.php
new file mode 100644
index 0000000..2f31343
--- /dev/null
+++ b/modules/Mall/Http/Requests/OrderBuy/OrderBuyRequest.php
@@ -0,0 +1,30 @@
+ 'required|integer',
+ 'qty' => 'nullable|integer',
+ 'address_id' => ['nullable', 'required', 'integer'],
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'goods_sku_id.required' => '缺少商品规格id',
+ 'goods_sku_id.integer' => '商品规格id必须是数字',
+ 'qty.integer' => '数量必须是数字',
+ 'address_id.required' => '缺少收货地址',
+ 'address_id.integer' => '收货地址必须是数字',
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Requests/Shop/ShopRequest.php b/modules/Mall/Http/Requests/Shop/ShopRequest.php
new file mode 100644
index 0000000..4271444
--- /dev/null
+++ b/modules/Mall/Http/Requests/Shop/ShopRequest.php
@@ -0,0 +1,47 @@
+ 'required|unique:mall_shops',
+ 'mobile' => 'required|phone:CN|unique:mall_shops',
+ 'description' => 'required',
+ 'province_id' => ['required', 'integer', new ProvinceRule()],
+ 'city_id' => ['required', 'integer', new CityRule()],
+ 'district_id' => ['required', 'integer', new DistrictRule()],
+ 'address' => 'required',
+ 'cover' => 'required',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'name.required' => '店铺名称必须填写',
+ 'name.unique' => '店铺名称不能重复',
+ 'mobile.required' => '联系方式必须填写',
+ 'mobile.phone' => '联系方式格式不正确',
+ 'mobile.unique' => '联系方式不能重复',
+ 'description.required' => '店铺简介必须填写',
+ 'province_id.required' => '店铺所在省份必须选择',
+ 'province_id.integer' => '店铺所在省份格式不正确',
+ 'city_id.required' => '店铺所在城市必须选择',
+ 'city_id.integer' => '店铺所在城市格式不正确',
+ 'district_id.required' => '店铺所在地区必须选择',
+ 'district_id.integer' => '店铺所在地区格式不正确',
+ 'address.required' => '店铺详细地址必须填写',
+ 'cover.required' => '店铺封面图必须上传',
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Activity/ActivityBaseResource.php b/modules/Mall/Http/Resources/Api/Activity/ActivityBaseResource.php
new file mode 100644
index 0000000..c2ac3a3
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Activity/ActivityBaseResource.php
@@ -0,0 +1,24 @@
+ $this->id,
+ 'cover' => $this->cover_url,
+ 'title' => $this->title,
+ 'description' => $this->description,
+ 'created_at' => (string)$this->created_at,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Activity/ActivityCollection.php b/modules/Mall/Http/Resources/Api/Activity/ActivityCollection.php
new file mode 100644
index 0000000..0b355cc
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Activity/ActivityCollection.php
@@ -0,0 +1,20 @@
+ $this->collection->map(function ($activity) {
+ return new ActivityBaseResource($activity);
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Activity/ActivityResource.php b/modules/Mall/Http/Resources/Api/Activity/ActivityResource.php
new file mode 100644
index 0000000..8bb3daf
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Activity/ActivityResource.php
@@ -0,0 +1,25 @@
+ $this->id,
+ 'cover' => $this->cover_url,
+ 'title' => $this->title,
+ 'description' => $this->description,
+ 'content' => $this->content,
+ 'created_at' => $this->created_at->format('Y-m-d'),
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Address/AddressResource.php b/modules/Mall/Http/Resources/Api/Address/AddressResource.php
new file mode 100644
index 0000000..c3131cd
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Address/AddressResource.php
@@ -0,0 +1,28 @@
+ $this->id,
+ 'name' => $this->name,
+ 'mobile' => $this->mobile,
+ 'province' => new RegionResource($this->province),
+ 'city' => new RegionResource($this->city),
+ 'district' => new RegionResource($this->district),
+ 'address' => $this->address,
+ 'full_address' => $this->getFullAddress(),
+ 'default' => $this->is_default,
+ 'created_at' => (string) $this->created_at,
+ 'updated_at' => (string) $this->updated_at,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Banner/BannerResource.php b/modules/Mall/Http/Resources/Api/Banner/BannerResource.php
new file mode 100644
index 0000000..07e4277
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Banner/BannerResource.php
@@ -0,0 +1,19 @@
+ $this->title,
+ 'cover' => $this->cover_url,
+ 'url' => $this->linker,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Category/CategoryBaseResource.php b/modules/Mall/Http/Resources/Api/Category/CategoryBaseResource.php
new file mode 100644
index 0000000..448a64c
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Category/CategoryBaseResource.php
@@ -0,0 +1,19 @@
+ $this->id,
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Category/CategoryResource.php b/modules/Mall/Http/Resources/Api/Category/CategoryResource.php
new file mode 100644
index 0000000..3291479
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Category/CategoryResource.php
@@ -0,0 +1,22 @@
+ $this->id,
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ 'children' => $this->when(!$this->children()->shown()->get()->isEmpty(), function () {
+ return self::collection($this->children()->shown()->get());
+ }),
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Goods/BrandResource.php b/modules/Mall/Http/Resources/Api/Goods/BrandResource.php
new file mode 100644
index 0000000..b02a01f
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/BrandResource.php
@@ -0,0 +1,18 @@
+ $this->id,
+ 'name' => $this->name,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Goods/CartResource.php b/modules/Mall/Http/Resources/Api/Goods/CartResource.php
new file mode 100644
index 0000000..a0bfdb3
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/CartResource.php
@@ -0,0 +1,24 @@
+ $this->id,
+ 'sku_id' => $this->sku_id,
+ 'goods_id' => $this->sku->goods_id,
+ 'qty' => $this->qty,
+ 'max_qty' => $this->sku->stock,
+ 'price' => (float) $this->sku->price,
+ 'cover' => $this->sku->cover_url,
+ 'name' => $this->sku->goods->name,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Goods/DetailResource.php b/modules/Mall/Http/Resources/Api/Goods/DetailResource.php
new file mode 100644
index 0000000..c503730
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/DetailResource.php
@@ -0,0 +1,24 @@
+ $this->id,
+ 'sku_id' => $this->sku_id,
+ 'qty' => $this->qty,
+ 'max_qty' => $this->sku->stock,
+ 'price' => (float) $this->sku->price,
+ 'cover' => ($this->sku->goods->type === Goods::TYPE_SINGLE) ? $this->sku->goods->cover_url : $this->sku->cover_url,
+ 'name' => $this->sku->goods->name,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php b/modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php
new file mode 100644
index 0000000..081ae16
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php
@@ -0,0 +1,33 @@
+ $this->id,
+ 'shop' => new ShopBaseInfoResource($this->shop),
+ 'is_self' => (bool) $this->shop->is_self,
+ 'name' => $this->name,
+ 'description' => $this->description ?? '',
+ 'cover' => $this->cover_url,
+ 'tags' => TagResource::collection($this->tags),
+ 'original_price' => (float) $this->original_price,
+ 'price' => [
+ 'show' => $this->price,
+ 'score' => $this->score,
+ 'price_min' => $this->price_min,
+ 'price_max' => $this->price_max,
+ ],
+ 'clicks' => $this->clicks,
+ 'sales' => $this->sales,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Goods/GoodsCollection.php b/modules/Mall/Http/Resources/Api/Goods/GoodsCollection.php
new file mode 100644
index 0000000..3559c1e
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/GoodsCollection.php
@@ -0,0 +1,20 @@
+ $this->collection->map(function ($goods) {
+ return new GoodsBaseResource($goods);
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Goods/GoodsResource.php b/modules/Mall/Http/Resources/Api/Goods/GoodsResource.php
new file mode 100644
index 0000000..92b8ffd
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/GoodsResource.php
@@ -0,0 +1,50 @@
+ $this->id,
+ 'shop' => new ShopBaseInfoResource($this->shop),
+ 'brand' => new BrandResource($this->brand),
+ 'category' => new CategoryBaseResource($this->category),
+ 'tags' => TagResource::collection($this->tags),
+
+ 'type' => $this->type,
+ 'pay_type' => $this->pay_type,
+ 'clicks' => $this->clicks,
+ 'sales' => $this->sales,
+
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ 'pictures' => $this->pictures_url,
+ 'description' => $this->description ?? '',
+ 'content' => $this->content,
+ 'created_at' => (string) $this->created_at,
+ 'original_price' => (float) $this->original_price,
+ 'price' => [
+ 'show' => $this->price,
+ 'score' => $this->score,
+ 'price_min' => $this->price_min,
+ 'price_max' => $this->price_max,
+ ],
+ 'specs' => SpecResource::collection($this->specs),
+ 'skus' => SkuResource::collection($this->skus),
+ 'canPick' => $user && $user->canPick(),
+ 'hasCase' => $user && (bool) $user->case,
+ 'hasLogin' => (bool) $user,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Goods/SkuBaseResource.php b/modules/Mall/Http/Resources/Api/Goods/SkuBaseResource.php
new file mode 100644
index 0000000..c9c228e
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/SkuBaseResource.php
@@ -0,0 +1,19 @@
+ $this->id,
+ 'goods_name' => $this->goods->name,
+ 'cover' => $this->cover_url,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Goods/SkuResource.php b/modules/Mall/Http/Resources/Api/Goods/SkuResource.php
new file mode 100644
index 0000000..add095f
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/SkuResource.php
@@ -0,0 +1,26 @@
+ $this->id,
+ 'goods_id' => $this->goods_id,
+ 'goods_name' => $this->goods->name,
+ 'cover' => $this->cover_url,
+ 'price' => (float) $this->price,
+ 'score' => (float) $this->score,
+ 'assets' => (float) $this->assets,
+ 'unit' => $this->unit ?? '',
+ 'stock' => $this->stock,
+ 'weight' => $this->weight,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Goods/SpecResource.php b/modules/Mall/Http/Resources/Api/Goods/SpecResource.php
new file mode 100644
index 0000000..b815d6c
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/SpecResource.php
@@ -0,0 +1,19 @@
+ $this->id,
+ 'name' => $this->name,
+ 'values' => SpecValueResource::collection($this->values),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Goods/SpecValueResource.php b/modules/Mall/Http/Resources/Api/Goods/SpecValueResource.php
new file mode 100644
index 0000000..947615d
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/SpecValueResource.php
@@ -0,0 +1,18 @@
+ $this->id,
+ 'value' => $this->value,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Goods/TagResource.php b/modules/Mall/Http/Resources/Api/Goods/TagResource.php
new file mode 100644
index 0000000..3fb6fd5
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Goods/TagResource.php
@@ -0,0 +1,18 @@
+ $this->id,
+ 'name' => $this->name,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Order/LogisticResource.php b/modules/Mall/Http/Resources/Api/Order/LogisticResource.php
new file mode 100644
index 0000000..3b76110
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/LogisticResource.php
@@ -0,0 +1,21 @@
+ $this->time,
+ 'context' => $this->context,
+ 'ftime' => $this->ftime,
+ 'areaName' => $this->areaName ?? "",
+ 'status' => $this->status,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Order/OrderCollection.php b/modules/Mall/Http/Resources/Api/Order/OrderCollection.php
new file mode 100644
index 0000000..e7f6dd9
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/OrderCollection.php
@@ -0,0 +1,34 @@
+ $this->collection->map(function ($order) {
+ return [
+ 'order_no' => $order->order_no,
+ 'shop' => new ShopBaseInfoResource($order->shop),
+ 'amount' => $order->amount,
+ 'freight' => $order->freight,
+ 'total' => $order->total,
+ 'type' => $order->type_text,
+ 'state' => $order->state_text,
+ 'can' => $order->getCanAction(),
+ 'is_logistic_show' => $order->is_logistic_show,
+ 'created_at' => (string) $order->created_at,
+ 'items' => OrderItemResource::collection($order->items),
+ ];
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php b/modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php
new file mode 100644
index 0000000..e5e7e4b
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php
@@ -0,0 +1,22 @@
+ $this->id,
+ 'name' => $this->name,
+ 'mobile' => $this->mobile,
+ 'express_name' => $this->express_id ? $this->express->name : '',
+ 'express_no' => $this->express_no,
+ 'full_address' => $this->getFullAddress(),
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Order/OrderItemResource.php b/modules/Mall/Http/Resources/Api/Order/OrderItemResource.php
new file mode 100644
index 0000000..3cedf7b
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/OrderItemResource.php
@@ -0,0 +1,26 @@
+ $this->id,
+ 'qty' => $this->qty,
+ 'price' => $this->price,
+ 'total' => $this->total,
+ 'can' => [
+ 'refund' => $this->canRefund(),
+ ],
+ 'sku' => $this->source,
+ 'refund_status_text' => $this->refund_status_text,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Order/OrderLogisticResource.php b/modules/Mall/Http/Resources/Api/Order/OrderLogisticResource.php
new file mode 100644
index 0000000..abc96c4
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/OrderLogisticResource.php
@@ -0,0 +1,22 @@
+ $this->express_id,
+ 'express_no' => $this->express_no,
+ 'order_state' => $this->order->state_text,
+ 'logistic_name' => $this->express->name,
+ 'logistic_cover' => $this->express->cover_url,
+ 'address' => $this->getFullAddress(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Order/OrderResource.php b/modules/Mall/Http/Resources/Api/Order/OrderResource.php
new file mode 100644
index 0000000..bd296e7
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/OrderResource.php
@@ -0,0 +1,32 @@
+ $this->order_no,
+ 'shop' => new ShopBaseInfoResource($this->shop),
+ 'amount' => $this->amount,
+ 'freight' => $this->freight,
+ 'total' => $this->total,
+ 'type' => $this->type_text,
+ 'state' => $this->state_text,
+ 'can' => $this->getCanAction(),
+ 'is_logistic_show' => $this->is_logistic_show,
+ 'items' => OrderItemResource::collection($this->items),
+ 'express' => new OrderExpressResource($this->express),
+ 'remark' => (string) $this->remark,
+ 'expired_at' => (string) $this->expired_at,
+ 'paid_at' => (string) $this->paid_at,
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Order/RefundCollection.php b/modules/Mall/Http/Resources/Api/Order/RefundCollection.php
new file mode 100644
index 0000000..05439a2
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/RefundCollection.php
@@ -0,0 +1,37 @@
+ $this->collection->map(function ($refund) {
+ return [
+ 'refund_no' => $refund->refund_no,
+ 'order_no' => $refund->order->order_no,
+ 'shop' => new ShopBaseInfoResource($refund->shop),
+ 'refund_total' => $refund->refund_total,
+ 'actual_total' => $refund->actual_total,
+ 'state' => [
+ 'state' => $refund->state,
+ 'text' => $refund->status_text,
+ 'remark' => $refund->state_text,
+ ],
+ 'can' => collect($refund->transitions())->map(function ($item) {
+ return $item->getName();
+ }),
+ 'created_at' => (string) $refund->created_at,
+ 'items' => RefundItemsResource::collection($refund->items),
+ ];
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Order/RefundItemsResource.php b/modules/Mall/Http/Resources/Api/Order/RefundItemsResource.php
new file mode 100644
index 0000000..40c8188
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/RefundItemsResource.php
@@ -0,0 +1,22 @@
+ $this->id,
+ 'price' => $this->price,
+ 'goods_id' => $this->source ? $this->source['goods_id'] : '',
+ 'cover' => $this->source ? $this->source['cover'] : '',
+ 'goods_name' => $this->source ? $this->source['goods_name'] : '',
+ 'qty' => $this->qty,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Order/RefundLogResource.php b/modules/Mall/Http/Resources/Api/Order/RefundLogResource.php
new file mode 100644
index 0000000..f3d6198
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/RefundLogResource.php
@@ -0,0 +1,22 @@
+ $this->id,
+ 'title' => $this->title,
+ 'remark' => $this->remark,
+ 'state_text' => $this->state_text,
+ 'isMy' => $this->refund->user->is($this->userable),
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Order/RefundResource.php b/modules/Mall/Http/Resources/Api/Order/RefundResource.php
new file mode 100644
index 0000000..18a0749
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Order/RefundResource.php
@@ -0,0 +1,40 @@
+ $this->id,
+ 'refund_no' => $this->refund_no,
+ 'order_no' => $this->order->order_no,
+ 'state' => [
+ 'state' => $this->state,
+ 'text' => $this->status_text,
+ 'remark' => $this->state_text,
+ ],
+ 'refund_total' => $this->refund_total,
+ 'actual_total' => $this->actual_total,
+ 'order' => [
+ 'order_id' => $this->order->id,
+ 'order_no' => $this->order->order_no,
+ 'paid_at' => $this->order->paid_at ? $this->order->paid_at->toDateTimeString() : '',
+ 'deliver_at' => $this->order->express->deliver_at ? $this->order->express->deliver_at->toDateTimeString() : '',
+ 'receive_at' => $this->order->express->receive_at ? $this->order->express->receive_at->toDateTimeString() : '',
+ ],
+ 'items' => RefundItemsResource::collection($this->items),
+ 'can' => collect($this->transitions())->map(function ($item) {
+ return $item->getName();
+ }),
+ 'log' => new RefundLogResource($this->logs()->oldest()->first()),
+ 'created_at' => $this->created_at->toDateTimeString(),
+ 'updated_at' => $this->updated_at->toDateTimeString(),
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Region/RegionResource.php b/modules/Mall/Http/Resources/Api/Region/RegionResource.php
new file mode 100644
index 0000000..0d14eef
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Region/RegionResource.php
@@ -0,0 +1,18 @@
+ $this->id,
+ 'name' => $this->name,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Shop/ExpressResource.php b/modules/Mall/Http/Resources/Api/Shop/ExpressResource.php
new file mode 100644
index 0000000..79c6023
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Shop/ExpressResource.php
@@ -0,0 +1,19 @@
+ $this->id,
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopBaseInfoResource.php b/modules/Mall/Http/Resources/Api/Shop/ShopBaseInfoResource.php
new file mode 100644
index 0000000..0e53054
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Shop/ShopBaseInfoResource.php
@@ -0,0 +1,20 @@
+ $this->id,
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ 'is_self' => (bool) $this->is_self,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopCollection.php b/modules/Mall/Http/Resources/Api/Shop/ShopCollection.php
new file mode 100644
index 0000000..b39e023
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Shop/ShopCollection.php
@@ -0,0 +1,27 @@
+ $this->collection->map(function ($shop) {
+ return [
+ 'shop_id' => $shop->id,
+ 'name' => $shop->name,
+ 'is_self' => (bool) $shop->is_self,
+ 'mobile' => $shop->mobile,
+ 'description' => $shop->description,
+ 'cover' => $shop->cover_url,
+ ];
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopEditResource.php b/modules/Mall/Http/Resources/Api/Shop/ShopEditResource.php
new file mode 100644
index 0000000..9e6acbf
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Shop/ShopEditResource.php
@@ -0,0 +1,35 @@
+ $this->id,
+ 'name' => $this->name,
+ 'mobile' => $this->mobile,
+ 'cover' => $this->cover_url,
+ 'description' => $this->description,
+ 'province' => [
+ 'id' => $this->province_id,
+ 'name' => $this->province->name,
+ ],
+ 'city' => [
+ 'id' => $this->city_id,
+ 'name' => $this->city->name,
+ ],
+ 'district' => [
+ 'id' => $this->district_id,
+ 'name' => $this->district->name,
+ ],
+ 'address' => $this->address ?? '',
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Shop/ShopResource.php b/modules/Mall/Http/Resources/Api/Shop/ShopResource.php
new file mode 100644
index 0000000..a749765
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Shop/ShopResource.php
@@ -0,0 +1,35 @@
+ $this->id,
+ 'name' => $this->name,
+ 'is_self' => (bool) $this->is_self,
+ 'description' => $this->description,
+ 'user' => [
+ 'user_id' => $this->user->id,
+ 'nickname' => $this->user->info->nickname,
+ 'avatar' => $this->user->info->avatar ?? '',
+ ],
+ 'mobile' => $this->mobile,
+ 'cover' => $this->cover_url,
+ 'goodsCount' => $this->goods()->count(),
+ 'region' => [
+ 'province' => $this->province->name,
+ 'city' => $this->city->name,
+ 'district' => $this->district->name,
+ 'address' => $this->address,
+ ],
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Http/Resources/Api/Video/VideoResource.php b/modules/Mall/Http/Resources/Api/Video/VideoResource.php
new file mode 100644
index 0000000..6026180
--- /dev/null
+++ b/modules/Mall/Http/Resources/Api/Video/VideoResource.php
@@ -0,0 +1,20 @@
+ $this->id,
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ 'path' => $this->path_url,
+ ];
+ }
+
+}
diff --git a/modules/Mall/Jobs/CloseOrder.php b/modules/Mall/Jobs/CloseOrder.php
new file mode 100644
index 0000000..1c67226
--- /dev/null
+++ b/modules/Mall/Jobs/CloseOrder.php
@@ -0,0 +1,67 @@
+order = $order;
+ }
+
+ public function handle()
+ {
+ $order = $this->order->fresh();
+ if ($order->canCancel()) {
+ $order->cancel();
+ foreach ($order->items as $item) {
+ if ($item->source['deduct_stock_type'] == Goods::DEDUCT_STOCK_ORDER) {
+ $item->item->addStock($item->qty); //加库存
+ }
+ }
+ }
+ }
+
+}
diff --git a/modules/Mall/Listeners/OrderCreatedListener.php b/modules/Mall/Listeners/OrderCreatedListener.php
new file mode 100644
index 0000000..df62a84
--- /dev/null
+++ b/modules/Mall/Listeners/OrderCreatedListener.php
@@ -0,0 +1,24 @@
+order);
+ }
+
+}
diff --git a/modules/Mall/Listeners/OrderPaidListener.php b/modules/Mall/Listeners/OrderPaidListener.php
new file mode 100644
index 0000000..57515a3
--- /dev/null
+++ b/modules/Mall/Listeners/OrderPaidListener.php
@@ -0,0 +1,42 @@
+order->refresh();
+ //增加销量
+ foreach ($order->items as $item) {
+ if ($item->source['deduct_stock_type'] == Goods::DEDUCT_STOCK_PAID) {
+ $item->item->deductStock($item->qty, $order->user_id);
+ }
+ $item->item->addSold($item->qty); //加销量
+ }
+
+ //扣除积分
+ if ($order->type == Order::TYPE_SCORE) {
+ $order->user->account->rule('score_buy', -$order->total, false, [
+ 'order_no' => $order->order_no,
+ ]);
+ }
+
+ }
+
+}
diff --git a/modules/Mall/Listeners/RefundAgreedListener.php b/modules/Mall/Listeners/RefundAgreedListener.php
new file mode 100644
index 0000000..8e284db
--- /dev/null
+++ b/modules/Mall/Listeners/RefundAgreedListener.php
@@ -0,0 +1,39 @@
+refund->refresh();
+
+ foreach ($refund->order->items as $item) {
+ $item->item->addStock($item->qty); //加库存
+ $item->item->deductSold($item->qty); //减销量
+ }
+ } catch (Exception $exception) {
+ Log::error($exception->getMessage());
+ }
+ }
+
+}
diff --git a/modules/Mall/Listeners/RefundCompletedListener.php b/modules/Mall/Listeners/RefundCompletedListener.php
new file mode 100644
index 0000000..e17de0b
--- /dev/null
+++ b/modules/Mall/Listeners/RefundCompletedListener.php
@@ -0,0 +1,29 @@
+refund->refresh();
+ $order = $refund->order->refresh();
+
+ if ($order->refund_items_count == $order->items()->count()) {
+ $order->completed();
+ }
+ }
+
+}
diff --git a/modules/Mall/Mall.php b/modules/Mall/Mall.php
new file mode 100644
index 0000000..ee4065c
--- /dev/null
+++ b/modules/Mall/Mall.php
@@ -0,0 +1,151 @@
+
+ */
+ public static function install()
+ {
+ Artisan::call('migrate', [
+ '--path' => 'modules/Mall/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ *
+ * @Date : 2021/3/12 11:35 上午
+ * @Author : < Jason.C >
+ */
+ public static function uninstall()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $mains = $menu::where('title', self::$mainTitle)->get();
+
+ foreach ($mains as $main) {
+ $main->delete();
+ }
+ }
+
+ protected static function createAdminMenu()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 20,
+ 'title' => self::$mainTitle,
+ 'icon' => 'fa-institution',
+ ]);
+
+ $main->children()->createMany([
+ [
+ 'order' => 0,
+ 'title' => '商城看板',
+ 'icon' => 'fa-bar-chart',
+ 'uri' => 'mall/dashboard',
+ ], [
+ 'order' => 0,
+ 'title' => '活动管理',
+ 'icon' => 'fa-bar-chart',
+ 'uri' => 'mall/activities',
+ ],
+ // [
+ // 'order' => 1,
+ // 'title' => '店铺管理',
+ // 'icon' => 'fa-puzzle-piece',
+ // 'uri' => 'mall/shops',
+ // ],
+ // [
+ // 'order' => 1,
+ // 'title' => '职位管理',
+ // 'icon' => 'fa-joomla',
+ // 'uri' => 'mall/jobs',
+ // ],
+ [
+ 'order' => 2,
+ 'title' => '商品列表',
+ 'icon' => 'fa-archive',
+ 'uri' => 'mall/goods',
+ ], [
+ 'order' => 3,
+ 'title' => '商品分类',
+ 'icon' => 'fa-bars',
+ 'uri' => 'mall/categories',
+ ],
+ // [
+ // 'order' => 4,
+ // 'title' => '商品标签',
+ // 'icon' => 'fa-tags',
+ // 'uri' => 'mall/tags',
+ // ],
+ [
+ 'order' => 5,
+ 'title' => '品牌管理',
+ 'icon' => 'fa-copyright',
+ 'uri' => 'mall/brands',
+ ], [
+ 'order' => 6,
+ 'title' => '订单管理',
+ 'icon' => 'fa-bars',
+ 'uri' => 'mall/orders',
+ ], [
+ 'order' => 6,
+ 'title' => '提货订单',
+ 'icon' => 'fa-bars',
+ 'uri' => 'mall/stock_orders',
+ ], [
+ 'order' => 7,
+ 'title' => '退款订单',
+ 'icon' => 'fa-bars',
+ 'uri' => 'mall/refunds',
+ ], [
+ 'order' => 8,
+ 'title' => '物流公司',
+ 'icon' => 'fa-cab',
+ 'uri' => 'mall/expresses',
+ ], [
+ 'order' => 9,
+ 'title' => '运费模板',
+ 'icon' => 'fa-send',
+ 'uri' => 'mall/deliveries',
+ ], [
+ 'order' => 10,
+ 'title' => '轮播图管理',
+ 'icon' => 'fa-th-large',
+ 'uri' => 'mall/banners',
+ ], [
+ 'order' => 11,
+ 'title' => '退货理由',
+ 'icon' => 'fa-th-large',
+ 'uri' => 'mall/reasons',
+ ], [
+ 'order' => 12,
+ 'title' => '收货地址',
+ 'icon' => 'fa-th-large',
+ 'uri' => 'mall/addresses',
+ ], [
+ 'order' => 13,
+ 'title' => '视频管理',
+ 'icon' => 'fa-th-large',
+ 'uri' => 'mall/videos',
+ ],
+ ]
+ );
+ }
+
+}
diff --git a/modules/Mall/Models/Activity.php b/modules/Mall/Models/Activity.php
new file mode 100644
index 0000000..71506eb
--- /dev/null
+++ b/modules/Mall/Models/Activity.php
@@ -0,0 +1,20 @@
+ '关闭',
+ 1 => '正常',
+ ];
+ protected $table = 'mall_activities';
+
+}
diff --git a/modules/Mall/Models/Address.php b/modules/Mall/Models/Address.php
new file mode 100644
index 0000000..3c835b6
--- /dev/null
+++ b/modules/Mall/Models/Address.php
@@ -0,0 +1,86 @@
+ 'boolean',
+ ];
+
+ /**
+ * 不参与版本记录的字段
+ * @var array|string[]
+ */
+ protected array $dontVersionable = ['updated_at'];
+
+ /**
+ * @var array
+ */
+ protected $guarded = [
+ 'created_at',
+ 'updated_at',
+ ];
+
+ public static function boot()
+ {
+ parent::boot();
+ /**
+ * 如果当前地址为默认地址,取消其他地址的默认地址设置
+ */
+ self::saved(function ($model) {
+ if ($model->is_default && $model->id) {
+ Address::where('id', '<>', $model->id)
+ ->where('user_id', $model->user_id)
+ ->where('is_default', 1)
+ ->update(['is_default' => 0]);
+ }
+// else {
+// Address::where('user_id', $model->user_id)
+// ->where('is_default', 1)
+// ->update(['is_default' => 0]);
+// }
+ });
+ }
+
+ /**
+ * Notes: 保存默认地址时转换格式,非0的都转换成0
+ * @Author:
+ * @Date : 2020/11/5 5:23 下午
+ * @param $value
+ */
+ protected function setIsDefaultAttribute($value): void
+ {
+ if (strtolower($value) === 'false' || $value === '0') {
+ $this->attributes['is_default'] = 0;
+ } else {
+ $this->attributes['is_default'] = !!$value;
+ }
+ }
+
+ /**
+ * Notes : 将地址设置为默认地址
+ * @Date : 2021/4/23 1:34 下午
+ * @Author : < Jason.C >
+ */
+ public function setDefault(): void
+ {
+ $this->is_default = 1;
+ $this->save();
+ }
+
+}
diff --git a/modules/Mall/Models/Banner.php b/modules/Mall/Models/Banner.php
new file mode 100644
index 0000000..89974d5
--- /dev/null
+++ b/modules/Mall/Models/Banner.php
@@ -0,0 +1,33 @@
+ '首页顶部',
+ self::POSITION_INDEX_CENTER => '体检官介绍',
+ ];
+
+ protected $table = 'mall_banners';
+
+}
diff --git a/modules/Mall/Models/Brand.php b/modules/Mall/Models/Brand.php
new file mode 100644
index 0000000..1d420ec
--- /dev/null
+++ b/modules/Mall/Models/Brand.php
@@ -0,0 +1,39 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function goods(): HasMany
+ {
+ return $this->hasMany(Goods::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Cart.php b/modules/Mall/Models/Cart.php
new file mode 100644
index 0000000..5edc646
--- /dev/null
+++ b/modules/Mall/Models/Cart.php
@@ -0,0 +1,29 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function sku(): BelongsTo
+ {
+ return $this->belongsTo(GoodsSku::class, 'sku_id');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Category.php b/modules/Mall/Models/Category.php
new file mode 100644
index 0000000..523b4b0
--- /dev/null
+++ b/modules/Mall/Models/Category.php
@@ -0,0 +1,56 @@
+setTitleColumn('name');
+ }
+
+ /**
+ * Notes : 分类商品
+ * @Date : 2021/5/12 9:49 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function goods(): HasMany
+ {
+ return $this->hasMany(Goods::class);
+ }
+
+}
diff --git a/modules/Mall/Models/Delivery.php b/modules/Mall/Models/Delivery.php
new file mode 100644
index 0000000..3834632
--- /dev/null
+++ b/modules/Mall/Models/Delivery.php
@@ -0,0 +1,39 @@
+ '按数量',
+ self::TYPE_BY_WEIGHT => '按重量',
+ ];
+
+ /**
+ * Notes : 模板对应的规则
+ * @Date : 2021/3/15 1:11 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function rules(): HasMany
+ {
+ return $this->hasMany(DeliveryRule::class);
+ }
+
+}
diff --git a/modules/Mall/Models/DeliveryRule.php b/modules/Mall/Models/DeliveryRule.php
new file mode 100644
index 0000000..cc85000
--- /dev/null
+++ b/modules/Mall/Models/DeliveryRule.php
@@ -0,0 +1,31 @@
+ 'json',
+ ];
+
+ /**
+ * Notes : 隶属的模板
+ * @Date : 2021/3/15 1:12 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function delivery(): BelongsTo
+ {
+ return $this->belongsTo(Delivery::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Express.php b/modules/Mall/Models/Express.php
new file mode 100644
index 0000000..c264667
--- /dev/null
+++ b/modules/Mall/Models/Express.php
@@ -0,0 +1,32 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function shops(): BelongsToMany
+ {
+ return $this->belongsToMany(Shop::class, 'mall_shop_express')
+ ->withTimestamps();
+ }
+
+}
diff --git a/modules/Mall/Models/Goods.php b/modules/Mall/Models/Goods.php
new file mode 100644
index 0000000..8b22036
--- /dev/null
+++ b/modules/Mall/Models/Goods.php
@@ -0,0 +1,267 @@
+ '单规格',
+ // self::TYPE_MULTIPLE => '多规格',
+ ];
+ /**
+ * 状态
+ */
+ const STATUS_AUDIT = 0;
+ const STATUS_UP = 1;
+ const STATUS_REJECT = 2;
+ const STATUS_DOWN = 3;
+ const STATUS_MAP = [
+ self::STATUS_AUDIT => '审核中',
+ self::STATUS_UP => '上架',
+ self::STATUS_REJECT => '驳回',
+ self::STATUS_DOWN => '下架',
+ ];
+ /**
+ * 库存的扣减方式
+ */
+ const DEDUCT_STOCK_ORDER = 1;
+ const DEDUCT_STOCK_PAID = 2;
+ const DEDUCT_STOCK_MAP = [
+ self::DEDUCT_STOCK_ORDER => '下单减库存',
+ self::DEDUCT_STOCK_PAID => '付款减库存',
+ ];
+ /**
+ * 支付方式
+ */
+ const PAY_TYPE_ONLINE = 1;
+ const PAY_TYPE_OFFLINE = 2;
+ const PAY_TYPE_ALL = 3;
+ const PAY_TYPE_MAP = [
+ self::PAY_TYPE_ONLINE => '在线支付',
+ self::PAY_TYPE_OFFLINE => '货到付款',
+ self::PAY_TYPE_ALL => '在线支付/货到付款',
+ ];
+
+ /**
+ * 商品类型
+ */
+ const CHANNEL_NORMAL = 1;
+ const CHANNEL_SCORE = 2;
+ const CHANNEL_FREE = 3;
+
+ const Channels = [
+ self::CHANNEL_NORMAL => '正常',
+ self::CHANNEL_SCORE => '积分兑换',
+ self::CHANNEL_FREE => '赠送',
+ ];
+
+ /**
+ * 展示列表,从数据库查询的字段
+ */
+ const LIST_SELECT_FIELDS = [
+ 'id',
+ 'name',
+ 'description',
+ 'cover',
+ 'shop_id',
+ 'original_price',
+ 'sales',
+ 'clicks',
+ ];
+
+ protected $table = 'mall_goods';
+
+ protected $casts = [
+ 'pictures' => 'json',
+ ];
+
+ protected $with = [
+ 'specs',
+ 'skus',
+ ];
+
+ /**
+ * 不参与版本记录的字段
+ *
+ * @var array|string[]
+ */
+ protected array $dontVersionable = ['clicks', 'updated_at'];
+
+ /**
+ * Notes : 商品分类
+ *
+ * @Date : 2021/5/12 9:48 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function category(): BelongsTo
+ {
+ return $this->belongsTo(Category::class);
+ }
+
+ /**
+ * Notes : 标签
+ *
+ * @Date : 2021/5/12 10:14 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function tags(): BelongsToMany
+ {
+ return $this->belongsToMany(Tag::class, 'mall_goods_tag');
+ }
+
+ /**
+ * Notes : 所属品牌
+ *
+ * @Date : 2021/5/11 11:00 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function brand(): BelongsTo
+ {
+ return $this->belongsTo(Brand::class);
+ }
+
+ /**
+ * Notes : 运费模板
+ *
+ * @Date : 2021/5/11 11:04 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function delivery(): BelongsTo
+ {
+ return $this->belongsTo(Delivery::class);
+ }
+
+ /**
+ * Notes : 商品属性
+ *
+ * @Date : 2021/5/12 11:31 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function specs(): HasMany
+ {
+ return $this->hasMany(GoodsSpec::class);
+ }
+
+ /**
+ * Notes : 商品SKU
+ *
+ * @Date : 2021/5/11 1:47 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function skus(): HasMany
+ {
+ return $this->hasMany(GoodsSku::class);
+ }
+
+ /**
+ * Notes : 获取规格的价格区间
+ *
+ * @Date : 2021/5/13 11:49 上午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ public function getPriceAttribute(): string
+ {
+ if ($this->type === self::TYPE_SINGLE) {
+ return $this->price_min;
+ } else {
+ return $this->price_min.'|'.$this->price_max;
+ }
+ }
+
+ /**
+ * Notes : 返回最低积分
+ *
+ * @Date : 2021/5/20 11:26 上午
+ * @Author : < Jason.C >
+ * @return float
+ */
+ public function getScoreAttribute(): float
+ {
+ return $this->skus->min('score') ?? 0;
+ }
+
+ /**
+ * Notes : 最低价格
+ *
+ * @Date : 2021/5/13 11:50 上午
+ * @Author : < Jason.C >
+ * @return float
+ */
+ public function getPriceMinAttribute(): float
+ {
+ return $this->skus->min('price') ?? 0;
+ }
+
+ /**
+ * Notes : 最高价格
+ *
+ * @Date : 2021/5/13 11:51 上午
+ * @Author : < Jason.C >
+ * @return float
+ */
+ public function getPriceMaxAttribute(): float
+ {
+ return $this->skus->max('price') ?? 0;
+ }
+
+ /**
+ * Notes : 按价格排序
+ *
+ * @Date : 2021/5/17 14:18
+ * @Author : Mr.wang
+ * @param Builder $query
+ * @param string $direction
+ * @return Builder
+ */
+ public function scopeOrderByPrice(Builder $query, string $direction = 'asc'): Builder
+ {
+ if ($direction) {
+ $price = DB::raw('MIN(price) as min_price');
+ } else {
+ $price = DB::raw('MAX(price) as min_price');
+ }
+
+ $minPrice = DB::table('mall_goods_skus')
+ ->select('goods_id', $price)
+ ->whereNull('deleted_at')
+ ->groupBy('goods_id');
+
+ return $query->joinSub($minPrice, 'min_price', function ($join) {
+ $join->on('mall_goods.id', '=', 'min_price.goods_id');
+ })->orderBy('min_price', $direction);
+ }
+
+}
diff --git a/modules/Mall/Models/GoodsSku.php b/modules/Mall/Models/GoodsSku.php
new file mode 100644
index 0000000..2732a27
--- /dev/null
+++ b/modules/Mall/Models/GoodsSku.php
@@ -0,0 +1,62 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function goods(): BelongsTo
+ {
+ return $this->belongsTo(Goods::class);
+ }
+
+ /**
+ * Notes : 当前规格是否可购买
+ *
+ * @Date : 2021/5/14 10:08
+ * @Author : Mr.wang
+ * @param int $qty
+ * @return bool
+ */
+ public function canBuy(int $qty = 1): bool
+ {
+ if ($this->goods->channel == Goods::CHANNEL_FREE) {
+ return true;
+ }
+
+ if ($this->goods->status == Goods::STATUS_UP && $this->stock >= $qty) {
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/modules/Mall/Models/GoodsSpec.php b/modules/Mall/Models/GoodsSpec.php
new file mode 100644
index 0000000..7ceba09
--- /dev/null
+++ b/modules/Mall/Models/GoodsSpec.php
@@ -0,0 +1,36 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function goods(): BelongsTo
+ {
+ return $this->belongsTo(Goods::class);
+ }
+
+ /**
+ * Notes : 属性值
+ * @Date : 2021/5/11 4:52 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function values(): HasMany
+ {
+ return $this->hasMany(GoodsSpecValue::class, 'spec_id');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/GoodsSpecValue.php b/modules/Mall/Models/GoodsSpecValue.php
new file mode 100644
index 0000000..dddb6c1
--- /dev/null
+++ b/modules/Mall/Models/GoodsSpecValue.php
@@ -0,0 +1,24 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function spec(): BelongsTo
+ {
+ return $this->belongsTo(GoodsSpec::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Job.php b/modules/Mall/Models/Job.php
new file mode 100644
index 0000000..e6f34dd
--- /dev/null
+++ b/modules/Mall/Models/Job.php
@@ -0,0 +1,19 @@
+ 'json',
+ ];
+ protected $appends = ['total'];
+ /**
+ * 不参与版本记录的字段
+ *
+ * @var array|string[]
+ */
+ protected array $dontVersionable = ['updated_at'];
+
+ const STATUS_INIT = 'INIT'; // 订单初始化:用户已下单,未付款
+ const STATUS_CANCEL = 'CANCEL'; // 订单取消:用户未支付并取消订单,超时未支付后自动取消订单
+ const STATUS_PAID = 'PAID'; // 已支付:用户付款完成,等待发货
+ const STATUS_DELIVERED = 'DELIVERED'; // 已发货:卖家已发货
+ const STATUS_SIGNED = 'SIGNED'; // 已签收:用户已签收
+ const STATUS_COMPLETED = 'COMPLETED'; // 已完成:用户签收N天后,完成订单,不再做任何操作,
+
+ const REFUND_APPLY = 'REFUND_APPLY'; // 申请退款
+ const REFUND_AGREE = 'REFUND_AGREE'; // 同意退款
+ const REFUND_DELIVER = 'REFUND_DELIVER'; // 等待客户退货
+ const REFUND_DELIVERD = 'REFUND_DELIVERD'; // 客户发货
+ const REFUND_REFUSE = 'REFUND_REFUSE'; // 拒绝退款
+ const REFUND_PROCESS = 'REFUND_PROCESS'; // 退款中
+ const REFUND_COMPLETED = 'REFUND_COMPLETED'; // 退款完成
+
+ const STATUS_MAP = [
+ self::STATUS_INIT => '未付款',
+ self::STATUS_CANCEL => '已取消',
+ self::STATUS_PAID => '待发货',
+ self::STATUS_DELIVERED => '已发货',
+ self::STATUS_SIGNED => '已签收',
+ self::STATUS_COMPLETED => '完成',
+ self::REFUND_APPLY => '申请退款',
+ self::REFUND_AGREE => '同意退款',
+ self::REFUND_REFUSE => '拒绝退款',
+ self::REFUND_DELIVER => '等待退货',
+ self::REFUND_DELIVERD => '客户退货',
+ self::REFUND_PROCESS => '退款中',
+ self::REFUND_COMPLETED => '退款完成',
+ ];
+
+ const CANCEL_BY_USER = 2; // 买家取消
+ const CANCEL_BY_SELLER = 3; // 卖家取消
+ const CANCEL_BY_SYSTEM = 4; // 系统取消
+
+ const TYPE_NORMAL = 1; // 正常
+ const TYPE_SAMPLE = 2; // 提货
+ const TYPE_SCORE = 3; // 积分兑换
+
+ const TYPE_MAP = [
+ self::TYPE_NORMAL => '正常',
+ self::TYPE_SAMPLE => '提货',
+ self::TYPE_SCORE => '积分兑换',
+ ];
+
+ const CHANNEL_USER = 1;
+ const CHANNEL_SYSTEM = 2;
+
+ const CHANNEL_MAP = [
+ self::CHANNEL_USER => '会员',
+ self::CHANNEL_SYSTEM => '公司',
+ ];
+
+ public static function boot()
+ {
+ parent::boot();
+
+ self::creating(function ($model) {
+ $time = explode(' ', microtime());
+
+ $counter = $model->whereDate('created_at', Carbon::today())->count() + 1;
+
+ $len = config('mall.order_no_counter_length');
+
+ $len = $len < 6 ? 6 : $len;
+ $len = $len > 16 ? 16 : $len;
+
+ $model->order_no = date('YmdHis').
+ sprintf('%06d', $time[0] * 1e6).
+ sprintf('%0'.$len.'d', $counter);
+
+ $model->state = self::STATUS_INIT;
+ $model->expired_at = Carbon::now()->addMinutes(10);
+ });
+ }
+
+ /**
+ * 获取该模型的路由的自定义键名
+ *
+ * @return string
+ */
+ public function getRouteKeyName(): string
+ {
+ return 'order_no';
+ }
+
+ /**
+ * Notes : 获取订单总额
+ *
+ * @Date : 2021/3/16 1:48 下午
+ * @Author : < Jason.C >
+ * @return float
+ */
+ public function getTotalAttribute(): float
+ {
+ if ($this->original) {
+ return sprintf("%.2f", $this->original['amount'] + $this->original['freight']);
+ }
+
+ return sprintf("%.2f", $this->amount + $this->freight);
+ }
+
+ /**
+ * Notes : 订单状态文本
+ *
+ * @Date : 2021/5/10 3:24 下午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ public function getStateTextAttribute(): string
+ {
+ return self::STATUS_MAP[$this->state];
+ }
+
+ /**
+ * Notes: 订单类型
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/13 16:53
+ * @return string
+ */
+ public function getTypeTextAttribute(): string
+ {
+ return self::TYPE_MAP[$this->type];
+
+ }
+
+ /**
+ * Notes : 订单发货记录
+ *
+ * @Date : 2021/5/11 9:47 上午
+ * @Author : < Jason.C >
+ * @return HasOne
+ */
+ public function express(): HasOne
+ {
+ return $this->hasOne(OrderExpress::class);
+ }
+
+ /**
+ * Notes : 订单条目
+ *
+ * @Date : 2021/5/11 9:47 上午
+ * @Author : < Jason.C >
+ * @return HasMany
+ */
+ public function items(): HasMany
+ {
+ return $this->hasMany(OrderItem::class);
+ }
+
+ /**
+ * Notes: 获取商品
+ *
+ * @Author: 玄尘
+ * @Date : 2021/10/27 15:01
+ * @return \Illuminate\Database\Eloquent\HigherOrderBuilderProxy|mixed
+ */
+ public function getGoodsAttribute()
+ {
+ return $this->items()->first()->source;
+ }
+
+ /**
+ * Notes : 退款单
+ *
+ * @Date : 2021/5/11 9:48 上午
+ * @Author : < Jason.C >
+ * @return HasMany
+ */
+ public function refunds(): HasMany
+ {
+ return $this->hasMany(Refund::class);
+ }
+
+ /**
+ * Notes: 获取退款商品数
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/19 9:50
+ * @return int
+ */
+ public function getRefundItemsCountAttribute(): int
+ {
+ $order_id = $this->id;
+
+ return RefundItem::query()
+ ->whereHas('order_item', function ($query) use ($order_id) {
+ $query->where('order_id', $order_id);
+ })
+ ->whereHas('refund', function ($query) {
+ $query->whereNotIN('state', [Refund::REFUND_APPLY]);
+ })
+ ->count();
+ }
+
+ /**
+ * Notes : 工作流获取状态
+ *
+ * @Date : 2021/5/8 3:52 下午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ public function getCurrentState(): string
+ {
+ return $this->state;
+ }
+
+ /**
+ * Notes : 工作流设置状态
+ *
+ * @Date : 2021/5/8 4:01 下午
+ * @Author : < Jason.C >
+ * @param $state
+ */
+ public function setCurrentState($state)
+ {
+ $this->state = $state;
+ $this->save();
+ }
+
+ /**
+ * Notes : 当前订单能不能查看物流
+ *
+ * @Date : 2021/5/19 11:55
+ * @Author : Mr.wang
+ * @return bool
+ */
+ public function getIsLogisticShowAttribute(): bool
+ {
+ return in_array($this->state, [
+ self::STATUS_DELIVERED,
+ self::STATUS_SIGNED,
+ self::REFUND_COMPLETED,
+ ]);
+ }
+
+ public function getCanAction(): array
+ {
+ return [
+ 'pay' => $this->canPay(),
+ 'cancel' => $this->canCancel(),
+ 'sign' => $this->canSign(),
+ 'refund' => $this->canRefund(),
+ ];
+ }
+
+}
diff --git a/modules/Mall/Models/OrderExpress.php b/modules/Mall/Models/OrderExpress.php
new file mode 100644
index 0000000..a64ba10
--- /dev/null
+++ b/modules/Mall/Models/OrderExpress.php
@@ -0,0 +1,48 @@
+ '快递',
+ self::TYPE_LOGISTICS => '物流',
+ ];
+
+ /**
+ * Notes : 订单物流信息
+ *
+ * @Date : 2021/5/25 12:04 下午
+ * @Author : < Jason.C >
+ * @return BelongsTo
+ */
+ public function express(): BelongsTo
+ {
+ return $this->belongsTo(Express::class);
+ }
+
+ public function getTypeTextAttribute(): string
+ {
+ return self::TYPE_MAP[$this->type];
+ }
+
+}
diff --git a/modules/Mall/Models/OrderItem.php b/modules/Mall/Models/OrderItem.php
new file mode 100644
index 0000000..02fb015
--- /dev/null
+++ b/modules/Mall/Models/OrderItem.php
@@ -0,0 +1,121 @@
+ 'json',
+ ];
+
+ /**
+ * Notes: 关联商品
+ * @Author: 玄尘
+ * @Date : 2021/8/11 9:26
+ * @return \Illuminate\Database\Eloquent\Relations\MorphTo
+ */
+ public function item(): morphTo
+ {
+ return $this->morphTo();
+ }
+
+ /**
+ * Notes: 关联退款单
+ * @Author: 玄尘
+ * @Date : 2021/5/19 9:30
+ */
+ public function refund_items(): HasMany
+ {
+ return $this->hasMany(RefundItem::class, 'order_item_id');
+ }
+
+ /**
+ * Notes: 商品状态
+ * @Author: 玄尘
+ * @Date : 2021/6/17 9:07
+ */
+ public function getRefundStatusTextAttribute()
+ {
+ if ($this->isRefund()) {
+ $refund_item = $this->refund_items()->latest()->first();
+ if ($refund_item) {
+ return $refund_item->refund->status_test;
+ } else {
+ return '未知状态';
+ }
+ } else {
+ return '正常';
+ }
+
+ }
+
+ /**
+ * Notes: 是否申请退款
+ * @Author: 玄尘
+ * @Date : 2021/5/21 11:15
+ */
+ public function isRefund(): int
+ {
+ return $this->refund_items()->count() > 0;
+ }
+
+ /**
+ * Notes: 是否可以退款/货
+ * @Author: 玄尘
+ * @Date : 2021/5/19 9:32
+ */
+ public function canRefund(): bool
+ {
+ $can = true;
+
+ //是否已申请退款/货
+ if ($this->isRefund()) {
+ //最后一个申请
+ $refundItem = $this->refund_items()->latest()->first();
+ if ($refundItem && $refundItem->refund->state != Refund::REFUND_REFUSE) {
+ $can = false;
+ }
+ }
+
+ $is_post_sale = $this->source['is_post_sale'] ?? 0;
+
+ return $is_post_sale && $can && $this->order->can('refund');
+ }
+
+ /**
+ * Notes: 获取总额
+ * @Author: 玄尘
+ * @Date : 2021/5/20 13:13
+ * @return float
+ */
+ public function getTotalAttribute(): float
+ {
+ return sprintf("%.2f", $this->price * $this->qty);
+ }
+
+ /**
+ * Notes: 获取水晶总数
+ * @Author: 玄尘
+ * @Date : 2021/5/21 11:26
+ */
+ public function getTotalScoreAttribute(): string
+ {
+ $score = $this->source['score'] ?? 0;
+
+ return sprintf("%.2f", $score * $this->qty);
+
+ }
+
+}
diff --git a/modules/Mall/Models/Reason.php b/modules/Mall/Models/Reason.php
new file mode 100644
index 0000000..7c20280
--- /dev/null
+++ b/modules/Mall/Models/Reason.php
@@ -0,0 +1,30 @@
+belongsToMany(Shop::class, 'mall_shop_reasons')
+ ->withTimestamps();
+ }
+
+}
diff --git a/modules/Mall/Models/Refund.php b/modules/Mall/Models/Refund.php
new file mode 100644
index 0000000..716cb15
--- /dev/null
+++ b/modules/Mall/Models/Refund.php
@@ -0,0 +1,216 @@
+ '申请退款',
+ self::REFUND_AGREE => '同意退款',
+ self::REFUND_REFUSE => '拒绝退款',
+ self::REFUND_DELIVER => '等待客户退货',
+ self::REFUND_DELIVERED => '客户退货,等待签收',
+ self::REFUND_SIGNED => '已签收',
+ self::REFUND_PROCESS => '退款中',
+ self::REFUND_COMPLETED => '退款完成',
+ ];
+
+ /**
+ * @var array
+ */
+ protected $dates = [
+ 'refunded_at',
+ ];
+
+ public static function boot()
+ {
+ parent::boot();
+
+ self::creating(function ($model) {
+ $model->state = self::REFUND_APPLY;
+
+ $time = explode(' ', microtime());
+
+ $counter = $model->whereDate('created_at', Carbon::today())->count() + 1;
+
+ $len = config('mall.refund_no_counter_length');
+ $prefix = config('refund_no_counter_prefix');
+
+ $len = $len < 6 ? 6 : $len;
+ $len = $len > 16 ? 16 : $len;
+
+ $model->refund_no = $prefix . date('YmdHis') .
+ sprintf('%06d', $time[0] * 1e6) .
+ sprintf('%0' . $len . 'd', $counter);
+ });
+ }
+
+ /**
+ * 获取该模型的路由的自定义键名
+ * @return string
+ */
+ public function getRouteKeyName(): string
+ {
+ return 'refund_no';
+ }
+
+ /**
+ * Notes: 退款单详情
+ * @Author:
+ * @Date : 2019/11/22 4:25 下午
+ * @return HasMany
+ */
+ public function items(): HasMany
+ {
+ return $this->hasMany(RefundItem::class);
+ }
+
+ /**
+ * Notes: 退款单物流
+ * @Author:
+ * @Date : 2019/11/22 4:25 下午
+ * @return HasOne
+ */
+ public function express(): HasOne
+ {
+ return $this->hasOne(RefundExpress::class);
+ }
+
+ /***
+ * Notes: 获取状态
+ * @Author: 玄尘
+ * @Date : 2021/5/18 11:50
+ */
+ public function getStatusTextAttribute(): string
+ {
+ return self::STATUS_MAP[$this->state] ?? '---';
+ }
+
+ /**
+ * Notes: 获取退款状态
+ * @Author:
+ * @Date : 2019/11/22 4:25 下午
+ * @return string
+ */
+ protected function getStateTextAttribute(): string
+ {
+ switch ($this->state) {
+ case self::REFUND_APPLY:
+ $state = '退款申请中';
+ break;
+ case self::REFUND_AGREE:
+ $state = '同意退款';
+ break;
+ case self::REFUND_PROCESS:
+ $state = '退款中,请等待商家打款';
+ break;
+ case self::REFUND_COMPLETED:
+ $state = '退款完毕';
+ break;
+ case self::REFUND_REFUSE:
+ $state = '拒绝退款';
+ break;
+ case self::REFUND_DELIVER:
+ $state = '您的申请已经通过,请将商品退回给商家';
+ break;
+ case self::REFUND_DELIVERED:
+ $state = '客户退货,等待签收';
+ break;
+ case self::REFUND_SIGNED:
+ $state = '店家已签收,等待打款';
+ break;
+ default:
+ $state = '未知状态';
+ break;
+ }
+
+ return $state;
+ }
+
+ /**
+ * Notes : 关联店铺
+ * @Date : 2021/5/7 1:46 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function shop(): BelongsTo
+ {
+ return $this->belongsTo(Shop::class);
+ }
+
+ /**
+ * Notes: 关联日志
+ * @Author: 玄尘
+ * @Date : 2021/5/17 11:38
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function logs(): HasMany
+ {
+ return $this->hasMany(RefundLog::class);
+ }
+
+ /**
+ * Notes : 工作流获取状态
+ * @Date : 2021/5/8 3:52 下午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ public function getCurrentState(): string
+ {
+ return $this->state;
+ }
+
+ /**
+ * Notes : 工作流设置状态
+ * @Date : 2021/5/8 4:01 下午
+ * @Author : < Jason.C >
+ * @param $state
+ */
+ public function setCurrentState($state)
+ {
+ $this->state = $state;
+ $this->save();
+ }
+
+}
diff --git a/modules/Mall/Models/RefundExpress.php b/modules/Mall/Models/RefundExpress.php
new file mode 100644
index 0000000..24c4901
--- /dev/null
+++ b/modules/Mall/Models/RefundExpress.php
@@ -0,0 +1,22 @@
+ 'json',
+ ];
+
+ /**
+ * Notes: 关联order_item
+ * @Author: 玄尘
+ * @Date : 2021/5/19 9:50
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function order_item(): BelongsTo
+ {
+ return $this->belongsTo(OrderItem::class);
+ }
+
+}
diff --git a/modules/Mall/Models/RefundLog.php b/modules/Mall/Models/RefundLog.php
new file mode 100644
index 0000000..7966280
--- /dev/null
+++ b/modules/Mall/Models/RefundLog.php
@@ -0,0 +1,40 @@
+morphTo();
+ }
+
+ /**
+ * Notes: 状态
+ * @Author: 玄尘
+ * @Date : 2021/5/18 11:39
+ * @return string
+ */
+ public function getStateTextAttribute(): string
+ {
+ return Refund::STATUS_MAP[$this->state] ?? '---';
+ }
+
+}
diff --git a/modules/Mall/Models/Region.php b/modules/Mall/Models/Region.php
new file mode 100644
index 0000000..ad4bc1d
--- /dev/null
+++ b/modules/Mall/Models/Region.php
@@ -0,0 +1,63 @@
+
+ * @Date : 2019/11/19 3:01 下午
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function parent(): BelongsTo
+ {
+ return $this->belongsTo(__CLASS__, 'parent_id');
+ }
+
+ /**
+ * Notes: 下级地区
+ * @Author:
+ * @Date : 2019/11/19 3:01 下午
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function children(): HasMany
+ {
+ return $this->hasMany(__CLASS__, 'parent_id');
+ }
+
+ /**
+ * Notes : 获取区划层级名称
+ * @Date : 2021/5/6 1:47 下午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ public function getDepthNameAttribute(): string
+ {
+ switch ($this->depth) {
+ case 0:
+ return '国家级';
+ case 1:
+ return '省级';
+ case 2:
+ return '市级';
+ case 3:
+ return '区县';
+ default:
+ return '';
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Shop.php b/modules/Mall/Models/Shop.php
new file mode 100644
index 0000000..af528da
--- /dev/null
+++ b/modules/Mall/Models/Shop.php
@@ -0,0 +1,187 @@
+ '待审核',
+ self::STATUS_NORMAL => '正常',
+ self::STATUS_REJECT => '驳回申请',
+ self::STATUS_CLOSE => '关闭',
+ ];
+
+ const LABEL_MAP = [
+ self::STATUS_APPLYING => 'default',
+ self::STATUS_NORMAL => 'success',
+ self::STATUS_REJECT => 'warning',
+ self::STATUS_CLOSE => 'danger',
+ ];
+
+ protected $table = 'mall_shops';
+
+ protected $casts = [
+ 'pictures' => 'json',
+ 'configs' => 'json',
+ ];
+
+ /**
+ * 不参与版本记录的字段
+ * @var array|string[]
+ */
+ protected array $dontVersionable = ['updated_at'];
+
+ /**
+ * Notes : 店铺支持的物流
+ * @Date : 2021/5/6 5:20 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function expresses(): BelongsToMany
+ {
+ return $this->belongsToMany(Express::class, 'mall_shop_express')
+ ->withTimestamps();
+ }
+
+ /**
+ * Notes : 店铺的商品
+ * @Date : 2021/5/6 5:32 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function goods(): HasMany
+ {
+ return $this->hasMany(Goods::class);
+ }
+
+ /**
+ * Notes : 分类
+ * @Date : 2021/5/6 5:32 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function categories(): HasMany
+ {
+ return $this->hasMany(Category::class);
+ }
+
+ /**
+ * Notes : 店铺设置的标签
+ * @Date : 2021/5/13 10:34 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function tags(): HasMany
+ {
+ return $this->hasMany(Tag::class);
+ }
+
+ /**
+ * Notes : 店铺的品牌
+ * @Date : 2021/5/13 10:35 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function brands(): HasMany
+ {
+ return $this->hasMany(Brand::class);
+ }
+
+ /**
+ * Notes : 轮播图
+ * @Date : 2021/5/13 11:34 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function banners(): HasMany
+ {
+ return $this->hasMany(Banner::class);
+ }
+
+ /**
+ * Notes : 订单
+ * @Date : 2021/5/6 5:33 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function orders(): HasMany
+ {
+ return $this->hasMany(Order::class);
+ }
+
+ /**
+ * Notes : 退款订单
+ * @Date : 2021/5/7 9:16 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function refunds(): HasMany
+ {
+ return $this->hasMany(Refund::class);
+ }
+
+ /**
+ * Notes : 获取状态文本
+ * @Date : 2021/5/7 10:57 上午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ public function getStatusTextAttribute(): string
+ {
+ return self::STATUS_MAP[$this->status] ?? '未知状态';
+ }
+
+ /**
+ * Notes: 退货理由
+ * @Author: 玄尘
+ * @Date : 2021/5/18 15:31
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function reasons(): BelongsToMany
+ {
+ return $this->belongsToMany(Reason::class, 'mall_shop_reasons')
+ ->withTimestamps();
+ }
+
+ /**
+ * Notes : 店铺的员工
+ * @Date : 2021/7/1 4:25 下午
+ * @Author : < Jason.C >
+ */
+ public function staffers(): BelongsToMany
+ {
+ return $this->belongsToMany(User::class, 'mall_shop_staffers')
+ ->using(ShopStaffer::class)
+ ->withPivot('job_id')
+ ->withTimestamps();
+ }
+
+}
diff --git a/modules/Mall/Models/ShopStaffer.php b/modules/Mall/Models/ShopStaffer.php
new file mode 100644
index 0000000..0fcb9b0
--- /dev/null
+++ b/modules/Mall/Models/ShopStaffer.php
@@ -0,0 +1,31 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function job(): BelongsTo
+ {
+ return $this->belongsTo(Job::class);
+ }
+
+}
diff --git a/modules/Mall/Models/Tag.php b/modules/Mall/Models/Tag.php
new file mode 100644
index 0000000..81a95f2
--- /dev/null
+++ b/modules/Mall/Models/Tag.php
@@ -0,0 +1,29 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function goods(): BelongsToMany
+ {
+ return $this->belongsToMany(Goods::class, 'mall_goods_tag')->withTimestamps();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Traits/BelongsToOrder.php b/modules/Mall/Models/Traits/BelongsToOrder.php
new file mode 100644
index 0000000..744d7ff
--- /dev/null
+++ b/modules/Mall/Models/Traits/BelongsToOrder.php
@@ -0,0 +1,22 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function order(): BelongsTo
+ {
+ return $this->belongsTo(Order::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Traits/BelongsToRefund.php b/modules/Mall/Models/Traits/BelongsToRefund.php
new file mode 100644
index 0000000..0e3c7ca
--- /dev/null
+++ b/modules/Mall/Models/Traits/BelongsToRefund.php
@@ -0,0 +1,22 @@
+belongsTo(Refund::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Traits/BelongsToShop.php b/modules/Mall/Models/Traits/BelongsToShop.php
new file mode 100644
index 0000000..cd26f86
--- /dev/null
+++ b/modules/Mall/Models/Traits/BelongsToShop.php
@@ -0,0 +1,49 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function shop(): BelongsTo
+ {
+ return $this->belongsTo(Shop::class);
+ }
+
+ /**
+ * Notes : 作用域查询
+ * @Date : 2021/6/21 8:57 上午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Modules\Mall\Models\Shop $shop
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeByShop(Builder $query, Shop $shop): Builder
+ {
+ return $query->where('shop_id', $shop->getKey());
+ }
+
+ /**
+ * Notes : 店铺ID 作用域查询
+ * @Date : 2021/6/21 9:04 上午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param int $shopId
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeByShopId(Builder $query, int $shopId): Builder
+ {
+ return $query->where('shop_id', $shopId);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Traits/GoodsAttribute.php b/modules/Mall/Models/Traits/GoodsAttribute.php
new file mode 100644
index 0000000..886287c
--- /dev/null
+++ b/modules/Mall/Models/Traits/GoodsAttribute.php
@@ -0,0 +1,211 @@
+goods->type == Goods::TYPE_SINGLE) {
+
+ }
+ return $this->stock ?? 0;
+ }
+
+ /**
+ * Notes: 扣除库存方法
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:12
+ * @param $stock
+ * @param $user_id
+ * @return bool
+ */
+ public function deductStock($stock, $user_id): bool
+ {
+ if ($this->goods->channel == Goods::CHANNEL_FREE) {
+ $user = User::find($user_id);
+ $user->addHold($stock);
+ } else {
+ $this->decrement('stock', $stock);
+ }
+ return true;
+ }
+
+ /**
+ * Notes: 增加库存方法
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:12
+ * @param $stock
+ */
+ public function addStock($stock)
+ {
+ $this->increment('stock', $stock);
+ }
+
+ /**
+ * Notes: 增加销量
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:12
+ * @param $sold
+ */
+ public function addSold($sold)
+ {
+ $this->goods()->increment('sales', $sold);
+ }
+
+ /**
+ * Notes: 减少销量
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:12
+ * @param $sold
+ */
+ public function deductSold($sold)
+ {
+ $this->goods()->decrement('sales', $sold);
+ }
+
+ /**
+ * Notes: 获取卖家ID
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:14
+ * @return int
+ */
+ public function getShopIdAttribute(): int
+ {
+ return $this->goods->shop_id ?? 0;
+ }
+
+ /**
+ * Notes: 获取卖家type
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:14
+ * @return string|null
+ */
+ public function getShopTypeAttribute(): ?string
+ {
+ if ($this->goods && $this->goods->shop) {
+ return get_class($this->goods->shop);
+ }
+
+ return null;
+ }
+
+ /**
+ * Notes: 获取卖家名称
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:14
+ * @return false|string|null
+ */
+ public function getShopNameAttribute()
+ {
+ if ($this->goods && $this->goods->shop) {
+ return $this->goods->shop->name;
+ }
+
+ return null;
+ }
+
+ /**
+ * Notes: 重量
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:15
+ * @return int
+ */
+ public function getGoodsWeight(): int
+ {
+ return $this->weight ?? 0;
+ }
+
+ /**
+ * Notes: 返回source
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:15
+ * @return array
+ */
+ public function getSource(): array
+ {
+ return [
+ 'goods_sku_id' => $this->id,
+ 'goods_id' => $this->goods_id,
+ 'deduct_stock_type' => $this->goods->deduct_stock_type,
+ 'goods_name' => $this->getGoodsName(),
+ // 'unit' => $this->getItemValue(),
+ 'cover' => $this->goods->cover_url,
+ 'price' => $this->getItemPrice(),
+ 'stock' => $this->getGoodsStock(),
+ 'weight' => $this->weight,
+ 'is_post_sale' => $this->goods->is_post_sale ?? 0,
+ 'score' => $this->score, // 积分/原石
+ 'assets' => $this->assets, // 资产
+ ];
+ }
+
+ /**
+ * Notes: 获取商品名称
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:19
+ * @return string
+ */
+ public function getGoodsName(): string
+ {
+ return $this->goods->name ?? '--';
+ }
+
+ /**
+ * Notes: 获取商品规格名称
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:16
+ * @return string
+ */
+ public function getItemValue(): string
+ {
+ switch ($this->goods->type) {
+ case Goods::TYPE_SINGLE:
+ return '';
+ case Goods::TYPE_MULTIPLE:
+ return $this->unit ?? '';
+ }
+ }
+
+ /**
+ * Notes: 获取商品单价
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/14 11:16
+ * @return mixed
+ */
+ public function getItemPrice()
+ {
+ $tags = $this->goods->tags()->pluck('tag_id')->toArray();
+ //1 为试用产品
+ if (in_array(1, $tags)) {
+ return 0;
+ }
+
+ return $this->price;
+ }
+
+}
diff --git a/modules/Mall/Models/Traits/HasRegion.php b/modules/Mall/Models/Traits/HasRegion.php
new file mode 100644
index 0000000..f520465
--- /dev/null
+++ b/modules/Mall/Models/Traits/HasRegion.php
@@ -0,0 +1,60 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function province(): BelongsTo
+ {
+ return $this->belongsTo(Region::class, 'province_id');
+ }
+
+ /**
+ * Notes : 市
+ * @Date : 2021/5/11 9:50 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function city(): BelongsTo
+ {
+ return $this->belongsTo(Region::class, 'city_id');
+ }
+
+ /**
+ * Notes : 区
+ * @Date : 2021/5/11 9:50 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function district(): BelongsTo
+ {
+ return $this->belongsTo(Region::class, 'district_id');
+ }
+
+ /**
+ * Notes : 获取完整地址
+ * @Date : 2021/5/11 9:49 上午
+ * @Author : < Jason.C >
+ * @param string $separators 地址的分隔符
+ * @return string
+ */
+ public function getFullAddress(string $separators = ' '): string
+ {
+ return
+ $this->province->name . $separators .
+ $this->city->name . $separators .
+ $this->district->name . $separators .
+ $this->address;
+ }
+
+}
diff --git a/modules/Mall/Models/Traits/OrderActions.php b/modules/Mall/Models/Traits/OrderActions.php
new file mode 100644
index 0000000..2d42d17
--- /dev/null
+++ b/modules/Mall/Models/Traits/OrderActions.php
@@ -0,0 +1,188 @@
+
+ * @throws \Exception
+ */
+ public function cancel(): bool
+ {
+ if ($this->canCancel()) {
+ // 修改状态机
+ $this->apply('cancel');
+
+ event(new OrderCanceled($this));
+
+ return true;
+ } else {
+ throw new \Exception('订单状态不可取消');
+ }
+ }
+
+ /**
+ * 订单支付
+ *
+ * @throws \Exception
+ */
+ public function pay(): bool
+ {
+ if ($this->can('pay')) {
+ // 修改状态机
+ $this->paid_at = Carbon::now();
+ $this->apply('pay');
+
+ event(new OrderPaid($this));
+
+ //增加记录
+ $this->addTimeline();
+
+ return true;
+ } else {
+ throw new \Exception('订单状态不可支付');
+ }
+ }
+
+ /**
+ * Notes : 订单发货
+ *
+ * @Date : 2021/5/14 16:45
+ * @Author : Mr.wang
+ * @param int $expressId 物流公司ID
+ * @param string $expressNo 物流单号
+ * @return bool
+ * @throws \Exception
+ */
+ public function deliver($expressId, $expressNo, $type = 1, $person = ''): bool
+ {
+ if ($this->can('deliver')) {
+ if ($this->refund_items_count == $this->items()->count()) {
+ throw new \Exception('商品已全部退款/货不能发货');
+ }
+
+ DB::transaction(function () use ($expressId, $expressNo, $type, $person) {
+ $this->express()->update([
+ 'express_id' => $expressId ?? null,
+ 'express_no' => $expressNo ?? null,
+ 'type' => $type,
+ 'person' => $person,
+ 'deliver_at' => now(),
+ ]);
+ // 修改状态机
+ $this->apply('deliver');
+
+ event(new OrderDelivered($this));
+
+ if ($this->type == Order::TYPE_SAMPLE) {
+ $this->user->notify(new SystemOrderDelivered('发货提醒', $this));
+ }
+
+ });
+
+ return true;
+ } else {
+ throw new \Exception('订单状态不可发货');
+ }
+ }
+
+ /**
+ * Notes : 订单签收
+ *
+ * @Date : 2021/5/14 17:09
+ * @Author : Mr.wang
+ * @return bool
+ * @throws \Exception
+ */
+ public function sign(): bool
+ {
+ if ($this->can('sign')) {
+ DB::transaction(function () {
+ $this->express()->update([
+ 'receive_at' => now(),
+ ]);
+ // 修改状态机
+ $this->apply('sign');
+ });
+ event(new OrderSigned($this));
+
+ return true;
+ } else {
+ throw new \Exception('订单状态不可签收');
+ }
+ }
+
+ /**
+ * Notes : 订单完成
+ *
+ * @Date : 2021/5/21 2:57 下午
+ * @Author : < Jason.C >
+ * @return bool
+ * @throws \Exception
+ */
+ public function complete(): bool
+ {
+ if ($this->can('complete')) {
+ // 修改状态机
+ $this->apply('complete');
+
+ event(new OrderCompleted($this));
+
+ return true;
+ } else {
+ throw new \Exception('订单状态不可完成');
+ }
+ }
+
+ /**
+ * Notes: 设置退款
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/9 9:53
+ */
+ public function refund()
+ {
+ if ($this->can('refund')) {
+ // 修改状态机
+ $this->apply('refund');
+
+ return true;
+ } else {
+ throw new \Exception('订单状态不可设置退款');
+ }
+ }
+
+ /**
+ * Notes: 退款完成
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/9 12:00
+ */
+ public function completed()
+ {
+ if ($this->can('completed')) {
+ // 修改状态机
+ $this->apply('completed');
+
+ return true;
+ } else {
+ throw new \Exception('订单状态不可设置退款完成');
+ }
+ }
+
+}
diff --git a/modules/Mall/Models/Traits/OrderCando.php b/modules/Mall/Models/Traits/OrderCando.php
new file mode 100644
index 0000000..9ba04e3
--- /dev/null
+++ b/modules/Mall/Models/Traits/OrderCando.php
@@ -0,0 +1,82 @@
+
+ * @Date : 2019/11/20 3:39 下午
+ * @return bool
+ */
+ public function canPay(): bool
+ {
+ return $this->can('pay');
+ }
+
+ /**
+ * Notes: 是否可取消
+ *
+ * @Author:
+ * @Date : 2019/11/20 3:39 下午
+ * @return bool
+ */
+ public function canCancel(): bool
+ {
+ return in_array($this->state, [Order::STATUS_INIT,]);
+ }
+
+ /**
+ * 可发货
+ *
+ * @Author:
+ * @Date :2018-10-22T17:12:13+0800
+ * @return boolean
+ */
+ public function canDeliver(): bool
+ {
+ return $this->can('deliver');
+ }
+
+ /**
+ * 可签收
+ *
+ * @Author:
+ * @Date :2018-10-22T17:12:43+0800
+ * @return boolean
+ */
+ public function canSign(): bool
+ {
+ return $this->can('sign');
+ }
+
+ /**
+ * 可完成订单
+ *
+ * @Author:
+ * @Date :2018-10-25T17:35:12+0800
+ * @return boolean
+ */
+ public function canComplete(): bool
+ {
+ return $this->can('complete');
+ }
+
+ /**
+ * 可申请退款
+ *
+ * @Author:
+ * @Date :2018-10-22T17:11:45+0800
+ * @return boolean
+ */
+ public function canRefund(): bool
+ {
+ return $this->can('refund');
+ }
+
+}
diff --git a/modules/Mall/Models/Traits/OrderScopes.php b/modules/Mall/Models/Traits/OrderScopes.php
new file mode 100644
index 0000000..e677261
--- /dev/null
+++ b/modules/Mall/Models/Traits/OrderScopes.php
@@ -0,0 +1,138 @@
+
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeUnPay(Builder $query): Builder
+ {
+ return $query->where('state', self::STATUS_INIT);
+ }
+
+ /**
+ * Notes : 已支付,待发货
+ *
+ * @Date : 2021/5/17 11:00 上午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopePaid(Builder $query): Builder
+ {
+ return $query->where('state', self::STATUS_PAID);
+ }
+
+ /**
+ * Notes : 已发货,代签收
+ *
+ * @Date : 2021/5/17 11:03 上午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeDelivered(Builder $query): Builder
+ {
+ return $query->where('state', self::STATUS_DELIVERED);
+ }
+
+ /**
+ * Notes : 已签收
+ *
+ * @Date : 2021/5/17 11:04 上午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeSigned(Builder $query): Builder
+ {
+ return $query->where('state', self::STATUS_SIGNED);
+ }
+
+ /**
+ * Notes : 已完成的订单
+ *
+ * @Date : 2021/5/17 11:04 上午
+ * @Author : < Jason.C >
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeCompleted(Builder $query): Builder
+ {
+ return $query->where('state', self::STATUS_COMPLETED);
+ }
+
+ /**
+ * Notes : 订单列表排除退款部分
+ *
+ * @Date : 2021/5/18 9:50
+ * @Author : Mr.wang
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeCommon(Builder $query): Builder
+ {
+ return $query->whereIn('state', [
+ self::STATUS_INIT,
+ self::STATUS_CANCEL,
+ self::STATUS_PAID,
+ self::STATUS_DELIVERED,
+ self::STATUS_SIGNED,
+ self::STATUS_COMPLETED,
+ ]);
+ }
+
+ /**
+ * Notes: 签收和发货
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/30 8:33
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeDeliveredAndSigned(Builder $query): Builder
+ {
+ return $query->whereIn('state', [
+ self::STATUS_DELIVERED,
+ self::STATUS_SIGNED,
+ ]);
+ }
+
+ /**
+ * Notes: 积分兑换订单
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/19 14:40
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeTypeScore(Builder $query): Builder
+ {
+ return $query->where('type', Order::TYPE_SCORE);
+ }
+
+ /**
+ * Notes: 提货订单
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/19 14:40
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeTypeSample(Builder $query): Builder
+ {
+ return $query->where('type', Order::TYPE_SAMPLE);
+ }
+
+}
diff --git a/modules/Mall/Models/Traits/RefundActions.php b/modules/Mall/Models/Traits/RefundActions.php
new file mode 100644
index 0000000..a243db7
--- /dev/null
+++ b/modules/Mall/Models/Traits/RefundActions.php
@@ -0,0 +1,318 @@
+operator = $operator;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 设置备注
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 16:12
+ * @param $remark
+ * @return $this
+ */
+ public function setRemark($remark)
+ {
+ $this->remark = $remark;
+
+ return $this;
+ }
+
+ /**
+ * Notes: 同意退款
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 11:59
+ * @param $remark
+ * @return bool
+ * @throws \Exception
+ */
+ public function agree($remark = null): bool
+ {
+ if (! $this->can('agree')) {
+ throw new Exception("退款单状态不可以同意退款");
+ }
+
+ DB::transaction(function () use ($remark) {
+ $this->actual_total = $this->refund_total;
+ $this->save();
+
+ $this->apply('agree');
+ $this->setOrderApply($this->order, 'agree');
+
+ if ($this->order->express) {
+ $this->express()->create();
+ }
+
+ $this->setLogs($remark);
+
+ //订单已经进入发货流程,需要客户退货
+ if ($this->order->express && $this->order->express->deliver_at) {
+ $this->apply('deliver');
+ $this->setOrderApply($this->order, 'user_deliver');
+ } else {
+ //未发货设置为退款中
+ $this->apply('process');
+ $this->setOrderApply($this->order, 'process');
+ }
+
+ event(new RefundAgreed($this));
+ });
+
+ return true;
+ }
+
+ /**
+ * Notes: 拒绝退款
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 12:01
+ * @param string|null $remark
+ * @return bool
+ * @throws \Exception
+ */
+ public function refuse(string $remark = null): bool
+ {
+ if (! $this->can('refuse')) {
+ throw new Exception("退款单状态不可以拒绝退款");
+ }
+
+ DB::transaction(function () use ($remark) {
+
+ $this->apply('refuse');
+ $this->setOrderApply($this->order, 'refuse');
+
+ $this->setLogs($remark);
+
+ event(new RefundRefused($this));
+ });
+
+ return true;
+ }
+
+ /**
+ * Notes: 退货退款中
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 14:29
+ * @param $company
+ * @param string $number
+ * @return bool
+ * @throws \Exception
+ */
+ public function deliver($company, string $number): bool
+ {
+ if ($this->can('delivered')) {
+ DB::transaction(function () use ($company, $number) {
+ $this->express()->update([
+ 'company' => $company,
+ 'number' => $number,
+ 'deliver_at' => now(),
+ ]);
+
+ // 修改状态机
+ $this->apply('delivered');
+ $this->setOrderApply($this->order, 'user_deliverd');
+
+ $this->setLogs('客户退货');
+
+ });
+
+ return true;
+ } else {
+ throw new Exception('订单状态不可发货');
+ }
+
+ }
+
+ /**
+ * Notes: 确认收货
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 14:30
+ * @return bool
+ * @throws \Exception
+ */
+ public function receive(): bool
+ {
+ if (! $this->can('sign')) {
+ throw new Exception('退款单状态不可以确认收货');
+ }
+
+ $this->express->receive_at = now();
+ $this->express->save();
+
+ $this->apply('sign');
+ //设置为退款中
+ $this->apply('process');
+ $this->setOrderApply($this->order, 'process');
+
+ $this->setLogs('店家签收');
+
+ return true;
+ }
+
+ /**
+ * Notes: 标记退款完成
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 14:32
+ * @return bool
+ * @throws \Exception
+ */
+ public function complete(): bool
+ {
+ if (! $this->can('completed')) {
+ throw new Exception("订单状态不对,不可设置完成");
+ }
+
+ DB::transaction(function () {
+ $this->apply('completed');
+ //设置时间
+ $this->refunded_at = now();
+ $this->save();
+
+ $this->setLogs('订单标记完成');
+
+ event(new RefundCompleted($this));
+ });
+
+ return true;
+ }
+
+ /**
+ * Notes: 退款
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 14:52
+ * @throws \Exception
+ */
+ public function returns(): bool
+ {
+ try {
+ $payment = $this->order->payment;
+ if (! $payment) {
+ throw new Exception("退款失败,未找到支付信息");
+ }
+ //微信支付
+ if ($payment->driver == Payment::DRIVER_WECHAT) {
+ $order = [
+ 'out_trade_no' => $payment->trade_id,
+ 'out_refund_no' => $this->refund_no,
+ 'total_fee' => $payment->total * 100,
+ 'refund_fee' => $this->actual_total * 100,
+ ];
+ $result = app('pay.wechat')->refund($order);
+
+ if ($result->result_code != 'SUCCESS') {
+ throw new Exception("退款失败,".$result->return_msg);
+ }
+
+ $this->setLogs('退款完成');
+
+ return true;
+ } elseif ($payment->driver == Payment::DRIVER_ALIPAY) {//支付宝支付
+
+ $order = [
+ 'out_trade_no' => $this->order->order_no,
+ 'refund_amount' => $this->actual_total,
+ ];
+
+ $result = app('pay.alipay')->refund($order);
+
+ if ($result->code != '10000') {
+ throw new Exception("退款失败,".$result->msg);
+ }
+
+ return true;
+ } elseif ($payment->driver == Payment::DRIVER_SCORE) {//水滴支付
+
+ $this->user->account->rule('score_buy_refund', $this->actual_total, false, [
+ 'order_no' => $this->order->order_no,
+ 'refund_no' => $this->refund_no,
+ ]);
+
+ return true;
+ } else {
+ throw new Exception("退款失败,未找到支付路径");
+ }
+ } catch (Exception $exception) {
+ throw new Exception($exception->getMessage());
+ }
+
+ }
+
+ /**
+ * Notes: 记录日志
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 16:03
+ * @param null $remark
+ * @param null $title
+ * @param null $pictures
+ */
+ public function setLogs($remark = null, $title = null, $pictures = null)
+ {
+ if (empty($this->operator)) {
+ $this->operator = $this->user;
+ }
+
+ if (empty($this->operator)) {
+ $this->operator = Admin::user();
+ }
+
+ $refund = $this->refresh();
+ $logs = [
+ 'userable_type' => get_class($this->operator),
+ 'userable_id' => $this->operator->id,
+ 'remark' => $remark,
+ 'title' => $title,
+ 'pictures' => $pictures,
+ 'state' => $refund->state,
+ ];
+
+ $refund->logs()->create($logs);
+ }
+
+ /**
+ * Notes: 修改订单状态机
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/17 9:22
+ */
+ public function setOrderApply($order, $status)
+ {
+ //如果都已经退款/货
+ if ($order->refund_items_count == $order->items()->count()) {
+ $order->apply($status);
+ }
+ }
+
+}
diff --git a/modules/Mall/Models/Traits/ShopActions.php b/modules/Mall/Models/Traits/ShopActions.php
new file mode 100644
index 0000000..421073f
--- /dev/null
+++ b/modules/Mall/Models/Traits/ShopActions.php
@@ -0,0 +1,94 @@
+
+ * @return bool
+ * @throws \Exception
+ */
+ public function close(): bool
+ {
+ if ($this->status != Shop::STATUS_NORMAL) {
+ throw new Exception('当前状态不可关闭');
+ }
+
+ $this->status = Shop::STATUS_CLOSE;
+
+ return $this->save();
+ }
+
+ /**
+ * Notes : 开启店铺
+ * @Date : 2021/5/7 5:02 下午
+ * @Author : < Jason.C >
+ * @return bool
+ * @throws \Exception
+ */
+ public function open(): bool
+ {
+ if ($this->status != Shop::STATUS_CLOSE) {
+ throw new Exception('当前状态不可开启');
+ }
+
+ $this->status = Shop::STATUS_NORMAL;
+
+ return $this->save();
+ }
+
+ /**
+ * Notes : 通过申请
+ * @Date : 2021/5/7 5:03 下午
+ * @Author : < Jason.C >
+ * @return bool
+ * @throws \Exception
+ */
+ public function pass(): bool
+ {
+ if ($this->status != Shop::STATUS_APPLYING) {
+ throw new Exception('当前状态不可审核' . $this->status);
+ }
+
+ // 店铺审核通过后,增加物流的关联
+
+ $expressIds = Express::where('status', 1)->pluck('id');
+ $this->expresses()->attach($expressIds);
+ $reasonIds = Reason::where('status', 1)->pluck('id');
+ $this->reasons()->attach($reasonIds);
+
+ $this->status = Shop::STATUS_CLOSE;
+
+ return $this->save();
+ }
+
+ /**
+ * Notes : 驳回申请
+ * @Date : 2021/5/7 5:03 下午
+ * @Author : < Jason.C >
+ * @param string $reason
+ * @return bool
+ * @throws \Exception
+ */
+ public function reject(string $reason): bool
+ {
+ if ($this->status != Shop::STATUS_APPLYING) {
+ throw new Exception('当前状态不可驳回');
+ }
+
+ $this->status = Shop::STATUS_REJECT;
+ $this->reject_reason = $reason;
+
+ return $this->save();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Models/Video.php b/modules/Mall/Models/Video.php
new file mode 100644
index 0000000..3a3fa95
--- /dev/null
+++ b/modules/Mall/Models/Video.php
@@ -0,0 +1,22 @@
+parseImageUrl($this->path);
+ }
+
+}
diff --git a/modules/Mall/Providers/EventServiceProvider.php b/modules/Mall/Providers/EventServiceProvider.php
new file mode 100644
index 0000000..76abb95
--- /dev/null
+++ b/modules/Mall/Providers/EventServiceProvider.php
@@ -0,0 +1,38 @@
+ [
+ OrderCreatedListener::class,
+ ],
+ OrderPaid::class => [
+ OrderPaidListener::class,
+ ],
+ RefundAgreed::class => [
+ RefundAgreedListener::class,
+ ],
+ RefundCompleted::class => [
+ RefundCompletedListener::class,
+ ],
+ ];
+
+}
diff --git a/modules/Mall/Providers/MallServiceProvider.php b/modules/Mall/Providers/MallServiceProvider.php
new file mode 100644
index 0000000..5c251e4
--- /dev/null
+++ b/modules/Mall/Providers/MallServiceProvider.php
@@ -0,0 +1,100 @@
+ Authenticate::class,
+ 'agent.owner' => ShopOwner::class,
+ ];
+
+ /**
+ * Boot the application events.
+ * @return void
+ */
+ public function boot(): void
+ {
+ $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+
+ if ($this->app->runningInConsole()) {
+ $this->publishes([module_path($this->moduleName, 'Database/Seeders') => database_path('seeders')]);
+ }
+ }
+
+ /**
+ * Register the service provider.
+ * @return void
+ */
+ public function register(): void
+ {
+ $this->registerConfig();
+
+ $this->app->register(RouteServiceProvider::class);
+
+ $this->app->singleton('workflow', function ($app) {
+ return new WorkflowRegistry($app['config']->get('mall.workflow'));
+ });
+
+ $viewPath = resource_path('views/modules/' . $this->moduleNameLower);
+
+ $sourcePath = module_path($this->moduleName, 'Resources/views');
+
+ $this->publishes([
+ $sourcePath => $viewPath,
+ ], 'views');
+
+ $this->loadViewsFrom(array_merge(array_map(function ($path) {
+ return $path . '/modules/' . $this->moduleNameLower;
+ }, Config::get('view.paths')), [$sourcePath]), $this->moduleNameLower);
+
+ foreach ($this->routeMiddleware as $key => $middleware) {
+ Route::aliasMiddleware($key, $middleware);
+ }
+ }
+
+ /**
+ * Register config.
+ * @return void
+ */
+ protected function registerConfig(): void
+ {
+ $this->publishes([
+ module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'),
+ ], 'config');
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
+ );
+ }
+
+ /**
+ * Get the services provided by the provider.
+ * @return array
+ */
+ public function provides(): array
+ {
+ return ['workflow'];
+ }
+
+}
diff --git a/modules/Mall/Providers/RouteServiceProvider.php b/modules/Mall/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..c9d8f6f
--- /dev/null
+++ b/modules/Mall/Providers/RouteServiceProvider.php
@@ -0,0 +1,80 @@
+mapApiRoutes();
+
+ $this->mapAdminRoutes();
+
+ $this->mapAgentRoutes();
+ }
+
+ protected function mapApiRoutes()
+ {
+ Route::as(config('api.route.as'))
+ ->domain(config('api.route.domain'))
+ ->middleware(config('api.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('api.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/api.php'));
+ }
+
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+
+ /**
+ * Notes : 中台路由
+ * @Date : 2021/5/7 9:25 上午
+ * @Author : < Jason.C >
+ */
+ protected function mapAgentRoutes()
+ {
+ Route::as(config('agent.route.as'))
+ ->domain(config('agent.route.domain'))
+ ->middleware(array_merge(config('agent.route.middleware_auth'), ['agent.auth']))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('agent.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/agent.php'));
+ }
+
+}
diff --git a/modules/Mall/Providers/WorkflowRegistry.php b/modules/Mall/Providers/WorkflowRegistry.php
new file mode 100644
index 0000000..aed1ed3
--- /dev/null
+++ b/modules/Mall/Providers/WorkflowRegistry.php
@@ -0,0 +1,135 @@
+registry = new Registry();
+ $this->config = $config;
+ $this->dispatcher = new EventDispatcher();
+
+ foreach ($this->config as $name => $workflowData) {
+ $this->addFromArray($name, $workflowData);
+ }
+ }
+
+ /**
+ * Notes : 添加一个工作流
+ * @Date : 2021/5/8 3:48 下午
+ * @Author : < Jason.C >
+ * @param Workflow $workflow
+ * @param string $supportStrategy
+ */
+ public function add(Workflow $workflow, string $supportStrategy)
+ {
+ $this->registry->addWorkflow($workflow, new InstanceOfSupportStrategy($supportStrategy));
+ }
+
+ /**
+ * Notes : 获取工作流
+ * @Date : 2021/5/8 3:49 下午
+ * @Author : < Jason.C >
+ * @param object $subject
+ * @param string|null $workflowName
+ * @return Workflow
+ */
+ public function get(object $subject, string $workflowName = null): Workflow
+ {
+ return $this->registry->get($subject, $workflowName);
+ }
+
+ /**
+ * 批量添加工作流到对象
+ * @param string $name
+ * @param array $workflowData
+ */
+ public function addFromArray(string $name, array $workflowData)
+ {
+ $builder = new DefinitionBuilder($workflowData['places']);
+
+ foreach ($workflowData['transitions'] as $transitionName => $transition) {
+ foreach ((array) $transition['from'] as $form) {
+ $builder->addTransition(new Transition($transitionName, $form, $transition['to']));
+ }
+ }
+
+ $definition = $builder->build();
+ $markingStore = $this->getMarkingStoreInstance($workflowData);
+ $workflow = $this->getWorkflowInstance($name, $workflowData, $definition, $markingStore);
+
+ foreach ($workflowData['supports'] as $supportedClass) {
+ $this->add($workflow, $supportedClass);
+ }
+ }
+
+ /**
+ * Notes : 根据配置获取工作流或状态机
+ * @Date : 2021/5/8 3:50 下午
+ * @Author : < Jason.C >
+ * @param string $name
+ * @param array $workflowData
+ * @param Definition $definition
+ * @param MethodMarkingStore $markingStore
+ * @return Workflow
+ */
+ protected function getWorkflowInstance(
+ string $name,
+ array $workflowData,
+ Definition $definition,
+ MethodMarkingStore $markingStore
+ ): Workflow {
+ if (isset($workflowData['type']) && $workflowData['type'] === 'state_machine') {
+ $className = StateMachine::class;
+ } else {
+ $className = Workflow::class;
+ }
+
+ return new $className($definition, $markingStore, $this->dispatcher, $name);
+ }
+
+ /**
+ * Notes : 获取对象存储的实例
+ * @Date : 2021/5/8 3:49 下午
+ * @Author : < Jason.C >
+ * @param array $workflowData
+ * @return MethodMarkingStore
+ */
+ protected function getMarkingStoreInstance(array $workflowData): MethodMarkingStore
+ {
+ $markingStoreData = $workflowData['marking_store'] ?? [];
+ $property = $markingStoreData['property'] ?? '';
+
+ $singleState = $markingStoreData['type'] === 'single_state';
+
+ return new MethodMarkingStore($singleState, $property);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/README.md b/modules/Mall/README.md
new file mode 100644
index 0000000..b1c03f1
--- /dev/null
+++ b/modules/Mall/README.md
@@ -0,0 +1,37 @@
+# UzTech Mall Module 多用户商城模块
+
+## 1. 使用
+
+> php artisan module:publish-config Mall
+
+## 2. traits 说明
+
+### 1. UserHasAddress 用户地址列表
+
+## 3. 功能说明
+
+### 1. 物流管理,主后台添加一个主表,各商户,通过多对多的关系来确定使用哪些物流
+
+### 2.
+
+## 4. 工作流说明
+
+> 参考文档
+>
+> https://github.com/brexis/laravel-workflow
+>
+> https://symfony.com/doc/current/components/workflow.html
+
+```php
+ $order = Order::find(2);
+ // 方法1
+ $app = app('workflow');
+ $workflow = $app->get($order);
+ if ($workflow->can($order, 'pay')) {
+ $workflow->apply($order, 'pay')
+ }
+ // 方法2,引入 WithWorkflow
+ $order->can('pay');
+ // 方法3
+ $workflow = Modules\Mall\Facades\Workflow::get($order)->can('pay');
+```
\ No newline at end of file
diff --git a/modules/Mall/Resources/views/admin/order/detail.blade.php b/modules/Mall/Resources/views/admin/order/detail.blade.php
new file mode 100644
index 0000000..e51adbf
--- /dev/null
+++ b/modules/Mall/Resources/views/admin/order/detail.blade.php
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
+ @foreach(\Modules\Mall\Models\Order::STATUS_MAP as $key=>$status)
+ - state==$key) class="act" @endif>{{ $status }}
+ @endforeach
+
+
+
+
+
+
+
+
+
+
+
+ | 订单信息 |
+ 购买人 |
+ 收货信息 |
+ 费用信息 |
+ 订单状态 |
+ 支付渠道 |
+ 支付时间 |
+
+
+
+
+ | {{ $order->order_no }} |
+ {{ $order->user->info->nickname }} |
+
+ @if ($order->express)
+ 收件人:{{$order->express->name}}
+ 电话:{{$order->express->mobile}}
+ 地址:{{$order->express->getFullAddress()}}
+
+ @endif
+ |
+
+ 商品总额:¥ {{$order->amount}}元
+ 订单总额:¥ {{$order->total}}元
+ 说明: {{$order->remark}}
+
+ 运费:¥ {{$order->freight}}元
+
+
+ 支付总额: ¥ {{$order->total}}元
+
+
+ |
+ {{ $order->state_text }} |
+ {{ $order->payment->driver_text ?? '---' }} |
+ {{ $order->paid_at }} |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | id |
+ 商品名称 |
+ {{-- 属性 | --}}
+ 数量 |
+ 单价 |
+ {{-- 水晶 | --}}
+ {{-- 退款/退货 | --}}
+
+
+
+ @foreach ($order->items as $item)
+
+ | {{ $item->id }} |
+ {{ $item->source['goods_name'] }} |
+ {{-- {{ $item->source['unit']??'---' }} | --}}
+ {{ $item->qty }} |
+ {{ $item->price }} |
+ {{-- {{ $item->source['score'] }} | --}}
+ {{-- {{ $item->isRefund()?'是':'否' }} | --}}
+
+ @endforeach
+
+
+
+
+
+
+
+ @if ($order->express)
+
+
+
+
+
+
+
+
+ | Id |
+ 类型 |
+ 收件人 |
+ 电话 |
+ 地址 |
+ @if($order->express->express_no)
+ 物流公司 |
+ 物流单号 |
+ @endif
+ 经办人 |
+
+
+
+
+ | {{ $order->express->id }} |
+ {{ $order->express->type_text }} |
+ {{ $order->express->name }} |
+ {{ $order->express->mobile }} |
+ {{ $order->express->getFullAddress() }} |
+ @if($order->express->express_no)
+ 物流公司:{{$order->express->express->name}} |
+ 物流单号:{{$order->express->express_no }} |
+ @endif
+ {{ $order->express->person }} |
+
+
+
+
+
+
+
+ @endif
+
+ @if ($order->payments->isNotEmpty())
+
+
+
+
+
+
+
+
+ | Id |
+ 支付方式 |
+ 支付金额(元) |
+ 流水号 |
+ 支付单号 |
+ 状态 |
+ 支付时间 |
+
+
+
+ @foreach ($order->payments as $payment)
+
+ | {{ $payment->id }} |
+ {{ $payment->driver_text }} |
+ {{ $payment->total }} |
+ {{ $payment->trade_id }} |
+ {{ $payment->transaction_id }} |
+ {{ $payment->state_text }} |
+ {{ $payment->created_at }} |
+
+ @endforeach
+
+
+
+
+
+
+ @endif
+
+ @if ($order->refunds->isNotEmpty())
+
+
+
+
+
+
+
+
+ | 退款/货单号 |
+ 申请退款金额 |
+ 实退金额 |
+ 状态 |
+ 申请时间 |
+ 退款时间 |
+
+
+
+ @foreach ($order->refunds as $refund)
+
+ | {{ $refund->refund_no }} |
+ {{ $refund->refund_total }} |
+ {{ $refund->actual_total }} |
+ {{ $refund->state_text }} |
+ {{ $refund->created_at }} |
+ {{ $refund->refunded_at }} |
+
+ @endforeach
+
+
+
+
+
+
+ @endif
+
+
diff --git a/modules/Mall/Resources/views/admin/stock_order/detail.blade.php b/modules/Mall/Resources/views/admin/stock_order/detail.blade.php
new file mode 100644
index 0000000..e51adbf
--- /dev/null
+++ b/modules/Mall/Resources/views/admin/stock_order/detail.blade.php
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
+ @foreach(\Modules\Mall\Models\Order::STATUS_MAP as $key=>$status)
+ - state==$key) class="act" @endif>{{ $status }}
+ @endforeach
+
+
+
+
+
+
+
+
+
+
+
+ | 订单信息 |
+ 购买人 |
+ 收货信息 |
+ 费用信息 |
+ 订单状态 |
+ 支付渠道 |
+ 支付时间 |
+
+
+
+
+ | {{ $order->order_no }} |
+ {{ $order->user->info->nickname }} |
+
+ @if ($order->express)
+ 收件人:{{$order->express->name}}
+ 电话:{{$order->express->mobile}}
+ 地址:{{$order->express->getFullAddress()}}
+
+ @endif
+ |
+
+ 商品总额:¥ {{$order->amount}}元
+ 订单总额:¥ {{$order->total}}元
+ 说明: {{$order->remark}}
+
+ 运费:¥ {{$order->freight}}元
+
+
+ 支付总额: ¥ {{$order->total}}元
+
+
+ |
+ {{ $order->state_text }} |
+ {{ $order->payment->driver_text ?? '---' }} |
+ {{ $order->paid_at }} |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | id |
+ 商品名称 |
+ {{-- 属性 | --}}
+ 数量 |
+ 单价 |
+ {{-- 水晶 | --}}
+ {{-- 退款/退货 | --}}
+
+
+
+ @foreach ($order->items as $item)
+
+ | {{ $item->id }} |
+ {{ $item->source['goods_name'] }} |
+ {{-- {{ $item->source['unit']??'---' }} | --}}
+ {{ $item->qty }} |
+ {{ $item->price }} |
+ {{-- {{ $item->source['score'] }} | --}}
+ {{-- {{ $item->isRefund()?'是':'否' }} | --}}
+
+ @endforeach
+
+
+
+
+
+
+
+ @if ($order->express)
+
+
+
+
+
+
+
+
+ | Id |
+ 类型 |
+ 收件人 |
+ 电话 |
+ 地址 |
+ @if($order->express->express_no)
+ 物流公司 |
+ 物流单号 |
+ @endif
+ 经办人 |
+
+
+
+
+ | {{ $order->express->id }} |
+ {{ $order->express->type_text }} |
+ {{ $order->express->name }} |
+ {{ $order->express->mobile }} |
+ {{ $order->express->getFullAddress() }} |
+ @if($order->express->express_no)
+ 物流公司:{{$order->express->express->name}} |
+ 物流单号:{{$order->express->express_no }} |
+ @endif
+ {{ $order->express->person }} |
+
+
+
+
+
+
+
+ @endif
+
+ @if ($order->payments->isNotEmpty())
+
+
+
+
+
+
+
+
+ | Id |
+ 支付方式 |
+ 支付金额(元) |
+ 流水号 |
+ 支付单号 |
+ 状态 |
+ 支付时间 |
+
+
+
+ @foreach ($order->payments as $payment)
+
+ | {{ $payment->id }} |
+ {{ $payment->driver_text }} |
+ {{ $payment->total }} |
+ {{ $payment->trade_id }} |
+ {{ $payment->transaction_id }} |
+ {{ $payment->state_text }} |
+ {{ $payment->created_at }} |
+
+ @endforeach
+
+
+
+
+
+
+ @endif
+
+ @if ($order->refunds->isNotEmpty())
+
+
+
+
+
+
+
+
+ | 退款/货单号 |
+ 申请退款金额 |
+ 实退金额 |
+ 状态 |
+ 申请时间 |
+ 退款时间 |
+
+
+
+ @foreach ($order->refunds as $refund)
+
+ | {{ $refund->refund_no }} |
+ {{ $refund->refund_total }} |
+ {{ $refund->actual_total }} |
+ {{ $refund->state_text }} |
+ {{ $refund->created_at }} |
+ {{ $refund->refunded_at }} |
+
+ @endforeach
+
+
+
+
+
+
+ @endif
+
+
diff --git a/modules/Mall/Routes/admin.php b/modules/Mall/Routes/admin.php
new file mode 100644
index 0000000..1c395d4
--- /dev/null
+++ b/modules/Mall/Routes/admin.php
@@ -0,0 +1,85 @@
+ 'mall',
+ 'namespace' => 'Admin',
+ 'as' => 'mall.',
+], function (Router $router) {
+ $router->get('dashboard', 'DashboardController@index');
+ /**
+ * 版本记录
+ */
+ $router->get('versions/{model}/{key}', 'VersionController@index')->name('versions');
+ /**
+ * 店铺管理
+ */
+ $router->get('shops/ajax', 'ShopController@ajax')->name('shops.ajax');
+ $router->resource('shops', 'ShopController');
+ $router->resource('shops.staffers', 'StafferController');
+ $router->resource('jobs', 'JobController');
+ /**
+ * 订单管理
+ */
+ $router->resource('orders', 'OrderController')->only(['index', 'show', 'create', 'store']);
+ $router->resource('stock_orders', 'StockOrderController')->only(['index', 'show']);
+ $router->resource('stock_orders_by_system', 'StockOrderBySystemController')->only([
+ 'index', 'show', 'create', 'store'
+ ]);
+ $router->resource('refunds', 'RefundController')->only(['index', 'show']);
+ /**
+ * 商品分类,品牌
+ */
+ $router->get('categories/ajax', 'CategoryController@ajax')->name('categories.ajax');
+ $router->resource('categories', 'CategoryController');
+ $router->get('brands/ajax', 'BrandController@ajax')->name('brands.ajax');
+ $router->resource('brands', 'BrandController');
+ $router->get('tags/ajax', 'TagController@ajax')->name('tags.ajax');
+ $router->resource('tags', 'TagController');
+ /**
+ * 商品管理
+ */
+ $router->resource('goods', 'GoodsController');
+ $router->resource('goods.skus', 'SkuController');
+ $router->resource('goods.specs', 'SpecController');
+ $router->resource('goods.specs.values', 'SpecValueController');
+ /**
+ * 物流管理
+ */
+ $router->resource('expresses', 'ExpressController');
+ /**
+ * 运费模板
+ */
+ $router->get('deliveries/ajax', 'DeliveryController@ajax')->name('deliveries.ajax');
+ $router->resource('deliveries', 'DeliveryController');
+ $router->resource('deliveries.rules', 'DeliveryRuleController');
+ /**
+ * 地域组件
+ */
+ $router->get('regions/ajax', 'RegionController@ajax')->name('regions.ajax');
+ $router->get('regions/getRegion', 'RegionController@region')->name('regions.region');
+ /**
+ * 商城轮播图
+ */
+ $router->resource('banners', 'BannerController');
+
+ /**
+ * 退款/货原因
+ */
+ $router->resource('reasons', 'ReasonController');
+
+ /**
+ * 收货地址
+ */
+ $router->get('ajax/address', 'AjaxController@address')->name('ajax.address');
+ $router->get('ajax/goods', 'AjaxController@goods')->name('ajax.goods');
+ $router->resource('addresses', 'AddressController');
+ $router->resource('activities', 'ActivityController');
+ /**
+ * 视频管理
+ */
+ $router->resource('videos', 'VideoController');
+
+});
diff --git a/modules/Mall/Routes/agent.php b/modules/Mall/Routes/agent.php
new file mode 100644
index 0000000..842fafe
--- /dev/null
+++ b/modules/Mall/Routes/agent.php
@@ -0,0 +1,14 @@
+ 'mall',
+ 'namespace' => 'Agent',
+ 'as' => 'mall.',
+], function (Router $router) {
+ $router->get('', 'IndexController@index');
+ // 店铺管理
+ $router->get('shop', 'ShopController@index');
+});
diff --git a/modules/Mall/Routes/api.php b/modules/Mall/Routes/api.php
new file mode 100644
index 0000000..5c0c49d
--- /dev/null
+++ b/modules/Mall/Routes/api.php
@@ -0,0 +1,124 @@
+ 'mall',
+ 'namespace' => 'Api',
+], function (Router $router) {
+
+ /**
+ * 轮播图
+ */
+ $router->get('banners', 'BannerController@index');
+ /**
+ * 商品分类
+ */
+ $router->get('categories', 'CategoryController@index');
+ /**
+ * 商品分类
+ */
+ $router->get('tags', 'TagController@index');
+ /**
+ * 店铺列表
+ */
+ $router->get('shops', 'ShopController@index');
+ $router->get('shops/{shop}', 'ShopController@show')->where('shop', '[0-9]+');
+ $router->get('shops/{shop}/categories', 'ShopExtendController@categories');
+ $router->get('shops/{shop}/tags', 'ShopExtendController@tags');
+ $router->get('shops/{shop}/brands', 'ShopExtendController@brands');
+ $router->get('shops/{shop}/expresses', 'ShopExtendController@expresses');
+ $router->get('shops/{shop}/banners', 'ShopExtendController@banners');
+ /**
+ * 商品管理
+ */
+ $router->get('goods', 'GoodsController@index');
+});
+
+Route::group([
+ 'prefix' => 'mall',
+ 'namespace' => 'Api',
+ 'middleware' => config('api.route.middleware_guess'),
+], function (Router $router) {
+ /**
+ * 商城的首页
+ */
+ $router->get('', 'IndexController@index');
+ $router->get('carts/count', 'CartController@count');
+ $router->get('goods/{goods}', 'GoodsController@show');
+ /**
+ * 活动接口
+ */
+ $router->get('activities', 'ActivityController@index');
+ $router->get('activities/{activity}', 'ActivityController@show');
+
+});
+
+Route::group([
+ 'prefix' => 'mall',
+ 'namespace' => 'Api',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ /**
+ * 店铺操作
+ */
+ $router->get('shops/create', 'ShopController@create');
+ $router->post('shops', 'ShopController@store');
+ $router->get('shops/edit', 'ShopController@edit');
+ $router->put('shops', 'ShopController@update');
+ /**
+ * 购物车
+ */
+ $router->get('carts', 'CartController@index');
+ $router->post('carts', 'CartController@store');
+ $router->put('carts/{cart}', 'CartController@update');
+ $router->delete('carts/{cart}', 'CartController@destroy');
+ /**
+ * 订单管理
+ */
+ $router->get('orders/counts', 'OrderController@counts');
+ $router->get('orders', 'OrderController@index');
+ // 订单详情 可以通过 order_no 来查询
+ $router->get('orders/{order}', 'OrderController@show')->where(['order' => '[0-9]+']);
+ // 创建订单
+ $router->get('buy/goods', 'OrderBuyController@goodsCreate');//商品确认订单
+ $router->post('buy/goods', 'OrderBuyController@goodsBuy'); //商品购买
+ $router->get('buy/carts', 'OrderBuyController@cartsCreate');//购物车确认订单
+ $router->post('buy/carts', 'OrderBuyController@cartsBuy'); //购物车购买
+
+ $router->get('buy/samples', 'OrderBuyController@sampleCreate');//试用商品
+ $router->post('buy/samples', 'OrderBuyController@sampleBuy'); //试用商品购买
+
+ // 更新订单,地址或其他信息
+ $router->put('orders/{order}', 'OrderController@update')->where(['order' => '[0-9]+']);
+ // 查看物流
+ $router->get('orders/{order}/logistic', 'OrderController@logistic')->where(['order' => '[0-9]+']);
+ // 签收订单
+ $router->put('orders/{order}/sign', 'OrderController@sign')->where(['order' => '[0-9]+']);
+ // 取消订单
+ $router->put('orders/{order}/cancel', 'OrderController@cancel')->where(['order' => '[0-9]+']);
+ // 删除订单
+ $router->delete('orders/{order}', 'OrderController@destroy')->where(['order' => '[0-9]+']);
+ //退款
+ $router->get('orders/{order}/refund', 'OrderController@goods')->where(['order' => '[0-9]+']);
+ $router->post('orders/{order}/refund', 'OrderController@refund')->where(['order' => '[0-9]+']);
+ //订单支付
+ $router->get('pay/{order}/wechat', 'PayController@wechat')->where(['order' => '[0-9]+']);
+ $router->get('pay/{order}/alipay', 'PayController@alipay')->where(['order' => '[0-9]+']);
+ $router->post('pay/{order}/score', 'PayController@score')->where(['order' => '[0-9]+']);
+ //退货
+ $router->post('refunds/{refund}/deliver', 'RefundController@deliver')->where(['refund' => '[0-9]+']);
+
+ //退款售后
+ $router->get('refunds', 'RefundController@index');
+ $router->get('refunds/{refund}', 'RefundController@show')->where(['refund' => '[0-9]+']);
+ $router->get('refunds/{refund}/logs', 'RefundController@logs')->where(['refund' => '[0-9]+']);
+
+ /**
+ * 收货地址
+ */
+ $router->post('addresses/{address}/default', 'AddressController@setDefault');
+ $router->resource('addresses', 'AddressController');
+
+});
diff --git a/modules/Mall/Rules/CityRule.php b/modules/Mall/Rules/CityRule.php
new file mode 100644
index 0000000..b79646a
--- /dev/null
+++ b/modules/Mall/Rules/CityRule.php
@@ -0,0 +1,38 @@
+
+ * @Date : 2020/11/6 9:56 上午
+ * @param string $attribute
+ * @param mixed $value
+ * @return bool
+ */
+ public function passes($attribute, $value): bool
+ {
+ $city = Region::find($value);
+ if ($city->depth != 2) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 获取校验错误信息
+ * @return string
+ */
+ public function message(): string
+ {
+ return '市区信息选择有误';
+ }
+
+}
diff --git a/modules/Mall/Rules/DistrictRule.php b/modules/Mall/Rules/DistrictRule.php
new file mode 100644
index 0000000..8d1990f
--- /dev/null
+++ b/modules/Mall/Rules/DistrictRule.php
@@ -0,0 +1,38 @@
+
+ * @Date : 2020/11/6 9:56 上午
+ * @param string $attribute
+ * @param mixed $value
+ * @return bool
+ */
+ public function passes($attribute, $value): bool
+ {
+ $district = Region::find($value);
+ if ($district->depth != 3) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 获取校验错误信息
+ * @return string
+ */
+ public function message(): string
+ {
+ return '区县信息选择有误';
+ }
+
+}
diff --git a/modules/Mall/Rules/ProvinceRule.php b/modules/Mall/Rules/ProvinceRule.php
new file mode 100644
index 0000000..6972c34
--- /dev/null
+++ b/modules/Mall/Rules/ProvinceRule.php
@@ -0,0 +1,38 @@
+
+ * @Date : 2020/11/6 9:56 上午
+ * @param string $attribute
+ * @param mixed $value
+ * @return bool
+ */
+ public function passes($attribute, $value)
+ {
+ $province = Region::find($value);
+ if ($province->depth != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 获取校验错误信息
+ * @return string
+ */
+ public function message()
+ {
+ return '省份信息选择有误';
+ }
+
+}
diff --git a/modules/Mall/Traits/HasAddresses.php b/modules/Mall/Traits/HasAddresses.php
new file mode 100644
index 0000000..68be014
--- /dev/null
+++ b/modules/Mall/Traits/HasAddresses.php
@@ -0,0 +1,22 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function addresses(): HasMany
+ {
+ return $this->hasMany(Address::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Traits/HasCart.php b/modules/Mall/Traits/HasCart.php
new file mode 100644
index 0000000..0a03e48
--- /dev/null
+++ b/modules/Mall/Traits/HasCart.php
@@ -0,0 +1,22 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function carts(): HasMany
+ {
+ return $this->hasMany(Cart::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Traits/HasOrders.php b/modules/Mall/Traits/HasOrders.php
new file mode 100644
index 0000000..9346f5b
--- /dev/null
+++ b/modules/Mall/Traits/HasOrders.php
@@ -0,0 +1,53 @@
+
+ * @return HasMany
+ */
+ public function orders(): HasMany
+ {
+ return $this->hasMany(Order::class);
+ }
+
+ /**
+ * Notes: 是否可以申请提货
+ *
+ * @Author: 玄尘
+ * @Date : 2021/10/22 14:13
+ * @return bool
+ */
+ public function canPick(): bool
+ {
+ return $this->case && $this->case->status == GoutCase::STATUS_PASS &&
+ $this->identities()->where('order', '>', 1)->count() > 0 &&
+ $this->userStock->stock > $this->userStock->hold;
+ }
+
+ /**
+ * Notes: 提货
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/27 16:18
+ */
+ public function pickGoods()
+ {
+ if (! $this->canPick()) {
+ return '不可提货';
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Traits/HasShop.php b/modules/Mall/Traits/HasShop.php
new file mode 100644
index 0000000..13e59f0
--- /dev/null
+++ b/modules/Mall/Traits/HasShop.php
@@ -0,0 +1,22 @@
+
+ * @return \Illuminate\Database\Eloquent\Relations\HasOne
+ */
+ public function shop(): HasOne
+ {
+ return $this->hasOne(Shop::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/Traits/WithWorkflow.php b/modules/Mall/Traits/WithWorkflow.php
new file mode 100644
index 0000000..b82793d
--- /dev/null
+++ b/modules/Mall/Traits/WithWorkflow.php
@@ -0,0 +1,48 @@
+
+ * @param $transition
+ * @param null $workflow
+ * @return mixed
+ */
+ public function apply($transition, $workflow = null)
+ {
+ return Workflow::get($this, $workflow)->apply($this, $transition);
+ }
+
+ /**
+ * Notes : 是否可以执行
+ * @Date : 2021/5/8 5:36 下午
+ * @Author : < Jason.C >
+ * @param $transition
+ * @param null $workflow
+ * @return mixed
+ */
+ public function can($transition, $workflow = null)
+ {
+ return Workflow::get($this, $workflow)->can($this, $transition);
+ }
+
+ /**
+ * Notes : 可执行的操作
+ * @Date : 2021/5/8 5:37 下午
+ * @Author : < Jason.C >
+ * @param null $workflow
+ * @return mixed
+ */
+ public function transitions($workflow = null)
+ {
+ return Workflow::get($this, $workflow)->getEnabledTransitions($this);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Mall/composer.json b/modules/Mall/composer.json
new file mode 100644
index 0000000..0012e20
--- /dev/null
+++ b/modules/Mall/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "uztech/mall-module",
+ "description": "多用户商城模块",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Jason.Chen",
+ "email": "chenjxlg@163.com"
+ }
+ ],
+ "require": {
+ "php": "^7.3|^8.0",
+ "encore/laravel-admin": "^1.8",
+ "genealabs/laravel-model-caching": "^0.11.3",
+ "jasonc/api": "^5.0.0",
+ "joshbrw/laravel-module-installer": "^2.0",
+ "laravel/framework": "^8.5",
+ "maatwebsite/excel": "^3.1",
+ "nwidart/laravel-modules": "^8.2",
+ "propaganistas/laravel-phone": "^4.3",
+ "overtrue/laravel-versionable": "^2.6",
+ "symfony/workflow": "^5.2",
+ "wang-tech-commits/kuaidi100-api": "^1.0"
+ },
+ "extra": {
+ "module-dir": "modules",
+ "laravel": {
+ "providers": [],
+ "aliases": {
+ }
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Shop\\": ""
+ }
+ }
+}
diff --git a/modules/Mall/docs/README.md b/modules/Mall/docs/README.md
new file mode 100644
index 0000000..69957f1
--- /dev/null
+++ b/modules/Mall/docs/README.md
@@ -0,0 +1,25 @@
+# 商城模块文档
+
+## 1. 安装
+
+```shell
+composer require maatwebsite/excel
+composer require propaganistas/laravel-phone
+composer require overtrue/laravel-versionable
+composer require symfony/workflow
+composer require wang-tech-commits/kuaidi100-api
+```
+
+## 2. Traits 说明
+
+### 1. HasAddresses
+
+对象拥有收货地址,可对收货地址管理
+
+### 2. HasCart
+
+### 3. HasOrders
+
+### 4. HasShop
+
+### 5. WithWorkflow
\ No newline at end of file
diff --git a/modules/Mall/docs/商品接口.md b/modules/Mall/docs/商品接口.md
new file mode 100644
index 0000000..e69de29
diff --git a/modules/Mall/module.json b/modules/Mall/module.json
new file mode 100644
index 0000000..646ff8e
--- /dev/null
+++ b/modules/Mall/module.json
@@ -0,0 +1,20 @@
+{
+ "name": "Mall",
+ "alias": "mall",
+ "description": "多用户商城模块",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\Mall\\Providers\\MallServiceProvider",
+ "Modules\\Mall\\Providers\\EventServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [
+ "User"
+ ],
+ "config": true,
+ "configName": "用户模块",
+ "version": "1.0.0",
+ "author": "Jason.Chen"
+}
diff --git a/modules/Notification/Channels/AppChannel.php b/modules/Notification/Channels/AppChannel.php
new file mode 100644
index 0000000..865bc53
--- /dev/null
+++ b/modules/Notification/Channels/AppChannel.php
@@ -0,0 +1,45 @@
+bindAlias();
+ // $api = new GTClient("https://restapi.getui.com", "APPKEY", "APPID", "MASTERSECRET");
+ // 设置推送参数
+ // $push = new \GTPushRequest();
+ // $push->setRequestId(uniqid());
+ // $message = new GTPushMessage();
+ // $notify = new GTNotification();
+ // $notify->setTitle("设置通知标题");
+ // $notify->setBody("设置通知内容");
+ // // 点击通知后续动作,目前支持以下后续动作:
+ // // 1、intent:打开应用内特定页面url:打开网页地址。
+ // // 2、payload:自定义消息内容启动应用。
+ // // 3、payload_custom:自定义消息内容不启动应用。
+ // // 4、startapp:打开应用首页。
+ // // 5、none:纯通知,无后续动作
+ // $notify->setClickType("none");
+ // $message->setNotification($notify);
+ // $push->setPushMessage($message);
+ // $push->setCid("CID");
+ // //处理返回结果
+ // $result = $api->pushApi()->pushToSingleByCid($push);
+
+ $notification->toApp($notifiable);
+ }
+
+}
diff --git a/modules/Notification/Config/config.php b/modules/Notification/Config/config.php
new file mode 100644
index 0000000..bc1883c
--- /dev/null
+++ b/modules/Notification/Config/config.php
@@ -0,0 +1,34 @@
+ 'Notification',
+
+ /*
+ |--------------------------------------------------------------------------
+ | 消息分类
+ |--------------------------------------------------------------------------
+ */
+ 'types' => [
+ 'App\\Notifications\\SystemOpenVip' => [
+ 'name' => '开通会员',
+ 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_04.jpg',
+ ],
+ 'App\\Notifications\\SystemOrderDelivered' => [
+ 'name' => '发货',
+ 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_02.png',
+ ],
+ 'App\\Notifications\\SystemRemindUserSign' => [
+ 'name' => '打卡',
+ 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_01.png',
+ ],
+ 'App\\Notifications\\SystemUpdateCase' => [
+ 'name' => '上传报告',
+ 'icon' => 'http://api.siyuankunlun.com/storage/materials/2022/08/09/newsIcon_05.png',
+ ],
+ ],
+];
\ No newline at end of file
diff --git a/modules/Notification/Database/Migrations/0000_00_00_000000_create_notification_templates_table.php b/modules/Notification/Database/Migrations/0000_00_00_000000_create_notification_templates_table.php
new file mode 100644
index 0000000..0fca391
--- /dev/null
+++ b/modules/Notification/Database/Migrations/0000_00_00_000000_create_notification_templates_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->string('title')->comment('模板标题');
+ $table->string('slug')->unique()->comment('唯一标识');
+ $table->text('content')->comment('模板内容');
+ $table->boolean('status')->unsigned()->default(0);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('notification_templates');
+ }
+
+}
diff --git a/modules/Notification/Http/Controllers/Admin/IndexController.php b/modules/Notification/Http/Controllers/Admin/IndexController.php
new file mode 100644
index 0000000..2411401
--- /dev/null
+++ b/modules/Notification/Http/Controllers/Admin/IndexController.php
@@ -0,0 +1,47 @@
+column('title', '模板标题');
+ $grid->column('slug', '调用标识');
+ $grid->column('status', '状态')->bool();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form((new Template())->disableModelCaching());
+
+ $form->text('title', '模板标题')
+ ->required();
+ $form->text('slug', '调用标识')
+ ->required();
+ $form->textarea('content', '模板内容')
+ ->required();
+ $form->switch('status', '状态');
+
+ $this->withUrl($form);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Notification/Http/Controllers/Admin/SettingController.php b/modules/Notification/Http/Controllers/Admin/SettingController.php
new file mode 100644
index 0000000..2735fa2
--- /dev/null
+++ b/modules/Notification/Http/Controllers/Admin/SettingController.php
@@ -0,0 +1,17 @@
+
+ * @return JsonResponse
+ */
+ public function index(): JsonResponse
+ {
+ $user = Api::user();
+
+ $result = [];
+ foreach (config('notification.types') as $type => $name) {
+ $result[] = [
+ 'type' => Str::remove('App\\Notifications\\', $type),
+ 'icon' => $name['icon'],
+ 'name' => $name['name'],
+ 'count' => $user->unreadNotifications()->where('type', $type)->count(),
+ 'title' => data_get($user->unreadNotifications()->where('type', $type)->latest()->value('data'),
+ 'title') ?? '',
+ ];
+ }
+
+ return $this->success($result);
+ }
+
+ /**
+ * Notes : 未读消息数量
+ *
+ * @Date : 2021/5/20 12:10 下午
+ * @Author :
+ */
+ public function counts(): JsonResponse
+ {
+ $user = Api::user();
+
+ if ($user) {
+ $count = $user->unreadNotifications()->count();
+ } else {
+ $count = 0;
+ }
+
+ return $this->success($count);
+ }
+
+ /**
+ * Notes : 分类读取消息
+ *
+ * @Date : 2021/5/18 3:12 下午
+ * @Author :
+ * @param string $type
+ * @return JsonResponse
+ */
+ public function type(string $type): JsonResponse
+ {
+ $list = DatabaseNotification::query()
+ ->whereHasMorph(
+ 'notifiable',
+ User::class,
+ function (Builder $query) {
+ $query->where('id', Api::userId());
+ }
+ )
+ ->where('type', 'like', "%$type")
+ ->orderBy('read_at')
+ ->orderByDesc('created_at')->paginate();
+
+ return $this->success(new NotificationCollection($list));
+ }
+
+ /**
+ * Notes : 消息详情,自动标记已读
+ *
+ * @Date : 2021/5/18 3:17 下午
+ * @Author :
+ * @param string $id
+ * @return JsonResponse
+ */
+ public function show(string $id): JsonResponse
+ {
+ $notification = DatabaseNotification::findOrFail($id);
+
+ $notification->markAsRead();
+
+ return $this->success(new NotificationResource($notification));
+ }
+
+ /**
+ * Notes : 全部标记已读
+ *
+ * @Date : 2021/5/18 3:22 下午
+ * @Author :
+ */
+ public function markAsRead(string $type = null): JsonResponse
+ {
+ $user = Api::user();
+
+ $user->unreadNotifications()->when($type, function ($query) use ($type) {
+ $query->where('type', 'like', "%$type");
+ })->update(['read_at' => now()]);
+
+ return $this->success('');
+ }
+
+ /**
+ * Notes : 清空消息
+ *
+ * @Date : 2021/5/18 5:49 下午
+ * @Author :
+ * @param string|null $type
+ * @return JsonResponse
+ */
+ public function clean(string $type = null): JsonResponse
+ {
+ $user = Api::user();
+
+ $user->notifications()->when($type, function ($query) use ($type) {
+ $query->where('type', 'like', "%$type");
+ })->delete();
+
+ return $this->success('');
+ }
+
+}
diff --git a/modules/Notification/Http/Resources/NotificationCollection.php b/modules/Notification/Http/Resources/NotificationCollection.php
new file mode 100644
index 0000000..f96bd69
--- /dev/null
+++ b/modules/Notification/Http/Resources/NotificationCollection.php
@@ -0,0 +1,20 @@
+ $this->collection->map(function ($notification) {
+ return new NotificationResource($notification);
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Notification/Http/Resources/NotificationResource.php b/modules/Notification/Http/Resources/NotificationResource.php
new file mode 100644
index 0000000..7056345
--- /dev/null
+++ b/modules/Notification/Http/Resources/NotificationResource.php
@@ -0,0 +1,23 @@
+ $this->id,
+ 'type' => Str::remove('App\\Notifications\\', $this->type),
+ 'title' => $this->data['title'] ?? '',
+ 'content' => $this->data['content'] ?? '',
+ 'read_at' => (string) $this->read_at,
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Notification/Models/Template.php b/modules/Notification/Models/Template.php
new file mode 100644
index 0000000..803a336
--- /dev/null
+++ b/modules/Notification/Models/Template.php
@@ -0,0 +1,17 @@
+
+ */
+ public static function install()
+ {
+ Artisan::call('migrate', [
+ '--path' => 'modules/Notification/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ protected static function createAdminMenu()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 40,
+ 'title' => self::$mainTitle,
+ 'icon' => 'fa-commenting-o',
+ ]);
+
+ $main->children()->createMany([
+ [
+ 'order' => 1,
+ 'title' => '消息模板',
+ 'icon' => 'fa-dashboard',
+ 'uri' => 'notifications/templates',
+ ],
+ [
+ 'order' => 2,
+ 'title' => '消息设置',
+ 'icon' => 'fa-cogs',
+ 'uri' => 'notifications/settings',
+ ],
+ ]);
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ *
+ * @Date : 2021/3/12 11:35 上午
+ * @Author :
+ */
+ public static function uninstall()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $mains = $menu::where('title', self::$mainTitle)->get();
+
+ foreach ($mains as $main) {
+ $main->delete();
+ }
+ }
+
+}
diff --git a/modules/Notification/Providers/NotificationServiceProvider.php b/modules/Notification/Providers/NotificationServiceProvider.php
new file mode 100644
index 0000000..86ff431
--- /dev/null
+++ b/modules/Notification/Providers/NotificationServiceProvider.php
@@ -0,0 +1,67 @@
+loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->registerConfig();
+
+ $this->app->register(RouteServiceProvider::class);
+ }
+
+ /**
+ * Register config.
+ *
+ * @return void
+ */
+ protected function registerConfig()
+ {
+ $this->publishes([
+ module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'),
+ ], 'config');
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
+ );
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides(): array
+ {
+ return [];
+ }
+
+}
diff --git a/modules/Notification/Providers/RouteServiceProvider.php b/modules/Notification/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..83dc6c8
--- /dev/null
+++ b/modules/Notification/Providers/RouteServiceProvider.php
@@ -0,0 +1,65 @@
+mapAdminRoutes();
+ $this->mapApiRoutes();
+ }
+
+ protected function mapApiRoutes()
+ {
+ Route::as(config('api.route.as'))
+ ->domain(config('api.route.domain'))
+ ->middleware(config('api.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('api.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/api.php'));
+ }
+
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+
+}
diff --git a/modules/Notification/README.md b/modules/Notification/README.md
new file mode 100644
index 0000000..aa32b2c
--- /dev/null
+++ b/modules/Notification/README.md
@@ -0,0 +1,154 @@
+# Notification
+
+通知模块
+
+## 1. 安装
+
+```shell
+composer require getuilaboratory/getui-pushapi-php-client-v2
+```
+
+## 2. 接口文档
+
+### 1. 获取未读消息数量(不登陆返回0)
+
+> GET: /api/notifications/counts
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": 16
+}
+```
+
+### 2. 消息主页
+
+> GET: /api/notifications
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": [
+ {
+ "type": "SystemNotification",
+ "icon": "",
+ "name": "系统通知",
+ "count": 16,
+ "title": "登录成功"
+ },
+ {
+ "type": "OrderNotification",
+ "icon": "",
+ "name": "订单通知",
+ "count": 0,
+ "title": ""
+ },
+ {
+ "type": "ActivityNotification",
+ "icon": "",
+ "name": "活动通知",
+ "count": 0,
+ "title": ""
+ }
+ ]
+}
+```
+
+### 3. 分类消息的列表
+
+> GET: /api/notifications/{type}/list
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": {
+ "data": [
+ {
+ "notification_id": "91e31d1e-95e1-40ff-88d8-8049df974860",
+ "type": "SystemNotification",
+ "title": "登录成功",
+ "content": "您于北京时间:2021-07-02 15:31:03,通过【账号密码】渠道,在IP:1.190.203.218成功登录系统。请注意保管好您的账号密码。",
+ "read_at": "",
+ "created_at": "2021-07-02 15:31:03"
+ }
+ ],
+ "page": {
+ "current": 1,
+ "total_page": 2,
+ "per_page": 15,
+ "has_more": true,
+ "total": 16
+ }
+ }
+}
+```
+
+### 4. 消息详情
+
+> GET: /api/notifications/{notification_id}
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": {
+ "notification_id": "91e31d1e-95e1-40ff-88d8-8049df974860",
+ "type": "SystemNotification",
+ "title": "登录成功",
+ "content": "您于北京时间:2021-07-02 15:31:03,通过【账号密码】渠道,在IP:1.190.203.218成功登录系统。请注意保管好您的账号密码。",
+ "read_at": "2021-07-02 16:27:07",
+ "created_at": "2021-07-02 15:31:03"
+ }
+}
+```
+
+### 5. 全部标记已读
+
+> PUT: /api/notifications
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": ""
+}
+```
+
+### 6. 全部标记已读(分类)
+
+> PUT: /api/notifications/{type}
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": ""
+}
+```
+
+### 7. 清空全部消息
+
+> DELETE: /api/notifications
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": ""
+}
+```
+
+### 8. 清空分类消息
+
+> DELETE: /api/notifications/{type}
+
+```json
+{
+ "status": "SUCCESS",
+ "status_code": 200,
+ "data": ""
+}
+```
\ No newline at end of file
diff --git a/modules/Notification/Routes/admin.php b/modules/Notification/Routes/admin.php
new file mode 100644
index 0000000..ce83f58
--- /dev/null
+++ b/modules/Notification/Routes/admin.php
@@ -0,0 +1,13 @@
+ 'notifications',
+ 'namespace' => 'Admin',
+ 'as' => 'notification.',
+], function (Router $router) {
+ $router->resource('templates', 'IndexController');
+ $router->get('settings', 'SettingController@index');
+});
diff --git a/modules/Notification/Routes/api.php b/modules/Notification/Routes/api.php
new file mode 100644
index 0000000..1369fa8
--- /dev/null
+++ b/modules/Notification/Routes/api.php
@@ -0,0 +1,26 @@
+ 'notifications',
+ 'namespace' => 'Api',
+ 'middleware' => config('api.route.middleware_guess'),
+], function (Router $router) {
+ // 用户未读消息数量,不登陆返回0
+ $router->get('counts', 'IndexController@counts');
+});
+
+Route::group([
+ 'prefix' => 'notifications',
+ 'namespace' => 'Api',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('', 'IndexController@index');
+ $router->get('{type}/list', 'IndexController@type');
+ $router->put('{type?}', 'IndexController@markAsRead');
+ $router->delete('{type?}', 'IndexController@clean');
+ $router->get('{id}', 'IndexController@show');
+});
+
diff --git a/modules/Notification/composer.json b/modules/Notification/composer.json
new file mode 100644
index 0000000..f47780e
--- /dev/null
+++ b/modules/Notification/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "uztech/notification-module",
+ "description": "",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Jason.Chen",
+ "email": "chenjxlg@163.com"
+ }
+ ],
+ "require": {
+ "genealabs/laravel-model-caching": "^0.11.3",
+ "getuilaboratory/getui-pushapi-php-client-v2": "dev-master"
+ },
+ "extra": {
+ "module-dir": "modules"
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Notification\\": ""
+ }
+ }
+}
diff --git a/modules/Notification/module.json b/modules/Notification/module.json
new file mode 100644
index 0000000..d657f67
--- /dev/null
+++ b/modules/Notification/module.json
@@ -0,0 +1,17 @@
+{
+ "name": "Notification",
+ "alias": "notification",
+ "description": "消息通知管理模块",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\Notification\\Providers\\NotificationServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [
+ "Linker"
+ ],
+ "version": "1.0.0",
+ "author": "Jason.Chen"
+}
diff --git a/modules/Payment/.gitignore b/modules/Payment/.gitignore
new file mode 100644
index 0000000..4b6cb11
--- /dev/null
+++ b/modules/Payment/.gitignore
@@ -0,0 +1,4 @@
+.idea
+vendor
+.DS_Store
+composer.lock
\ No newline at end of file
diff --git a/modules/Payment/Config/config.php b/modules/Payment/Config/config.php
new file mode 100644
index 0000000..b2105e5
--- /dev/null
+++ b/modules/Payment/Config/config.php
@@ -0,0 +1,18 @@
+ 3,
+ 'logger' => true,//是否开启日志,yansongda/pay v3版本有效
+ /*
+ |--------------------------------------------------------------------------
+ | 退款单编号前缀
+ |--------------------------------------------------------------------------
+ */
+ 'refund_no_counter_prefix' => 'RF',
+ /*
+ |--------------------------------------------------------------------------
+ | 退款单编号计数器位数,取值范围 6 - 16 - 退款编号前缀位数
+ |--------------------------------------------------------------------------
+ */
+ 'refund_no_counter_length' => 6,
+];
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_alipays_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_alipays_table.php
new file mode 100644
index 0000000..4f5365d
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_alipays_table.php
@@ -0,0 +1,42 @@
+id();
+ $table->string('name');
+ $table->string('app_id');
+ $table->string('notify_url')->nullable();
+ $table->string('return_url')->nullable();
+ $table->text('ali_public_key')->nullable();
+ $table->text('private_key')->nullable();
+ $table->string('app_cert_path')->nullable()->comment('应用公钥证书');
+ $table->string('alipay_cert_path')->nullable()->comment('支付宝公钥证书');
+ $table->string('alipay_root_cert_path')->nullable()->comment('支付宝根证书文件');
+ $table->json('log');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_alipays');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_bills_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_bills_table.php
new file mode 100644
index 0000000..9b4e94f
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_bills_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->date('date')->comment('账单日期');
+ $table->decimal('total', 20)->comment('累计收款');
+ $table->decimal('refund', 20)->comment('累计退款');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_bills');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_notifies_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_notifies_table.php
new file mode 100644
index 0000000..4136297
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_notifies_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('payment_id');
+ $table->json('payload');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_notifies');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_redpacks_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_redpacks_table.php
new file mode 100644
index 0000000..fd4b8ee
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_redpacks_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->boolean('type')->default(0)->index()->comment('0:普通红包,1:裂变红包');
+ $table->uuid('redpack_no')->comment('商户订单号')->index();
+ $table->decimal('amount')->comment('发放金额');
+ $table->unsignedTinyInteger('number')->default(1)->comment('总发放个数');
+ $table->enum('driver', ['alipay', 'wechat'])->index()->comment('付款渠道');
+ $table->string('to')->index()->comment('用户OPENID');
+ $table->string('status', 16)->nullable()->comment('发放状态');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_redpacks');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_refunds_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_refunds_table.php
new file mode 100644
index 0000000..4b97049
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_refunds_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->unsignedBigInteger('payment_id')->index();
+ $table->uuid('refund_no')->comment('退款单号');
+ $table->decimal('total', 20)->comment('退款金额');
+ $table->timestamp('refund_at')->nullable()->comment('退款时间');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_refunds');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_settings_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_settings_table.php
new file mode 100644
index 0000000..1ba2926
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_settings_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->string('name')->comment('配置名称');
+ $table->tinyInteger('trade_id_counter_length')->default(6);
+ $table->boolean('in_use')->default(0)->comment('是否是使用中的配置');
+ $table->unsignedBigInteger('alipay_id')->nullable();
+ $table->unsignedBigInteger('wechat_id')->nullable();
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_settings');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_transfers_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_transfers_table.php
new file mode 100644
index 0000000..2a88814
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_transfers_table.php
@@ -0,0 +1,40 @@
+id();
+ $table->uuid('transfer_no')->index()->comment('商户订单号');
+ $table->decimal('amount')->comment('转账金额');
+ $table->enum('driver', ['alipay', 'wechat'])->index()->comment('付款渠道');
+ $table->string('to')->index()->comment('微信openid,支付宝identity');
+ $table->boolean('check')->default(0)->comment('是否校验真实姓名');
+ $table->string('user_name')->nullable()->comment('收款人姓名');
+ $table->string('desc')->nullable()->comment('付款说明');
+ $table->string('status', 16)->nullable()->comment('转账状态');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_transfers');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_wechats_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_wechats_table.php
new file mode 100644
index 0000000..f4ca3b3
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payment_wechats_table.php
@@ -0,0 +1,42 @@
+id();
+ $table->string('name');
+ $table->string('appid')->nullable();
+ $table->string('app_id')->nullable();
+ $table->string('miniapp_id')->nullable();
+ $table->string('mch_id');
+ $table->string('key');
+ $table->string('notify_url')->nullable();
+ $table->string('cert_client')->nullable();
+ $table->string('cert_key')->nullable();
+ $table->json('log');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payment_wechats');
+ }
+
+}
diff --git a/modules/Payment/Database/Migrations/0000_00_00_000000_create_payments_table.php b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payments_table.php
new file mode 100644
index 0000000..5a5d8b5
--- /dev/null
+++ b/modules/Payment/Database/Migrations/0000_00_00_000000_create_payments_table.php
@@ -0,0 +1,44 @@
+id();
+ $table->morphs('order');
+ $table->unsignedBigInteger('user_id')->index();
+ $table->uuid('trade_id')->unique();
+ $table->decimal('total', 20, 2)->unsigned()->comment('支付金额');
+ $table->enum('driver', ['alipay', 'wechat', 'score'])->index()->comment('支付方式');
+ $table->string('gateway', 16)->comment('支付渠道');
+ $table->string('state', 16)->index()->comment('订单状态');
+ $table->uuid('transaction_id')->nullable()->index()->comment('三方交易号');
+ $table->timestamp('paid_at')->nullable()->comment('订单支付时间');
+ $table->timestamps();
+
+ $table->index('created_at');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('payments');
+ }
+
+}
diff --git a/modules/Payment/Events/Paid.php b/modules/Payment/Events/Paid.php
new file mode 100644
index 0000000..4393c34
--- /dev/null
+++ b/modules/Payment/Events/Paid.php
@@ -0,0 +1,39 @@
+payment = $payment;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Facades/Pay.php b/modules/Payment/Facades/Pay.php
new file mode 100644
index 0000000..4050a40
--- /dev/null
+++ b/modules/Payment/Facades/Pay.php
@@ -0,0 +1,31 @@
+disableFilter();
+
+ $grid->column('id', '#ID#');
+ $grid->column('name', '支付名称');
+ $grid->column('app_id', '应用APPID');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Alipay());
+
+ $form->text('name', '支付名称')->required();
+
+ $form->text('app_id', '应用APPID')->required();
+ $form->url('notify_url', '通知地址');
+ $form->url('return_url', '返回地址');
+
+ $form->textarea('ali_public_key', '支付公钥');
+ $form->textarea('private_key', '私钥');
+
+ $form->text('app_cert_path', '应用公钥证书')->help('相对/storage/app下的路径如:certs/XXX.crt');
+ $form->text('alipay_cert_path', '支付宝公钥证书')->help('相对/storage/app下的路径如:certs/XXX.crt');
+ $form->text('alipay_root_cert_path', '支付宝根证书文件')->help('相对/storage/app下的路径如:certs/XXX.crt');
+
+ $form->embeds('log', '日志配置', function (Form\EmbeddedForm $form) {
+ $form->text('file', '日志文件名');
+ $form->select('level')->options([
+ 'info' => 'info',
+ 'debug' => 'debug',
+ ])->default('info');
+ $form->select('type', '记录方式')->options([
+ 'daily' => '按日期',
+ 'single' => '单文件',
+ ])->default('daily');
+ $form->number('max_file')
+ ->default(30)
+ ->help('当 【记录方式】 为 【按日期】 时有效');
+ });
+
+ return $form;
+ }
+
+ public function ajax(Request $request)
+ {
+ $q = $request->q;
+
+ return Alipay::where('name', 'like', "%$q%")
+ ->orWhere('app_id', 'like', "%$q%")
+ ->paginate(null, ['id', 'name as text']);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Admin/BillController.php b/modules/Payment/Http/Controllers/Admin/BillController.php
new file mode 100644
index 0000000..d99746c
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Admin/BillController.php
@@ -0,0 +1,26 @@
+column('date', '账单日期');
+ $grid->column('total', '累计收款');
+ $grid->column('refund', '累计退款');
+ $grid->column('created_at', '统计时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Admin/IndexController.php b/modules/Payment/Http/Controllers/Admin/IndexController.php
new file mode 100644
index 0000000..e4472cd
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Admin/IndexController.php
@@ -0,0 +1,60 @@
+disableCreateButton();
+ $grid->disableActions();
+
+ $grid->quickSearch('trade_id')->placeholder('交易单号');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('trade_id', '交易单号');
+ $filter->like('transaction_id', '交易单号');
+ $filter->like('user.username', '下单用户');
+ $filter->between('created_at', '下单时间')->datetime();
+
+ });
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->equal('driver', '支付通道')->select(Payment::DRIVER_MAP);
+ $filter->equal('state', '支付状态')->select(Payment::STATUS_MAP);
+ $filter->between('paid_at', '支付时间')->datetime();
+ });
+ });
+
+ $grid->column('trade_id', '交易单号');
+ $grid->column('transaction_id', '支付单号');
+ $grid->column('user.username', '下单用户');
+ $grid->column('driver', '支付通道')
+ ->using(Payment::DRIVER_MAP)
+ ->label(Payment::DRIVER_LABEL_MAP);
+ $grid->column('gateway', '支付渠道');
+ $grid->column('state', '支付状态')
+ ->using(Payment::STATUS_MAP)
+ ->label(Payment::STATUS_LABEL_MAP);
+ $grid->column('total', '应付金额');
+ $grid->column('paid_at', '支付时间');
+ $grid->column('退款状态')->display(function () {
+ return $this->refund_status_text;
+ })->link(function () {
+ return route('admin.payment.refunds.index', ['payment[trade_id]' => $this->trade_id]);
+ }, '_self');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Admin/RedpackController.php b/modules/Payment/Http/Controllers/Admin/RedpackController.php
new file mode 100644
index 0000000..3703c10
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Admin/RedpackController.php
@@ -0,0 +1,50 @@
+disableActions();
+ $grid->disableCreateButton();
+
+ $grid->quickSearch('redpack_no')->placeholder('红包编号');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('redpack_no', '红包编号');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('driver', '发送渠道')->select(Payment::DRIVER_MAP);
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('type', '红包类型')->select(Redpack::TYPE_MAP);
+ });
+ });
+
+ $grid->column('redpack_no', '红包编号');
+ $grid->column('driver', '发送渠道')
+ ->using(Payment::DRIVER_MAP)
+ ->label(Payment::DRIVER_LABEL_MAP);
+ $grid->column('type', '红包类型')
+ ->using(Redpack::TYPE_MAP);
+ $grid->column('amount', '红包金额');
+ $grid->column('to', '发送对象');
+ $grid->column('status', '发送状态');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Admin/RefundController.php b/modules/Payment/Http/Controllers/Admin/RefundController.php
new file mode 100644
index 0000000..4b29364
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Admin/RefundController.php
@@ -0,0 +1,47 @@
+disableCreateButton();
+ $grid->disableActions();
+
+ $grid->quickSearch('refund_no')->placeholder('退款单号');
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('payment.trade_id', '订单编号');
+ });
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('refund_no', '退款单号');
+ });
+ });
+
+ $grid->column('payment.trade_id', '订单编号')
+ ->link(function () {
+ return route('admin.payment.index', ['trade_id' => $this->payment->trade_id]);
+ }, '_self');
+ $grid->column('payment.total', '订单金额');
+ $grid->column('refund_no', '退款单号');
+ $grid->column('total', '退款金额');
+ $grid->column('退款类型')->display(function () {
+ return $this->total === $this->payment->total ? '全额退款' : '部分退款';
+ });
+ $grid->column('refund_at', '退款时间');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Admin/SettingController.php b/modules/Payment/Http/Controllers/Admin/SettingController.php
new file mode 100644
index 0000000..a110e86
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Admin/SettingController.php
@@ -0,0 +1,73 @@
+disableFilter();
+
+ $grid->model()->orderByDesc('in_use');
+
+ $grid->column('name', '配置名称');
+ $grid->column('trade_id_counter_length', '计数器位数');
+ $grid->column('in_use', '使用中')->bool();
+ $grid->column('wechat.name', '微信支付');
+ $grid->column('alipay.name', '支付宝');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Setting());
+
+ $form->text('name', '配置名称')
+ ->required();
+ $form->number('trade_id_counter_length', '计数器位数')
+ ->required()
+ ->default(4)
+ ->rules('integer|max:16', [
+ 'max' => '计数器最大不能超过16',
+ ]);
+ $form->switch('in_use', '当前配置');
+
+ $form->select('wechat_id', '微信支付')
+ ->options(function ($wechatId) {
+ if ($wechatId) {
+ $wechat = Wechat::find($wechatId);
+ if ($wechat) {
+ return [$wechat->id => $wechat->name];
+ }
+ }
+ })
+ ->ajax(route('admin.payment.wechats.ajax'));
+ $form->select('alipay_id', '支付宝')
+ ->options(function ($alipayId) {
+ if ($alipayId) {
+ $alipay = Alipay::find($alipayId);
+ if ($alipay) {
+ return [$alipay->id => $alipay->name];
+ }
+ }
+ })
+ ->ajax(route('admin.payment.alipays.ajax'));
+
+ return $form;
+ }
+
+}
+
diff --git a/modules/Payment/Http/Controllers/Admin/TransferController.php b/modules/Payment/Http/Controllers/Admin/TransferController.php
new file mode 100644
index 0000000..1f8e4a1
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Admin/TransferController.php
@@ -0,0 +1,50 @@
+disableActions();
+ $grid->disableCreateButton();
+
+ $grid->quickSearch('transfer_no')->placeholder('转账单号');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('transfer_no', '转账单号');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('driver', '转账渠道')->select(Payment::DRIVER_MAP);
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user_name', '收款人姓名');
+ });
+ });
+
+ $grid->column('transfer_no', '转账单号');
+ $grid->column('driver', '转账渠道')
+ ->using(Payment::DRIVER_MAP)
+ ->label(Payment::DRIVER_LABEL_MAP);;
+ $grid->column('to', '收款用户');
+ $grid->column('check', '实名校验')->bool();
+ $grid->column('user_name', '收款人姓名');
+ $grid->column('desc', '转账附言');
+ $grid->column('status', '转账状态');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Admin/WechatController.php b/modules/Payment/Http/Controllers/Admin/WechatController.php
new file mode 100644
index 0000000..4f4a004
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Admin/WechatController.php
@@ -0,0 +1,96 @@
+disableFilter();
+ $grid->column('id', '#ID#');
+ $grid->column('name', '支付名称');
+ $grid->column('appid', 'APP APPID');
+ $grid->column('app_id', '公众号 APPID');
+ $grid->column('miniapp_id', '小程序 APPID');
+ $grid->column('mch_id', '商户号');
+
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Wechat());
+
+ $form->text('name', '支付名称')
+ ->required();
+
+ $form->text('appid', 'APP APPID');
+ $form->text('app_id', '公众号 APPID');
+ $form->text('miniapp_id', '小程序 APPID');
+ $form->text('mch_id', '商户号')
+ ->required()
+ ->rules('required|size:10', [
+ 'required' => '商户号必须填写',
+ 'size' => '商户号长度应为:size位',
+ ]);
+ $form->text('key', '支付密钥')
+ ->required()->rules('required|size:32', [
+ 'required' => '支付密钥必须填写',
+ 'size' => '支付密钥长度应为:size位',
+ ]);
+ $form->url('notify_url', '通知地址');
+
+ // $form->file('cert_client', '公钥证书')
+ // ->disk('local')
+ // ->move('certs')
+ // ->removable();
+ //
+ // $form->file('cert_key', '私钥证书')
+ // ->disk('local')
+ // ->move('certs')
+ // ->removable();
+
+ $form->text('cert_client', '公钥证书')->help('相对/storage/app下的路径如:certs/apiclient_key.pem');
+ $form->text('cert_key', '私钥证书')->help('相对/storage/app下的路径如:certs/apiclient_cert.pem');
+
+ $form->embeds('log', '日志配置', function (Form\EmbeddedForm $form) {
+ $form->text('file', '日志文件名')->default('wechat');
+ $form->select('level')->options([
+ 'info' => 'info',
+ 'debug' => 'debug',
+ ])->default('info');
+ $form->select('type', '记录方式')->options([
+ 'daily' => '按日期',
+ 'single' => '单文件',
+ ])->default('daily');
+ $form->number('max_file')
+ ->default(30)
+ ->help('当 【记录方式】 为 【按日期】 时有效');
+ });
+
+ return $form;
+ }
+
+ public function ajax(Request $request)
+ {
+ $q = $request->q;
+
+ return Wechat::where('name', 'like', "%$q%")
+ ->orWhere('mch_id', 'like', "%$q%")
+ ->paginate(null, ['id', 'name as text']);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Api/GatewayController.php b/modules/Payment/Http/Controllers/Api/GatewayController.php
new file mode 100644
index 0000000..2f66d34
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Api/GatewayController.php
@@ -0,0 +1,29 @@
+
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(): JsonResponse
+ {
+ $setting = Setting::orderByDesc('in_use')->first();
+
+ return $this->success([
+ 'alipay' => (bool) $setting->alipay_id,
+ 'wechat' => (bool) $setting->wechat_id,
+ ]);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Http/Controllers/Api/NotifyController.php b/modules/Payment/Http/Controllers/Api/NotifyController.php
new file mode 100644
index 0000000..92d0858
--- /dev/null
+++ b/modules/Payment/Http/Controllers/Api/NotifyController.php
@@ -0,0 +1,63 @@
+verify();
+ } else {
+ $data = $alipay->callback();
+ }
+
+ $this->getPaymentByTradeNo($data->out_trade_no);
+
+ //设置支付完成
+ if ($this->payment) {
+ $this->payment->paid();
+ $this->payment->order->pay();
+ }
+
+ return $alipay->success();
+
+ }
+
+ public function wechat()
+ {
+ $wechat = Pay::wechat();
+
+ if (config('payment.version', 2) == 2) {
+ $data = $wechat->verify();
+ $this->getPaymentByTradeNo($data->out_trade_no);
+ } else {
+ $data = $wechat->callback();
+ $this->getPaymentByTradeNo($data->resource['ciphertext']['out_trade_no']);
+ }
+
+ //设置支付完成
+ if ($this->payment) {
+ $this->payment->paid();
+ $this->payment->order->pay();
+ }
+
+ return $wechat->success();
+ }
+
+ public function getPaymentByTradeNo($tradeNo)
+ {
+ $this->payment = Payment::where('trade_id', $tradeNo)->first();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Models/Alipay.php b/modules/Payment/Models/Alipay.php
new file mode 100644
index 0000000..8f4775f
--- /dev/null
+++ b/modules/Payment/Models/Alipay.php
@@ -0,0 +1,56 @@
+ 'json',
+ ];
+
+ /**
+ * Notes : 生成配置文件
+ *
+ * @Date : 2021/5/26 2:21 下午
+ * @Author : < Jason.C >
+ * @return int[]
+ */
+ public function toConfig(): array
+ {
+ $ali_public_key = $this->ali_public_key ?? '';
+ if ($this->alipay_cert_path) {
+ $ali_public_key = storage_path('app/'.$this->alipay_cert_path);
+ }
+
+ return [
+ 'app_id' => $this->app_id ?? '',
+ 'notify_url' => $this->notify_url ?? '',
+ 'return_url' => $this->return_url ?? '',
+ 'ali_public_key' => $ali_public_key,
+ 'app_secret_cert' => $this->private_key ?? '',
+ //应用公钥证书
+ 'app_public_cert_path' => $this->app_cert_path ? storage_path('app/'.$this->app_cert_path) : '',
+ //支付宝公钥证书
+ 'alipay_public_cert_path' => $this->alipay_cert_path ? storage_path('app/'.$this->alipay_cert_path) : '',
+ 'alipay_root_cert_path' => $this->alipay_root_cert_path ? storage_path('app/'.$this->alipay_root_cert_path) : '',
+ 'logger' => [
+ 'enable' => true,
+ 'file' => storage_path('logs/alipay/'.$this->log['file'].'.log'),
+ 'level' => $this->log['level'],
+ 'type' => $this->log['type'],
+ 'max_file' => (int) $this->log['max_file'],
+ ],
+ ];
+ }
+
+}
diff --git a/modules/Payment/Models/Bill.php b/modules/Payment/Models/Bill.php
new file mode 100644
index 0000000..42759bc
--- /dev/null
+++ b/modules/Payment/Models/Bill.php
@@ -0,0 +1,12 @@
+ '未支付',
+ self::STATUS_SUCCESS => '支付成功',
+ self::STATUS_FAIL => '支付失败',
+ self::STATUS_REFUND => '转入退款',
+ self::STATUS_CLOSED => '支付关闭',
+ self::STATUS_REVOKED => '支付撤销',
+ self::STATUS_ERROR => '支付错误',
+ self::STATUS_FINISHED => '支付完结',
+ ];
+
+ const STATUS_LABEL_MAP = [
+ self::STATUS_NOPAY => 'default',
+ self::STATUS_SUCCESS => 'success',
+ self::STATUS_FAIL => 'warning',
+ self::STATUS_REFUND => 'info',
+ self::STATUS_CLOSED => 'primary',
+ self::STATUS_REVOKED => 'primary',
+ self::STATUS_ERROR => 'danger',
+ self::STATUS_FINISHED => 'success',
+ ];
+
+ /**
+ * 支付方式
+ */
+ const DRIVER_ALIPAY = 'alipay';
+ const DRIVER_WECHAT = 'wechat';
+ const DRIVER_SCORE = 'score';
+
+ const DRIVER_MAP = [
+ self::DRIVER_ALIPAY => '支付宝',
+ self::DRIVER_WECHAT => '微信支付',
+ self::DRIVER_SCORE => '水滴支付',
+ ];
+
+ const DRIVER_LABEL_MAP = [
+ self::DRIVER_ALIPAY => 'info',
+ self::DRIVER_WECHAT => 'success',
+ ];
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ self::creating(function ($model) {
+ $time = explode(' ', microtime());
+
+ $counter = $model->whereDate('created_at', Carbon::today())->count() + 1;
+
+ $len = Setting::orderByDesc('in_use')->value('trade_id_counter_length');
+
+ $len = $len < 6 ? 6 : $len;
+ $len = $len > 16 ? 16 : $len;
+
+ $model->trade_id = date('YmdHis').
+ sprintf('%06d', $time[0] * 1e6).
+ sprintf('%0'.$len.'d', $counter);
+
+ $model->state = self::STATUS_NOPAY;
+ });
+ }
+
+ /**
+ * Notes: 支付渠道
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/18 11:14
+ * @return string
+ */
+ public function getDriverTextAttribute(): string
+ {
+ return self::DRIVER_MAP[$this->driver];
+ }
+
+ /**
+ * Notes: 支付状态
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/18 11:17
+ * @return string
+ */
+ public function getStateTextAttribute(): string
+ {
+ return self::STATUS_MAP[$this->state];
+ }
+
+ /**
+ * Notes : 要支付的模型
+ *
+ * @Date : 2021/4/21 1:59 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\MorphTo
+ */
+ public function order(): MorphTo
+ {
+ return $this->morphTo();
+ }
+
+ /**
+ * Notes : 结果通知
+ *
+ * @Date : 2021/4/23 11:49 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function notifies(): HasMany
+ {
+ return $this->hasMany(PaymentNotify::class);
+ }
+
+ /**
+ * Notes : 退款单
+ *
+ * @Date : 2021/6/1 11:13 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function refunds(): HasMany
+ {
+ return $this->hasMany(Refund::class);
+ }
+
+ /**
+ * Notes : 退款状态
+ *
+ * @Date : 2021/6/1 11:40 上午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ public function getRefundStatusTextAttribute(): string
+ {
+ if ($this->refunds->count()) {
+ return ($this->refunds->sum('total') == $this->total) ? '全额退款' : '部分退款';
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Notes : 直接通过 order 来设置关联的支付订单
+ *
+ * @Date : 2021/4/21 12:43 下午
+ * @Author : < Jason.C >
+ * @param \App\Models\Model $order
+ */
+ public function setOrderAttribute(Model $order)
+ {
+ $this->attributes['order_type'] = get_class($order);
+ $this->attributes['order_id'] = $order->getKey();
+ }
+
+ /**
+ * Notes : 通过模型来设置所属用户
+ *
+ * @Date : 2021/4/21 1:48 下午
+ * @Author : < Jason.C >
+ * @param \Modules\User\Models\User $user
+ */
+ public function setUserAttribute(User $user)
+ {
+ $this->attributes['user_id'] = $user->getKey();
+ }
+
+ /**
+ * Notes : 支付成功,调用的方法
+ *
+ * @Date : 2021/4/20 5:42 下午
+ * @Author : < Jason.C >
+ */
+ public function paid(): void
+ {
+ $this->state = self::STATUS_SUCCESS;
+ $this->paid_at = now();
+ $this->save();
+
+ event(new Paid($this));
+ }
+
+ /**
+ * Notes : 根据支付订单,获取支付渠道需要的接口数据
+ *
+ * @Date : 2021/4/21 4:24 下午
+ * @Author : < Jason.C >
+ * @param string $title 订单标题
+ * @param array $extends 扩展数据, 微信公众号 ['openid' => OPENID]
+ * @return mixed
+ * @throws \Exception
+ */
+ public function getPaymentParams(string $title, array $extends = [])
+ {
+ $order['out_trade_no'] = $this->trade_id;
+
+ $driver = $this->driver;
+ $gateway = $this->gateway;
+
+ if ($driver === self::DRIVER_WECHAT) {
+ $order['out_trade_no'] = $this->trade_id;
+ if (config('payment.version', 2) == 2) {
+ $order['body'] = $title;
+ $order['total_fee'] = $this->total * 100;
+ } else {
+ $order['description'] = $title;
+ $order['amount']['total'] = $this->total * 100;
+ }
+ } elseif ($driver === self::DRIVER_ALIPAY) {
+ $order['total_amount'] = $this->total;
+ $order['out_trade_no'] = $this->trade_id;
+ $order['subject'] = $title;
+ } else {
+ throw new Exception('unsupported driver');
+ }
+
+ if (! empty($extends)) {
+ $order = array_merge($order, $extends);
+ }
+
+ return Pay::$driver()->$gateway($order);
+ }
+
+}
diff --git a/modules/Payment/Models/PaymentNotify.php b/modules/Payment/Models/PaymentNotify.php
new file mode 100644
index 0000000..06bf157
--- /dev/null
+++ b/modules/Payment/Models/PaymentNotify.php
@@ -0,0 +1,27 @@
+ 'json',
+ ];
+
+ /**
+ * Notes : 所属支付单
+ *
+ * @Date : 2021/5/26 9:55 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function payment(): BelongsTo
+ {
+ return $this->belongsTo(Payment::class);
+ }
+
+}
diff --git a/modules/Payment/Models/Redpack.php b/modules/Payment/Models/Redpack.php
new file mode 100644
index 0000000..49f6b82
--- /dev/null
+++ b/modules/Payment/Models/Redpack.php
@@ -0,0 +1,25 @@
+ '普通红包',
+ self::GROUP_REDPACK => '裂变红包',
+ ];
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Models/Refund.php b/modules/Payment/Models/Refund.php
new file mode 100644
index 0000000..ee4173c
--- /dev/null
+++ b/modules/Payment/Models/Refund.php
@@ -0,0 +1,49 @@
+whereDate('created_at', Carbon::today())->count() + 1;
+ $len = config('payment.refund_no_counter_length');
+ $prefix = config('payment.refund_no_counter_prefix');
+
+ $len = $len < 6 ? 6 : $len;
+ $len = $len > 16 ? 16 : $len;
+
+ $model->refund_no = $prefix.date('YmdHis').
+ sprintf('%06d', $time[0] * 1e6).
+ sprintf('%0'.$len.'d', $counter);
+ });
+ }
+
+ /**
+ * Notes : 支付订单
+ *
+ * @Date : 2021/6/1 11:13 上午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function payment(): BelongsTo
+ {
+ return $this->belongsTo(Payment::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Models/Setting.php b/modules/Payment/Models/Setting.php
new file mode 100644
index 0000000..dd65435
--- /dev/null
+++ b/modules/Payment/Models/Setting.php
@@ -0,0 +1,83 @@
+ 'boolean',
+ ];
+
+ public static function boot()
+ {
+ parent::boot();
+
+ self::saved(function ($model) {
+ if ($model->in_use && $model->id) {
+ self::where('id', '<>', $model->id)
+ ->where('in_use', 1)
+ ->update(['in_use' => 0]);
+ }
+
+// Cache::tags('payment_config')->flush();
+ });
+ }
+
+ /**
+ * Notes : 挂载的微信支付
+ *
+ * @Date : 2021/5/26 2:01 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function wechat(): BelongsTo
+ {
+ return $this->belongsTo(Wechat::class);
+ }
+
+ /**
+ * Notes : 挂载的支付宝
+ *
+ * @Date : 2021/5/26 2:18 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function alipay(): BelongsTo
+ {
+ return $this->belongsTo(Alipay::class);
+ }
+
+ /**
+ * Notes : 获取微信/支付宝的配置
+ *
+ * @Date : 2021/5/26 2:19 下午
+ * @Author : < Jason.C >
+ * @param string $type
+ * @return array
+ * @throws \Exception
+ */
+ public static function getDefaultConfig(string $type): array
+ {
+ if (! in_array($type, ['wechat', 'alipay'])) {
+ throw new Exception('不支持的支付渠道');
+ }
+
+ $setting = self::orderByDesc('in_use')->first();
+
+ return $setting->$type->toConfig();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Models/Traits/WithConfig.php b/modules/Payment/Models/Traits/WithConfig.php
new file mode 100644
index 0000000..b63c43d
--- /dev/null
+++ b/modules/Payment/Models/Traits/WithConfig.php
@@ -0,0 +1,37 @@
+ [
+ 'default' => $defaule_config,
+ ],
+ 'logger' => array_merge($defaule_config['logger'], [
+ 'enable' => config('payment.logger', true),
+ ]),
+ ];
+ }
+
+ return $config;
+
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/Models/Transfer.php b/modules/Payment/Models/Transfer.php
new file mode 100644
index 0000000..2f66784
--- /dev/null
+++ b/modules/Payment/Models/Transfer.php
@@ -0,0 +1,17 @@
+ 'json',
+ ];
+
+ /**
+ * Notes : 生成配置文件
+ *
+ * @Date : 2021/5/26 2:21 下午
+ * @Author : < Jason.C >
+ * @return int[]
+ */
+ public function toConfig(): array
+ {
+ return [
+ 'app_id' => $this->appid,//app
+ 'mp_app_id' => $this->app_id,//mp
+ 'mini_app_id' => $this->miniapp_id,//mini
+ 'mch_id' => $this->mch_id,
+ // 必填-商户秘钥
+ 'mch_secret_key' => $this->key,
+ // 必填-商户私钥 字符串或路径
+ 'mch_secret_cert' => $this->cert_key ? storage_path('app/'.$this->cert_key) : '',
+ // 必填-商户公钥证书路径
+ 'mch_public_cert_path' => $this->cert_client ? storage_path('app/'.$this->cert_client) : '',
+ 'logger' => [
+ 'file' => storage_path('logs/wechat/'.$this->log['file'].'.log'),
+ 'level' => $this->log['level'],
+ 'type' => $this->log['type'],
+ 'max_file' => (int) $this->log['max_file'],
+ ],
+ ];
+ }
+
+}
diff --git a/modules/Payment/Payment.php b/modules/Payment/Payment.php
new file mode 100644
index 0000000..e206c28
--- /dev/null
+++ b/modules/Payment/Payment.php
@@ -0,0 +1,123 @@
+
+ */
+ public static function install()
+ {
+ Artisan::call('migrate', [
+ '--path' => 'modules/Payment/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ *
+ * @Date : 2021/3/12 11:35 上午
+ * @Author : < Jason.C >
+ */
+ public static function uninstall()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $settingMenu = $menu::where('title', '支付设置')->get();
+
+ foreach ($settingMenu as $main) {
+ $main->delete();
+ }
+
+ $mains = $menu::where('title', self::$mainTitle)->get();
+
+ foreach ($mains as $main) {
+ $main->delete();
+ }
+ }
+
+ protected static function createAdminMenu()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 30,
+ 'title' => self::$mainTitle,
+ 'icon' => 'fa-wordpress',
+ ]);
+
+ $main->children()->createMany([
+ [
+ 'order' => 1,
+ 'title' => '支付订单',
+ 'icon' => 'fa-bars',
+ 'uri' => 'payments',
+ ],
+ [
+ 'order' => 2,
+ 'title' => '退款订单',
+ 'icon' => 'fa-bars',
+ 'uri' => 'payments/refunds',
+ ],
+ [
+ 'order' => 3,
+ 'title' => '转账订单',
+ 'icon' => 'fa-edit',
+ 'uri' => 'payments/transfers',
+ ],
+ [
+ 'order' => 4,
+ 'title' => '现金红包',
+ 'icon' => 'fa-folder',
+ 'uri' => 'payments/redpacks',
+ ],
+ [
+ 'order' => 5,
+ 'title' => '日账单',
+ 'icon' => 'fa-bars',
+ 'uri' => 'payments/bills',
+ ],
+ ]);
+
+ $settingMenu = $main->children()->create([
+ 'order' => 99,
+ 'title' => '支付设置',
+ 'icon' => 'fa-cogs',
+ 'uri' => '',
+ ]);
+
+ $settingMenu->children()->createMany([
+ [
+ 'order' => 13,
+ 'title' => '支付配置',
+ 'icon' => 'fa-cogs',
+ 'uri' => 'payments/settings',
+ ],
+ [
+ 'order' => 14,
+ 'title' => '微信支付',
+ 'icon' => 'fa-wechat',
+ 'uri' => 'payments/wechats',
+ ],
+ [
+ 'order' => 15,
+ 'title' => '支付宝',
+ 'icon' => 'fa-adn',
+ 'uri' => 'payments/alipays',
+ ],
+ ]);
+ }
+
+}
diff --git a/modules/Payment/Providers/PaymentServiceProvider.php b/modules/Payment/Providers/PaymentServiceProvider.php
new file mode 100644
index 0000000..d827a42
--- /dev/null
+++ b/modules/Payment/Providers/PaymentServiceProvider.php
@@ -0,0 +1,98 @@
+loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->registerConfig();
+ $this->app->register(RouteServiceProvider::class);
+
+
+ $this->app->singleton('pay.alipay', function () {
+ $alipay = Setting::getDefaultConfig('alipay');
+
+ return Pay::alipay([
+ 'alipay' => [
+ 'default' => $alipay,
+ ],
+ 'logger' => array_merge($alipay['logger'], [
+ 'enable' => config('payment.logger', true),
+ ]),
+ 'notify_url' => route('api.payment.notify.alipay')
+ ]);
+ });
+
+ $this->app->singleton('pay.wechat', function () {
+ $wechat = Setting::getDefaultConfig('wechat');
+ return Pay::wechat([
+ 'wechat' => [
+ 'default' => Setting::getDefaultConfig('wechat'),
+ ],
+ 'logger' => array_merge($wechat['logger'], [
+ 'enable' => config('payment.logger', true),
+ ]),
+ 'notify_url' => route('api.payment.notify.alipay')
+ ]);
+ });
+
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides(): array
+ {
+ return ['pay.alipay', 'pay.wechat'];
+ }
+
+ /**
+ * Register config.
+ *
+ * @return void
+ */
+ protected function registerConfig()
+ {
+ $this->publishes([
+ module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'),
+ ], 'payment-config');
+
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
+ );
+ }
+
+}
diff --git a/modules/Payment/Providers/RouteServiceProvider.php b/modules/Payment/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..858e961
--- /dev/null
+++ b/modules/Payment/Providers/RouteServiceProvider.php
@@ -0,0 +1,63 @@
+mapApiRoutes();
+
+ $this->mapAdminRoutes();
+ }
+
+ protected function mapApiRoutes()
+ {
+ Route::as(config('api.route.as'))
+ ->domain(config('api.route.domain'))
+ ->middleware(config('api.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('api.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/api.php'));
+ }
+
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+
+}
diff --git a/modules/Payment/README.md b/modules/Payment/README.md
new file mode 100644
index 0000000..a56f4fd
--- /dev/null
+++ b/modules/Payment/README.md
@@ -0,0 +1,46 @@
+# 支付模块
+
+## 1. 安装
+
+```shell
+composer require yansongda/pay
+```
+
+## 2. 文档参考
+
+> https://github.com/yansongda/pay
+
+## 3. 生成支付订单
+```php
+use Modules\Payment\Traits\WithPayments;
+
+$order = Order::first();
+
+$payment = $order->createWechatPayment(User::first(), -1, 'mp');
+
+$payment->getPaymentParams();
+```
+
+
+## 4. 支付渠道 GATEWAY 参考值
+// 支付宝
+//web 电脑支付 array $order Response
+//wap 手机网站支付 array $order Response
+//app APP 支付 array $order Response
+//mini 小程序支付 array $order Collection
+//scan 扫码支付 array $order Collection
+// 不支持的
+//pos 刷卡支付 array $order Collection
+//transfer 账户转账 array $order Collection
+
+// 微信支付
+//mp 公众号支付 array $order Collection
+//wap 手机网站支付 array $order Response
+//app APP 支付 array $order JsonResponse
+//miniapp 小程序支付 array $order Collection
+//scan 扫码支付 array $order Collection
+// 不支持的
+//pos 刷卡支付 array $order Collection
+//transfer 账户转账 array $order Collection
+//redpack 普通红包 array $order Collection
+//groupRedpack 裂变红包 array $order Collection
\ No newline at end of file
diff --git a/modules/Payment/Routes/admin.php b/modules/Payment/Routes/admin.php
new file mode 100644
index 0000000..b2898a8
--- /dev/null
+++ b/modules/Payment/Routes/admin.php
@@ -0,0 +1,23 @@
+ 'payments',
+ 'namespace' => 'Admin',
+ 'as' => 'payment.',
+], function (Router $router) {
+ $router->get('', 'IndexController@index')->name('index');
+ $router->get('refunds', 'RefundController@index')->name('refunds.index');
+ $router->get('bills', 'BillController@index');
+ $router->resource('settings', 'SettingController');
+
+ $router->get('wechats/ajax', 'WechatController@ajax')->name('wechats.ajax');
+ $router->resource('wechats', 'WechatController');
+ $router->get('alipays/ajax', 'AlipayController@ajax')->name('alipays.ajax');
+ $router->resource('alipays', 'AlipayController');
+
+ $router->get('redpacks', 'RedpackController@index');
+ $router->get('transfers', 'TransferController@index');
+});
diff --git a/modules/Payment/Routes/api.php b/modules/Payment/Routes/api.php
new file mode 100644
index 0000000..8bbf5ca
--- /dev/null
+++ b/modules/Payment/Routes/api.php
@@ -0,0 +1,14 @@
+ 'payments',
+ 'namespace' => 'Api',
+], function (Router $router) {
+ $router->get('gateways', 'GatewayController@index');
+
+ $router->any('notify/wechat', 'NotifyController@wechat')->name('payment.notify.wechat');
+ $router->any('notify/alipay', 'NotifyController@alipay')->name('payment.notify.alipay');
+});
diff --git a/modules/Payment/Traits/WithPayments.php b/modules/Payment/Traits/WithPayments.php
new file mode 100644
index 0000000..de031fc
--- /dev/null
+++ b/modules/Payment/Traits/WithPayments.php
@@ -0,0 +1,160 @@
+
+ * @throws \Exception
+ */
+ protected function getTotalPayAmount()
+ {
+ if (! in_array('amount_field', array_keys(get_class_vars(__CLASS__))) || empty($this->amount_field)) {
+ throw new Exception('Model need [amount_field] property');
+ }
+
+ return $this->getOriginal($this->amount_field);
+ }
+
+ /**
+ * Notes : 关联支付订单
+ *
+ * @Date : 2021/4/21 1:49 下午
+ * @Author : < Jason.C >
+ * @return \Illuminate\Database\Eloquent\Relations\MorphMany
+ */
+ public function payments(): MorphMany
+ {
+ return $this->morphMany(Payment::class, 'order');
+ }
+
+ /**
+ * Notes: 关联支付信息
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 9:26
+ * @return \Illuminate\Database\Eloquent\Relations\MorphOne
+ */
+ public function payment(): MorphOne
+ {
+ return $this->morphOne(Payment::class, 'order')->where('state', Payment::STATUS_SUCCESS);
+ }
+
+ /**
+ * Notes: 是否支付
+ *
+ * @Author: 玄尘
+ * @Date : 2021/5/17 9:04
+ */
+ public function isPay(): bool
+ {
+ return $this->payments()->where('state', Payment::STATUS_SUCCESS)->exists();
+ }
+
+ /**
+ * Notes : 创建微信支付订单
+ *
+ * @Date : 2021/4/21 1:55 下午
+ * @Author : < Jason.C >
+ * @param \Modules\User\Models\User $user 下单用户
+ * @param float $total 订单金额
+ * @param string $gateway 支付渠道
+ * @return \Illuminate\Database\Eloquent\Model
+ * @throws \Exception
+ */
+ public function createWechatPayment(User $user, float $total = -1, string $gateway = 'mp'): Model
+ {
+ $gatewayAll = ['mp', 'wap', 'app', 'miniapp', 'scan'];
+ if (config('payment.version', 2) == 3) {
+ $gatewayAll = ['mp', 'wap', 'app', 'mini', 'scan'];
+ if ($gateway == 'miniapp') {
+ $gateway = 'mini';
+ }
+ }
+
+ if (! in_array($gateway, $gatewayAll)) {
+ throw new Exception('Unsupported Wechat gateway');
+ }
+
+ // 自动获取订单金额
+ if ($total < 0) {
+ $total = $this->getTotalPayAmount();
+ }
+
+ return $this->payments()->create([
+ 'user' => $user,
+ 'total' => $total,
+ 'driver' => Payment::DRIVER_WECHAT,
+ 'gateway' => $gateway,
+ ]);
+ }
+
+ /**
+ * Notes : 创建支付宝订单
+ *
+ * @Date : 2021/4/23 10:24 上午
+ * @Author : < Jason.C >
+ * @param \Modules\User\Models\User $user 下单用户
+ * @param float $total 订单金额
+ * @param string $gateway 支付渠道
+ * @return \Illuminate\Database\Eloquent\Model
+ * @throws \Exception
+ */
+ public function createAlipayPayment(User $user, float $total = -1, string $gateway = 'web'): Model
+ {
+ if (! in_array($gateway, ['web', 'wap', 'app', 'mini', 'scan'])) {
+ throw new Exception('Unsupported Alipay gateway');
+ }
+ // 自动获取订单金额
+ if ($total < 0) {
+ $total = $this->getTotalPayAmount();
+ }
+
+ return $this->payments()->create([
+ 'user' => $user,
+ 'total' => $total,
+ 'driver' => Payment::DRIVER_ALIPAY,
+ 'gateway' => $gateway,
+ ]);
+ }
+
+ /**
+ * Notes: 水滴支付
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/5 15:50
+ * @param User $user
+ * @param float $total
+ * @param string $gateway
+ * @return Model
+ * @throws Exception
+ */
+ public function createScorePayment(User $user, float $total = -1, string $gateway = 'web'): Model
+ {
+ // 自动获取订单金额
+ if ($total < 0) {
+ $total = $this->getTotalPayAmount();
+ }
+
+ return $this->payments()->create([
+ 'user' => $user,
+ 'total' => $total,
+ 'driver' => Payment::DRIVER_SCORE,
+ 'gateway' => $gateway,
+ ]);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Payment/composer.json b/modules/Payment/composer.json
new file mode 100644
index 0000000..bf69f61
--- /dev/null
+++ b/modules/Payment/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "jasonc/payment-module",
+ "description": "支付模块",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Jason.Chen",
+ "email": "chenjxlg@163.com"
+ }
+ ],
+ "require": {
+ "genealabs/laravel-model-caching": "^0.11.3",
+ "yansongda/pay": "^v2.10.0"
+ },
+ "extra": {
+ "module-dir": "modules"
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Payment\\": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/Payment/module.json b/modules/Payment/module.json
new file mode 100644
index 0000000..2148e07
--- /dev/null
+++ b/modules/Payment/module.json
@@ -0,0 +1,15 @@
+{
+ "name": "Payment",
+ "alias": "payment",
+ "description": "支付管理模块",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\Payment\\Providers\\PaymentServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [],
+ "version": "1.0.0",
+ "author": "Jason.Chen"
+}
\ No newline at end of file
diff --git a/modules/README.md b/modules/README.md
new file mode 100644
index 0000000..b492f2a
--- /dev/null
+++ b/modules/README.md
@@ -0,0 +1,81 @@
+# 模块存放目录
+
+## 1. 模块列表
+
+### 1. User 【用户模块】
+
+> 模块地址:http://git.yuzhankeji.cn/UzTech/laravel-user-module.git
+>
+> 后台菜单排序:10
+
+### 2. Cms 【内容管理】
+
+> 模块地址:https://git.yuzhankeji.cn/UzTech/laravel-cms-module
+>
+> 后台菜单排序:15
+
+### 3. Mall 【商城模块】
+
+> 模块地址:https://git.yuzhankeji.cn/UzTech/laravel-mall-module
+>
+> 后台菜单排序:20
+
+### 4. Payment 【支付中心】
+
+> 模块地址:https://git.yuzhankeji.cn/UzTech/laravel-payment-module
+>
+> 后台菜单排序:25
+
+### 5. Settlement 【结算中心】
+
+> 模块地址:
+>
+> 后台菜单排序:30
+
+### 6. Chain 【区块链管理】
+
+> 模块地址:
+>
+> 后台菜单排序:35
+
+### 7. Notification 【消息中心】
+
+> 模块地址:
+>
+> 后台菜单排序:40
+
+### 8. Linker 【应用链接管理】
+
+> 模块地址:
+>
+> 后台菜单排序:45
+
+### 9. AppVersion 【APP版本管理】
+
+> 模块地址:
+>
+> 后台菜单排序:50
+
+### 10. OmniForm 【万能表单】
+
+> 模块地址:
+>
+> 后台菜单排序:55
+
+### 11. Tao 【淘宝客联盟】
+
+> 模块地址:
+>
+> 后台菜单排序:60
+
+### 12. Advert 【广告联盟】
+
+> 模块地址:
+>
+> 后台菜单排序:65
+
+### 13. Aliyun 【阿里云管理】
+
+> 模块地址:
+>
+> 后台菜单排序:90
\ No newline at end of file
diff --git a/modules/Storage/.gitignore b/modules/Storage/.gitignore
new file mode 100644
index 0000000..4b6cb11
--- /dev/null
+++ b/modules/Storage/.gitignore
@@ -0,0 +1,4 @@
+.idea
+vendor
+.DS_Store
+composer.lock
\ No newline at end of file
diff --git a/modules/Storage/Config/config.php b/modules/Storage/Config/config.php
new file mode 100644
index 0000000..f5e27d3
--- /dev/null
+++ b/modules/Storage/Config/config.php
@@ -0,0 +1,45 @@
+ 5 * 1024 * 1024,
+
+ 'upload_path' => 'uploads',
+
+ 'driver' => env('FILESYSTEM_DRIVER', 'public'),
+
+ 'disks' => [
+ 'public' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/public'),
+ 'url' => env('APP_URL').'/storage',
+ 'visibility' => 'public',
+ ],
+
+ 'oss' => [
+ 'driver' => 'oss',
+ 'root' => '', // 设置上传时根前缀
+ 'access_key' => env('OSS_ACCESS_KEY'),
+ 'secret_key' => env('OSS_SECRET_KEY'),
+ 'endpoint' => env('OSS_ENDPOINT'),
+ 'bucket' => env('OSS_BUCKET'),
+ 'isCName' => env('OSS_IS_CNAME', false),
+ 'cdnHost' => env('OSS_CDN_HOST'),
+ 'buckets' => [
+ 'test' => [
+ 'access_key' => env('OSS_ACCESS_KEY'),
+ 'secret_key' => env('OSS_SECRET_KEY'),
+ 'bucket' => env('OSS_TEST_BUCKET'),
+ 'endpoint' => env('OSS_TEST_ENDPOINT'),
+ 'isCName' => env('OSS_TEST_IS_CNAME', false),
+ 'cdnHost' => env('OSS_TEXT_CDN_HOST'),
+ ],
+ ],
+ // 以下是STS用的,不使用STS直传可不用配置
+ 'RegionId' => env('OSS_STS_REGION_ID', 'cn-beijing'),
+ 'RoleArn' => env('OSS_STS_ROLE_ARN', ''),
+ ],
+ ],
+
+];
diff --git a/modules/Storage/Database/Migrations/0000_00_00_000000_create_file_storages_table.php b/modules/Storage/Database/Migrations/0000_00_00_000000_create_file_storages_table.php
new file mode 100644
index 0000000..4be3bc0
--- /dev/null
+++ b/modules/Storage/Database/Migrations/0000_00_00_000000_create_file_storages_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->string('driver', 16);
+ $table->string('hash', 32)->index();
+ $table->string('type', 32);
+ $table->unsignedInteger('size');
+ $table->string('path');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('file_storages');
+ }
+
+}
diff --git a/modules/Storage/Http/Controllers/OssController.php b/modules/Storage/Http/Controllers/OssController.php
new file mode 100644
index 0000000..e82d29f
--- /dev/null
+++ b/modules/Storage/Http/Controllers/OssController.php
@@ -0,0 +1,133 @@
+uploadPath = Config::get('storage.upload_path').date('/Y/m/d');
+ }
+
+ /**
+ * Notes : 普通文件上传
+ *
+ * @Date : 2021/4/25 5:25 下午
+ * @Author :
+ * @param Request $request
+ * @return mixed
+ */
+ public function upload(Request $request)
+ {
+ $upload = $request->file('upload');
+
+ $size = File::size($upload->path());
+
+ if ($size > Config::get('storage.max_upload_size')) {
+ return $this->failed('超过最大允许上传大小', 422);
+ }
+ $exists = true;
+ $hash = File::hash($upload->path());
+
+ $existFile = StorageModel::where('driver', Config::get('storage.driver'))->where('hash', $hash)->first();
+
+ if ($existFile) {
+ $fullName = $existFile->path;
+ } else {
+ $path = $this->uploadPath;
+ $fileName = $hash.'.'.$upload->getClientOriginalExtension();
+ $fullName = $path.'/'.$fileName;
+
+ $uploaded = Storage::putFileAs($path, $upload, $fileName);
+
+ if (! $uploaded) {
+ return $this->failed('文件上传失败', 422);
+ }
+ $exists = false;
+ StorageModel::create([
+ 'hash' => $hash,
+ 'driver' => Config::get('storage.driver'),
+ 'type' => $upload->getClientMimeType(),
+ 'size' => $size,
+ 'path' => $fullName,
+ ]);
+ }
+
+ return $this->success([
+ 'exists' => $exists,
+ 'size' => $size,
+ 'path' => $fullName,
+ 'url' => Storage::url($fullName),
+ ]);
+ }
+
+ /**
+ * Notes : 多图片统一上传
+ *
+ * @Date : 2021/8/23 13:49
+ * @Author : Mr.wang
+ * @param Request $request
+ * @return mixed
+ */
+ public function uploads(Request $request)
+ {
+ $fullFile = [];
+ $files = [];
+ if ($request->file()) {
+ foreach ($request->file() as $key => $upload) {
+ $size = File::size($upload->path());
+
+ if ($size > Config::get('storage.max_upload_size')) {
+ $message = '第'.$key + 1 .'张图片超过最大允许上传大小';
+
+ return $this->failed($message, 422);
+ }
+ $hash = File::hash($upload->path());
+
+ $existFile = StorageModel::where('driver', Config::get('storage.driver'))
+ ->where('hash', $hash)
+ ->first();
+ if ($existFile) {
+ $fullName = $existFile->path;
+ } else {
+ $path = $this->uploadPath;
+ $fileName = $hash.'.'.$upload->getClientOriginalExtension();
+ $fullName = $path.'/'.$fileName;
+
+ $uploaded = Storage::putFileAs($path, $upload, $fileName);
+
+ if (! $uploaded) {
+ return $this->failed('文件上传失败', 422);
+ }
+ StorageModel::create([
+ 'hash' => $hash,
+ 'driver' => Config::get('storage.driver'),
+ 'type' => $upload->getClientMimeType(),
+ 'size' => $size,
+ 'path' => $fullName,
+ ]);
+ }
+ $fullFile[] = Storage::url($fullName);
+ $files[] = $fullName;
+ }
+
+ return $this->success([
+ 'path' => $files,
+ 'url' => $fullFile,
+ ]);
+ } else {
+ return $this->failed('没有图片');
+ }
+ }
+
+}
diff --git a/modules/Storage/Http/Controllers/StsController.php b/modules/Storage/Http/Controllers/StsController.php
new file mode 100644
index 0000000..65efaec
--- /dev/null
+++ b/modules/Storage/Http/Controllers/StsController.php
@@ -0,0 +1,80 @@
+
+ * @return mixed
+ */
+ public function config()
+ {
+ $config = Config::get('storage.disks.oss');
+
+ try {
+ AlibabaCloud::accessKeyClient(
+ $config['access_key'],
+ $config['secret_key'],
+ )->regionId($config['RegionId'])->asDefaultClient();
+ } catch (ClientException $e) {
+ return $this->failed($e->getErrorMessage());
+ }
+
+ try {
+ $Policy = [
+ 'Version' => "1",
+ 'Statement' => [
+ [
+ 'Effect' => 'Allow',
+ 'Action' => 'oss:*',
+ 'Resource' => [
+ "*",
+ ],
+ ],
+ ],
+ ];
+
+ $result = AlibabaCloud::rpc()
+ ->product('Sts')
+ ->scheme('https') // https | http
+ ->version('2015-04-01')
+ ->action('AssumeRole')
+ ->method('POST')
+ ->host('sts.aliyuncs.com')
+ ->options([
+ 'query' => [
+ 'RegionId' => $config['RegionId'],
+ 'RoleArn' => $config['RoleArn'],
+ 'RoleSessionName' => 'RoleSessionName',
+ // 'Policy' => json_encode($Policy),
+ 'DurationSeconds' => 3600,
+ ],
+ ])
+ ->request();
+
+ return $this->success([
+ 'secure' => true,
+ 'bucket' => $config['bucket'],
+ 'region' => $config['RegionId'], // 前缀带 oss- ?
+ 'accessKeyId' => $result->Credentials->AccessKeyId,
+ 'accessKeySecret' => $result->Credentials->AccessKeySecret,
+ 'stsToken' => $result->Credentials->SecurityToken,
+ 'endpoint' => $config['endpoint'],
+ 'cname' => $config['isCName'],
+ ]);
+ } catch (ClientException | ServerException $e) {
+ return $this->failed($e->getErrorMessage());
+ }
+ }
+}
diff --git a/modules/Storage/Models/Storage.php b/modules/Storage/Models/Storage.php
new file mode 100644
index 0000000..d195d96
--- /dev/null
+++ b/modules/Storage/Models/Storage.php
@@ -0,0 +1,12 @@
+mapApiRoutes();
+ }
+
+ protected function mapApiRoutes()
+ {
+ Route::as(config('api.route.as'))
+ ->domain(config('api.route.domain'))
+ ->middleware(config('api.route.middleware'))
+ ->namespace($this->moduleNamespace)
+ ->prefix(config('api.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/api.php'));
+ }
+
+}
diff --git a/modules/Storage/Providers/StorageServiceProvider.php b/modules/Storage/Providers/StorageServiceProvider.php
new file mode 100644
index 0000000..00873b6
--- /dev/null
+++ b/modules/Storage/Providers/StorageServiceProvider.php
@@ -0,0 +1,73 @@
+loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->registerConfig();
+
+ $driver = Config::get('storage.driver');
+ Config::set('filesystems.default', $driver);
+ $disks = Config::get('storage.disks');
+ Config::set('filesystems.disks.'.$driver, data_get($disks, $driver));
+
+ $this->app->register(RouteServiceProvider::class);
+ }
+
+ /**
+ * Register config.
+ *
+ * @return void
+ */
+ protected function registerConfig()
+ {
+ $this->publishes([
+ module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'),
+ ], 'config');
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
+ );
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides(): array
+ {
+ return [];
+ }
+
+}
diff --git a/modules/Storage/README.md b/modules/Storage/README.md
new file mode 100644
index 0000000..0d2f29c
--- /dev/null
+++ b/modules/Storage/README.md
@@ -0,0 +1,70 @@
+# 文件存储模块
+
+## 1. 前端上传接口
+
+## 2. 配置STS直传
+
+### 步骤一:创建RAM用户
+
+- 登录RAM控制台。
+- 在左侧导航栏的人员管理菜单下,单击用户。
+- 单击新建用户。
+- 输入登录名称和显示名称。
+- 在访问方式区域下,选择编程访问,然后单击确定。
+- 单击复制,保存访问密钥(AccessKey ID 和 AccessKey Secret)。
+
+### 步骤二:为RAM用户授予请求AssumeRole的权限
+
+- 单击已创建RAM用户右侧对应的添加权限。
+- 在添加权限页面,选择AliyunSTSAssumeRoleAccess权限。
+- 单击确定。
+
+### 步骤三:创建用于获取临时访问凭证的角色
+
+- 在左侧导航栏,单击RAM角色管理。
+- 单击新建RAM角色,选择可信实体类型为阿里云账号,单击下一步。
+- 在新建RAM角色页面,RAM角色名称填写为RamOssTest,选择云账号为当前云账号。
+- 单击完成。
+- 单击复制,保存角色的ARN。
+
+### 步骤四:为角色授予上传文件的权限
+
+- 在左侧导航栏的权限管理菜单下,单击权限策略管理。
+- 单击创建权限策略。
+- 在新建自定义权限策略页面,填写策略名称,配置模式选择脚本配置,并在策略内容中赋予角色向目标存储空间examplebucket下的目录exampledir上传文件的权限。
+```json
+{
+ "Version": "1",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "oss:PutObject"
+ ],
+ "Resource": [
+ "acs:oss:*:*:examplebucket/exampledir",
+ "acs:oss:*:*:examplebucket/exampledir/*"
+ ]
+ }
+ ]
+}
+```
+- 单击确定。
+
+### 步骤五:获取临时访问凭证
+
+- 您可以通过调用STS服务接口AssumeRole 或者使用各语言STS SDK来获取临时访问凭证。
+> 说明 临时访问凭证有效时间单位为秒,最小值为900,最大值以当前角色设定的最大会话时间为准。详情请参见设置角色最大会话时间。
+
+### 步骤六:使用临时访问凭证上传文件至OSS
+
+### 常见问题
+
+- 报错The security token you provided is invalid.如何处理?
+> 请确保完整填写步骤五获取到的SecurityToken。
+
+- 报错The OSS Access Key Id you provided does not exist in our records.如何处理?
+> 临时访问凭证已过期,过期后自动失效。请使用临时访问密钥(AccessKeyId和AccessKeySecret)向App服务器申请新的临时访问凭证。具体操作,请参见步骤五。
+
+- 获取STS时报错NoSuchBucket如何处理?
+> 出现这种报错通常是STS的endpoint填写错误。请根据您的地域,填写正确的STS接入地址。各地域的STS接入地址请参见接入地址。
\ No newline at end of file
diff --git a/modules/Storage/Routes/api.php b/modules/Storage/Routes/api.php
new file mode 100644
index 0000000..225f985
--- /dev/null
+++ b/modules/Storage/Routes/api.php
@@ -0,0 +1,12 @@
+ 'storage',
+], function (Router $router) {
+ $router->post('upload', 'OssController@upload');
+ $router->post('uploads', 'OssController@uploads');
+ $router->get('sts', 'StsController@config');
+});
diff --git a/modules/Storage/Storage.php b/modules/Storage/Storage.php
new file mode 100644
index 0000000..7e8c82f
--- /dev/null
+++ b/modules/Storage/Storage.php
@@ -0,0 +1,34 @@
+
+ */
+ public static function install()
+ {
+ Artisan::call('migrate', [
+ '--path' => 'modules/Storage/Database/Migrations',
+ ]);
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ * @Date : 2021/3/12 11:35 上午
+ * @Author : < Jason.C >
+ */
+ public static function uninstall()
+ {
+
+ }
+
+}
diff --git a/modules/Storage/composer.json b/modules/Storage/composer.json
new file mode 100644
index 0000000..03e4118
--- /dev/null
+++ b/modules/Storage/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "uztech/storage-module",
+ "description": "文件存储模块",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Jason.Chen",
+ "email": "chenjxlg@163.com"
+ }
+ ],
+ "require": {
+ "alibabacloud/sts": "^1.8",
+ "jasonc/laravel-filesystem-oss": "^3.0"
+ },
+ "extra": {
+ "module-dir": "modules"
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Storage\\": ""
+ }
+ }
+}
diff --git a/modules/Storage/module.json b/modules/Storage/module.json
new file mode 100644
index 0000000..2c12094
--- /dev/null
+++ b/modules/Storage/module.json
@@ -0,0 +1,15 @@
+{
+ "name": "Storage",
+ "alias": "storage",
+ "description": "文件存储模块,文件上传,文件管理",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\Storage\\Providers\\StorageServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [],
+ "version": "1.0.0",
+ "author": "Jason.Chen"
+}
diff --git a/modules/Task/Config/config.php b/modules/Task/Config/config.php
new file mode 100644
index 0000000..3c99eb9
--- /dev/null
+++ b/modules/Task/Config/config.php
@@ -0,0 +1,5 @@
+ 'Task'
+];
diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_task_categories_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_categories_table.php
new file mode 100644
index 0000000..3685fd2
--- /dev/null
+++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_categories_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->string('title')->comment('分类标题');
+ $table->string('remark')->comment('分类描述');
+ $table->string('cover')->nullable()->comment('展示图片');
+ $table->boolean('status')->default(0)->comment('状态');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('task_categories');
+ }
+
+}
diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_task_logs_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_logs_table.php
new file mode 100644
index 0000000..d195c23
--- /dev/null
+++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_logs_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->unsignedBigInteger('task_id')->index();
+ $table->json('source')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('task_logs');
+ }
+}
diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_task_users_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_users_table.php
new file mode 100644
index 0000000..fcb759f
--- /dev/null
+++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_task_users_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->unsignedBigInteger('task_id')->index();
+ $table->timestamp('task_at')->index()->comment('做任务的日期');
+ $table->integer('total')->default(1)->comment('任务量');
+ $table->boolean('status')->default(0)->index()->comment('状态');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('task_users');
+ }
+}
diff --git a/modules/Task/Database/Migrations/0000_00_00_000000_create_tasks_table.php b/modules/Task/Database/Migrations/0000_00_00_000000_create_tasks_table.php
new file mode 100644
index 0000000..2977267
--- /dev/null
+++ b/modules/Task/Database/Migrations/0000_00_00_000000_create_tasks_table.php
@@ -0,0 +1,52 @@
+id();
+ $table->unsignedBigInteger('category_id')->default(0)->index();
+ $table->string('title', 100)->nullable()->comment('任务标题');
+ $table->string('sub_title')->nullable()->comment('副标题');
+ $table->string('key', 100)->nullable()->comment('任务标识');
+ $table->string('ico', 200)->nullable()->comment('任务图标');
+ $table->string('cover')->nullable()->comment('图片');
+ $table->integer('position')->default(0)->comment('定位');
+ $table->json('identity')->nullable()->comment('可用身份');
+ $table->string('remark', 100)->nullable()->comment('任务描述');
+ $table->string('tips', 100)->nullable()->comment('任务奖励提示');
+ $table->text('description')->nullable()->comment('任务详细说明');
+ $table->string('url', 200)->nullable()->comment('任务目标连接');
+ $table->integer('rule_id')->nullable()->comment('对应账变规则');
+ $table->decimal('rule_number', 8, 2)->default(0)->comment('任务发放的数值');
+ $table->string('type', 20)->nullable()->comment('任务计量类型');
+ $table->unsignedInteger('task_number')->default(0)->comment('任务数额');
+ $table->boolean('company_certify')->nullable()->comment('是否企业认证');
+ $table->string('cycle', 20)->nullable()->comment('周期');
+ $table->boolean('status')->default(0)->comment('状态');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('tasks');
+ }
+
+}
diff --git a/modules/Task/Facades/TaskFacade.php b/modules/Task/Facades/TaskFacade.php
new file mode 100644
index 0000000..7e15191
--- /dev/null
+++ b/modules/Task/Facades/TaskFacade.php
@@ -0,0 +1,105 @@
+shown()->where('key', $key)->first();
+
+ if ($task) {
+ $timeRange = self::getTimeInterval($task->cycle);
+
+ $task->taskLogs()->create([
+ 'user_id' => $user_id,
+ 'source' => $source,
+ ]);
+
+ $taskUser = $task->taskUsers()
+ ->when($task->cycle != 'one', function ($q) use ($timeRange) {
+ $q->whereBetween('task_at', $timeRange);
+ })
+ ->byUserId($user_id)
+ ->first();
+
+ if (! $taskUser) {
+ $taskUser = $task->taskUsers()->create([
+ 'user_id' => $user_id,
+ 'task_at' => now(),
+ 'total' => $total,
+ ]);
+ } else {
+ $taskUser->incrementTotal($total);//增加数值
+ }
+
+ //存在任务
+ if ($taskUser->total >= $task->task_number && $taskUser->status == User::STATUS_INTI) {
+ $taskUser->status = User::STATUS_FINISH;
+ $taskUser->save();
+ $variable = $task->getRuleNumber();
+ info('key '.$key.' variable'.$variable);
+
+ if ($variable > 0) {
+ //发放水滴
+ $taskUser->user->account->rule($task->rule_id, $variable, false, $source ?? []);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Notes: 获取时间区间
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/20 10:37
+ * @param $type
+ * @param string $date
+ * @return array|void
+ */
+ public static function getTimeInterval($type, string $date = '')
+ {
+ switch ($type) {
+ case 'day':
+ return [
+ 'start_at' => Carbon::parse($date)->startOfDay(),
+ 'end_at' => Carbon::parse($date)->endOfDay(),
+ ];
+ case 'week':
+ return [
+ 'start_at' => Carbon::parse($date)->startOfWeek(),
+ 'end_at' => Carbon::parse($date)->endOfWeek(),
+ ];
+ case 'month':
+ return [
+ 'start_at' => Carbon::parse($date)->startOfMonth(),
+ 'end_at' => Carbon::parse($date)->endOfMonth(),
+ ];
+ case 'year':
+ return [
+ 'start_at' => Carbon::parse($date)->startOfYear(),
+ 'end_at' => Carbon::parse($date)->endOfYear(),
+ ];
+ case 'one':
+ return [
+ 'start_at' => '',
+ 'end_at' => Carbon::parse($date)->endOfDay(),
+ ];
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/Task/Http/Controllers/Admin/CategoryController.php b/modules/Task/Http/Controllers/Admin/CategoryController.php
new file mode 100644
index 0000000..dddaa67
--- /dev/null
+++ b/modules/Task/Http/Controllers/Admin/CategoryController.php
@@ -0,0 +1,54 @@
+column('id', '#ID#');
+ $grid->column('cover', '分类图片')->image('', 50, 100);
+ $grid->column('title', '分类名称');
+ $grid->column('remark', '描述');
+ $grid->column('status', '状态')->switch([
+ 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'],
+ ]);
+ $grid->column('created_at', '创建时间');
+ $grid->column('updated_at', '更新时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Category());
+ $form->text('title', '分类名称')->required();
+ $form->text('remark', '描述')->required();
+
+ $this->cover($form);
+
+ $this->withUrl($form);
+
+ $form->switch('status', '是否开启')->default(1)->states([
+ 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'],
+ ]);
+
+ return $form;
+ }
+
+}
diff --git a/modules/Task/Http/Controllers/Admin/TaskController.php b/modules/Task/Http/Controllers/Admin/TaskController.php
new file mode 100644
index 0000000..648fc51
--- /dev/null
+++ b/modules/Task/Http/Controllers/Admin/TaskController.php
@@ -0,0 +1,139 @@
+filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('title', '任务名称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('category_id', '分类')->select(Category::shown()->pluck('title', 'id'));
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->select((new Task())->status_map);
+ });
+ });
+
+ $grid->column('id', '#ID#');
+ $grid->column('ico', '任务图标')->image('', 40, 40);
+ $grid->column('cover', '图片')->image('', 40, 40);
+ $grid->column('title', '任务名称');
+ $grid->column('key', '任务关键字');
+ $grid->column('category.title', '所属分类');
+ $grid->column('rule.title', '账变名称');
+ $grid->column('remark', '任务描述');
+ $grid->column('tips', '任务奖励提示');
+ $grid->column('type', '任务计量类型')->using(Task::TYPES);
+ $grid->column('cycle', '任务计量周期')->using(Task::CYCLES);
+// $grid->column('company_certify', '企业认证')
+// ->using(Task::COMPANY_CERTIFICATIONS)
+// ->label(Task::COMPANY_CERTIFICATION_LABEL);
+
+ $status = [
+ 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'],
+ ];
+
+// $grid->column('展示位置')
+// ->display(function () {
+// $data = [];
+// foreach ($this->position as $position) {
+// $data[] = $this->position_map[$position];
+// }
+//
+// return $data;
+// })
+// ->label();
+
+ $grid->column('身份')
+ ->display(function () {
+ return $this->getIdentities();
+ })->label();
+ $grid->column('status', '状态')->switch($status);
+
+ $grid->column('created_at', '创建时间');
+ $grid->column('updated_at', '更新时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Task());
+
+
+ $form->text('title', '任务标题')->required();
+// $form->text('sub_title', '副标题');
+// $form->text('remark', '任务描述');
+ $form->text('tips', '任务奖励提示')->required();
+ $form->text('key', '任务关键字')->required();
+ $form->select('category_id', '所属分类')
+ ->options(Category::query()->shown()->pluck('title', 'id'))
+ ->required();
+ $this->cover($form, 'ico', 'ICO图标');
+ $this->cover($form, 'cover', '图片');
+// $form->multipleSelect('position', '展示位置')
+// ->options($form->model()->position_map);
+ $form->multipleSelect('identity', '可用身份')
+ ->options(Identity::pluck('name', 'id'));
+
+ $form->select('rule_id', '对应账变规则')
+ ->options(function () {
+ return AccountRule::where('type', 'score')->pluck('title', 'id');
+ })
+ ->required();
+ $form->currency('rule_number', '账变数值')
+ ->default(0)
+ ->help('0为获取账变设置的数值')
+ ->required();
+
+ $this->withUrl($form);
+
+// $form->radio('company_certify', '企业认证')
+// ->options(Task::COMPANY_CERTIFICATIONS)
+// ->default(Task::COMPANY_CERTIFICATION_NO)
+// ->required();
+ $form->radio('type', '任务计量类型')->default(Task::TYPE_COUNT)->options(Task::TYPES)->required();
+ $form->radio('cycle', '任务计量周期')->default(Task::CYCLE_DAY)->options(Task::CYCLES)->required();
+ $form->number('task_number', '任务量')->default(1)->required();
+// $form->ueditor('description', '任务详情')->required();
+ $form->switch('status', '是否开启')->states([
+ 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'],
+ ]);
+ $form->saving(function (Form $form) {
+ $task = Task::query()->where('key', $form->key)->first();
+ if ($task && $form->isCreating()) {
+ $error = new MessageBag([
+ 'title' => '错误',
+ 'message' => '任务关键字已经存在',
+ ]);
+ return back()->withInput()->with(compact('error'));
+ }
+ });
+ return $form;
+ }
+
+}
diff --git a/modules/Task/Http/Controllers/Admin/UserController.php b/modules/Task/Http/Controllers/Admin/UserController.php
new file mode 100644
index 0000000..4a69320
--- /dev/null
+++ b/modules/Task/Http/Controllers/Admin/UserController.php
@@ -0,0 +1,50 @@
+disableCreateButton();
+ $grid->disableActions();
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('user_id', '下单用户')->select()->ajax(route('admin.user.users.ajax'));
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('task_id', '任务')->select(Task::query()->pluck('title', 'id'));
+ });
+ });
+
+ $grid->column('id', '#ID#');
+ $grid->column('task.title', '任务');
+ $grid->column('user.username', '用户');
+ $grid->column('task.task_number', '任务量');
+ $grid->column('total', '完成次数');
+ $grid->column('task_at', '时间');
+
+ return $grid;
+ }
+}
diff --git a/modules/Task/Http/Controllers/Api/CategoryController.php b/modules/Task/Http/Controllers/Api/CategoryController.php
new file mode 100644
index 0000000..48ca567
--- /dev/null
+++ b/modules/Task/Http/Controllers/Api/CategoryController.php
@@ -0,0 +1,21 @@
+get();
+
+ return $this->success(CategoryResource::collection($tasks));
+ }
+
+}
diff --git a/modules/Task/Http/Controllers/Api/TaskController.php b/modules/Task/Http/Controllers/Api/TaskController.php
new file mode 100644
index 0000000..fc14cea
--- /dev/null
+++ b/modules/Task/Http/Controllers/Api/TaskController.php
@@ -0,0 +1,39 @@
+category_id ?? '';
+
+ $tasks = Task::query()
+ ->when($category_id, function ($q) use ($category_id) {
+ $q->where('category_id', $category_id);
+ })
+ ->Shown()
+ ->when($user, function ($q) use ($user) {
+ $q->ofUser($user);
+ })
+ ->get();
+
+ return $this->success(TaskBaseResource::collection($tasks));
+ }
+
+ public function show(Task $task)
+ {
+ return $this->success(new TaskResource($task));
+ }
+
+}
diff --git a/modules/Task/Http/Controllers/Api/UserController.php b/modules/Task/Http/Controllers/Api/UserController.php
new file mode 100644
index 0000000..c4436ce
--- /dev/null
+++ b/modules/Task/Http/Controllers/Api/UserController.php
@@ -0,0 +1,76 @@
+where('key', $key)->first();
+ if ($task && $task->status != Task::STATUS_UP) {
+ throw new \Exception('任务关闭');
+ }
+
+ $toDayStartTimestamp = Carbon::now()->startOfDay()->timestamp;//当天开始的时间戳
+
+ $app = Factory::miniProgram([
+ 'app_id' => env('WECHAT_MINI_PROGRAM_APPID'),
+ 'secret' => env('WECHAT_MINI_PROGRAM_SECRET'),
+ 'response_type' => 'collection',
+ ]);
+
+ $session = $app->auth->session($request->code);
+ if ($session->errcode) {
+ return $this->failed($session->errmsg);
+ }
+
+ $decryptedData = $app->encryptor->decryptData(
+ $session->session_key,
+ $request->iv,
+ $request->encryptData
+ );
+
+ $list = collect($decryptedData['stepInfoList']);
+
+ if ($list->isNotEmpty()) {
+ $toDay = $list->where('timestamp', $toDayStartTimestamp)->first();
+ if ($toDay) {
+ TaskFacade::do($key, Api::userId(), [
+ 'steps' => $toDay['step']
+ ], $toDay['step']);
+ }
+
+ if ($toDay['step'] >= $task->task_number) {
+ return $this->success('任务完成');
+ } else {
+ throw new \Exception('您今天的步数未达标');
+ }
+ } else {
+ throw new \Exception('未获取到微信运动数据');
+ }
+
+
+ } catch (\Exception $exception) {
+ return $this->failed($exception->getMessage());
+ }
+ }
+
+}
diff --git a/modules/Task/Http/Resources/CategoryResource.php b/modules/Task/Http/Resources/CategoryResource.php
new file mode 100644
index 0000000..d563b7c
--- /dev/null
+++ b/modules/Task/Http/Resources/CategoryResource.php
@@ -0,0 +1,21 @@
+ $this->id,
+ 'title' => $this->title,
+ 'remark' => $this->remark,
+ 'cover' => $this->cover_url,
+ 'url' => $this->linker,
+ ];
+ }
+
+}
diff --git a/modules/Task/Http/Resources/TaskBaseResource.php b/modules/Task/Http/Resources/TaskBaseResource.php
new file mode 100644
index 0000000..dffa506
--- /dev/null
+++ b/modules/Task/Http/Resources/TaskBaseResource.php
@@ -0,0 +1,27 @@
+ $this->id,
+ 'ico' => $this->ico_url,
+ 'key' => $this->key,
+ 'cover' => $this->cover_url,
+ 'title' => $this->title,
+ 'tips' => $this->tips,
+ 'linker' => $this->linker,
+ 'user' => $this->getUserTask($user),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Task/Http/Resources/TaskResource.php b/modules/Task/Http/Resources/TaskResource.php
new file mode 100644
index 0000000..29fc35f
--- /dev/null
+++ b/modules/Task/Http/Resources/TaskResource.php
@@ -0,0 +1,36 @@
+ $this->id,
+ 'ico' => $this->ico_url,
+ 'key' => $this->key,
+ 'cover' => $this->cover_url,
+ 'title' => $this->title,
+ 'sub_title' => $this->sub_title,
+ 'remark' => $this->remark,
+ 'tips' => $this->tips,
+ 'url' => $this->linker,
+ 'category' => new CategoryResource($this->category),
+ 'rule' => [
+ 'id' => $this->rule->id,
+ 'name' => $this->rule->name,
+ 'title' => $this->rule->title,
+ ],
+ 'user' => $this->getUserTask($user),
+ 'content' => $this->description,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Task/Models/Category.php b/modules/Task/Models/Category.php
new file mode 100644
index 0000000..e9fad45
--- /dev/null
+++ b/modules/Task/Models/Category.php
@@ -0,0 +1,21 @@
+ 'json',
+ ];
+
+}
diff --git a/modules/Task/Models/Task.php b/modules/Task/Models/Task.php
new file mode 100644
index 0000000..64344b7
--- /dev/null
+++ b/modules/Task/Models/Task.php
@@ -0,0 +1,203 @@
+ '日',
+ self::CYCLE_WEEK => '周',
+ self::CYCLE_MONTH => '月',
+ self::CYCLE_YEAR => '年',
+ self::CYCLE_ONE => '一次',
+ ];
+
+ const TYPE_SUM = 'sum';
+ const TYPE_COUNT = 'count';
+
+ const TYPES = [
+ self::TYPE_SUM => '和值',
+ self::TYPE_COUNT => '计数',
+ ];
+
+ const COMPANY_CERTIFICATION_YES = 1;
+ const COMPANY_CERTIFICATION_NO = 0;
+
+ const COMPANY_CERTIFICATIONS = [
+ self::COMPANY_CERTIFICATION_YES => '是',
+ self::COMPANY_CERTIFICATION_NO => '否',
+ ];
+
+ const COMPANY_CERTIFICATION_LABEL = [
+ self::COMPANY_CERTIFICATION_YES => 'info',
+ self::COMPANY_CERTIFICATION_NO => 'success',
+ ];
+
+ const POSITION_RECOMMENDED = 1;
+
+ public array $position_map = [
+ self::POSITION_RECOMMENDED => '推荐',
+ ];
+
+ public string $cover_field = 'cover';
+
+ public $casts = [
+ 'identity' => 'json',
+ ];
+
+
+ const STATUS_DOWN = 0;
+ const STATUS_UP = 1;
+
+ public array $status_map = [
+ self::STATUS_UP => '开启',
+ self::STATUS_DOWN => '关闭',
+ ];
+
+ public function getIcoUrlAttribute(): string
+ {
+ return $this->parseImageUrl($this->ico);
+ }
+
+ /**
+ * Notes: 关联分类
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/19 16:45
+ * @return BelongsTo
+ */
+ public function category(): BelongsTo
+ {
+ return $this->belongsTo(Category::class);
+ }
+
+ /**
+ * Notes: 关联规则
+ *
+ * @Author: 玄尘
+ * @Date : 2021/9/30 14:30
+ * @return BelongsTo
+ */
+ public function rule(): BelongsTo
+ {
+ return $this->belongsTo(AccountRule::class, 'rule_id');
+ }
+
+ /**
+ * Notes: 获取账变值
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/20 11:05
+ */
+ public function getRuleNumber()
+ {
+ $variable = $this->rule_number;
+ if (! $variable) {
+ $variable = $this->rule ? $this->rule->variable : 0;
+ }
+
+ return $variable;
+ }
+
+ /**
+ * Notes: description
+ *
+ * @Author: 玄尘
+ * @Date : 2021/9/29 11:53
+ * @param Builder $query
+ * @param User $user
+ * @return Builder
+ */
+ public function scopeOfUser(Builder $query, User $user): Builder
+ {
+ $identity = $user->identities->first();
+ return $query->where('identity', 'like', "%$identity->id%");
+ }
+
+ /**
+ * Notes: description
+ *
+ * @Author: 玄尘
+ * @Date : 2021/9/29 11:53
+ * @param Builder $query
+ * @param Identity $identity
+ * @return Builder
+ */
+ public function scopeOfIdentity(Builder $query, Identity $identity): Builder
+ {
+ return $query->where('identity', 'like', "%$identity->id%");
+ }
+
+ /**
+ * Notes: 是否完成
+ *
+ * @Author: 玄尘
+ * @Date : 2021/9/30 13:25
+ */
+ public function getFinish($user): bool
+ {
+ $rule = $this->rule;
+ if ($rule) {
+ if (method_exists($this, $rule->name)) {
+ return $this->{$rule->name}($user);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Notes: 任务数据
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/20 10:43
+ */
+ public function taskUsers(): HasMany
+ {
+ return $this->hasMany(TaskUser::class);
+ }
+
+ /**
+ * Notes: 任务记录
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/20 10:45
+ */
+ public function taskLogs(): HasMany
+ {
+ return $this->hasMany(Log::class);
+ }
+
+ public function getIdentities()
+ {
+ return Identity::query()->whereIn('id',$this->identity)->pluck('name');
+ }
+
+}
diff --git a/modules/Task/Models/Traits/BelongsToTask.php b/modules/Task/Models/Traits/BelongsToTask.php
new file mode 100644
index 0000000..a016867
--- /dev/null
+++ b/modules/Task/Models/Traits/BelongsToTask.php
@@ -0,0 +1,20 @@
+belongsTo(Task::class);
+ }
+}
diff --git a/modules/Task/Models/Traits/TaskAttribute.php b/modules/Task/Models/Traits/TaskAttribute.php
new file mode 100644
index 0000000..66f97b1
--- /dev/null
+++ b/modules/Task/Models/Traits/TaskAttribute.php
@@ -0,0 +1,52 @@
+ $this->task_number,
+ ];
+ if ($user) {
+ $timeRange = TaskFacade::getTimeInterval($this->cycle);
+
+ $taskUser = $this->taskUsers()
+ ->when($this->cycle != 'one', function ($q) use ($timeRange) {
+ $q->whereBetween('task_at', $timeRange);
+ })
+ ->byUser($user)
+ ->first();
+
+ if ($taskUser) {
+ return array_merge($data, [
+ 'task_id' => $taskUser->id,
+ 'finish' => $taskUser->status,
+ 'total' => min($taskUser->total, $data['all']),
+ ]);
+ } else {
+ return array_merge($data, [
+ 'finish' => 0,
+ 'total' => 0,
+ ]);
+ }
+ } else {
+ return array_merge($data, [
+ 'finish' => 0,
+ 'total' => 0,
+ ]);
+ }
+ }
+
+}
diff --git a/modules/Task/Models/User.php b/modules/Task/Models/User.php
new file mode 100644
index 0000000..52d5bb7
--- /dev/null
+++ b/modules/Task/Models/User.php
@@ -0,0 +1,41 @@
+ '进行中',
+ self::STATUS_FINISH => '完成',
+ ];
+
+ /**
+ * Notes: 增加任务数
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/20 11:10
+ * @param int $step
+ */
+ public function incrementTotal(int $step = 1)
+ {
+ if ($this->task->key == 'steps') {
+ $this->total = $step;
+ $this->save();
+ } else {
+ $this->increment('total', $step);
+ }
+ }
+}
diff --git a/modules/Task/Providers/RouteServiceProvider.php b/modules/Task/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..4761122
--- /dev/null
+++ b/modules/Task/Providers/RouteServiceProvider.php
@@ -0,0 +1,78 @@
+mapApiRoutes();
+
+ $this->mapAdminRoutes();
+ }
+
+ /**
+ * Define the "web" routes for the application.
+ * These routes all receive session state, CSRF protection, etc.
+ *
+ * @return void
+ */
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace.'\\Admin')
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+
+ /**
+ * Define the "api" routes for the application.
+ * These routes are typically stateless.
+ *
+ * @return void
+ */
+ protected function mapApiRoutes()
+ {
+ Route::as(config('api.route.as'))
+ ->domain(config('api.route.domain'))
+ ->middleware(config('api.route.middleware'))
+ ->namespace($this->moduleNamespace.'\\Api')
+ ->prefix(config('api.route.prefix').'/tasks')
+ ->group(module_path($this->moduleName, 'Routes/api.php'));
+ }
+
+}
diff --git a/modules/Task/Providers/TaskServiceProvider.php b/modules/Task/Providers/TaskServiceProvider.php
new file mode 100644
index 0000000..92ff57a
--- /dev/null
+++ b/modules/Task/Providers/TaskServiceProvider.php
@@ -0,0 +1,67 @@
+registerConfig();
+ $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->app->register(RouteServiceProvider::class);
+ }
+
+ /**
+ * Register config.
+ *
+ * @return void
+ */
+ protected function registerConfig()
+ {
+ $this->publishes([
+ module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'),
+ ], 'config');
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
+ );
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides(): array
+ {
+ return [];
+ }
+
+}
diff --git a/modules/Task/Routes/admin.php b/modules/Task/Routes/admin.php
new file mode 100644
index 0000000..c48ef08
--- /dev/null
+++ b/modules/Task/Routes/admin.php
@@ -0,0 +1,11 @@
+resource('tasks/categories', 'CategoryController');
+ $router->resource('tasks/users', 'UserController');
+ $router->resource('tasks', 'TaskController');
+});
diff --git a/modules/Task/Routes/api.php b/modules/Task/Routes/api.php
new file mode 100644
index 0000000..b6ec12d
--- /dev/null
+++ b/modules/Task/Routes/api.php
@@ -0,0 +1,21 @@
+ config('api.route.middleware_guess'),
+], function (Router $router) {
+ $router->get('', 'TaskController@index');
+ $router->get('{task}', 'TaskController@show')->where('task', '[0-9]+');;
+ $router->get('categories', 'CategoryController@index');
+ $router->get('wechat_step', 'TaskController@wechatStep');
+});
+
+Route::group([
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('wechat_step', 'UserController@wechatStep');
+});
+
+
diff --git a/modules/Task/Task.php b/modules/Task/Task.php
new file mode 100644
index 0000000..cdd8fc4
--- /dev/null
+++ b/modules/Task/Task.php
@@ -0,0 +1,81 @@
+ 'modules/Task/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ protected static function createAdminMenu()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $exists = $menu::where('title', self::$mainTitle)->exists();
+ if (! $exists) {
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 3,
+ 'title' => '任务管理',
+ 'icon' => 'fa-archive',
+ ]);
+ $main->children()
+ ->create([
+ 'order' => 0,
+ 'title' => '任务列表',
+ 'icon' => 'fa-align-left',
+ 'uri' => 'tasks',
+ ]);
+
+ $main->children()
+ ->create([
+ 'order' => 1,
+ 'title' => '分类管理',
+ 'icon' => 'fa-align-left',
+ 'uri' => 'tasks/categories',
+ ]);
+ $main->children()
+ ->create([
+ 'order' => 3,
+ 'title' => '任务记录',
+ 'icon' => 'fa-align-left',
+ 'uri' => 'tasks/users',
+ ]);
+ }
+ }
+
+ /**
+ * Notes: 卸载模块的一些操作
+ *
+ * @Author: 玄尘
+ * @Date : 2021/9/26 11:24
+ */
+ public static function uninstall()
+ {
+ $menu = config('admin.database.menu_model');
+
+ $mains = $menu::where('title', self::$mainTitle)->get();
+
+ foreach ($mains as $main) {
+ $main->delete();
+ }
+ }
+
+}
diff --git a/modules/Task/Traits/HasTasks.php b/modules/Task/Traits/HasTasks.php
new file mode 100644
index 0000000..4167c42
--- /dev/null
+++ b/modules/Task/Traits/HasTasks.php
@@ -0,0 +1,52 @@
+hasMany(User::class);
+ }
+
+ /**
+ * Notes: 获取任务数据
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/21 9:54
+ */
+ public function getTaskCount($task_id): array
+ {
+ $task = Task::find($task_id);
+
+ return $task->getUserTask($this);
+ }
+
+ /**
+ * Notes: 所有任务数据
+ *
+ * @Author: 玄尘
+ * @Date: 2022/10/21 9:59
+ * @param $task_id
+ * @return array
+ */
+ public function getTasksCount(): array
+ {
+ return $this->tasks()->map(function ($taskUser) {
+ return $taskUser->task->getUserTask($this);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/modules/Task/composer.json b/modules/Task/composer.json
new file mode 100644
index 0000000..1d93905
--- /dev/null
+++ b/modules/Task/composer.json
@@ -0,0 +1,18 @@
+{
+ "name": "xuanchen/task_module",
+ "description": "",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Chen.Xuan",
+ "email": "122383162@qq.com"
+ }
+ ],
+ "require": {
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\Task\\": ""
+ }
+ }
+}
diff --git a/modules/Task/module.json b/modules/Task/module.json
new file mode 100644
index 0000000..2992b4f
--- /dev/null
+++ b/modules/Task/module.json
@@ -0,0 +1,15 @@
+{
+ "name": "Task",
+ "alias": "task",
+ "description": "任务模块",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\Task\\Providers\\TaskServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "version": "1.0.0",
+ "author": "玄尘",
+ "requires": []
+}
diff --git a/modules/Task/package.json b/modules/Task/package.json
new file mode 100644
index 0000000..4599509
--- /dev/null
+++ b/modules/Task/package.json
@@ -0,0 +1,17 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "npm run development",
+ "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
+ "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
+ "watch-poll": "npm run watch -- --watch-poll",
+ "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
+ "prod": "npm run production",
+ "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
+ },
+ "devDependencies": {
+ "cross-env": "^7.0",
+ "laravel-mix": "^5.0.1",
+ "laravel-mix-merge-manifest": "^0.1.2"
+ }
+}
diff --git a/modules/User/.gitignore b/modules/User/.gitignore
new file mode 100644
index 0000000..4b6cb11
--- /dev/null
+++ b/modules/User/.gitignore
@@ -0,0 +1,4 @@
+.idea
+vendor
+.DS_Store
+composer.lock
\ No newline at end of file
diff --git a/modules/User/.styleci.yml b/modules/User/.styleci.yml
new file mode 100644
index 0000000..1d210e6
--- /dev/null
+++ b/modules/User/.styleci.yml
@@ -0,0 +1,17 @@
+risky: false
+
+version: 7.4
+
+preset: laravel
+
+tab-width: 4
+
+use-tabs: false
+
+#enabled:
+# - "align_phpdoc"
+
+disabled:
+ - "unalign_equals"
+ - "no_blank_lines_after_class_opening"
+ - "no_blank_lines_after_phpdoc"
diff --git a/modules/User/Config/config.php b/modules/User/Config/config.php
new file mode 100644
index 0000000..df3b2b0
--- /dev/null
+++ b/modules/User/Config/config.php
@@ -0,0 +1,98 @@
+ 'User',
+
+ /*
+ |--------------------------------------------------------------------------
+ | 后台是否显示新增用户按钮
+ |--------------------------------------------------------------------------
+ */
+ 'create_user_by_admin' => env('USER_CREATE_BY_ADMIN', true),
+
+ /*
+ |--------------------------------------------------------------------------
+ | 开启用户基础数据的缓存
+ |--------------------------------------------------------------------------
+ */
+ 'cache_user' => env('USER_USE_CACHE', false),
+
+ /*
+ |--------------------------------------------------------------------------
+ | 缓存用户的配置
+ |--------------------------------------------------------------------------
+ */
+ 'cache_config' => [
+ 'cache_ttl' => env('CACHE_USER_TTL', 3600),
+ 'cache_channel' => env('CACHE_USER_CHANNEL', 'every'),
+ 'model_with' => env('CACHE_USER_MODEL_WITH'),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | 用户默认的头像地址
+ |--------------------------------------------------------------------------
+ */
+ 'avatar' => env('USER_DEFAULT_AVATAR', ''),
+
+ /*
+ |--------------------------------------------------------------------------
+ | 默认推荐用ID
+ |--------------------------------------------------------------------------
+ */
+ 'default_parent_id' => 0,
+
+ /*
+ |--------------------------------------------------------------------------
+ | 推广码的加密配置
+ |--------------------------------------------------------------------------
+ */
+ 'invite_code' => [
+ 'salt' => 'Uz.Tech',
+ 'length' => 10,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | 社会化登录平台配置(暂时加入支付宝,微信,抖音的配置信息)
+ |--------------------------------------------------------------------------
+ */
+ 'socialite' => [
+ 'alipay' => [
+ 'client_id' => env('ALIPAY_CLIENT_ID', ''),
+ 'rsa_private_key' => env('RSA_PRIVATE_KEY', ''),
+ 'redirect' => '',
+ ],
+ 'douyin' => [
+ 'client_id' => env('DOUYIN_CLIENT_ID', ''),
+ 'client_secret' => env('DOUYIN_CLIENT_SECRET', ''),
+ 'redirect' => '',
+ ],
+ 'wechat' => [
+ 'client_id' => env('WECHAT_CLIENT_ID', ''),
+ 'client_secret' => env('WECHAT_CLIENT_SECRET', ''),
+ 'redirect' => '',
+ // 开放平台 - 第三方平台所需
+ 'component' => [
+ // or 'app_id', 'component_app_id' as key
+ 'id' => '',
+ // or 'app_token', 'access_token', 'component_access_token' as key
+ 'token' => '',
+ ],
+ ],
+ 'unicloud' => [
+ 'key' => env('UNICLOUD_API_KEY', ''),
+ 'secret' => env('UNICLOUD_API_SECRET', ''),
+ 'self_secret' => env('UNICLOUD_SELF_SECRET', ''),
+ 'domain' => env('UNICLOUD_DOMAIN', ''),
+ 'cloud_function_url' => [
+ 'one_key' => env('UNICLOUD_FUNCTION_URL_ONE_KEY', ''),
+ ],
+ ],
+ ],
+];
diff --git a/modules/User/Config/identity.php b/modules/User/Config/identity.php
new file mode 100644
index 0000000..1c14a79
--- /dev/null
+++ b/modules/User/Config/identity.php
@@ -0,0 +1,19 @@
+ false,
+ 'conditions' => [
+ 'price' => '开通金额',
+ ],
+ 'rules' => [
+ 'buy_crystal' => '购物贡献比例',
+ 'give_crystal' => '开通贡献值',
+ 'store_sales' => '店铺销售',
+ 'barter_in' => '易货(进)',
+ 'barter_out' => '易货(出)',
+ ],
+];
\ No newline at end of file
diff --git a/modules/User/Console/Commands/UserIdentityOver.php b/modules/User/Console/Commands/UserIdentityOver.php
new file mode 100644
index 0000000..5ade631
--- /dev/null
+++ b/modules/User/Console/Commands/UserIdentityOver.php
@@ -0,0 +1,57 @@
+where('default', 1)->first();
+ if ($identity) {
+ IdentityMiddle::query()
+ ->whereNotNull('ended_at')
+ ->where('ended_at', '<', now())
+ ->chunkById(1000, function ($middles) use ($identity) {
+ foreach ($middles as $middle) {
+ $middle->user->joinIdentity($identity->id, IdentityLog::CHANNEL_SYSTEM);
+ }
+ });
+ }
+
+ }
+}
diff --git a/modules/User/Console/Kernel.php b/modules/User/Console/Kernel.php
new file mode 100644
index 0000000..ec5d5c7
--- /dev/null
+++ b/modules/User/Console/Kernel.php
@@ -0,0 +1,19 @@
+command('xuanchen:auto-set-user-identity-over')->everyMinute();
+ $schedule->command('xuanchen:auto-remind-user-case')->dailyAt('11:00');
+ $schedule->command('xuanchen:auto-remind-user-case')->dailyAt('19:00');
+ $schedule->command('xuanchen:auto-remind-user-sign')->dailyAt('11:00');
+ $schedule->command('xuanchen:auto-remind-user-sign')->dailyAt('19:00');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_logs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_logs_table.php
new file mode 100644
index 0000000..64bae54
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_logs_table.php
@@ -0,0 +1,41 @@
+id();
+ $table->unsignedBigInteger('account_id');
+ $table->unsignedBigInteger('rule_id');
+ $table->string('type', 50)->nullable();
+ $table->decimal('amount');
+ $table->decimal('balance');
+ $table->boolean('frozen')->default(0);
+ $table->string('remark')->nullable();
+ $table->json('source')->nullable();
+ $table->timestamp('settle_at')->nullable()->comment('列入结算日期');
+ $table->timestamp('frozen_at')->nullable()->comment('可解冻时间');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_account_logs');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_rules_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_rules_table.php
new file mode 100644
index 0000000..745a839
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_account_rules_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->string('title', 50);
+ $table->string('name', 50);
+ $table->string('type', 50);
+ $table->decimal('variable')->default(0);
+ $table->integer('trigger')->default(0);
+ $table->boolean('deductions');
+ $table->string('remark', 200);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_account_rules');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_accounts_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_accounts_table.php
new file mode 100644
index 0000000..70b43cd
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_accounts_table.php
@@ -0,0 +1,35 @@
+unsignedBigInteger('user_id')->primary();
+ $table->decimal('balance', 20, 2)->default(0);
+ $table->decimal('score', 20, 2)->default(0);
+ $table->decimal('coins', 20, 2)->default(0);
+ $table->decimal('other', 20, 2)->default(0);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_accounts');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_certifications_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_certifications_table.php
new file mode 100644
index 0000000..2a61b4f
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_certifications_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->boolean('verified')->default(0);
+ $table->string('name', 32);
+ $table->string('id_card', 32)->index();
+ $table->string('front_card')->nullable();
+ $table->string('back_card')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_certifications');
+ }
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php
new file mode 100644
index 0000000..fe92e91
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php
@@ -0,0 +1,53 @@
+id();
+ $table->string('name')->comment('身份名称');
+ $table->string('cover')->nullable()->comment('身份的LOGO');
+ $table->string('description')->nullable()->comment('简介');
+ $table->integer('stock')->default(0)->comment('水的库存');
+ $table->unsignedInteger('order')->default(0)->comment('展示排序');
+ $table->json('conditions')->nullable()->comment('升级的条件,JSON');
+ $table->json('rules')->nullable()->comment('身份权益,JSON');
+ $table->json('rights')->nullable()->comment('前台显示的权益');
+ $table->json('ruleshows')->nullable()->comment('前台显示的权益');
+ $table->tinyInteger('can_buy')->default(0)->comment('是否可以前台购买');
+ $table->tinyInteger('years')->default(0)->comment('有效期(年)');
+ $table->boolean('status')->default(0);
+ $table->boolean('serial_open')->default(0)->comment('是否开启身份编号');
+ $table->unsignedInteger('serial_places')->default(0)->comment('身份编号位数');
+ $table->string('serial_prefix', 4)->nullable()->comment('身份编号前缀');
+ $table->string('protocol_url')->nullable()->comment('协议地址');
+ $table->boolean('default')->default(0)->comment('是否默认身份');
+ $table->tinyInteger('channel')->default(1)->comment('缴费渠道');
+ $table->integer('total')->default(0)->comment('可开通总数');
+ $table->tinyInteger('job')->default(0)->comment('职位');
+ $table->timestamp('end_at')->nullable()->comment('结束时间');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_identities');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_logs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_logs_table.php
new file mode 100644
index 0000000..ae563a0
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_logs_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->unsignedBigInteger('user_id')->comment('用户ID');
+ $table->unsignedBigInteger('before')->comment('变化前身份');
+ $table->unsignedBigInteger('after')->comment('变化后身份');
+ $table->string('channel', 20)->nullable()->comment('变化渠道');
+ $table->string('remark', 200)->nullable()->comment('备注');
+ $table->json('source')->nullable()->comment('附加溯源信息');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_identity_logs');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_table.php
new file mode 100644
index 0000000..fb3ff89
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identity_table.php
@@ -0,0 +1,37 @@
+unsignedBigInteger('user_id')->index();
+ $table->unsignedBigInteger('identity_id')->index();
+ $table->dateTime('started_at')->nullable()->comment('身份的开始时间');
+ $table->dateTime('ended_at')->nullable()->comment('角色的结束时间');
+ $table->string('serial', '25')->nullable()->comment('身份生成的编号');
+ $table->timestamps();
+
+ $table->primary(['user_id', 'identity_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_identitie');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_infos_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_infos_table.php
new file mode 100644
index 0000000..459528f
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_infos_table.php
@@ -0,0 +1,34 @@
+unsignedInteger('user_id')->primary();
+ $table->string('nickname')->nullable();
+ $table->string('avatar')->nullable();
+ $table->json('extends')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_infos');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php
new file mode 100644
index 0000000..b004d1b
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php
@@ -0,0 +1,42 @@
+id();
+ $table->unsignedBigInteger('user_id');
+ $table->unsignedBigInteger('identity_id');
+ $table->decimal('price');
+ $table->tinyInteger('year')->default(1)->comment('开通年限');
+ $table->string('state');
+ $table->string('name')->comment('姓名');
+ $table->string('card_no')->comment('银行卡号');
+ $table->string('cover')->comment('凭证');
+ $table->tinyInteger('type')->comment('1 开通 2 续费');
+ $table->tinyInteger('channel')->default(1)->comment('1 身份 2 体验官');
+ $table->timestamps();
+ $table->index(['user_id', 'identity_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_orders');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_relations_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_relations_table.php
new file mode 100644
index 0000000..16f2566
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_relations_table.php
@@ -0,0 +1,34 @@
+unsignedInteger('user_id')->primary();
+ $table->unsignedInteger('parent_id')->default(0)->comment('上级用户ID');
+ $table->unsignedInteger('layer')->default(1)->comment('所在层级');
+ $table->longText('bloodline')->nullable()->comment('血缘线');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_relations');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_service_identity_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_service_identity_table.php
new file mode 100644
index 0000000..0979471
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_service_identity_table.php
@@ -0,0 +1,33 @@
+unsignedBigInteger('service_id')->index();
+ $table->unsignedBigInteger('identity_id')->index();
+ $table->timestamps();
+ $table->primary(['service_id', 'identity_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_service_identity');
+ }
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_services_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_services_table.php
new file mode 100644
index 0000000..cd69112
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_services_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->string('name');
+ $table->string('mobile');
+ $table->string('cover');
+ $table->boolean('status');
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_services');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_configs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_configs_table.php
new file mode 100644
index 0000000..e11fc2e
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_configs_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->json('params')->nullable()->comment('参数');
+ $table->json('tasks')->nullable()->comment('参数');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_sign_configs');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_logs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_logs_table.php
new file mode 100644
index 0000000..d56a23d
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_logs_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->date('date');
+ $table->boolean('type')->default(0)->comment('0:签到,1:补签');
+ $table->string('remark')->nullable()->comment('预留');
+ $table->timestamp('created_at');
+
+ $table->unique(['user_id', 'date']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_sign_logs');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php
new file mode 100644
index 0000000..915861a
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php
@@ -0,0 +1,38 @@
+id();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->unsignedInteger('continue_days')->default(0)->comment('连续签到天数');
+ $table->unsignedInteger('counts')->default(0)->comment('累计签到天数');
+ $table->timestamp('last_sign_at')->nullable();
+ $table->tinyInteger('need_case')->default(0)->comment('是否需要上传报告');
+ $table->timestamp('reset_at')->nullable()->comment('重置连续次数日期');
+ $table->tinyInteger('is_finish')->default(0)->comment('是否完成');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_signs');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_configs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_configs_table.php
new file mode 100644
index 0000000..d59d2ec
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_configs_table.php
@@ -0,0 +1,38 @@
+id();
+ $table->boolean('debug')->default(1);
+ $table->string('debug_code', 16);
+ $table->tinyInteger('length')->default(4);
+ $table->tinyInteger('expires')->default(5)->comment('有效期,单位:分钟');
+ $table->json('template')->nullable();
+ $table->string('default_gateway', 16);
+ $table->boolean('in_use')->default(0);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_sms_configs');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_gateways_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_gateways_table.php
new file mode 100644
index 0000000..a09e3d5
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_gateways_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('name', 16);
+ $table->string('slug', 16);
+ $table->json('configs');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_sms_gateways');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_table.php
new file mode 100644
index 0000000..9ff4324
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sms_table.php
@@ -0,0 +1,38 @@
+id();
+ $table->string('mobile', 16);
+ $table->string('channel', 16)->comment('验证渠道,一般对应TEMPLATE_ID');
+ $table->string('gateway', 16)->nullable()->comment('发送网关');
+ $table->string('content')->comment('短信内容,验证或通知');
+ $table->boolean('used')->default(0);
+ $table->timestamps();
+
+ $table->index(['mobile', 'channel']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_sms');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_socialite_configs_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_socialite_configs_table.php
new file mode 100644
index 0000000..4dec94d
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_socialite_configs_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('name', 100);
+ $table->string('slug', 100);
+ $table->json('configs')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_socialite_configs');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_apps_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_apps_table.php
new file mode 100644
index 0000000..e1f70d3
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_apps_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('user_wechat_id');
+ $table->string('openid')->unique();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_wechat_apps');
+ }
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_minis_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_minis_table.php
new file mode 100644
index 0000000..a4693d5
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_minis_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('user_wechat_id');
+ $table->string('openid')->unique();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_wechat_minis');
+ }
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_officials_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_officials_table.php
new file mode 100644
index 0000000..762c1ad
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechat_officials_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->foreignId('user_wechat_id');
+ $table->string('openid');
+ $table->boolean('subscribe')->comment('关注公众平台')->default(0);
+ $table->timestamp('subscribed_at')->nullable();
+ $table->string('subscribe_scene', 32)->comment('关注场景')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_wechat_officials');
+ }
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechats_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechats_table.php
new file mode 100644
index 0000000..ff0307f
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_wechats_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->foreignId('user_id');
+ $table->string('unionid')->comment('unionid')->nullable();
+ $table->string('nickname')->comment('微信昵称')->nullable();
+ $table->string('avatar')->comment('微信头像')->nullable();
+ $table->boolean('sex')->comment('性别')->default(0);
+ $table->string('country')->nullable();
+ $table->string('province')->nullable();
+ $table->string('city')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_wechats');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php
new file mode 100644
index 0000000..c3b716e
--- /dev/null
+++ b/modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php
@@ -0,0 +1,35 @@
+id()->startingValue(10000);
+ $table->string('username')->unique();
+ $table->string('password')->nullable();
+ $table->boolean('status')->default(1)->comment('״̬');
+ $table->string('remember_token')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('users');
+ }
+
+}
diff --git a/modules/User/Database/Migrations/2021_12_07_101850_create_user_certification_configs_table.php b/modules/User/Database/Migrations/2021_12_07_101850_create_user_certification_configs_table.php
new file mode 100644
index 0000000..8827074
--- /dev/null
+++ b/modules/User/Database/Migrations/2021_12_07_101850_create_user_certification_configs_table.php
@@ -0,0 +1,45 @@
+id();
+ $table->boolean('is_open')->default(0)->comment('开通第三方认证');
+ $table->boolean('is_ocr_open')->default(0)->comment('开通OCR认证,第三方认证开启时有效');
+ $table->boolean('type')->default(1);
+ $table->string('code')->nullable()->comment('个人认证接口code');
+ $table->string('url')->nullable()->comment('个人认证接口地址');
+ $table->string('ocr_appid')->nullable()->comment('ocr认证配置');
+ $table->string('ocr_secretkey')->nullable()->comment('ocr认证配置');
+ $table->boolean('status')->default(0);
+ $table->timestamps();
+ });
+
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_certification_configs');
+ Artisan::call('db:seed', [
+ '--class' => UserCertificationConfigsSeeder::class,
+ ]);
+ }
+}
diff --git a/modules/User/Database/Migrations/2022_07_26_160901_create_user_stocks_table.php b/modules/User/Database/Migrations/2022_07_26_160901_create_user_stocks_table.php
new file mode 100644
index 0000000..1ade178
--- /dev/null
+++ b/modules/User/Database/Migrations/2022_07_26_160901_create_user_stocks_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->unsignedBigInteger('user_id');
+ $table->integer('stock')->comment('总数');
+ $table->integer('hold')->default(0)->comment('持有数量');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_stocks');
+ }
+}
diff --git a/modules/User/Database/Migrations/2022_08_02_143704_create_user_stock_logs_table.php b/modules/User/Database/Migrations/2022_08_02_143704_create_user_stock_logs_table.php
new file mode 100644
index 0000000..0eae415
--- /dev/null
+++ b/modules/User/Database/Migrations/2022_08_02_143704_create_user_stock_logs_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->unsignedBigInteger('user_stock_id')->index();
+ $table->unsignedBigInteger('identity_id')->index();
+ $table->string('type')->comment('类型');
+ $table->integer('variable');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_stock_logs');
+ }
+}
diff --git a/modules/User/Database/Migrations/2022_09_07_141907_create_user_logs_table.php b/modules/User/Database/Migrations/2022_09_07_141907_create_user_logs_table.php
new file mode 100644
index 0000000..10dcb29
--- /dev/null
+++ b/modules/User/Database/Migrations/2022_09_07_141907_create_user_logs_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->unsignedBigInteger('user_id')->index();
+ $table->unsignedBigInteger('admin_id')->index();
+ $table->string('remark')->comment('备注内容');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_logs');
+ }
+}
diff --git a/modules/User/Database/Migrations/2022_09_07_170415_add_stock_to_user_orders_table.php b/modules/User/Database/Migrations/2022_09_07_170415_add_stock_to_user_orders_table.php
new file mode 100644
index 0000000..ffe740f
--- /dev/null
+++ b/modules/User/Database/Migrations/2022_09_07_170415_add_stock_to_user_orders_table.php
@@ -0,0 +1,32 @@
+integer('stock')->default(0)->comment('水数量')->after('year');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('user_orders', function (Blueprint $table) {
+
+ });
+ }
+}
diff --git a/modules/User/Database/Migrations/2022_09_08_111009_add_scource_to_user_orders_table.php b/modules/User/Database/Migrations/2022_09_08_111009_add_scource_to_user_orders_table.php
new file mode 100644
index 0000000..502cf6b
--- /dev/null
+++ b/modules/User/Database/Migrations/2022_09_08_111009_add_scource_to_user_orders_table.php
@@ -0,0 +1,32 @@
+json('source')->nullable()->after('cover');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('user_orders', function (Blueprint $table) {
+
+ });
+ }
+}
diff --git a/modules/User/Database/Migrations/2022_09_28_142001_create_user_subscribes_table.php b/modules/User/Database/Migrations/2022_09_28_142001_create_user_subscribes_table.php
new file mode 100644
index 0000000..bede40d
--- /dev/null
+++ b/modules/User/Database/Migrations/2022_09_28_142001_create_user_subscribes_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('unionid')->index();
+ $table->string('openid')->index();
+ $table->string('subscribe')->default(0);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_subscribes');
+ }
+}
diff --git a/modules/User/Database/Migrations/2022_09_28_142001_create_user_wechat_subscribes_table.php b/modules/User/Database/Migrations/2022_09_28_142001_create_user_wechat_subscribes_table.php
new file mode 100644
index 0000000..9a1e823
--- /dev/null
+++ b/modules/User/Database/Migrations/2022_09_28_142001_create_user_wechat_subscribes_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('unionid')->index();
+ $table->string('openid')->index();
+ $table->string('subscribe')->default(0);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_subscribes');
+ }
+}
diff --git a/modules/User/Database/Seeders/UserCertificationConfigsSeeder.php b/modules/User/Database/Seeders/UserCertificationConfigsSeeder.php
new file mode 100644
index 0000000..fd132a6
--- /dev/null
+++ b/modules/User/Database/Seeders/UserCertificationConfigsSeeder.php
@@ -0,0 +1,23 @@
+ 0,
+ 'is_ocr_open' => 0,
+ 'type' => 1,
+ 'code' => '',
+ 'url' => '',
+ 'ocr_appid' => '',
+ 'ocr_secretkey' => '',
+ 'status' => 0,
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Events/UserCertificationSuccess.php b/modules/User/Events/UserCertificationSuccess.php
new file mode 100644
index 0000000..a015ddd
--- /dev/null
+++ b/modules/User/Events/UserCertificationSuccess.php
@@ -0,0 +1,27 @@
+certification = $certification;
+ }
+
+}
diff --git a/modules/User/Events/UserJoinIdentity.php b/modules/User/Events/UserJoinIdentity.php
new file mode 100644
index 0000000..d1e006f
--- /dev/null
+++ b/modules/User/Events/UserJoinIdentity.php
@@ -0,0 +1,38 @@
+user = $user;
+ $this->identity = $identity;
+ }
+
+}
diff --git a/modules/User/Events/UserLoginSuccess.php b/modules/User/Events/UserLoginSuccess.php
new file mode 100644
index 0000000..995a7c7
--- /dev/null
+++ b/modules/User/Events/UserLoginSuccess.php
@@ -0,0 +1,51 @@
+user = $user;
+ $this->request = $request;
+ $this->gateway = $gateway;
+ }
+
+}
diff --git a/modules/User/Events/UserOrderPaid.php b/modules/User/Events/UserOrderPaid.php
new file mode 100644
index 0000000..222cf5f
--- /dev/null
+++ b/modules/User/Events/UserOrderPaid.php
@@ -0,0 +1,29 @@
+order = $order;
+ }
+
+}
diff --git a/modules/User/Events/UserRemoveIdentity.php b/modules/User/Events/UserRemoveIdentity.php
new file mode 100644
index 0000000..089ea14
--- /dev/null
+++ b/modules/User/Events/UserRemoveIdentity.php
@@ -0,0 +1,38 @@
+user = $user;
+ $this->identity = $identity;
+ }
+
+}
diff --git a/modules/User/Events/UserSignReplenishSuccess.php b/modules/User/Events/UserSignReplenishSuccess.php
new file mode 100644
index 0000000..afa3d07
--- /dev/null
+++ b/modules/User/Events/UserSignReplenishSuccess.php
@@ -0,0 +1,25 @@
+log = $log;
+ }
+
+}
diff --git a/modules/User/Events/UserSignSuccess.php b/modules/User/Events/UserSignSuccess.php
new file mode 100644
index 0000000..01d7de2
--- /dev/null
+++ b/modules/User/Events/UserSignSuccess.php
@@ -0,0 +1,25 @@
+log = $log;
+ }
+
+}
diff --git a/modules/User/Events/UserUpdateIdentity.php b/modules/User/Events/UserUpdateIdentity.php
new file mode 100644
index 0000000..8eeb273
--- /dev/null
+++ b/modules/User/Events/UserUpdateIdentity.php
@@ -0,0 +1,42 @@
+user = $user;
+ $this->before = $before;
+ $this->after = $after;
+ }
+
+}
diff --git a/modules/User/Facades/Calendar.php b/modules/User/Facades/Calendar.php
new file mode 100644
index 0000000..67cfa5f
--- /dev/null
+++ b/modules/User/Facades/Calendar.php
@@ -0,0 +1,109 @@
+startOfMonth()->startOfWeek(0)->toDateString();
+ $end = Carbon::parse($date)->endOfMonth()->endOfWeek(6)->toDateString();
+ $data = self::getBetweenDate($start, $end);
+
+ $calendar = Carbon::parse($date);
+
+ $days = $data->map(function ($today) use ($calendar) {
+ return [
+ 'today' => $today->toDateString(),
+ 'day' => (int) $today->format('j'),
+ 'isPast' => $today->isPast(),//是否为之前的日期
+ 'isMonth' => $today->isSameMonth($calendar),//当前月
+ ];
+ });
+
+ return [
+ 'start' => $start,
+ 'end' => $end,
+ 'calendar' => $days
+ ];
+
+ }
+
+ /**
+ * Notes: 返回日期区间
+ *
+ * @Author: 玄尘
+ * @Date: 2022/2/8 16:52
+ * @param string $start_at 开始日期
+ * @param string $end_at 结束日期
+ */
+ public static function getBetweenDate(string $start_at, string $end_at)
+ {
+ return collect(CarbonPeriod::create($start_at, $end_at));
+ }
+
+ /**
+ * Notes: 获取日期 某一天的前一天后一天
+ *
+ * @Author: 玄尘
+ * @Date: 2022/1/21 11:03
+ * @param string $today
+ * @param string $type
+ * @return string
+ */
+ public static function day(string $today = '', string $type = ''): string
+ {
+ $variate = ['before' => -1, 'after' => 1, '' => 0][$type];
+
+ return Carbon::parse($today)->addDays($variate)->toDateString();
+ }
+
+ /**
+ * Notes: 获取日期 某一天的前一天后一天
+ *
+ * @Author: 玄尘
+ * @Date: 2022/1/21 11:03
+ * @param string $month
+ * @param string $type
+ * @return string
+ */
+ public static function getFirstDayByMonth(string $month = '', string $type = ''): string
+ {
+ $month = Carbon::parse($month);
+ if ($type == 'before') {
+ $month = $month->subMonth();
+ }
+
+ if ($type == 'after') {
+ $month = $month->addMonth();
+ }
+
+ return $month->startOfMonth()->toDateString();
+ }
+
+ /**
+ * Notes: 获取年份
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/10 9:30
+ * @param $start
+ */
+ public static function getYears($startYear)
+ {
+ $years = CarbonPeriod::years($startYear);
+ return $years;
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Facades/Sms.php b/modules/User/Facades/Sms.php
new file mode 100644
index 0000000..f3bf4b7
--- /dev/null
+++ b/modules/User/Facades/Sms.php
@@ -0,0 +1,122 @@
+
+ * @param string|null $key 配置的主键
+ * @return mixed
+ */
+ public static function getConfig(string $key = null)
+ {
+ $model = SmsConfig::orderByDesc('in_use')->first();
+
+ $config = [
+ 'debug' => $model->debug ?? true,
+ 'debug_code' => $model->debug_code ?? '0000',
+ 'length' => $model->length ?? 4,
+ 'template' => $model->template ?? [],
+ 'default' => [
+ 'strategy' => OrderStrategy::class,
+ 'gateways' => [
+ $model->default_gateway ?? 'aliyun',
+ ],
+ ],
+ 'gateways' => [
+ $model->default_gateway => $model->getGateway(),
+ ],
+ ];
+
+// dd($config);
+
+ if (isset($config['gateways']['aliyun'])) {
+ $config['gateways']['aliyun']['access_key_id'] = $config['gateways']['aliyun']['APP_ID'];
+ $config['gateways']['aliyun']['access_key_secret'] = $config['gateways']['aliyun']['APP_SECRET'];
+ $config['gateways']['aliyun']['sign_name'] = $config['gateways']['aliyun']['SIGN_NAME'];
+ }
+
+ return Arr::get($config, $key) ?? $config;
+ }
+
+ /**
+ * Notes : 发送验证码短信
+ *
+ * @Date : 2021/5/26 4:17 下午
+ * @Author :
+ * @param string $mobile 手机号码
+ * @param string $channel 验证通道
+ * @throws InvalidArgumentException
+ * @throws NoGatewayAvailableException
+ */
+ public static function sendVerificationCode(string $mobile, string $channel = 'DEFAULT')
+ {
+ $message = new VerificationCode(self::getConfig(), $channel);
+
+ if (! self::getConfig('debug')) {
+ app('sms')->send($mobile, $message);
+ }
+
+ SmsModel::create([
+ 'mobile' => $mobile,
+ 'channel' => $channel,
+ 'gateway' => self::getConfig('debug') ? 'debug' : self::getConfig('default.gateways.0'),
+ 'content' => $message->code,
+ ]);
+ }
+
+ /**
+ * 验证短信
+ *
+ * @Author:
+ * @Date :2018-11-07T14:26:38+0800
+ * @param string $mobile 手机号码
+ * @param string $code 验证码
+ * @param string $channel 验证通道
+ * @return bool
+ */
+ public static function checkCode(string $mobile, string $code, string $channel = 'DEFAULT'): bool
+ {
+ $Sms = SmsModel::where('mobile', $mobile)->where('channel', $channel)->first();
+
+ if ($Sms && $Sms->content == $code) {
+ if ($Sms->used && ! self::getConfig('debug')) {
+ return false;
+ }
+ # todo 有效期判定
+ $Sms->used = 1;
+ $Sms->save();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Notes : 发送通知短信
+ *
+ * @Date : 2021/5/26 5:08 下午
+ * @Author :
+ * @param string|array $mobile 接受短信的手机号
+ * @param string $content 短信内容
+ */
+ public static function sendNotification($mobile, string $content)
+ {
+ # todo. 这里要实现批量发送,短信通道等操作,待测试
+ }
+
+}
diff --git a/modules/User/Facades/UserSign.php b/modules/User/Facades/UserSign.php
new file mode 100644
index 0000000..964fe8b
--- /dev/null
+++ b/modules/User/Facades/UserSign.php
@@ -0,0 +1,308 @@
+sign->continue_days;
+ $counts = $user->sign->counts;
+ if (SignLog::where('user_id', $user->id)
+ ->whereDate('date', (clone $date)->subDay()->toDateString())
+ ->exists()) {
+ $continue_days++;
+ } else {
+ $continue_days = 1;
+ }
+
+ $crystals = self::getSignCrystal($continue_days);
+ $rule_name = $params['rule_name'];
+ $str = "第{$continue_days}天签到";
+ if ($crystals > 0) {
+ $user->account->rule($rule_name, 0, false, [
+ 'frozen_at' => now()->toDateTimeString(),
+ 'settle_at' => now()->toDateTimeString(),
+ 'remark' => '第'.$continue_days.'天签到',
+ ]);
+ $str .= ",获得{$crystals}水滴"."\n";
+// $str .= '第'.$continue_days.'天签到';
+ }
+
+
+ $task_crystals = self::getTaskCrystal($continue_days);
+ if ($task_crystals > 0) {
+// $user->account->rule($rule_name, $task_crystals, true, [
+// 'frozen_at' => now()->toDateTimeString(),
+// 'settle_at' => now()->toDateTimeString(),
+// 'remark' => '连续签到'.$continue_days.'天',
+// ]);
+// $str .= '连续签到'.$continue_days.'天,获得水晶'.$task_crystals;
+ $str .= '连续签到'.$continue_days.'天';
+ }
+
+ $user->sign->update([
+ 'continue_days' => $continue_days,
+ 'counts' => ++$counts,
+ 'last_sign_at' => $date,
+ ]);
+ SignLog::create([
+ 'user_id' => $user->id,
+ 'date' => $date,
+ 'type' => 0,
+ ]);
+
+ return [true, $str];
+ } catch (Exception $e) {
+ return [false, $e->getMessage()];
+ }
+ }
+
+ /**
+ * 返回连续签到额外任务
+ *
+ * @param int $continue_days
+ * @return int
+ */
+ public static function getTaskCrystal(int $continue_days = 1): int
+ {
+ $tasks = SignConfig::getTasks();
+ $task = $tasks->firstWhere('day', $continue_days);
+
+ return $task['number'] ?? 0;
+ }
+
+ public static function getNextTaskCrystal($continue_days = 1)
+ {
+ $tasks = SignConfig::getTasks();
+
+ return $tasks->where('day', ' > ', $continue_days)
+ ->sortBy('day')
+ ->first();
+ }
+
+ /**
+ * 返回签到奖励
+ *
+ * @param int $continue_days
+ * @return HigherOrderCollectionProxy|int|mixed
+ */
+ public static function getSignCrystal(int &$continue_days = 1)
+ {
+ $params = SignConfig::getParams();
+ $crystals = 0;
+
+ switch ($params['type']) {
+ case 'single':
+ $crystals = $params['single_number'];
+ break;
+ case 'continuous':
+ $crystals = (int) $params['continuous_base'] + (int) bcmul($continue_days - 1,
+ $params['continuous_incremental'],
+ 0);
+ break;
+ case 'cycle':
+ if ($continue_days > $params['cycle_day']) {
+ $continue_days -= $params['cycle_day'];
+ }
+ $crystals = (int) $params['cycle_base'] + (int) bcmul($continue_days - 1, $params['cycle_incremental'],
+ 0);
+ break;
+ }
+
+ return $crystals;
+ }
+
+ /**
+ * Notes : 某一天是否可签到
+ *
+ * @Date : 2021/5/28 3:25 下午
+ * @Author :
+ * @param User $user
+ * @param Carbon $date
+ * @return bool
+ */
+ public static function canSign(User $user, DateTime $date): bool
+ {
+ $identityMiddle = $user->identityMiddle()->first();
+ $start_at = $identityMiddle->started_at;
+ if (! $start_at) {
+ $start_at = $identityMiddle->created_at;
+ }
+
+ return $identityMiddle->identity->order > 1 &&
+ Carbon::parse($date)->endOfDay()->gt($start_at) &&
+ ! $user->IsSign($date) &&
+ $date->isToday();
+ }
+
+ /**
+ * Notes: 是否可以补签
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/12 8:51
+ * @param User $user
+ * @param DateTime $date
+ * @return bool
+ */
+ public static function canReSign(User $user, DateTime $date): bool
+ {
+ $identityMiddle = $user->identityMiddle()->first();
+ $start_at = $identityMiddle->started_at;
+ if (! $start_at) {
+ $start_at = $identityMiddle->created_at;
+ }
+
+ return ! $user->IsSign($date) &&
+ now()->endOfDay()->gt($date) &&
+ ! $date->isToday() &&
+ Carbon::parse($date)->endOfDay()->gt($start_at);
+ }
+
+ /**
+ * Notes: 获取不可打卡的信息
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/10 13:18
+ */
+ public static function getNotSignText(User $user, DateTime $date)
+ {
+ $identityMiddle = $user->identityMiddle()->first();
+ if (SignLog::where('user_id', $user->id)->whereDate('date', $date)->exists()) {
+ return '今日已打卡';
+ }
+
+ if ($identityMiddle->identity->order < 2) {
+ return '未开通会员';
+ }
+
+ $started_at = $identityMiddle->started_at;
+ if (! $started_at) {
+ $started_at = $identityMiddle->created_at;
+ }
+
+ if ($started_at->gt(Carbon::parse($date)->startOfDay())) {
+ return '当前时间还未开通会员';
+ }
+
+
+ }
+
+ /**
+ * Notes : 补签
+ *
+ * @Date : 2021/5/28 3:15 下午
+ * @Author :
+ * @param User $user
+ * @param DateTime $date
+ * @return bool
+ * @throws Exception
+ */
+ public static function replenish(User $user, DateTime $date): array
+ {
+ if (! self::canReSign($user, $date)) {
+ throw new Exception(self::getNotSignText($user, $date));
+ }
+ $params = SignConfig::getParams();
+
+ $continue_days = $user->sign->continue_days;
+
+ $crystals = self::getSignCrystal($continue_days);
+ $rule_name = $params['rule_name'];
+ $str = $date->format('Y-m-d')."号补签";
+
+ if ($crystals > 0) {
+ $user->account->rule($rule_name, 0, false, [
+ 'frozen_at' => now()->toDateTimeString(),
+ 'settle_at' => now()->toDateTimeString(),
+ 'remark' => now()->toDateTimeString().'天补签到',
+ ]);
+ $str .= ",获得{$crystals}水滴"."\n";
+ }
+
+ $counts = $user->sign->counts;
+
+
+ $user->sign->update([
+ 'counts' => ++$counts,
+ ]);
+
+ $log = SignLog::create([
+ 'user_id' => $user->id,
+ 'date' => $date,
+ 'type' => 1,
+ ]);
+
+ self::recalculateContinueDays($user);
+
+ return [true, $str];
+ }
+
+ /**
+ * Notes: 重新计算连续签到日期
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/4 9:34
+ */
+ public static function recalculateContinueDays(User $user)
+ {
+ $reset_at = $user->sign->reset_at;
+ $days = SignLog::query()
+ ->byUser($user)
+ ->when($reset_at, function ($q) use ($reset_at) {
+ $q->where('date', '>=', $reset_at);
+ })
+ ->latest('date')
+ ->pluck('date');
+
+ $continue_days = 1;
+ foreach ($days as $key => $day) {
+ if (isset($days[$key + 1])) {
+ if ($day->subDay()->isSameDay($days[$key + 1])) {
+ $continue_days++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ $user->sign->update([
+ 'continue_days' => max($continue_days, 1),
+ 'counts' => $user->signLogs()->count()
+ ]);
+ return true;
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/AccountController.php b/modules/User/Http/Controllers/Admin/AccountController.php
new file mode 100644
index 0000000..712f185
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/AccountController.php
@@ -0,0 +1,80 @@
+model()->orderByDesc('user_id');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.username', '用户名');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.info.nickname', '用户昵称');
+ });
+ });
+
+ $grid->disableActions();
+ $grid->disableCreateButton();
+
+ $grid->model()->with(['user.info']);
+
+ $grid->column('user.username', '用户名');
+ $grid->column('用户昵称')->display(function () {
+ return $this->user->info->nickname;
+ });
+ $grid->column('balance', '余额');
+ $grid->column('score', '水滴');
+// $grid->column('coins');
+// $grid->column('other');
+ $grid->column('updated_at', '更新时间');
+ $grid->column('账户日志')->display(function () {
+ return '账户日志';
+ })->link(function () {
+ return route('admin.user.account.logs', $this);
+ }, '_self');
+
+ return $grid;
+ }
+
+ public function detail($id): Grid
+ {
+ $grid = new Grid(new AccountLog());
+
+ $grid->disableCreateButton();
+ $grid->disableActions();
+
+ if (is_numeric($id)) {
+ $grid->model()->where('account_id', $id);
+ } else {
+ $grid->column('用户')
+ ->display(function () {
+ return $this->account->user->username."({$this->account->user->info->nickname})";
+ });
+ }
+
+ $grid->column('type', '账户类型');
+ $grid->column('rule.title', '账变规则');
+ $grid->column('amount', '账变金额');
+ $grid->column('balance', '当期余额');
+ $grid->column('remark', '备注');
+ $grid->column('source', '详情')->hide();
+ $grid->column('created_at', '账变时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/AddUserRemark.php b/modules/User/Http/Controllers/Admin/Actions/AddUserRemark.php
new file mode 100644
index 0000000..3f722bc
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/AddUserRemark.php
@@ -0,0 +1,32 @@
+addLog(Admin::user(), $request->remark);
+
+ return $this->response()->success('添加备注成功')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+ public function form(User $user)
+ {
+ $this->textarea('remark', '备注')->required();
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/Certification/ConfigPublish.php b/modules/User/Http/Controllers/Admin/Actions/Certification/ConfigPublish.php
new file mode 100644
index 0000000..2763277
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/Certification/ConfigPublish.php
@@ -0,0 +1,26 @@
+', $model->id)->update(['status' => 0]);
+ $model->status = 1;
+ $model->save();
+
+ return $this->response()->success('发布配置完成')->refresh();
+ }
+
+ public function dialog()
+ {
+ $this->confirm('确定使用配置么?');
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/Certification/Replicate.php b/modules/User/Http/Controllers/Admin/Actions/Certification/Replicate.php
new file mode 100644
index 0000000..33e97af
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/Certification/Replicate.php
@@ -0,0 +1,33 @@
+replicate();
+
+ $new->status = 0;
+ $new->save();
+ });
+
+ return $this->response()->success('复制菜单完成')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error('复制菜单出错了')->refresh();
+ }
+ }
+
+ public function dialog()
+ {
+ $this->confirm('确定复制配置么?');
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php b/modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php
new file mode 100644
index 0000000..c8758ac
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php
@@ -0,0 +1,69 @@
+join_identity_id;
+ $remark = $request->remark;
+ $identity = Identity::find($identity_id);
+ $price = $identity->getCondition('price', '0');
+
+ $data = [
+ 'user_id' => $user->id,
+ 'identity_id' => $identity_id,
+ 'year' => 1,
+ 'type' => 1,
+ 'stock' => $identity->stock,
+ 'name' => '',
+ 'card_no' => '',
+ 'cover' => '',
+ 'state' => Order::STATE_INIT,
+ 'price' => $price,
+ ];
+
+ $order = Order::create($data);
+ $order->pay();
+
+//
+// $user->joinIdentity($identity_id, 'System', [
+// 'remark' => $remark,
+// ]);
+
+ return $this->response()->success('加入身份成功')->refresh();
+ } catch (Exception $e) {
+ return $this->response()->error($e->getMessage())->refresh();
+ }
+ }
+
+ public function form(User $user)
+ {
+ $userIdentity = $user->identityFirst();
+ if (empty($userIdentity)) {
+ $identities = Identity::whereIn('order', [2, 3, 4, 5])->pluck('name', 'id');
+ } elseif ($userIdentity->job == Identity::JOB_YK) {
+ $identities = Identity::whereIn('order', [2, 3, 4, 5])->pluck('name', 'id');
+ } else {
+ $identities = Identity::where('order', '>', $userIdentity->order)->pluck('name', 'id');
+ }
+ $this->select('join_identity_id', '加入身份')
+ ->options($identities)
+ ->required();
+ $this->text('remark', '加入备注');
+ $this->confirm('确认加入该身份?');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/Pay.php b/modules/User/Http/Controllers/Admin/Actions/Pay.php
new file mode 100644
index 0000000..21c25fd
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/Pay.php
@@ -0,0 +1,25 @@
+pay();
+
+ return $this->response()->success('支付状态调整成功')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/Refund.php b/modules/User/Http/Controllers/Admin/Actions/Refund.php
new file mode 100644
index 0000000..490b753
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/Refund.php
@@ -0,0 +1,25 @@
+refund();
+
+ return $this->response()->success('退款成功')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/RemoveIdentity.php b/modules/User/Http/Controllers/Admin/Actions/RemoveIdentity.php
new file mode 100644
index 0000000..a0b6234
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/RemoveIdentity.php
@@ -0,0 +1,48 @@
+remove_identity_id;
+ $remark = $request->remark;
+ $user->removeIdentity($identity_id, 'System', [
+ 'remark' => $remark,
+ ]);
+
+ $defaultIdentity = Identity::where('default', 1)->first();
+ if ($defaultIdentity) {
+ $user->joinIdentity($defaultIdentity->id, IdentityLog::CHANNEL_SYSTEM);
+ }
+
+ return $this->response()->success('移除身份成功')->refresh();
+ } catch (Exception $e) {
+ return $this->response()->error($e->getMessage())->refresh();
+ }
+ }
+
+ public function form(Model $model)
+ {
+ $this->select('remove_identity_id', '移除身份')
+ ->options(Identity::whereIn('id', $model->identities()->get()->pluck('id'))
+ ->pluck('name', 'id'))
+ ->required();
+ $this->text('remark', '移除说明');
+ $this->confirm('确认移除该身份?');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/UpdateRelation.php b/modules/User/Http/Controllers/Admin/Actions/UpdateRelation.php
new file mode 100644
index 0000000..f4705fb
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/UpdateRelation.php
@@ -0,0 +1,35 @@
+updateParent($request->parent_id)) {
+ return $this->response()->success('变更成功')->refresh();
+ } else {
+ return $this->response()->error('变更失败')->refresh();
+ }
+ } catch (Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+ public function form()
+ {
+ $this->text('parent_id', '目标ID');
+ $this->confirm('确认变更隶属?');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/UserStatusInit.php b/modules/User/Http/Controllers/Admin/Actions/UserStatusInit.php
new file mode 100644
index 0000000..e46319c
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/UserStatusInit.php
@@ -0,0 +1,27 @@
+update([
+ 'status' => User::STATUS_INIT
+ ]);
+
+ return $this->response()->success('设置成功')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Actions/UserStatusRefund.php b/modules/User/Http/Controllers/Admin/Actions/UserStatusRefund.php
new file mode 100644
index 0000000..16d3837
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Actions/UserStatusRefund.php
@@ -0,0 +1,27 @@
+update([
+ 'status' => User::STATUS_REFUND
+ ]);
+
+ return $this->response()->success('设置成功')->refresh();
+ } catch (\Exception $exception) {
+ return $this->response()->error($exception->getMessage())->refresh();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/CertificationConfigController.php b/modules/User/Http/Controllers/Admin/CertificationConfigController.php
new file mode 100644
index 0000000..d83043c
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/CertificationConfigController.php
@@ -0,0 +1,68 @@
+disableFilter();
+ $grid->model()->orderBy('id', 'desc');
+ $grid->actions(function ($action) {
+ $action->disableView();
+ $action->add(new Replicate);
+
+ if ($this->row->status == 0) {
+ $action->add(new ConfigPublish);
+ } else {
+ $action->disableEdit();
+ $action->disableDelete();
+ }
+ });
+ $grid->column('id', 'ID');
+ $grid->column('配置HASH')->display(function () {
+ return md5($this->created_at);
+ });
+ $grid->column('is_open', '开通网络认证')->bool();
+ $grid->column('is_ocr_open', '开通OCR认证')->bool();
+ $grid->column('type', '认证类型')
+ ->using(UserCertificationConfig::TYPE)
+ ->label();
+ $grid->column('status', '状态')->bool();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ protected function form(): Form
+ {
+ $form = new Form(new UserCertificationConfig());
+
+ $states = [
+ 'on' => ['value' => 1, 'text' => '开启', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'],
+ ];
+ $form->switch('is_open', '开通网络认证')->states($states);
+ $form->switch('is_ocr_open', '开通OCR认证')->states($states);
+ $form->radioButton('type', '认证类型')
+ ->options(UserCertificationConfig::TYPE)
+ ->required();
+ $form->password('code', '阿里云code')->help('开启认证时必填');
+ $form->url('url', '阿里云接口地址')->help('开启OCR认证时必填');
+ $form->text('ocr_appid', 'OCRAPPID')->help('开启OCR认证时必填');
+ $form->password('ocr_secretkey', 'OCRSecret')->help('开启OCR认证时必填');
+
+ return $form;
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/CertificationController.php b/modules/User/Http/Controllers/Admin/CertificationController.php
new file mode 100644
index 0000000..48676b3
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/CertificationController.php
@@ -0,0 +1,35 @@
+disableCreateButton();
+ $grid->disableActions();
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '姓名');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('id_card', '身份证号');
+ });
+ });
+
+ $grid->column('id', '#ID#');
+ $grid->column('name', '姓名');
+ $grid->column('id_card', '身份证号');
+ $grid->column('created_at', '申请时间');
+
+ return $grid;
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/GatewayController.php b/modules/User/Http/Controllers/Admin/GatewayController.php
new file mode 100644
index 0000000..b91d85e
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/GatewayController.php
@@ -0,0 +1,42 @@
+disableFilter();
+
+ $grid->column('name', '网关名称');
+ $grid->column('slug', '网关标识');
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new SmsGateway());
+
+ $form->text('name', '网关名称')->required();
+ $form->text('slug', '网关标识')->required();
+ $form->keyValue('configs', '网关配置')->value([
+ 'APP_ID' => '',
+ 'APP_SECRET' => '',
+ 'SIGN_NAME' => '',
+ ]);
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/IdentitiesController.php b/modules/User/Http/Controllers/Admin/IdentitiesController.php
new file mode 100644
index 0000000..6c1bd61
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/IdentitiesController.php
@@ -0,0 +1,226 @@
+model()->oldest('id')->oldest('order');
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->scope('trashed', '回收站')->onlyTrashed();
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('name', '身份名称');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('status', '状态')->select([
+ 0 => '禁用',
+ 1 => '正常',
+ ]);
+ });
+ });
+
+ $grid->model()->oldest()->withCount('users');
+
+ $grid->column('id', '#ID#');
+ $grid->column('cover', '展示图片')->image('', 40);
+ $grid->column('name', '身份名称');
+ $grid->column('order', '排序');
+ $grid->column('status', '状态')->bool();
+ $grid->column('serial_open', '是否开启编号')->bool();
+ $grid->column('serial_places', '编号长度');
+ $grid->column('serial_prefix', '编号前缀');
+ $grid->column('years', '有效期(月)')
+ ->display(function () {
+ return $this->years > 0 ? $this->years : '永久';
+ });
+ $grid->column('stock', '送水(箱)');
+ $grid->column('users_count', '组内用户');
+ $grid->column('job', '身份')->using(Identity::JOBS)->label();
+ $grid->column('total', '可开通总数');
+ $grid->column('channel', '缴费渠道')
+ ->using(Identity::CHANNELS)
+ ->label([
+ Identity::CHANNEL_ONLINE => 'primary',
+ Identity::CHANNEL_OFFLINE => 'success',
+ ]);
+ foreach (config('identity.conditions') as $key => $title) {
+ $grid->column($title)->display(function () use ($key) {
+ return $this->getCondition($key, '无');
+ });
+ }
+ foreach (config('identity.rules') as $key => $title) {
+ $grid->column($title)->display(function () use ($key) {
+ return $this->getRule($key, '无');
+ });
+ }
+
+ $states = [
+ 'on' => ['value' => 1, 'text' => '是', 'color' => 'primary'],
+ 'off' => ['value' => 0, 'text' => '否', 'color' => 'default'],
+ ];
+ $grid->column('default', '默认身份')->switch($states);
+ $grid->column('can_buy', '前台开通')->switch($states);
+
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Identity());
+ $form->text('name', '身份名称')
+ ->required()
+ ->rules([
+ 'unique:Modules\User\Models\Identity,name,{{id}}',
+ ], [
+ 'unique' => '身份名称已经存在',
+ ]);
+ $this->cover($form, 'cover', '展示图片');
+ $form->number('order', '排序')
+ ->required()
+ ->default(0);
+ $form->textarea('description', '身份简介')
+ ->rules(['max:255'], [
+ 'max' => '简介内容最大不能超过:max个字符',
+ ]);
+ $form->switch('default', '默认身份')
+ ->default(0);
+ $form->switch('can_buy', '前台开通')
+ ->help('是否可以在前台开通身份')
+ ->default(0);
+ $form->number('stock', '库存')
+ ->help('开通身份送多少水')
+ ->default(0);
+
+ $form->number('years', '有效期(月)')
+ ->help('0未长期有效')
+ ->default(0);
+
+ $form->switch('status', '状态')
+ ->required()
+ ->default(1);
+ $form->select('serial_open', '是否开启编号')
+ ->options([
+ false => '关闭',
+ true => '开启',
+ ])
+ ->required();
+ $form->number('serial_places', '编号长度')
+ ->rules([
+ 'exclude_if:serial_open,true',
+ 'required',
+ ], [
+ 'required' => '编号开启必填',
+ ]);
+ $form->text('serial_prefix', '编号前缀');
+ $form->text('protocol_url', '协议地址')->help('显示开通身份签署的协议');
+ $form->radioButton('channel', '缴费渠道')
+ ->options(Identity::CHANNELS)
+ ->default(1);
+ $form->radioButton('job', '身份')
+ ->options(Identity::JOBS)
+ ->default(0)
+ ->when(Identity::JOB_TY, function (Form $form) {
+ $form->number('total', '可开通总数')->default(100);
+ $form->date('end_at', '结束日期');
+ });
+ $form->divider();
+
+ $form->table('conditions', '升级条件', function (NestedForm $form) {
+ $form->select('name', '条件')
+ ->required()
+ ->options(config('identity.conditions'))
+ ->rules('required');
+ $form->select('compare', '规则')
+ ->options([
+ '>' => '大于',
+ '>=' => '大于等于',
+ '=' => '等于',
+ '<' => '小于',
+ '<=' => '小于等于',
+ ])
+ ->required()
+ ->rules('required');
+ $form->text('value', '值')
+ ->required()
+ ->rules('required');
+ });
+//
+ $form->table('rules', '身份权益', function (NestedForm $form) {
+ $form->select('name', '权益')
+ ->required()
+ ->options(config('identity.rules'))
+ ->rules('required');
+ $form->select('compare', '规则')
+ ->options([
+ '>' => '大于',
+ '>=' => '大于等于',
+ '=' => '等于',
+ '<' => '小于',
+ '<=' => '小于等于',
+ ])
+ ->required()
+ ->rules('required');
+ $form->text('value', '值')
+ ->required()
+ ->rules('required');
+ });
+ $form->table('ruleshows', '身份权益(前台展示)', function (NestedForm $form) {
+ $form->image('icon', '权益图片');
+ $form->select('name', '权益')
+ ->options(config('identity.show_rules'))
+ ->required()
+ ->rules('required');
+ $form->text('value', '值')
+ ->required()
+ ->rules('required');
+ });
+//
+ $form->table('rights', '可享受权益', function (NestedForm $form) {
+ $form->text('name', '名称')
+ ->required()
+ ->rules('required');
+ $form->image('cover', '权益图片');
+ $form->text('remark', '备注')
+ ->required()
+ ->rules('required');
+ $form->number('order', '排序');
+ });
+
+// $form->saving(function ($form) {
+//// dd(request()->all());
+// });
+
+
+ $form->saved(function ($form) {
+ $model = $form->model();
+ if ($model->default == 1) {
+ Identity::where('id', '!=', $model->id)
+ ->where('default', 1)
+ ->update([
+ 'default' => 0,
+ ]);
+ }
+ });
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/IdentityLogController.php b/modules/User/Http/Controllers/Admin/IdentityLogController.php
new file mode 100644
index 0000000..e2cd205
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/IdentityLogController.php
@@ -0,0 +1,60 @@
+disableActions();
+ $grid->disableCreateButton();
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.username', '用户名');
+ });
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.info.nickname', '用户昵称');
+ });
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('channel', '变更方式')->select(IdentityLog::CHANNEL_MAP);
+ });
+ });
+
+ $grid->column('user.username', '用户名');
+ $grid->column('用户昵称')->display(function () {
+ return $this->user->info->nickname;
+ });
+ $grid->column('before_identity.name', '变更前身份')->display(function () {
+ if ($this->before == 0) {
+ return '无';
+ }
+
+ return $this->before_identity->name;
+ });
+ $grid->column('after_identity.name', '变更后身份')->display(function () {
+ if ($this->after == 0) {
+ return '无';
+ }
+
+ return $this->after_identity->name;
+ });
+ $grid->column('channel', '变更方式')->using(IdentityLog::CHANNEL_MAP);
+ $grid->column('remark', '备注');
+ $grid->column('created_at', '变更时间');
+
+ return $grid;
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/IndexController.php b/modules/User/Http/Controllers/Admin/IndexController.php
new file mode 100644
index 0000000..0d4df03
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/IndexController.php
@@ -0,0 +1,156 @@
+
+ * @return Grid
+ */
+ public function grid(): Grid
+ {
+ $grid = new Grid(new User());
+
+ if (config('user.create_user_by_admin')) {
+ $grid->quickCreate(function (Grid\Tools\QuickCreate $create) {
+ $create->text('username', '用户名')->required();
+ $create->password('password', '登录密码')->required();
+ $create->text('info.nickname', '用户昵称')->required();
+ });
+ } else {
+ $grid->disableCreateButton();
+ }
+
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ ! config('user.create_user_by_admin') && $actions->disableEdit();
+ $actions->disableDelete();
+ $actions->disableView();
+ $actions->add(new JoinIdentity());
+ $actions->add(new RemoveIdentity());
+ $actions->add(new UpdateRelation());
+ });
+
+ $grid->quickSearch('username')->placeholder('快速搜索用户名');
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('username', '用户名');
+ });
+
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('info.nickname', '用户昵称');
+ $filter->like('identityMiddle.serial', '会员编号(数字)');
+ });
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('identities.id', '身份')->select(Identity::pluck('name', 'id'));
+ $filter->equal('parent.username', '推荐人');
+ });
+ });
+
+ $grid->model()->withCount('addresses')->with(['info', 'parent', 'identities', 'addresses']);
+
+ $grid->column('info.avatar', '头像')->image('', 32);
+ $grid->column('id', '用户ID');
+ $grid->column('username', '用户名');
+ $grid->column('info.nickname', '用户昵称');
+
+// $grid->column('addresses_count', '收货地址')
+// ->link(function () {
+// return route('admin.mall.addresses.index', ['user_id' => $this->id]);
+// }, '_self');
+ $grid->column('推荐人')->display(function () {
+ return $this->parent->username ?? '无';
+ });
+ $grid->column('identities', '用户身份')
+ ->display(function () {
+ $data = [];
+ foreach ($this->identities as $identity) {
+ $data[] = $identity->name.' : '.$identity->serial_prefix.$identity->getOriginal('pivot_serial');
+ }
+
+ return $data;
+ })
+ ->label();
+
+
+ $grid->column('是否关注')
+ ->display(function () {
+ return $this->isOfficialSubscribe();
+ })
+ ->bool();
+ $grid->column('邀请码')
+ ->display(function () {
+ return Hashids::connection('code')->encode($this->id);
+ });
+ $grid->column('created_at', '注册时间');
+
+ return $grid;
+ }
+
+ /**
+ * Notes : 编辑表单
+ *
+ * @Date : 2021/7/15 5:09 下午
+ * @Author :
+ * @return Form
+ * @throws Exception
+ */
+ public function form(): Form
+ {
+ $form = new Form(new User());
+
+ if ($form->isCreating() && ! config('user.create_user_by_admin')) {
+ throw new Exception('不运允许添加用户');
+ }
+
+ if ($form->isCreating() && ! config('user.edit_user_by_admin')) {
+ throw new Exception('不运允许编辑用户');
+ }
+
+ $form->text('username', '用户名')
+ ->required()
+ ->rules('unique:users,username,{{id}}');
+ $form->password('password', '登录密码')
+ ->required()
+ ->rules('min:6');
+ $form->text('info.nickname', '用户昵称');
+
+ return $form;
+ }
+
+ /**
+ * Notes : User 列表选择, 这里没有判断,用户是否已经有店铺了,如果判断的情况,可能导致当前用户 无法被选中
+ *
+ * @Date : 2021/5/6 4:35 下午
+ * @Author :
+ */
+ public function ajax(Request $request)
+ {
+ $q = $request->get('q');
+
+ return User::leftJoin('user_infos as info', 'users.id', '=', 'info.user_id')
+ ->where('username', 'like', "%$q%")
+ ->orWhere('info.nickname', 'like', "%$q%")
+ ->select('id', DB::raw('CONCAT(username, " [", info.nickname, "]") as text'))
+ ->paginate();
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/OrderController.php b/modules/User/Http/Controllers/Admin/OrderController.php
new file mode 100644
index 0000000..6ceed49
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/OrderController.php
@@ -0,0 +1,88 @@
+model()->latest()->where('identity_id', '>', 2);
+ $grid->disableCreateButton();
+
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableEdit();
+ $actions->disableDelete();
+ $actions->disableView();
+ if ($actions->row->canPay()) {
+ $actions->add(new Pay());
+ }
+
+ if ($actions->row->canRefund()) {
+ $actions->add(new Refund());
+ }
+
+ });
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->like('user.username', '用户名');
+ $filter->like('user.info.nickname', '用户昵称');
+ });
+
+ $filter->column(1 / 2, function (Grid\Filter $filter) {
+ $filter->equal('identity.id', '身份')->select(function () {
+ return Identity::query()->where('order', '>', 1)->pluck('name', 'id');
+ });
+ $filter->equal('state', '状态')->select(Order::STATES);
+ });
+
+ });
+
+ $grid->column('id', '用户ID');
+ $grid->column('升级用户')->display(function () {
+ return $this->user->username."({$this->user->info->nickname})";
+ });
+ $grid->column('name', '打款人姓名');
+ $grid->column('cover', '打款凭证')->gallery(['width' => 60, 'height' => 60]);
+ $grid->column('identity.name', '开通身份');
+ $grid->column('price', '应打款额')->editable();
+ $grid->column('state', '状态')->using(Order::STATES)->label();
+ $grid->column('type', '类型')->using(Order::TYPES)->label();
+
+ $grid->column('created_at', '升级时间');
+
+ return $grid;
+ } catch (Exception $exception) {
+ dd('Payment 模块不存在,无法加载订单数据');
+ }
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Order());
+
+ $form->decimal('price', '金额')->required();
+
+ return $form;
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/RuleController.php b/modules/User/Http/Controllers/Admin/RuleController.php
new file mode 100644
index 0000000..d753226
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/RuleController.php
@@ -0,0 +1,60 @@
+disableTools();
+ $grid->actions(function (Grid\Displayers\Actions $actions) {
+ $actions->disableView();
+ $actions->disableDelete();
+ });
+ $grid->column('id', '#ID#');
+ $grid->column('title', '规则名称');
+ $grid->column('name', '规则关键字');
+ $grid->column('type', '规则账户')->using(Account::TYPES);
+ $grid->column('variable', '固定额度');
+ $grid->column('created_at', '创建时间');
+ $grid->column('updated_at', '更新时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new AccountRule());
+ $form->text('title', '规则名称')->required();
+ if ($form->isCreating()) {
+ $form->text('name', '规则关键字')->required();
+ } else {
+ $form->text('name', '规则关键字')
+ ->readonly()
+ ->required();
+ }
+ $form->select('type', '规则账户')
+ ->options(Account::TYPES)
+ ->required();
+ $form->number('variable', '固定额度')->default(0);
+ $states = [
+ 'on' => ['value' => 1, 'text' => '是', 'color' => 'success'],
+ 'off' => ['value' => 0, 'text' => '否', 'color' => 'danger'],
+ ];
+ $form->switch('deductions', '立即扣款')->states($states);
+ $form->textarea('remark', '描述')->required();
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/Selectable/Identities.php b/modules/User/Http/Controllers/Admin/Selectable/Identities.php
new file mode 100644
index 0000000..073b128
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/Selectable/Identities.php
@@ -0,0 +1,38 @@
+identities)->name;
+ };
+ }
+
+ public function make()
+ {
+ $this->column('id', '#ID#');
+ $this->column('name', '身份名称');
+ $this->column('status', '状态')->bool();
+ $this->column('created_at', '时间');
+
+ $this->filter(function (Filter $filter) {
+ $filter->like('name', '身份名称');
+ });
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/ServiceController.php b/modules/User/Http/Controllers/Admin/ServiceController.php
new file mode 100644
index 0000000..fe822ee
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/ServiceController.php
@@ -0,0 +1,51 @@
+disableTools();
+
+ $grid->model()->with(['identities']);
+
+ $grid->column('id', '#ID#');
+ $grid->column('name', '姓名');
+ $grid->column('mobile', '联系方式');
+ $grid->column('status', '状态')->bool();
+ $grid->column('identities', '服务身份')
+ ->belongsToMany(Identities::class);
+ $grid->column('created_at', '创建时间');
+ $grid->column('updated_at', '更新时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new Service());
+ $form->text('name', '姓名')->required();
+ $form->mobile('mobile', '联系方式')->required();
+ $this->cover($form, 'cover', '客服二维码');
+ $form->switch('status', '显示')->default(1);
+ $form->belongsToMany('identities', Identities::class, '服务身份');
+
+ return $form;
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/SignBannerController.php b/modules/User/Http/Controllers/Admin/SignBannerController.php
new file mode 100644
index 0000000..082e560
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/SignBannerController.php
@@ -0,0 +1,40 @@
+column('id', '#ID#');
+ $grid->column('title', '标题');
+ $grid->column('cover', '封面图')->image('', 100, 100);
+ $grid->column('status', '状态')->bool();
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new SignBanner());
+
+ $form->text('title', '标题')->required();
+ $this->cover($form);
+ $form->switch('status', '状态')->default(1);
+
+ return $form;
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/SignConfigController.php b/modules/User/Http/Controllers/Admin/SignConfigController.php
new file mode 100644
index 0000000..78e9f6c
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/SignConfigController.php
@@ -0,0 +1,61 @@
+ 1]);
+
+ $form = new Form(new SignConfig());
+ $form->disableCreatingCheck();
+ $form->disableEditingCheck();
+ $form->tools(function (Form\Tools $tools) {
+ $tools->disableDelete();
+ $tools->disableView();
+ $tools->disableList();
+ });
+ $form->embeds('params', '配置', function (EmbeddedForm $form) {
+ $form->radio('open', '是否开启')->options([
+ 0 => '关闭',
+ 1 => '开启',
+ ])->required();
+ $form->text('rule_name', '签到账变关键字')->default('');
+ $form->radio('show_type', '签到展示类型')
+ ->options(SignConfig::SHOWTYPES)
+ ->default(SignConfig::SHOWTYPES_DAY)
+ ->required();
+ $form->radio('type', '签到类型')
+ ->options(SignConfig::TYPES)
+ ->required();
+ $form->number('single_number', '单次奖励')->default(0);
+ $form->number('continuous_base', '连续基础值')->default(0);
+ $form->number('continuous_incremental', '连续增量')->default(0);
+ $form->number('cycle_day', '周期天数')->default(0);
+ $form->number('cycle_base', '周期基础值')->default(0);
+ $form->number('cycle_incremental', '周期增量')->default(0);
+ });
+ $form->table('tasks', '特殊奖励', function ($form) {
+ $form->number('day', '天数')->default(0);
+ $form->number('number', '奖励')->default(0);
+ });
+
+ return $form;
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/SignTextController.php b/modules/User/Http/Controllers/Admin/SignTextController.php
new file mode 100644
index 0000000..e4528f1
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/SignTextController.php
@@ -0,0 +1,38 @@
+column('id', '#ID#');
+ $grid->column('title', '标题');
+ $grid->column('description', '一级描述');
+ $grid->column('sub_description', '二级描述');
+ $grid->column('status', '状态')->bool();
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new SignText());
+
+ $form->text('title', '标题')->required();
+ $form->text('description', '一级描述')->required();
+ $form->text('sub_description', '二级描述')->required();
+ $form->switch('status', '状态')->default(1);
+
+ return $form;
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/SmsConfigController.php b/modules/User/Http/Controllers/Admin/SmsConfigController.php
new file mode 100644
index 0000000..29978c9
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/SmsConfigController.php
@@ -0,0 +1,54 @@
+disableFilter();
+ $grid->model()->orderByDesc('in_use');
+
+ $grid->column('debug', '调试模式')->bool();
+ $grid->column('debug_code', '调试验证码');
+ $grid->column('length', '验证码位数');
+ $grid->column('expires', '有效期/分钟');
+ $grid->column('default_gateway', '默认网关');
+ $grid->column('in_use', '使用中')->bool();
+ $grid->column('created_at', '创建时间');
+
+ return $grid;
+ }
+
+ public function form(): Form
+ {
+ $form = new Form(new SmsConfig());
+
+ $form->select('default_gateway', '默认网关')->options(function () {
+ return SmsGateway::pluck('name', 'slug');
+ })->required();
+ $form->switch('debug', '调试模式');
+ $form->text('debug_code', '调试验证码')->setWidth(2)->required();
+ $form->number('length', '验证码位数')->default(4)->required();
+ $form->number('expires', '有效期/分钟')->default(5)->required();
+ $form->keyValue('template', '渠道模板')->value([
+ 'DEFAULT' => '',
+ 'LOGIN' => '',
+ 'REGISTER' => '',
+ ])->required();
+ $form->switch('in_use', '使用中');
+
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/SmsController.php b/modules/User/Http/Controllers/Admin/SmsController.php
new file mode 100644
index 0000000..6d32b93
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/SmsController.php
@@ -0,0 +1,37 @@
+disableCreateButton();
+ $grid->disableActions();
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->equal('mobile', '接收手机');
+ });
+ });
+
+ $grid->column('mobile', '接收手机');
+ $grid->column('channel', '短信渠道');
+ $grid->column('gateway', '发送网关');
+ $grid->column('content', '短信内容');
+ $grid->column('used', '使用')->bool();
+ $grid->column('created_at', '发送时间');
+
+ return $grid;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Admin/StockController.php b/modules/User/Http/Controllers/Admin/StockController.php
new file mode 100644
index 0000000..0812cf4
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/StockController.php
@@ -0,0 +1,60 @@
+
+ * @return Grid
+ */
+ public function grid(): Grid
+ {
+ $grid = new Grid(new UserStock());
+
+
+ $grid->disableCreateButton();
+ $grid->disableActions();
+
+
+ $grid->filter(function (Grid\Filter $filter) {
+ $filter->column(1 / 3, function (Grid\Filter $filter) {
+ $filter->like('user.username', '用户名');
+ });
+ });
+
+ $grid->model()->withCount('logs')->with(['user.info']);
+
+ $grid->column('user.username', '用户名');
+ $grid->column('用户昵称')
+ ->display(function () {
+ return $this->user->info->nickname;
+ });
+
+ $grid->column('stock', '总数');
+ $grid->column('hold', '提货数')
+ ->link(function () {
+ return admin_url('/users/stocks/'.$this->id.'/logs');
+ }, '_blank');
+ $grid->column('剩余')
+ ->display(function () {
+ return $this->residue;
+ });
+
+ $grid->column('created_at', '注册时间');
+
+ return $grid;
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Admin/StockLogController.php b/modules/User/Http/Controllers/Admin/StockLogController.php
new file mode 100644
index 0000000..2275369
--- /dev/null
+++ b/modules/User/Http/Controllers/Admin/StockLogController.php
@@ -0,0 +1,58 @@
+header($stock->user->info->nickname)
+ ->description('库存记录')
+ ->body($this->grid($stock));
+ }
+
+ /**
+ * Notes : 用户管理列表
+ *
+ * @Date : 2021/3/11 1:59 下午
+ * @Author :
+ * @return Grid
+ */
+ public function grid($stock): Grid
+ {
+ $grid = new Grid(new UserStockLog());
+
+ $grid->disableCreateButton();
+ $grid->disableActions();
+
+ $grid->model()->where('user_stock_id', $stock->id)->with(['userStock']);
+
+ $grid->column('id', 'ID');
+ $grid->column('身份')
+ ->display(function () {
+ if ($this->identity) {
+ return $this->identity->name;
+ } else {
+ return '---';
+ }
+ });
+ $grid->column('type', '类型')
+ ->using(UserStockLog::TYPES)
+ ->label();
+ $grid->column('variable', '数量');
+ $grid->column('created_at', '操作时间');
+ return $grid;
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Account/LogController.php b/modules/User/Http/Controllers/Api/Account/LogController.php
new file mode 100644
index 0000000..1cc1808
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Account/LogController.php
@@ -0,0 +1,61 @@
+account->logs()
+ ->where('type', 'score')
+ ->paginate();
+ $data = [
+ 'score' => $user->account->score,
+ 'logs' => new UserAccountLogCollection($logs)
+ ];
+ return $this->success($data);
+ }
+
+ /**
+ * Notes:现金账户
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/19 8:37
+ */
+ public function balance(): \Illuminate\Http\JsonResponse
+ {
+ $user = Api::user();
+ $logs = $user->account->logs()->where('type', 'balance')->paginate();
+
+ $yesterday = $user->account->logs()
+ ->whereDate('created_at', now()->subDay()->toDateTime())
+ ->where('type', 'balance')
+ ->where('amount', '>', 0)
+ ->sum('amount') ?? 0;
+
+ $withdraw = $user->withdraws()->sum('amount') ?? 0;
+
+ $data = [
+ 'account' => [
+ 'balance' => floatval($user->account->balance),
+ 'yesterday' => floatval($yesterday),
+ 'withdraw' => floatval($withdraw),
+ ],
+ 'logs' => new UserAccountLogCollection($logs)
+ ];
+ return $this->success($data);
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Auth/LoginController.php b/modules/User/Http/Controllers/Api/Auth/LoginController.php
new file mode 100644
index 0000000..26cb16a
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Auth/LoginController.php
@@ -0,0 +1,63 @@
+
+ * @param LoginRequest $request
+ * @return JsonResponse
+ */
+ public function index(LoginRequest $request): JsonResponse
+ {
+ $credentials = [
+ 'username' => $request->username,
+ 'password' => $request->password,
+ ];
+
+ $token = Api::attempt($credentials);
+
+ if ($token) {
+ $user = User::where('username', $request->username)->first();
+ event(new UserLoginSuccess($user, $request, '账号密码'));
+
+ return $this->success([
+ 'access_token' => $token,
+ 'token_type' => 'Bearer',
+ ]);
+ } else {
+ return $this->failed('用户名或密码错误');
+ }
+ }
+
+ /**
+ * Notes : 退出登录,撤销所有令牌,这个需要配合 api:^5.0 使用
+ *
+ * @Date : 2021/9/22 11:07 上午
+ * @Author :
+ * @return JsonResponse
+ */
+ public function logout(): JsonResponse
+ {
+ $user = Api::user();
+
+ if ($user) {
+ $user->tokens()->delete();
+ }
+
+ return $this->success('');
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Auth/RegisterController.php b/modules/User/Http/Controllers/Api/Auth/RegisterController.php
new file mode 100644
index 0000000..40becee
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Auth/RegisterController.php
@@ -0,0 +1,43 @@
+
+ * @param RegisterRequest $request
+ * @return JsonResponse
+ */
+ public function index(RegisterRequest $request): JsonResponse
+ {
+ $username = $request->username;
+ $password = $request->password;
+
+ $user = User::create([
+ 'username' => $username,
+ 'password' => $password,
+ ]);
+
+ $token = Api::login($user);
+
+ event(new UserLoginSuccess($user, $request, '用户注册'));
+
+ return $this->success([
+ 'access_token' => $token,
+ 'token_type' => 'Bearer',
+ ]);
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Auth/SmsController.php b/modules/User/Http/Controllers/Api/Auth/SmsController.php
new file mode 100644
index 0000000..d3111d7
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Auth/SmsController.php
@@ -0,0 +1,114 @@
+
+ * @param SmsRequest $request
+ * @return JsonResponse
+ */
+ public function send(SmsRequest $request): JsonResponse
+ {
+ $mobile = $request->mobileNo;
+
+ try {
+ Sms::sendVerificationCode($mobile);
+ $isExists = User::where('username', $mobile)->exists();
+ return $this->success([
+ 'new' => ! $isExists,
+ 'message' => '短信发送成功',
+ ]);
+ } catch (Exception $exception) {
+ return $this->failed($exception->getException('aliyun')->getMessage());
+ }
+ }
+
+ /**
+ * Notes : 短信验证码登录
+ *
+ * @Date : 2021/7/20 10:15 上午
+ * @Author :
+ * @param LoginSmsRequest $request
+ * @return JsonResponse
+ */
+ public function login(LoginSmsRequest $request): JsonResponse
+ {
+ $mobileNo = $request->mobileNo;
+ $code = $request->code;
+ $invite_code = $request->invite ?? '';
+ $channel_code = $request->channel ?? '';//渠道
+ $channel = '';
+ if ($channel_code) {
+ $channel = UserChannel::query()
+ ->where('code', $channel_code)
+ ->first();
+ }
+
+ $parent = 0;
+
+ if ($invite_code) {
+ $invite = Hashids::connection('code')->decode($invite_code);
+
+ if (empty($invite)) {
+ return $this->failed('邀请码不正确');
+ }
+ $parent = $invite[0];
+ $parentUser = User::find($parent);
+
+ //普通用户无法分享
+ if ($parentUser && $parentUser->identityFirst()->id == 1) {
+ $parent = 0;
+ }
+ }
+
+ $check = Sms::checkCode($mobileNo, $code);
+ if ($check == false) {
+ return $this->failed('验证码不正确', 422);
+ }
+ $user = User::firstOrCreate([
+ 'username' => $mobileNo,
+ ], [
+ 'parent_id' => $parent,
+ 'password' => 111111,
+ ]);
+
+ $is_new = $user->wasRecentlyCreated;
+
+ $message = '';
+ if ($is_new) {
+ if ($user->parent && $parent && $user->parent->id != $parent) {
+ $message = "您已与用户{$user->parent->info->nickname}绑定隶属关系,此次邀请码无效";
+ }
+
+ }
+
+
+ $token = Api::login($user);
+
+ event(new UserLoginSuccess($user, $request, '手机验证码'));
+
+ return $this->success([
+ 'token_type' => 'Bearer',
+ 'access_token' => $token,
+ 'message' => $message,
+ 'is_new' => $is_new,
+ ]);
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Auth/WechatController.php b/modules/User/Http/Controllers/Api/Auth/WechatController.php
new file mode 100644
index 0000000..df47b12
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Auth/WechatController.php
@@ -0,0 +1,154 @@
+url ?? '';
+ $scopes = $request->scopes ?? ['snsapi_userinfo'];
+
+ $web_url = config('user.web.base');
+ $redirect = $web_url.$pages;
+ if (! is_array($scopes)) {
+ $scopes = [$scopes];
+ }
+ $response = $app->oauth->scopes($scopes)->redirect($redirect);
+
+ return $this->success($response);
+ }
+
+ /**
+ * Notes: 获取jssdk
+ *
+ * @Author: 玄尘
+ * @Date : 2021/7/1 11:08
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function getJsSdk(Request $request)
+ {
+ $url = $request->url;
+ $jsApiList = $request->jsApiList ?? [];
+ $openTagList = $request->openTagList ?? [];
+ $app = app('wechat.official_account');
+
+ if ($url) {
+ $app->jssdk->setUrl($url);
+ }
+
+ if (! is_array($jsApiList)) {
+ $jsApiList = [$jsApiList];
+ }
+
+ if (! is_array($openTagList)) {
+ $openTagList = [$openTagList];
+ }
+
+ return $this->success($app->jssdk->buildConfig($jsApiList, false, false, true, $openTagList));
+ }
+
+ /**
+ * Notes: 微信分享
+ *
+ * @Author: 玄尘
+ * @Date : 2021/10/26 16:52
+ * @param Request $request
+ * @return JsonResponse|mixed
+ * @throws InvalidArgumentException
+ * @throws InvalidConfigException
+ * @throws RuntimeException
+ * @throws GuzzleException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function officialShare(Request $request)
+ {
+ $url = $request->url ?? '';
+ if (empty($url)) {
+ return $this->failed('地址必须传');
+ }
+
+ $app = app('wechat.official_account');
+
+ $apis = [
+ 'updateAppMessageShareData',
+ 'updateTimelineShareData',
+ // 'showOptionMenu',
+ // 'showMenuItems',
+ 'showAllNonBaseMenuItem',
+ ];
+
+ $app->jssdk->setUrl($url);
+ $cog = $app->jssdk->buildConfig($apis);
+
+ return $this->success($cog);
+ }
+
+
+ /**
+ * Notes: 获取小程序openid
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/26 14:17
+ * @return mixed|void
+ */
+ public function getMiniOpenid(Request $request)
+ {
+ $validator = \Validator::make($request->all(), [
+ 'code' => 'required',
+ ], [
+ 'code.required' => '缺少参数:code',
+ ]);
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first());
+ }
+
+ $weChat = app('wechat.mini_program');
+ $session = $weChat->auth->session($request->code);
+
+ if (isset($session->errcode)) {
+ return $this->failed($session->errmsg);
+ }
+
+ return $this->success($session->openid);
+ }
+
+ /**
+ * Notes: 获取公众号openid
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/26 14:21
+ */
+ public function getOfficialOpenid()
+ {
+ try {
+ $weUser = $this->getUserFromCode();
+ return $weUser->getId();
+ } catch (\Exception $exception) {
+ return $this->failed($exception->getMessage());
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Api/Certification/IndexController.php b/modules/User/Http/Controllers/Api/Certification/IndexController.php
new file mode 100644
index 0000000..4c5a5dd
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Certification/IndexController.php
@@ -0,0 +1,90 @@
+certification) {
+ return $this->failed('暂无认证记录');
+ }
+
+ return $this->success(new UserCertificationResource($user->certification));
+ }
+
+ /**
+ * Notes : 判定认证与秘钥
+ *
+ * @Date : 2021/5/17 16:52
+ * @Author : Mr.wang
+ * @return JsonResponse
+ */
+ public function certified(): JsonResponse
+ {
+ $user = Api::user();
+ if (! $user) {
+ return $this->success(false);
+ }
+
+ return $this->success((bool) $user->certification);
+ }
+
+ /**
+ * Notes : 保存认证信息
+ *
+ * @Date : 2021/7/20 10:24 上午
+ * @Author :
+ * @param CertificationRequest $request
+ * @return mixed
+ */
+ public function store(CertificationRequest $request)
+ {
+ $user = Api::user();
+ if ($user->certification) {
+ return $this->failed('用户已存在认证信息');
+ }
+ $keys = [
+ 'name' => $request->name,
+ 'idcard' => $request->id_card,
+ 'front_card' => $request->front_card ?? '',
+ 'back_card' => $request->back_card ?? '',
+ ];
+ $config = new UserCertificationConfig();
+ $data = $config->autoVerified($keys);
+ if ($data['code'] == 1) {
+ $verified = 1;
+ } else {
+ return $this->failed($data['message']);
+ }
+ $result = UserCertification::updateOrCreate([
+ 'user_id' => $user->id,
+ ], [
+ 'name' => $request->name,
+ 'id_card' => $request->id_card,
+ 'front_card' => $request->front_card ?? '',
+ 'back_card' => $request->back_card ?? '',
+ 'verified' => $verified,
+ ]);
+ if ($result) {
+ $user->info()->update([
+ 'nickname' => $request->name,
+ ]);
+
+ return $this->success('操作成功');
+ } else {
+ return $this->failed('操作失败');
+ }
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Favorite/IndexController.php b/modules/User/Http/Controllers/Api/Favorite/IndexController.php
new file mode 100644
index 0000000..80d027c
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Favorite/IndexController.php
@@ -0,0 +1,45 @@
+category_id ?? 0;
+ $favorteArticles = $user->favorites()
+ ->when($category_id, function ($q) use ($category_id) {
+ $q->whereHasMorph('favoriteable', [Article::class], function ($query) use ($category_id) {
+ $query->whereHas('categories', function ($q) use ($category_id) {
+ $q->where('cms_categories.id', $category_id);
+ });
+ });
+ })
+ ->paginate();
+
+ $data = [
+ 'categories' => new CategoryCollection(Category::query()->get()),
+ 'favorites' => new FavoriteCollection($favorteArticles),
+ ];
+ return $this->success($data);
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Identity/IndexController.php b/modules/User/Http/Controllers/Api/Identity/IndexController.php
new file mode 100644
index 0000000..3903a18
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Identity/IndexController.php
@@ -0,0 +1,409 @@
+type ?? '';
+ $user = Api::user();
+ $list = Identity::where('order', '>', 1)->where('can_buy', 1)->get();
+ $data = [
+ 'user' => [
+ 'username' => $user->username,
+ 'nickname' => $user->info->nickname,
+ 'avatar' => $user->info->avatar,
+ 'identity' => $user->identities->count()
+ ? new IdentityMiddleResource($user->identityMiddle()->first())
+ : '',
+ ],
+ 'identities' => UserIdentityResource::collection($list),
+ ];
+
+ return $this->success($data);
+ }
+
+ /**
+ * Notes: 获取可开通的身份
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/4 9:37
+ * @param Identity $identity
+ * @return JsonResponse
+ */
+ public function create(Identity $identity): JsonResponse
+ {
+ $user = Api::user();
+ $ended_at = '';
+ //可开通的身份ids
+ $ids = Identity::where('can_buy', 1)->pluck('id')->toArray();
+ $identity_middles = $user->identityMiddle;
+
+ //是否可以前台开通
+ if (in_array($identity->id, $ids)) {
+ //有有效期
+ if ($identity->years) {
+ //已经开通的身份ids
+ $identity_ids = $identity_middles->pluck('identity_id')->toArray();
+ //是否已经开通了此身份
+ if (in_array($identity->id, $identity_ids)) {
+ $ended_at = $identity_middles->where('identity_id', $identity->id)
+ ->first()->ended_at ?? now()->format('Y-m-d H:i:s');
+
+ $ended_at = Carbon::parse($ended_at)->addMonths($identity->years)->format('Y/m/d');
+ } else {
+ $ended_at = now()->addMonths($identity->years)->format('Y/m/d');
+ }
+ } else {
+ $ended_at = '永久';
+ }
+
+ }
+
+// $price = $identity->getCondition('price', '0');
+// if ($identity->job == Identity::JOB_JK) {
+// $coupon =
+// }
+
+ $price = $identity->getCondition('price', '0');
+ $cost = $identity->getCondition('cost', '0');
+ if (! $cost) {
+ $cost = $price;
+ }
+
+ $coupon_price = 0;
+ if ($identity->job == Identity::JOB_JK) {
+ $coupons = $this->getCreateOrderCoupon($user, $identity, 0);
+ if ($coupons->isNotEmpty()) {
+ $user_coupon = $coupons->first();
+ $coupon_price = $user_coupon->price;
+ if ($user_coupon->coupon->type == Coupon::TYPE_REDUCTION) {
+ $price = $price > $user_coupon->price ? bcsub($price, $coupon_price, 2) : 0;
+ }
+ }
+ }
+
+
+ $data = [
+ 'identity' => [
+ 'identity_id' => $identity->id,
+ 'name' => $identity->name,
+ 'cover' => $identity->cover_url,
+ 'description' => $ended_at ? '有效期至'.$ended_at : $identity->description,
+ 'coupon_price' => $coupon_price,
+ 'cost' => $cost,
+ 'price' => $price,
+ 'can_buy' => (bool) $identity->can_buy,
+ 'ended_at' => $ended_at,
+ 'service' => config('user.experience.service'),
+ 'protocol_url' => (string) $identity->protocol_url,//协议地址
+ ],
+ ];
+
+ return $this->success($data);
+
+ }
+
+ /**
+ * Notes: 开通会员
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/4 10:02
+ * @param Identity $identity
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function store(Identity $identity, Request $request): JsonResponse
+ {
+ $user = Api::user();
+
+ $price = $request->price ?? 0;
+ if ($identity->job != Identity::JOB_TY) {
+ $validator = Validator::make($request->all(), [
+ 'name' => 'required',
+ // 'card_no' => 'required|numeric',
+ 'cover' => 'required',
+ 'price' => 'required',
+ ], [
+ 'name.required' => '缺少姓名',
+ 'card_no.required' => '缺少银行卡号',
+ 'card_no.numeric' => '银行卡号只能是数字',
+ 'cover.required' => '缺少打款凭证',
+ 'price.required' => '缺少打款金额',
+ ]);
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first());
+ }
+
+
+// if ($identity->job == Identity::JOB_NK && ! $user->parent) {
+// return $this->failed('没有推荐人不可开通年卡会员');
+// }
+ } else {
+ $total = $identity->users()->count();
+ if ($total >= 100) {
+ return $this->failed('体验官最多可开通100人');
+ }
+ $end_at = $identity->end_at;
+ if ($end_at) {
+ $end_at = Carbon::parse($end_at)->endOfDay();
+ if (now()->gt($end_at)) {
+ return $this->failed('体验官活动已过期');
+ }
+ }
+
+
+ $tencentMap = app('xuanchen.tencent.map');
+ $res = $tencentMap->api()->ip(request()->ip())->toArray();
+ if (! in_array($res['ad_info']['city'], ['深圳', '深圳市'])) {
+// return $this->failed('体验官活动只限于深圳用户参加');
+ }
+ }
+
+
+ if (! $identity->can_buy) {
+ return $this->failed('当前身份不可开通,请联系客服开通。');
+ }
+
+ $type = Order::TYPE_OPEN;
+ $year = $request->year ?? 1;
+ //是否可以开通多身份 false不可
+ if (config('identity.can_has_many_identity') == false) {
+ if ($user->identities->isNotEmpty()) {
+ $this_identity = $user->identities->first();
+
+ if ($this_identity->order > $identity->order) {
+ return $this->failed('不可降级开通');
+ }
+
+ if ($this_identity->id == $identity->id) {
+ $type = Order::TYPE_RENEW;
+ }
+
+ }
+ }
+
+
+ $hasOne = Order::query()
+ ->byUser($user)
+ ->where('identity_id', $identity->id)
+ ->where('state', Order::STATE_INIT)
+ ->exists();
+
+ if ($hasOne) {
+ return $this->failed('您已经提交过了,请等待审核');
+ }
+
+
+
+ if (! $price) {
+ $price = $identity->getCondition('price', '0');
+ $price = $price * $year;
+ }
+
+ $data = [
+ 'user_id' => $user->id,
+ 'identity_id' => $identity->id,
+ 'year' => $year,
+ 'type' => $type,
+ 'stock' => $identity->stock,
+ 'name' => $request->name,
+ 'card_no' => $request->card_no,
+ 'cover' => $request->cover,
+ 'state' => Order::STATE_INIT,
+ 'price' => $price,
+ ];
+
+ $order = Order::create($data);
+
+ if ($order) {
+ if ($coupon_price > 0) {
+ $order->useCouponLog()->create([
+ 'sourceable_type' => get_class($order),
+ 'sourceable_id' => $order->id,
+ 'coupon_grant_id' => $user_coupon->id,
+ ]);
+ }
+ if ($identity->job == Identity::JOB_TY) {
+ return $this->success([
+ 'order_id' => $order->id,
+ 'openids' => $user->wechat->getOpenids()
+ ]);
+ } else {
+ return $this->success('提交成功,请等待后台审核');
+ }
+ } else {
+ return $this->failed('创建订单失败,请稍后再试');
+ }
+ }
+
+ /**
+ * Notes: 微信支付
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/7 14:42
+ * @param Order $order
+ * @return JsonResponse
+ * @throws Exception
+ */
+ public function wechat(Order $order, Request $request): JsonResponse
+ {
+ $channel = $request->channel ?? 'mp';
+ $openid = $request->openid ?? '';
+
+ $user = Api::user();
+
+ if (! $order->canPay()) {
+ return $this->failed('支付失败,订单不可支付');
+ }
+
+ if ($order->user()->isNot($user)) {
+ return $this->failed('支付失败,您没有权限支付');
+ }
+
+ $extends = [
+ 'notify_url' => route('api.payment.notify.wechat'),
+ ];
+
+ if (! $openid) {
+ $openid = $user->wechat->getUserOpenid($channel);
+ }
+
+ $payment = $order->createWechatPayment($user, $order->price, $channel);
+ if (! $openid) {
+ return $this->failed('支付失败,缺少openid');
+ }
+
+ if (config('payment.version', 2) == 2) {
+ $extends = array_merge($extends, [
+ 'openid' => $openid,
+ ]);
+ } else {
+ $extends = array_merge($extends, [
+ 'payer' => [
+ 'openid' => $openid,
+ ],
+ ]);
+ }
+
+ $notify = $payment->getPaymentParams('商品下单', $extends);
+
+ if (config('payment.version', 2) == 2) {
+ $notify = $notify->getContent();
+ } else {
+ $notify = $notify->toJson();
+ }
+
+ $data = [
+ 'wechat' => $notify,
+ 'identity' => [
+ 'identity_id' => $order->identity_id,
+ 'name' => $order->identity->name,
+ ],
+ ];
+
+ return $this->success($data);
+ }
+
+ /**
+ * Notes: 支付宝支付
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/7 14:42
+ * @param Order $order
+ * @return JsonResponse
+ * @throws Exception
+ */
+ public function alipay(Order $order): JsonResponse
+ {
+ $user = Api::user();
+
+ if (! $order->canPay()) {
+ return $this->failed('支付失败,订单不可支付');
+ }
+
+ if ($order->user()->isNot($user)) {
+ return $this->failed('支付失败,您没有权限支付');
+ }
+
+ $payment = $order->createAlipayPayment($user, $order->price, 'app');
+
+ $notify = $payment->getPaymentParams('商品下单', [
+ 'notify_url' => route('api.payment.notify.alipay'),
+ ]);
+
+ $data = [
+ 'wechat' => $notify->getContent(),
+ 'identity' => [
+ 'identity_id' => $order->identity_id,
+ 'name' => $order->identity->name,
+ ],
+ ];
+
+ return $this->success($data);
+
+ }
+
+ /**
+ * Notes: 根据不同身份判定用户折扣
+ *
+ * @Author: Mr.wang
+ * @Date : 2021/6/10 11:00
+ * @return JsonResponse
+ */
+ public function rule(): JsonResponse
+ {
+ $user = Api::user();
+ $defaultIdentity = Identity::orderBy('order', 'desc')->first();
+ $defaultDiscount = $defaultIdentity->getRule('buy_discount', 0);
+ if (! $user) {
+ return $this->success([
+ 'message' => '开通节点 最高享'.($defaultDiscount / 10).'折优惠',
+ 'not_vip' => true,
+ 'discount' => $defaultDiscount / 100,
+ ]);
+ }
+
+ $identity = $user->identities->first();
+ $discount = $identity->getRule('buy_discount', 0);
+ if ($discount >= 100) {
+ $message = '开通节点 最高享'.($defaultDiscount / 10).'折优惠';
+ } else {
+ $message = $identity->name.'尊享'.($discount / 10).'折优惠';
+ }
+
+ return $this->success([
+ 'message' => $message,
+ 'not_vip' => (bool) $identity->default,
+ 'discount' => $discount / 100,
+ ]);
+ }
+
+ /**
+ * Notes: 获取身份权益
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/8 8:39
+ * @param Identity $identity
+ * @return JsonResponse
+ */
+ public function show(Identity $identity): JsonResponse
+ {
+ return $this->success(new UserIdentityResource($identity));
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/IndexController.php b/modules/User/Http/Controllers/Api/IndexController.php
new file mode 100644
index 0000000..02e2b93
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/IndexController.php
@@ -0,0 +1,200 @@
+
+ * @return JsonResponse
+ */
+ public function index(): JsonResponse
+ {
+ $user = Api::user();
+
+ return $this->success(new UserInfoResource($user));
+ }
+
+ /**
+ * Notes : 用户信息
+ *
+ * @Date : 2021/6/16 2:14 下午
+ * @Author : Mr.wang
+ * @return JsonResponse
+ */
+ public function info(): JsonResponse
+ {
+ $user = Api::user();
+
+ return $this->success(new UserInfoBaseResource($user));
+ }
+
+ /**
+ * Notes : 用户基础信息修改
+ *
+ * @Date : 2021/5/27 14:02
+ * @Author : Mr.wang
+ * @param Request $request
+ * @param string $key
+ * @return JsonResponse
+ */
+ public function update(Request $request, string $key): JsonResponse
+ {
+ $user = Api::user();
+
+ switch ($key) {
+ case 'nickname':
+ $validator = Validator::make($request->all(), [
+ 'value' => 'required',
+ ], [
+ 'value.required' => '用户昵称必须填写',
+ ]);
+ break;
+ case 'avatar':
+ $validator = Validator::make($request->all(), [
+ 'value' => ['required', 'regex:/[^\s]*\.(jpg|jpeg|gif|png)$/i'],
+ ], [
+ 'value.required' => '用户头像必须上传',
+ 'value.regex' => '头像地址格式不正确',
+ ]);
+ break;
+ default:
+ return $this->failed('路径不合法');
+ }
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first(), 422);
+ }
+ $result = $user->info()->update([$key => $request->value]);
+ if ($result) {
+ return $this->success('操作成功');
+ } else {
+ return $this->failed('失败');
+ }
+
+ }
+
+ /**
+ * Notes : 用户邀请码接口
+ *
+ * @Date : 2021/5/27 14:15
+ * @Author : Mr.wang
+ * @return JsonResponse
+ */
+ public function invite(): JsonResponse
+ {
+ $user = Api::user();
+ if ($user->identityFirst()->id > 1) {
+ $invite = Hashids::connection('code')->encode($user->id);
+ } else {
+ $invite = '';
+ }
+ $url = Config::get('user.invite_code.url').'?invite='.$invite;
+
+ $code = 'data:image/png;base64,'.base64_encode(QrCode::format('png')
+ ->size(100)
+ ->margin(3)
+ ->generate($url));
+
+ return $this->success([
+ 'code' => $code,
+ 'invite' => $invite,
+ 'children' => $user->children()->count(),
+ 'user_info' => new UserInfoBaseResource($user),
+ ]);
+ }
+
+ /**
+ * Notes : 绑定邀请码
+ *
+ * @Date : 2021/7/20 10:30 上午
+ * @Author : wang
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function bind(Request $request): JsonResponse
+ {
+ $invite = $request->invite ?? '';
+ if (empty($invite)) {
+ return $this->failed('邀请码不能为空');
+ }
+ $invite = Hashids::connection('code')->decode($invite);
+ if (empty($invite)) {
+ return $this->failed('邀请码不正确');
+ }
+
+ $user = Api::user();
+ if ($user->parent) {
+ return $this->failed('用户已有上级,不能重复绑定');
+ }
+
+ if ($user->updateParent($invite[0])) {
+ return $this->success('绑定成功');
+ } else {
+ return $this->failed('绑定失败');
+ }
+ }
+
+ /**
+ * Notes: 获取小程序码
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/28 13:13
+ */
+ public function getMiniCode(Request $request)
+ {
+ $user = Api::user();
+ $size = $request->size ?? '300';
+ $url = $request->url ?? 'pages/mall/index/index';
+
+ $name = "share_user_mini_{$user->id}.png";
+ $path = "share/".$name;
+ $file_path = storage_path("app/public/".$path);
+
+ $data = [
+ 'username' => $user->username,
+ 'nickname' => $user->info->nickname ?? '',
+ 'avatar' => $user->info->avatar ?? '',
+ ];
+
+ $invite = Hashids::connection('code')->encode($user->id);
+
+ $parse = parse_url($url);
+
+ $arr['invite'] = $invite;
+ $str = $parse['path'].'?'.http_build_query($arr);
+ if (! file_exists($file_path)) {
+ $app = app('wechat.mini_program');
+ $response = $app->app_code
+ ->get($str, [
+ 'width' => $size,
+ 'is_hyaline' => true,
+ ]);
+ if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
+ $response->saveAs(storage_path("app/public/share"), $name);
+ }
+ }
+
+ $data = array_merge($data, [
+ 'qrcode' => Storage::url($path),
+ ]);
+
+ return $this->success($data);
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Rank/IndexController.php b/modules/User/Http/Controllers/Api/Rank/IndexController.php
new file mode 100644
index 0000000..977370c
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Rank/IndexController.php
@@ -0,0 +1,131 @@
+toDateTimeString(),
+ ];
+
+ $vipUsers = $this->getRankDataTrait($dateBetween);
+ $data = [];
+ $i = 1;
+ foreach ($vipUsers as $user) {
+ $data[] = [
+ 'rank' => $i,
+ 'avatar' => $user->info->avatar, //头像
+ 'nickname' => $user->info->nickname,
+ 'rate' => '---',
+ 'number' => $user->countChild,
+ ];
+ }
+ return $this->success([
+ 'page_id' => 6,
+ 'rand' => $data
+ ]);
+ }
+
+ /**
+ * Notes: 推荐用户总排行
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/17 15:11
+ * @return JsonResponse
+ */
+ public function totalUser(): JsonResponse
+ {
+ $user = Api::user();
+
+ $users = User::query()
+ ->withCount('children')
+ ->take(10)
+ ->latest('children_count')
+ ->get()
+ ->map(function ($info) use ($user) {
+ return [
+ 'user' => [
+ 'user_id' => $info->id,
+ 'username' => $info->username,
+ 'nickname' => $info->info->nickname ?? '',
+ 'avatar' => $info->info->avatar ?? '',
+ ],
+ 'is_my' => $info->id == $user->id,
+ 'total' => $info->children_count,
+ ];
+ })
+ ->pad(10, [
+ 'user' => [
+ 'user_id' => 0,
+ 'username' => '---',
+ 'nickname' => '---',
+ 'avatar' => '---',
+ ],
+ 'is_my' => false,
+ 'total' => 0,
+ ]);
+
+ $users = $users->toArray();
+
+ $i = 1;
+ $top = $other = [];
+ $myRank = 0;
+ foreach ($users as &$rank) {
+ $rank['rank'] = $i;
+ if ($rank['is_my']) {
+ $myRank = $i;
+ }
+
+ if ($i < 4) {
+ $top[] = $rank;
+ } else {
+ $other[] = $rank;
+ }
+ $i++;
+ }
+
+ $childrenCount = $user->children()->count();
+ if (! $myRank) {
+ $myRank = User::query()
+ ->withCount('children')
+ ->latest('children_count')
+ ->where('children_count'.'>', $childrenCount)
+ ->count();
+ }
+
+ $data = [
+ 'user' => [
+ 'username' => $user->username,
+ 'nickname' => $user->info->nickname,
+ 'avatar' => $user->info->avatar,
+ 'total' => $user->sign->counts,
+ 'rank' => $myRank ?: $myRank + 1,
+ ],
+ 'top' => $top,
+ 'other' => $other,
+ ];
+
+ return $this->success($data);
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Relation/IndexController.php b/modules/User/Http/Controllers/Api/Relation/IndexController.php
new file mode 100644
index 0000000..e38e1dd
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Relation/IndexController.php
@@ -0,0 +1,46 @@
+larer ?? 1;
+
+ $user = Api::user();
+ $layer = bcadd($user->relation->layer, $layer);
+
+ $users = Relation::query()
+ ->where('bloodline', 'like', "%,".$user->id.",%")
+ ->when($layer, function ($q) use ($layer) {
+ $q->where('layer', $layer);
+ }, function ($q) {
+ $q->whereIn('layer', [2, 3]);
+ })
+ ->paginate();
+ $data = [
+ 'count' => $user->getRelationCount(),
+ 'users' => RelationResource::collection($users),
+ ];
+
+ return $this->success($data);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Controllers/Api/Service/IndexController.php b/modules/User/Http/Controllers/Api/Service/IndexController.php
new file mode 100644
index 0000000..08eed96
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Service/IndexController.php
@@ -0,0 +1,32 @@
+identity_id ?? 0;
+ if ($identityId) {
+ $identity = Identity::findOrFail($identityId);
+ $service = $identity->identities()->shown()->inRandomOrder()->first();
+ } else {
+ $user = Api::user();
+ $service = $user->identities->first()->identities()->shown()->inRandomOrder()->first();
+ }
+ if (empty($service)) {
+ return $this->success([]);
+ }
+
+ return $this->success(new UserServiceResource($service));
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Setting/IndexController.php b/modules/User/Http/Controllers/Api/Setting/IndexController.php
new file mode 100644
index 0000000..f119d47
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Setting/IndexController.php
@@ -0,0 +1,96 @@
+id)->first();
+ if ($wechat) {
+ if ($wechat->mini) {
+ $bool = true;
+ } else {
+ $bool = false;
+ }
+ } else {
+ $bool = false;
+ }
+
+ return $this->success([
+ 'avatar' => $user->info->avatar ?? '',
+ 'nickname' => $user->info->nickname ?? '',
+ 'is_bind' => $bool,
+ 'certification' => [
+ 'is_true' => (bool) $user->certification,
+ 'message' => $user->certification ? [
+ 'name' => $user->certification->name,
+ 'idcard' => $user->certification->id_card,
+ ] : [],
+ ],
+ ]);
+ }
+
+ /**
+ * Notes : 用户基础信息修改
+ *
+ * @Date : 2021/5/27 14:02
+ * @Author : Mr.wang
+ * @param Request $request
+ * @param string $key
+ * @return JsonResponse
+ */
+ public function update(Request $request, string $key): JsonResponse
+ {
+ $user = Api::user();
+
+ switch ($key) {
+ case 'nickname':
+ $validator = Validator::make($request->all(), [
+ 'value' => 'required',
+ ], [
+ 'value.required' => '用户昵称必须填写',
+ ]);
+ break;
+ case 'avatar':
+ $validator = Validator::make($request->all(), [
+ 'value' => ['required', 'regex:/[^\s]*\.(jpg|jpeg|gif|png)$/i'],
+ ], [
+ 'value.required' => '用户头像必须上传',
+ 'value.regex' => '头像地址格式不正确',
+ ]);
+ break;
+ default:
+ return $this->failed('路径不合法');
+ }
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first(), 422);
+ }
+
+ $result = $user->info()->update([$key => $request->value]);
+ if ($result) {
+ return $this->success('操作成功');
+ } else {
+ return $this->failed('失败');
+ }
+
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Sign/IndexController.php b/modules/User/Http/Controllers/Api/Sign/IndexController.php
new file mode 100644
index 0000000..1cac67a
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Sign/IndexController.php
@@ -0,0 +1,363 @@
+
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $type = SignConfig::getParams('show_type');
+
+ $user = Api::user();
+
+ switch ($type) {
+ case 'week':
+ ##todo 根据自然周进行展示,等待完善
+ break;
+ case 'month':
+ //获取年
+ $year = $request->year ?? now()->year;
+ //获取月份
+ $month = $request->month ?? now()->month;
+ $yearMonth = sprintf("%d-%s", $year, $month);
+ $data = $this->month($yearMonth, $user);
+ break;
+ default:
+ $days = $request->days ?? 7;
+ $data = $this->sevenDay($days, $user);
+ break;
+ }
+ return $this->success($data);
+ }
+
+ /**
+ * Notes : 7天签到图
+ *
+ * @Date : 2022/1/7 15:07
+ * @Author : Mr.wang
+ * @param $days
+ * @param $user
+ * @return array
+ */
+ private function sevenDay($days, $user)
+ {
+ $continue_days = $user->sign->continue_days;
+ $modDay = bcmod($continue_days, $days);
+ $cycle = bcdiv($continue_days, $days, 0);
+ $data = [
+ 'can_sign' => ! UserSign::canSign($user, now()),
+ 'next_task' => '',
+ ];
+ $task = UserSign::getNextTaskCrystal($continue_days);
+ if ($task) {
+ $data['next_task'] = [
+ 'day' => $task['day'],
+ 'diff' => $task['day'] - $continue_days,
+ 'crystal' => $task['number'] ?? 0,
+ ];
+ }
+ for ($i = 1; $i <= $days; $i++) {
+ $day = $cycle * $days + $i;
+ $text = '第'.$day.'天';
+ if ($i == $modDay) {
+ $text = '今天';
+ }
+ $data['lists'][$i] = [
+ 'sign' => ($i <= $modDay),
+ 'crystal' => UserSign::getSignCrystal($day),
+ 'text' => $text,
+ ];
+
+ }
+ if ($user->sign) {
+ return $data;
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * Notes : 一月签到图
+ *
+ * @Date : 2022/1/7 15:11
+ * @Author : Mr.wang
+ * @param $yearMonth
+ * @param $user
+ * @return array[]
+ */
+ private function month($yearMonth, $user)
+ {
+ $base = [
+ 'month' => $yearMonth,
+ 'continue' => $user->sign->continue_days,
+ 'total' => $user->sign->counts,
+ 'isSign' => $user->isSign(),
+ ];
+
+ $calendar = Calendar::show($yearMonth)['calendar'];
+ $calendar = $calendar->map(function ($info) use ($user) {
+ $canSign = UserSign::canSign($user, Carbon::parse($info['today']));
+ return array_merge($info, [
+ 'isSign' => $user->isSign($info['today']),
+ 'canSign' => $canSign,
+ 'canReSign' => UserSign::canReSign($user, Carbon::parse($info['today']))
+ ]);
+ });
+
+// //获取月份第一天所在的星期
+// $firstDayOfWeek = Carbon::parse("$yearMonth-01")->dayOfWeek;
+// //补全
+// $day = 0;
+// $calendar = [];
+// for ($i = 0; $i < 6; $i++) {
+// for ($j = 0; $j < 7; $j++) {
+// if ($firstDayOfWeek != 0 and $i == 0) {
+// //根据月初第一天所在的星期,计算出之前几天的日子
+// $day = Carbon::parse("$yearMonth-01")->subDays($firstDayOfWeek - $j)->day;
+// $date = Carbon::parse("$yearMonth-01")->subDays($firstDayOfWeek - $j);
+// $hidden = $date->lt(Carbon::parse("$yearMonth-01")->startOfMonth());
+// } else {
+// $day++;
+// $date = Carbon::parse("$yearMonth-01")->addDays($day - 1);
+// $hidden = $date->gt(Carbon::parse("$yearMonth-01")->endOfMonth());
+// }
+// $calen = [
+// 'date' => $date->format("j"),
+// 'isPast' => $date->isPast(),
+// 'isHidden' => $hidden,
+// 'isSign' => $user->isSign($date->format('Y-m-d')),
+// ];
+// $calendar[$i][] = $calen;
+// }
+// }
+
+ return [
+ 'base' => $base,
+ 'calendar' => $calendar->splitIn(5),
+ ];
+ }
+
+ /**
+ * Notes : 签到
+ *
+ * @Date : 2021/5/28 3:14 下午
+ * @Author :
+ * @return JsonResponse
+ */
+ public function sign(): JsonResponse
+ {
+ $user = Api::user();
+ try {
+ [$res, $message] = UserSign::signIn($user);
+
+ if ($res) {
+// $logs = $user->account->logs()
+// ->where('rule_id', 10)
+// ->whereDate('created_at', now())
+// ->get();
+
+
+ return $this->success($message);
+
+ } else {
+ return $this->failed($message);
+ }
+ } catch (Exception $exception) {
+ return $this->failed($exception->getMessage());
+ }
+ }
+
+ /**
+ * Notes : 预留补签功能
+ *
+ * @Date : 2021/5/28 3:14 下午
+ * @Author :
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function replenish(Request $request): JsonResponse
+ {
+ $user = Api::user();
+ $date = $request->date;
+ $date = Carbon::parse($date);
+
+ try {
+
+ [$res, $message] = UserSign::replenish($user, $date);
+
+ if ($res) {
+ return $this->success($message);
+ } else {
+ return $this->failed('补签失败');
+ }
+ } catch (Exception $exception) {
+ return $this->failed($exception->getMessage());
+ }
+ }
+
+ /**
+ * Notes: 月日历
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 11:44
+ * @return JsonResponse
+ */
+ public function date()
+ {
+ return $this->success(Calendar::show('2022-07'));
+ }
+
+ /**
+ * Notes: 签到封面
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/9 8:15
+ * @return JsonResponse
+ */
+ public function backgrounds(): JsonResponse
+ {
+ $user = Api::user();
+
+ $signTexts = SignText::shown()->get();
+ $signBanner = SignBanner::query()->shown()->inRandomOrder()->first();
+ $calendar = new OvertrueCalendar();
+ $lunarMonth = $calendar->toChinaMonth(now()->month);
+ $lunarDay = $calendar->toChinaDay(now()->day);
+
+ $invite = Hashids::connection('code')->encode($user->id);
+ $url = Config::get('user.invite_code.url').'?invite='.$invite;
+ $code = 'data:image/png;base64,'.base64_encode(QrCode::format('png')
+ ->size(100)
+ ->margin(3)
+ ->generate($url));
+
+ //锶源昆仑
+ return $this->success([
+ 'texts' => SignTextResource::collection($signTexts),
+ 'banner' => new SignBannerResource($signBanner),
+ 'time' => [
+ 'yearMonth' => now()->format('Y-m'),
+ 'time' => now()->format('H:i'),
+ 'day' => now()->format('d'),
+ 'lunar' => '农历'.$lunarMonth.$lunarDay,
+ ],
+ 'user' => [
+ 'username' => $user->username,
+ 'nickname' => $user->info->nickname ?? '',
+ 'avatar' => $user->info->avatar ?? '',
+ 'code' => $code,
+ ],
+ 'sign' => $user->getSignData()
+ ]);
+ }
+
+ /**
+ * Notes: 签到排行
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/17 13:43
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function rank(Request $request): JsonResponse
+ {
+ $user = Api::user();
+ $ranks = Sign::query()
+ ->latest('counts')
+ ->take(10)
+ ->get()
+ ->map(function ($info) use ($user) {
+ return [
+ 'user' => [
+ 'user_id' => $info->user_id,
+ 'username' => $info->user->username,
+ 'nickname' => $info->user->info->nickname ?? '',
+ 'avatar' => $info->user->info->avatar ?? '',
+ ],
+ 'is_my' => $info->user_id == $user->id,
+ 'total' => $info->counts,
+ ];
+ })
+ ->pad(10, [
+ 'user' => [
+ 'user_id' => 0,
+ 'username' => '---',
+ 'nickname' => '---',
+ 'avatar' => '---',
+ ],
+ 'is_my' => false,
+ 'total' => 0,
+ ]);
+
+ $ranks = $ranks->toArray();
+ $i = 1;
+ $top = $other = [];
+ $myRank = 0;
+ foreach ($ranks as &$rank) {
+ $rank['rank'] = $i;
+ if ($rank['user']['user_id'] == $user->id) {
+ $myRank = $i;
+ }
+
+ if ($i < 4) {
+ $top[] = $rank;
+ } else {
+ $other[] = $rank;
+ }
+ $i++;
+ }
+
+ if (! $myRank) {
+ $myRank = Sign::query()->where('counts', '>', $user->sign->counts)->count();
+ }
+
+ $data = [
+ 'user' => [
+ 'username' => $user->username,
+ 'nickname' => $user->info->nickname,
+ 'avatar' => $user->info->avatar,
+ 'total' => $user->sign->counts,
+ 'rank' => $myRank ?: $myRank + 1,
+ ],
+ 'top' => $top,
+ 'other' => $other,
+ ];
+
+ return $this->success($data);
+
+
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Socialite/Controller.php b/modules/User/Http/Controllers/Api/Socialite/Controller.php
new file mode 100644
index 0000000..ea0a2f0
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Socialite/Controller.php
@@ -0,0 +1,14 @@
+all(), [
+ 'access_token' => 'required',
+ 'openid' => 'required',
+ ], [
+ 'access_token.required' => 'access_token必须填写',
+ 'openid.required' => 'openid必须填写',
+ ]);
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first(), 422);
+ }
+ $data = [
+ 'access_token' => $request->access_token,
+ 'openid' => $request->openid,
+ ];
+ $secret = config('user.socialite.unicloud.self_secret', '');
+ $domain = config('user.socialite.unicloud.domain', '');
+ $url = config('user.socialite.unicloud.cloud_function_url.one_key', '');
+ if (! $secret || ! $domain || ! $url) {
+ return $this->failed('过程参数存在错误');
+ }
+ ksort($data);
+ $signString = urldecode(http_build_query($data));
+ $sign = hash_hmac('sha256', $signString, $secret);
+ $params = $signString.'&sign='.$sign;
+ $url = $domain.$url.'?'.$params;
+ $res = json_decode(file_get_contents($url));
+
+ if ($res->success) {
+ $user = User::firstOrCreate([
+ 'username' => $res->phoneNumber,
+ ], [
+ 'password' => 111111,
+ ]);
+
+ $token = Api::login($user);
+
+ event(new UserLoginSuccess($user, $request, '本机一键登录'));
+
+ return $this->success([
+ 'token_type' => 'Bearer',
+ 'access_token' => $token,
+ 'is_new' => $user->wasRecentlyCreated,
+ ]);
+
+ } else {
+ return $this->failed('一键登录失败');
+ }
+
+ }
+
+ public function query(Request $request)
+ {
+ $validator = Validator::make($request->all(), [
+ 'mobile' => 'required',
+ ], [
+ 'mobile.required' => '请传入手机号',
+ ]);
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first(), 422);
+ }
+ $user = User::firstOrCreate([
+ 'username' => $request->mobile,
+ ], [
+ 'password' => 111111,
+ ]);
+
+ $token = Api::login($user);
+
+ event(new UserLoginSuccess($user, $request, '本机一键登录'));
+
+ return $this->success([
+ 'token_type' => 'Bearer',
+ 'access_token' => $token,
+ 'is_new' => $user->wasRecentlyCreated,
+ ]);
+ }
+
+}
diff --git a/modules/User/Http/Controllers/Api/Socialite/WeChatController.php b/modules/User/Http/Controllers/Api/Socialite/WeChatController.php
new file mode 100644
index 0000000..2af3d58
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Socialite/WeChatController.php
@@ -0,0 +1,368 @@
+all(), [
+ 'union_id' => 'required',
+ 'mobileNo' => 'required|phone:CN,mobile',
+ 'code' => 'required',
+ ], [
+ 'union_id.required' => 'unionId必须填写',
+ 'mobileNo.required' => '手机号码必须填写',
+ 'mobileNo.phone' => '手机号码格式不正确',
+ 'code.required' => '验证码必须填写',
+ ]);
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first(), 422);
+ }
+ $unionId = $request->union_id;
+ $wechat = UserWechat::where('unionid', $unionId)->first();
+ if (empty($wechat)) {
+ $mobileNo = $request->mobileNo;
+ $code = $request->code;
+ $check = Sms::checkCode($mobileNo, $code);
+ if ($check == false) {
+ return $this->failed('验证码不正确', 422);
+ }
+ $user = User::firstOrCreate([
+ 'username' => $mobileNo,
+ ], [
+ 'password' => 111111,
+ ]);
+ $wechat = $user->wechat()->firstOrcreate([
+ 'unionid' => $unionId,
+ 'nickname' => $request->nickname ?? '',
+ 'avatar' => $request->avatar ?? '',
+ 'sex' => $request->gender ?? 0,
+ 'country' => $request->country ?? '',
+ 'province' => $request->province ?? '',
+ 'city' => $request->city ?? '',
+ ]);
+ $wechat->app()->firstOrcreate([
+ 'openid' => $request->open_id ?? '',
+ ]);
+ $user->info()->update([
+ 'nickname' => $request->nickname ?? $user->info->nickname,
+ 'avatar' => $request->avatar ?? '',
+ ]);
+ $token = Api::login($user);
+
+ return $this->success([
+ 'token_type' => 'Bearer',
+ 'access_token' => $token,
+ 'is_new' => $user->wasRecentlyCreated,
+ ]);
+ } else {
+ $token = Api::login($wechat->user);
+
+ event(new UserLoginSuccess(Api::user(), $request, '微信授权'));
+
+ return $this->success([
+ 'token_type' => 'Bearer',
+ 'access_token' => $token,
+ 'is_new' => false,
+ ]);
+ }
+ }
+
+ /**
+ * Notes : 小程序手机号登录
+ *
+ * @Date : 2021/7/29 17:21
+ * @Author : Mr.wang
+ * @param WechatMiniRequest $request
+ * @return JsonResponse
+ * @throws InvalidConfigException
+ */
+ public function mini(WechatMiniRequest $request): JsonResponse
+ {
+ $code = $request->code;
+ $this->getConfig();
+ $app = Factory::miniProgram($this->config);
+ $session = $app->auth->session($code);
+ if ($session->errcode) {
+ return $this->failed($session->errmsg);
+ }
+
+ try {
+ $decryptedData = $app->encryptor->decryptData(
+ $session->session_key,
+ $request->iv,
+ $request->encryptedData
+ );
+ $mobile = $decryptedData['purePhoneNumber'];
+ $user = User::where('username', $mobile)->first();
+ if (! $user) {
+ $user = User::create([
+ 'username' => $mobile,
+ 'password' => 111111,
+ ]);
+ $user->info()->create([
+ 'nickname' => $request->nickname ?? '',
+ 'avatar' => $request->avatar ?? '',
+ ]);
+ }
+ $token = Api::login($user);
+
+ event(new UserLoginSuccess(Api::user(), $request, '微信授权'));
+
+ return $this->success([
+ 'token_type' => 'Bearer',
+ 'access_token' => $token,
+ 'is_new' => $user->wasRecentlyCreated,
+ ]);
+ } catch (\Exception $exception) {
+ return $this->failed($exception->getMessage());
+ }
+ }
+
+ protected function getConfig()
+ {
+ $this->config = [
+ 'app_id' => env('WECHAT_MINI_APPID'),
+ 'secret' => env('WECHAT_MINI_SECRET'),
+ 'response_type' => 'collection',
+ ];
+ }
+
+ /**
+ * Notes : 公众号登录
+ *
+ * @Date : 2021/5/26 3:14 下午
+ * @Author : < Jason.C >
+ */
+ public function official()
+ {
+
+ }
+
+ public function query(Request $request): JsonResponse
+ {
+ $unionId = $request->union_id ?? '';
+ if (empty($unionId)) {
+ return $this->failed('', 404);
+ }
+ $wechat = UserWechat::where('unionid', $unionId)->first();
+ if (empty($wechat)) {
+ return $this->success([
+ 'union_id' => $unionId,
+ ]);
+ } else {
+ $token = Api::login($wechat->user);
+
+ event(new UserLoginSuccess(Api::user(), $request, '微信授权'));
+
+ return $this->success([
+ 'token_type' => 'Bearer',
+ 'access_token' => $token,
+ ]);
+ }
+ }
+
+ /**
+ * Notes: 入库微信信息
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/2 11:16
+ */
+ public function addWechatUser(): JsonResponse
+ {
+ try {
+ $user = Api::user();
+ if (! $user) {
+ throw new \Exception('操作失败请登录。');
+ }
+
+ $wechatUser = $user->wechat;
+
+ $weUser = $this->getUserFromCode();
+ $openid = $weUser->getId();
+ $baseUser = $this->getWechatUser($openid);
+ $unionid = '';
+ $raw = $weUser->getRaw();
+ if (isset($raw['unionid'])) {
+ $unionid = $raw['unionid'];
+ }
+
+ if (! $openid) {
+ if ($raw && $raw['errmsg']) {
+ throw new \Exception($raw['errmsg']);
+ }
+
+ throw new \Exception('code解密失败');
+ }
+
+ if (! $user->info->avatar) {
+ //更新头像
+ $user->info->update([
+ 'avatar' => $weUser->getAvatar(),
+ ]);
+ }
+
+
+ if (! $wechatUser) {
+ $wechatUser = $user->wechat()
+ ->firstOrCreate([
+ 'unionid' => $unionid,
+ 'nickname' => $weUser->getNickname(),
+ 'avatar' => $weUser->getAvatar(),
+ ]);
+ } elseif (empty($wechatUser['unionid']) && ! empty($unionid)) {
+ $wechatUser->update([
+ 'unionid' => $unionid,
+ ]);
+ }
+
+ if (! $wechatUser->official) {
+ $wechatUser->official()->firstOrCreate([
+ 'openid' => $weUser->getId(),
+ 'subscribe' => $baseUser->subscribe
+ ]);
+ } else {
+ $wechatUser->official()->update([
+ 'subscribe' => $baseUser->subscribe
+ ]);
+ }
+
+ //设置关注状态
+ if ($unionid) {
+ $this->setUserSubscribe($unionid, $openid, $baseUser->subscribe);
+ }
+
+ return $this->success([
+ 'openid' => $openid,
+ 'subscribe' => $baseUser->subscribe,
+ ]);
+ } catch (\Exception $exception) {
+ return $this->failed($exception->getMessage());
+ }
+ }
+
+ /**
+ * Notes: 小程序入库
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/23 14:46
+ * @param Request $request
+ * @return JsonResponse|mixed
+ */
+ public function miniAddWechatUser(Request $request)
+ {
+ $user = Api::user();
+
+ $validator = \Validator::make($request->all(), [
+ 'code' => 'required',
+ 'iv' => 'required',
+ 'encryptedData' => 'required',
+ ], [
+ 'code.required' => '缺少参数:code',
+ 'iv.required' => '缺少参数:iv',
+ 'encryptedData.mobile' => '缺少参数:encryptedData',
+ ]);
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first());
+ }
+
+ try {
+ $code = $request->code;
+
+ $unionid = '';
+ $weChat = app('wechat.mini_program');
+ $session = $weChat->auth->session($code);
+ if (isset($session->unionid)) {
+ $unionid = $session->unionid;
+ }
+
+ if (isset($session->errcode)) {
+ return $this->failed($session->errmsg);
+ }
+
+ $openid = $session->openid;
+ $wechatUser = $user->wechat;
+
+ $decryptedData = $weChat->encryptor->decryptData(
+ $session->session_key,
+ $request->iv,
+ $request->encryptedData
+ );
+
+ if (! $wechatUser) {
+ $wechatUser = $user->wechat()
+ ->firstOrCreate([
+ 'unionid' => $unionid,
+ 'nickname' => $decryptedData['nickName'] ?? '',
+ 'avatar' => $decryptedData['avatarUrl'] ?? '',
+ ]);
+ } elseif (empty($wechatUser['unionid']) && ! empty($unionid)) {
+ $wechatUser->update([
+ 'unionid' => $unionid,
+ ]);
+ }
+
+
+ if (! $wechatUser->mini) {
+ $wechatUser->mini()->firstOrCreate([
+ 'openid' => $openid,
+ ]);
+ }
+
+ $userSubscribe = UserSubscribe::query()
+ ->where('unionid', $unionid)
+ ->where('subscribe', 1)
+ ->first();
+
+ if ($userSubscribe && ! $wechatUser->official) {
+ if (! $wechatUser->official) {
+ $wechatUser->official()
+ ->firstOrCreate([
+ 'openid' => $userSubscribe->openid,
+ 'subscribe' => $userSubscribe->subscribe
+ ]);
+ } else {
+ $wechatUser->official()->update([
+ 'subscribe' => $userSubscribe->subscribe
+ ]);
+ }
+ }
+
+ return $this->success([
+ 'openid' => $openid,
+ 'subscribe' => $userSubscribe ? 1 : 0,
+ ]);
+
+ } catch (\Exception $e) {
+ return $this->failed($e->getMessage());
+ }
+ }
+}
diff --git a/modules/User/Http/Controllers/Api/Stock/IndexController.php b/modules/User/Http/Controllers/Api/Stock/IndexController.php
new file mode 100644
index 0000000..f6bda9a
--- /dev/null
+++ b/modules/User/Http/Controllers/Api/Stock/IndexController.php
@@ -0,0 +1,180 @@
+
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $state = $request->state ?? '';
+
+ $list = Order::byUser(Api::user())
+ ->TypeSample()
+ ->when($state, function ($query) use ($state) {
+ switch ($state) {
+ case 'init':
+ $query->paid()->unPay();
+ break;
+ case 'delivered':
+ $query->delivered();
+ break;
+ }
+ }, function ($query) {
+ $query->common();
+ })
+ ->paginate(8);
+
+ return $this->success(new OrderCollection($list));
+ }
+
+ /**
+ * Notes: 提货前置
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/1 11:32
+ * @return JsonResponse
+ */
+ public function create(): JsonResponse
+ {
+ $user = Api::user();
+ $addresses = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->get();
+ $address = Address::where('user_id', $user->id)->orderBy('is_default', 'desc')->first();
+
+ $data = [
+ 'addresses' => AddressResource::collection($addresses),
+ 'address' => $address ? new AddressResource($address) : '',
+ 'stockData' => $user->getStockData(),
+ 'dientity' => new UserIdentityBaseResource($user->identityFirst())
+ ];
+
+ return $this->success($data);
+ }
+
+ /**
+ * Notes: 提货
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/1 11:33
+ * @param Request $request
+ * @return JsonResponse|mixed
+ * @throws \Exception
+ */
+ public function store(Request $request)
+ {
+ $user = Api::user();
+
+ $validator = Validator::make($request->all(), [
+ 'address_id' => 'required|integer',
+ 'qty' => 'required|integer',
+ ], [
+ 'qty.required' => '缺少提货数量',
+ 'qty.integer' => '数量必须是数字',
+ 'address_id.required' => '缺少收货地址',
+ 'address_id.integer' => '收货地址必须是数字',
+ ]);
+
+ if ($validator->fails()) {
+ return $this->failed($validator->errors()->first());
+ }
+
+
+ $remark = $request->remark ?? '';
+ $address_id = $request->address_id;
+ $qty = $request->qty;
+
+ $address = Address::find($address_id);
+
+ $userIdentity = $user->identityFirst();
+ if ($userIdentity->job == Identity::JOB_TY) {
+ $shenzhen = Region::query()->where('name', '深圳市')->first();
+ if ($address->city_id != $shenzhen->id) {
+ return $this->failed('体验官收货地址只能选择深圳');
+ }
+ }
+
+ $goods_sku = GoodsSku::query()
+ ->whereHas('goods', function ($q) {
+ $q->where('channel', Goods::CHANNEL_FREE)->where('status', Goods::STATUS_UP);
+ })
+ ->first();
+
+ if (! $goods_sku) {
+ return $this->failed('缺少商品');
+ }
+
+ $stockData = $user->getStockData();
+
+ if ($qty > $stockData['residue']) {
+ return $this->failed('用户库存不足');
+ }
+
+ $detail = collect();
+ $item = new Item($goods_sku, $address, $qty);
+ $detail->push($item);
+
+ $orders = (new OrderFacade)->user($user)
+ ->remark($remark)
+ ->type(Order::TYPE_SAMPLE)
+ ->items($detail)
+ ->address($address)
+ ->create();
+
+ //提货自动完成
+ foreach ($orders as $order) {
+ if ($order->type == Order::TYPE_SAMPLE) {
+ $order->pay();
+ }
+ }
+
+ return $this->success('提货成功');
+
+ }
+
+ /**
+ * Notes: 账变记录
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 10:43
+ */
+ public function logs()
+ {
+ $user = Api::user();
+ $logs = UserStockLog::query()
+ ->ByUser($user)
+ ->oldest()
+ ->paginate();
+
+ return $this->success(new UserStockLogCollection($logs));
+ }
+
+}
diff --git a/modules/User/Http/Requests/CertificationRequest.php b/modules/User/Http/Requests/CertificationRequest.php
new file mode 100644
index 0000000..c01453c
--- /dev/null
+++ b/modules/User/Http/Requests/CertificationRequest.php
@@ -0,0 +1,34 @@
+ 'required|min:2|max:5',
+ 'id_card' => ['required', new IdCardRule(), 'unique:user_certifications'],
+ // 'front_card' => 'required',
+ // 'back_card' => 'required',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'name.required' => '认证用户姓名必须填写',
+ 'name.min' => '认证用户姓名至少:min个字符',
+ 'name.max' => '认证用户姓名最多:max个字符',
+ 'id_card.required' => '身份证号必须填写',
+ 'id_card.unique' => '身份证号已存在',
+ 'front_card.required' => '身份证正面图片必须上传',
+ 'back_card.required' => '身份证背面图片必须上传',
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Requests/LoginRequest.php b/modules/User/Http/Requests/LoginRequest.php
new file mode 100644
index 0000000..befa217
--- /dev/null
+++ b/modules/User/Http/Requests/LoginRequest.php
@@ -0,0 +1,41 @@
+
+ * @return string[]
+ */
+ public function rules(): array
+ {
+ return [
+ 'username' => 'required',
+ 'password' => 'required|min:6',
+ ];
+ }
+
+ /**
+ * Notes : 验证错误提示消息
+ *
+ * @Date : 2021/3/11 4:59 下午
+ * @Author :
+ * @return string[]
+ */
+ public function messages(): array
+ {
+ return [
+ 'username.required' => '用户名必须填写',
+ 'password.required' => '密码必须填写',
+ 'password.min' => '密码最少为:min个字符',
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Requests/LoginSmsRequest.php b/modules/User/Http/Requests/LoginSmsRequest.php
new file mode 100644
index 0000000..09cac4d
--- /dev/null
+++ b/modules/User/Http/Requests/LoginSmsRequest.php
@@ -0,0 +1,27 @@
+ 'required|phone:CN,mobile',
+ 'code' => 'required',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'mobileNo.required' => '手机号码必须填写',
+ 'mobileNo.phone' => '手机号码格式不正确',
+ 'code.required' => '验证码必须填写',
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Requests/RegisterRequest.php b/modules/User/Http/Requests/RegisterRequest.php
new file mode 100644
index 0000000..31b879a
--- /dev/null
+++ b/modules/User/Http/Requests/RegisterRequest.php
@@ -0,0 +1,42 @@
+
+ * @return string[]
+ */
+ public function rules(): array
+ {
+ return [
+ 'username' => 'required|unique:users',
+ 'password' => 'required|min:6',
+ ];
+ }
+
+ /**
+ * Notes : 验证错误提示消息
+ *
+ * @Date : 2021/3/11 4:59 下午
+ * @Author :
+ * @return string[]
+ */
+ public function messages(): array
+ {
+ return [
+ 'username.required' => '用户名必须填写',
+ 'username.unique' => '用户名已经被使用',
+ 'password.required' => '密码必须填写',
+ 'password.min' => '密码最少为:min个字符',
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Requests/SmsRequest.php b/modules/User/Http/Requests/SmsRequest.php
new file mode 100644
index 0000000..ddf8b03
--- /dev/null
+++ b/modules/User/Http/Requests/SmsRequest.php
@@ -0,0 +1,25 @@
+ 'required|phone:CN,mobile',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'mobileNo.required' => '手机号码必须填写',
+ 'mobileNo.phone' => '手机号码格式不正确',
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Requests/ThawOneRequest.php b/modules/User/Http/Requests/ThawOneRequest.php
new file mode 100644
index 0000000..b5d3c96
--- /dev/null
+++ b/modules/User/Http/Requests/ThawOneRequest.php
@@ -0,0 +1,26 @@
+ 'required',
+ 'all_ids' => 'required',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'thaw_id.required' => '请传入要领取的ID',
+ 'all_ids.required' => '传入当前总的ID',
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Requests/UpdateUserInfoRequest.php b/modules/User/Http/Requests/UpdateUserInfoRequest.php
new file mode 100644
index 0000000..f8bbefe
--- /dev/null
+++ b/modules/User/Http/Requests/UpdateUserInfoRequest.php
@@ -0,0 +1,26 @@
+ 'required',
+ 'avatar' => ['regex:/[^\s]*\.(jpg|jpeg|gif|png)$/i'],
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'nickname.required' => '用户昵称必须填写',
+ 'avatar.regex' => '头像地址格式不正确',
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Requests/WechatMiniRequest.php b/modules/User/Http/Requests/WechatMiniRequest.php
new file mode 100644
index 0000000..6a2fc55
--- /dev/null
+++ b/modules/User/Http/Requests/WechatMiniRequest.php
@@ -0,0 +1,28 @@
+ 'required',
+ 'iv' => 'required',
+ 'encryptedData' => 'required',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'code.required' => '缺失CODE',
+ 'iv.required' => '缺失向量',
+ 'encryptedData.required' => '缺失内容',
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/Account/CrystalResource.php b/modules/User/Http/Resources/Account/CrystalResource.php
new file mode 100644
index 0000000..918d37f
--- /dev/null
+++ b/modules/User/Http/Resources/Account/CrystalResource.php
@@ -0,0 +1,19 @@
+ $this->id,
+ 'title' => $this->rule->title,
+ 'amount' => $this->amount,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/Account/UserAccountLogCollection.php b/modules/User/Http/Resources/Account/UserAccountLogCollection.php
new file mode 100644
index 0000000..9789cdb
--- /dev/null
+++ b/modules/User/Http/Resources/Account/UserAccountLogCollection.php
@@ -0,0 +1,20 @@
+ $this->collection->map(function ($log) {
+ return new UserAccountLogResource($log);
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/Account/UserAccountLogResource.php b/modules/User/Http/Resources/Account/UserAccountLogResource.php
new file mode 100644
index 0000000..2765ed4
--- /dev/null
+++ b/modules/User/Http/Resources/Account/UserAccountLogResource.php
@@ -0,0 +1,30 @@
+ $this->id,
+ 'frozen' => [
+ 'value' => $this->frozen,
+ 'text' => $this->frozen ? '待发放' : '已发放',
+ ],
+ 'rule' => [
+ 'rule_id' => $this->rule->id,
+ 'name' => $this->rule->name,
+ 'title' => $this->rule->title,
+ 'remark' => $this->rule->remark,
+ ],
+ 'remark' => $this->source['remark'] ?? $this->rule->remark,
+ 'amount' => $this->amount > 0 ? '+'.floatval($this->amount) : floatval($this->amount),
+ 'created_at' => $this->created_at->format('Y-m-d H:i:s'),
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Resources/Account/UserAccountResource.php b/modules/User/Http/Resources/Account/UserAccountResource.php
new file mode 100644
index 0000000..79a3ef3
--- /dev/null
+++ b/modules/User/Http/Resources/Account/UserAccountResource.php
@@ -0,0 +1,22 @@
+ floatval($this->balance),//现金
+ 'score' => [
+ 'surplus' => floatval($this->score),//水滴余量
+ 'use' => 0,//水滴使用
+ 'plan' => 300,//水滴赠送
+ ],
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/Favorite/FavoriteCollection.php b/modules/User/Http/Resources/Favorite/FavoriteCollection.php
new file mode 100644
index 0000000..64ab46c
--- /dev/null
+++ b/modules/User/Http/Resources/Favorite/FavoriteCollection.php
@@ -0,0 +1,20 @@
+ $this->collection->map(function ($info) {
+ return new FavoriteResource($info);
+ }),
+ 'page' => $this->page(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/Favorite/FavoriteResource.php b/modules/User/Http/Resources/Favorite/FavoriteResource.php
new file mode 100644
index 0000000..add9bf9
--- /dev/null
+++ b/modules/User/Http/Resources/Favorite/FavoriteResource.php
@@ -0,0 +1,19 @@
+ $this->id,
+ 'favoriteable' => new ArticleBaseResource($this->favoriteable)
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/IdentityMiddleResource.php b/modules/User/Http/Resources/IdentityMiddleResource.php
new file mode 100644
index 0000000..8d48345
--- /dev/null
+++ b/modules/User/Http/Resources/IdentityMiddleResource.php
@@ -0,0 +1,27 @@
+identity->order == 1) {
+ $ended_at = '---';
+ } else {
+ $ended_at = $this->identity->years ? Carbon::parse($this->ended_at)->format('Y-m-d') : '永久';
+ }
+
+ return [
+ 'name' => $this->identity->name,
+ 'serial' => $this->identity->serial_prefix.$this->serial,
+ 'started_at' => $this->started_at ?? '',
+ 'ended_at' => $ended_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/RelationResource.php b/modules/User/Http/Resources/RelationResource.php
new file mode 100644
index 0000000..95ce652
--- /dev/null
+++ b/modules/User/Http/Resources/RelationResource.php
@@ -0,0 +1,24 @@
+ $this->user->id,
+// 'username' => substr_replace($this->user->username, '****', 3, 4),
+ 'username' => $this->user->username,
+ 'nickname' => $this->user->info->nickname ?? '',
+ 'avatar' => $this->user->info->avatar ?? '',
+ 'sex' => $this->user->case ? $this->user->case->sex_text : '--',
+ 'identity' => new UserIdentityBaseResource($this->user->identities->first()),
+ 'created_at' => (string) $this->user->created_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/Sign/SignBannerResource.php b/modules/User/Http/Resources/Sign/SignBannerResource.php
new file mode 100644
index 0000000..636189a
--- /dev/null
+++ b/modules/User/Http/Resources/Sign/SignBannerResource.php
@@ -0,0 +1,16 @@
+ $this->id,
+ 'cover' => $this->cover_url,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/Sign/SignTextResource.php b/modules/User/Http/Resources/Sign/SignTextResource.php
new file mode 100644
index 0000000..45b1851
--- /dev/null
+++ b/modules/User/Http/Resources/Sign/SignTextResource.php
@@ -0,0 +1,18 @@
+ $this->id,
+ 'title' => $this->title,
+ 'h1' => $this->description,
+ 'h2' => $this->sub_description,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/UserCertificationResource.php b/modules/User/Http/Resources/UserCertificationResource.php
new file mode 100644
index 0000000..fcb7743
--- /dev/null
+++ b/modules/User/Http/Resources/UserCertificationResource.php
@@ -0,0 +1,28 @@
+ $this->id,
+ 'name' => $this->name,
+ 'verified' => (bool) $this->verified,
+ 'id_card' => $this->id_card,
+ 'front_card' => $this->when($this->front_card, function () {
+ return Storage::url($this->front_card);
+ }),
+ 'back_card' => $this->when($this->back_card, function () {
+ return Storage::url($this->back_card);
+ }),
+ 'created_at' => (string) $this->created_at,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/UserIdentityBaseResource.php b/modules/User/Http/Resources/UserIdentityBaseResource.php
new file mode 100644
index 0000000..a7cf4d4
--- /dev/null
+++ b/modules/User/Http/Resources/UserIdentityBaseResource.php
@@ -0,0 +1,20 @@
+ $this->id,
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ 'order' => $this->order,
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Resources/UserIdentityResource.php b/modules/User/Http/Resources/UserIdentityResource.php
new file mode 100644
index 0000000..c74079d
--- /dev/null
+++ b/modules/User/Http/Resources/UserIdentityResource.php
@@ -0,0 +1,103 @@
+getCondition('price', '0');
+ $cost = $this->getCondition('cost', '0');
+ $identityName = $this->name;
+ $coupon_price = 0;
+
+ if ($user) {
+ $identity = $user->identityFirst();
+ if ($identity) {
+ //当前身份 可以续费
+ if ($identity->id == $this->id) {
+ $renew = (bool) $identity->years;
+ $text = '续费';
+ } elseif ($identity->order > $this->order) {
+ $text = '不可降级';
+ } else {
+ $identityOrder = Order::ByUser($user)
+ ->where('identity_id', $this->id)
+ ->where('state', Order::STATE_INIT)
+ ->first();
+ if ($identityOrder) {
+ $text = '等待审核';
+ } else {
+ $open = true;
+ }
+
+ }
+
+ if ($this->job == Identity::JOB_JK) {
+ $openIdentity = Identity::find($this->id);
+ $coupons = $this->getCreateOrderCoupon($user, $openIdentity, 0);
+ if ($coupons->isNotEmpty()) {
+ $user_coupon = $coupons->first();
+ $coupon_price = $user_coupon->price;
+ if ($user_coupon->coupon->type == Coupon::TYPE_REDUCTION) {
+ $price = $price > $user_coupon->price ? bcsub($price, $coupon_price, 2) : 0;
+ }
+ }
+ }
+
+ if ($this->job == Identity::JOB_HH && $identity->job == Identity::JOB_HH) {
+ $star = $identity->getOriginal('pivot_star', 0);
+ if ($star > 0) {
+ $identityName = config('identity.stars.'.$star, '').$identityName;
+ }
+ }
+ } else {
+ $open = true;
+ }
+ }
+
+ return [
+ 'identity_id' => $this->id,
+ 'name' => $identityName,
+ 'stock' => $this->stock,
+ 'years' => $this->years,
+ 'times' => $this->when($user && $this->id == $user->identityFirst()->id, function () use ($user) {
+ return new IdentityMiddleResource($user->identityMiddle()->first());
+ }, [
+ 'name' => '---',
+ 'serial' => '---',
+ 'started_at' => '---',
+ 'ended_at' => '---',
+ ]),
+ 'cover' => $this->cover_url,
+ 'order' => $this->order,
+ 'description' => $this->description ?? "",
+ 'coupon_price' => floatval($coupon_price),//开通金额
+ 'cost' => floatval($cost),//开通金额
+ 'price' => floatval($price),//开通金额
+ 'can' => [
+ 'buy' => (bool) $this->can_buy,
+ 'open' => $this->can_buy ? $open : false,//开通
+ 'renew' => $this->can_buy ? $renew : false,//续费
+ ],
+ 'buttonText' => $text,
+ 'rights' => $this->rights,
+ 'rules' => $this->getRules(),
+ 'not_rules' => $this->getNotRules(),
+ 'is_open' => $user && $this->id == $user->identityFirst()->id
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Resources/UserIdentityRightsResource.php b/modules/User/Http/Resources/UserIdentityRightsResource.php
new file mode 100644
index 0000000..93913c5
--- /dev/null
+++ b/modules/User/Http/Resources/UserIdentityRightsResource.php
@@ -0,0 +1,35 @@
+rights;
+ $data = [];
+ if ($rights){
+ for ($i = 1; $i <= count($rights); $i++) {
+ $data[$i] = [
+ 'name' => $rights['new_'.$i]['name'] ?? '',
+ 'cover' => ! empty($rights['new_'.$i]['cover']) ? Storage::url($rights['new_'.$i]['cover']) : '',
+ 'order' => $rights['new_'.$i]['order'] ?? '',
+ 'remark' => $rights['new_'.$i]['remark'] ?? '',
+ ];
+ }
+ }
+
+ return [
+ 'identity_id' => $this->id,
+ 'name' => $this->name,
+ 'cover' => $this->cover_url,
+ 'order' => $this->order,
+ 'rights' => $data,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/UserInfoBaseResource.php b/modules/User/Http/Resources/UserInfoBaseResource.php
new file mode 100644
index 0000000..3cc3ce4
--- /dev/null
+++ b/modules/User/Http/Resources/UserInfoBaseResource.php
@@ -0,0 +1,26 @@
+ $this->id,
+ 'username' => $this->username,
+ 'nickname' => $this->info->nickname ?? '',
+ 'avatar' => $this->info->avatar ?? '',
+ 'sign' => $this->getSignData(),
+ 'identity' => new UserIdentityBaseResource($this->identities->first()),
+ 'status' => $this->getStateData(),
+ 'canPick' => $this->canPick(),
+ 'nowStatus' => $this->getNowStatus(),
+ 'tag' => $this->tag
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/UserInfoResource.php b/modules/User/Http/Resources/UserInfoResource.php
new file mode 100644
index 0000000..5dabc26
--- /dev/null
+++ b/modules/User/Http/Resources/UserInfoResource.php
@@ -0,0 +1,60 @@
+identityFirst();
+ if ($identity->id > 2) {
+ $order = true;
+ } else {
+ $order = false;
+ }
+
+ return [
+ 'user_id' => $this->id,
+ 'username' => $this->username,
+ 'nickname' => $this->info->nickname ?? '',
+ 'avatar' => $this->info->avatar ?? '',
+ 'account' => new UserAccountResource($this->account),
+ 'identity' => new UserIdentityBaseResource($identity),
+ 'service' => new UserServiceResource($identity->identities()->first()),
+ 'water_mobile' => app('Conf_mall')['water_mobile'] ?? '',
+ 'invite' => Hashids::connection('code')->encode($this->id),
+ 'case' => $this->getStockData(),
+ 'user_wechat' => new UserWechatResource($this->wechat),
+ 'created_at' => (string) $this->created_at,
+ 'sign' => $this->getSignData(),
+ 'status' => $this->getStateData(),
+ 'nowStatus' => $this->getNowStatus(),
+ 'canPick' => $this->canPick(),
+ 'count' => [
+ 'coupon' => $this->couponGrants()->count(),//Żȯ
+ 'relation' => $this->getRelationCount(),//¼
+ 'invites' => $this->invites()->count(),//
+ 'notifications' => $this->unreadNotifications()->count(),//Ϣ
+ 'orders' => Order::byUser(Api::user())->common()->count(),//
+ 'refunds' => RefundModel::byUser(Api::user())->count(),//˿
+ ],
+ 'identityShow' => $this->when(true, function () use ($identity) {
+ return [
+ 'right' => new UserIdentityRightsResource($identity),
+ 'times' => new IdentityMiddleResource($this->identityMiddle()->first()),
+ 'is_top' => $identity->id == 4,
+ ];
+ }),
+ ];
+ }
+
+}
diff --git a/modules/User/Http/Resources/UserServiceResource.php b/modules/User/Http/Resources/UserServiceResource.php
new file mode 100644
index 0000000..f8b9659
--- /dev/null
+++ b/modules/User/Http/Resources/UserServiceResource.php
@@ -0,0 +1,20 @@
+ $this->id,
+ 'name' => $this->name,
+ 'code' => $this->cover_url,
+ 'mobile' => $this->mobile,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/UserSignResource.php b/modules/User/Http/Resources/UserSignResource.php
new file mode 100644
index 0000000..ff1f5c3
--- /dev/null
+++ b/modules/User/Http/Resources/UserSignResource.php
@@ -0,0 +1,22 @@
+ $this->continue_days,
+ 'counts' => $this->counts,
+ 'last_sign_at' => (string) $this->last_sign_at,
+ 'today_signed' => !UserSign::canSign($this->user, Carbon::today()),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/UserWechatResource.php b/modules/User/Http/Resources/UserWechatResource.php
new file mode 100644
index 0000000..645c4d3
--- /dev/null
+++ b/modules/User/Http/Resources/UserWechatResource.php
@@ -0,0 +1,22 @@
+ $this->id,
+ 'unionid' => $this->unionid,
+ 'nickname' => $this->nickname,
+ 'avatar' => $this->avatar,
+ 'mini' => $this->mini,
+ 'official' => $this->official,
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/stock/UserStockLogCollection.php b/modules/User/Http/Resources/stock/UserStockLogCollection.php
new file mode 100644
index 0000000..aa497a1
--- /dev/null
+++ b/modules/User/Http/Resources/stock/UserStockLogCollection.php
@@ -0,0 +1,21 @@
+ $this->collection->map(function ($log) {
+ return new UserStockLogResource($log);
+ }),
+ 'page' => $this->page(),
+ ];
+
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Http/Resources/stock/UserStockLogResource.php b/modules/User/Http/Resources/stock/UserStockLogResource.php
new file mode 100644
index 0000000..1905678
--- /dev/null
+++ b/modules/User/Http/Resources/stock/UserStockLogResource.php
@@ -0,0 +1,22 @@
+ $this->id,
+ 'variable' => $this->variable,
+ 'type' => $this->type_text,
+ 'created_at' => $this->created_at->format('Y-m-d H:i:s'),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Listeners/UserOrderPaidListeners.php b/modules/User/Listeners/UserOrderPaidListeners.php
new file mode 100644
index 0000000..09df97d
--- /dev/null
+++ b/modules/User/Listeners/UserOrderPaidListeners.php
@@ -0,0 +1,51 @@
+order->refresh();
+ $type = UserStockLog::TYPE_INIT;
+ if ($order->user->hasIdentity($order->identity->id)) {
+ $type = UserStockLog::TYPE_IN;
+ }
+
+ $channel = IdentityLog::CHANNEL_AUTO;
+ if (isset($order->source['channel'])) {
+ $channel = $order->source['channel'];
+ }
+
+ $order->user->joinIdentity($order->identity->id, $channel, [
+ 'serial' => $order->source['serial'] ?? ''
+ ]);//开通身份
+
+ $order->user->notify(new SystemOpenVip($order->user->identityMiddle()->first()));
+
+ if ($order->stock > 0) {
+ $order->user->addStock($order->identity->id, $type, $order->stock);//赠水
+ }
+ } catch (Exception $exception) {
+ throw new Exception($exception->getMessage());
+ }
+ }
+
+}
diff --git a/modules/User/Listeners/UserSignContinueDays.php b/modules/User/Listeners/UserSignContinueDays.php
new file mode 100644
index 0000000..49c6680
--- /dev/null
+++ b/modules/User/Listeners/UserSignContinueDays.php
@@ -0,0 +1,32 @@
+log;
+
+ # todo .连续签到天数的一个计算,用最后的签到日期,是否可行?
+ $sign = Sign::updateOrCreate([
+ 'user_id' => $log->user_id,
+ ], [
+ 'counts' => SignLog::query()->distinct()->where('user_id', $log->user_id)->count('date'),
+ 'last_sign_at' => Carbon::now(),
+ ]);
+ if ($sign->wasRecentlyCreated) {
+ $sign->continue_days = 1;
+ } else {
+ $sign->continue_days = $sign->getContinueDays();
+ }
+ $sign->save();
+ }
+
+}
diff --git a/modules/User/Models/Account.php b/modules/User/Models/Account.php
new file mode 100644
index 0000000..0c9afc0
--- /dev/null
+++ b/modules/User/Models/Account.php
@@ -0,0 +1,175 @@
+ '现金余额',
+ 'score' => '水滴',
+ 'coins' => '未使用',
+ 'other' => '其他',
+ ];
+
+ /**
+ * Notes : 账户日志
+ *
+ * @Date : 2021/4/27 11:22 上午
+ * @Author : < Jason.C >
+ * @return HasMany
+ */
+ public function logs(): HasMany
+ {
+ return $this->hasMany(AccountLog::class, 'account_id', 'user_id');
+ }
+
+ /**
+ * Notes: 执行账户规则
+ *
+ * @Author:
+ * @Date : 2019/11/28 1:24 下午
+ * @param $rule string|int
+ * @param float $variable
+ * @param bool $frozen
+ * @param array $source
+ * @return bool
+ * @throws Exception
+ */
+ public function rule($rule, float $variable = 0, bool $frozen = true, array $source = []): bool
+ {
+ if (is_numeric($rule)) {
+ $rule = AccountRule::findOrFail($rule);
+ } else {
+ $rule = AccountRule::where('name', $rule)->firstOrFail();
+ }
+
+ if ($rule->trigger == 0) {
+ // 不限制执行的
+ return $this->accountExecute($rule, $variable, $frozen, $source);
+ } elseif ($rule->trigger > $this->logs()
+ ->where('rule_id', $rule->id)
+ ->whereDate('created_at', Carbon::today())
+ ->count()) {
+ // 每日执行 trigger 次
+ return $this->accountExecute($rule, $variable, $frozen, $source);
+ } elseif ($rule->trigger < 0 && ! $this->logs()->where('rule_id', $rule->id)->first()) {
+ // 终身只能执行一次
+ return $this->accountExecute($rule, $variable, $frozen, $source);
+ }
+
+ throw new Exception('达到最大可执行次数');
+ }
+
+ /**
+ * Notes: 增加账户余额
+ *
+ * @Author:
+ * @Date : 2019/11/28 1:25 下午
+ * @param $type
+ * @param $variable
+ * @return bool
+ */
+ public function increase($type, $variable): bool
+ {
+ DB::transaction(function () use ($type, $variable) {
+ $this->increment($type, $variable);
+ $log = [
+ 'rule_id' => 0,
+ 'type' => $type,
+ 'variable' => $variable,
+ 'frozen' => 0,
+ 'balance' => $this->{$type},
+ 'source' => ['type' => 'increase'],
+ ];
+ $this->logs()->create($log);
+ });
+
+ return true;
+ }
+
+ /**
+ * Notes: 扣除账户金额
+ *
+ * @Author:
+ * @Date : 2019/11/28 1:25 下午
+ * @param $type
+ * @param $variable
+ * @return bool
+ * @throws Exception
+ */
+ public function decrease($type, $variable): bool
+ {
+
+ DB::transaction(function () use ($type, $variable) {
+ $this->decrement($type, $variable);
+ $log = [
+ 'rule_id' => 0,
+ 'type' => $type,
+ 'variable' => -$variable,
+ 'frozen' => 0,
+ 'balance' => $this->{$type},
+ 'source' => ['type' => 'deduct'],
+ ];
+ $this->logs()->create($log);
+ });
+
+ return true;
+ }
+
+ /**
+ * Notes: 执行账户规则
+ *
+ * @Author:
+ * @Date : 2019/11/28 1:41 下午
+ * @param AccountRule $rule
+ * @param $variable
+ * @param $frozen
+ * @param $source
+ * @return bool
+ * @throws Exception
+ */
+ protected function accountExecute(AccountRule $rule, $variable, $frozen, $source): bool
+ {
+ if ($variable != 0) {
+ $rule->variable = $variable;
+ }
+
+ DB::transaction(function () use ($rule, $frozen, $source) {
+ // 如果是扣款,立即执行,如果非冻结,也立即执行
+ if ($rule->variable < 0 || $rule->deductions == 1 || $frozen === false) {
+ $this->increment($rule->type, $rule->variable);
+ $frozen = false;
+ }
+ $log = [
+ 'rule_id' => $rule->id,
+ 'type' => $rule->type,
+ 'amount' => $rule->variable,
+ 'frozen' => $frozen,
+ 'balance' => $this->{$rule->type},
+ 'source' => $source ?: [],
+ 'remark' => $source['remark'] ?? $rule->remark,
+ 'settle_at' => $source['settle_at'] ?? null,
+ 'frozen_at' => $source['frozen_at'] ?? null,
+ ];
+ // 写入记录
+ $this->logs()->create($log);
+ });
+
+ return true;
+ }
+
+}
diff --git a/modules/User/Models/AccountLog.php b/modules/User/Models/AccountLog.php
new file mode 100644
index 0000000..efb9a0f
--- /dev/null
+++ b/modules/User/Models/AccountLog.php
@@ -0,0 +1,91 @@
+ 'json',
+ ];
+
+ /**
+ * Notes : 账户
+ *
+ * @Date : 2021/4/27 11:22 上午
+ * @Author : < Jason.C >
+ * @return BelongsTo
+ */
+ public function account(): BelongsTo
+ {
+ return $this->belongsTo(Account::class, 'account_id');
+ }
+
+ public function rule(): BelongsTo
+ {
+ return $this->belongsTo(AccountRule::class, 'rule_id');
+ }
+
+ /**
+ * Notes: 冻结一条账户记录
+ *
+ * @Author:
+ * @Date : 2019/12/1 10:48 上午
+ * @return bool
+ * @throws Exception
+ */
+ public function freeze(): bool
+ {
+ if ($this->frozen == 0) {
+ $this->account->decrement($this->type, $this->amount);
+ $this->frozen = 1;
+ $this->balance = $this->account->{$this->type};
+ $this->save();
+
+ return true;
+ } else {
+ throw new Exception('账目已冻结');
+ }
+ }
+
+ /**
+ * Notes: 解冻一条记录
+ *
+ * @Author:
+ * @Date : 2019/12/1 10:48 上午
+ * @return bool
+ * @throws Exception
+ */
+ public function thaw(): bool
+ {
+ if ($this->frozen == 1) {
+ $this->account->increment($this->type, $this->amount);
+ $this->frozen = 0;
+ $this->balance = $this->account->{$this->type};
+ $this->save();
+
+ return true;
+ } else {
+ throw new Exception('已经领取');
+ }
+ }
+
+ public function getAmountFormatAttribute(): string
+ {
+ return ($this->amount > 0 ? '+' : '').$this->amount;
+ }
+
+}
diff --git a/modules/User/Models/AccountRule.php b/modules/User/Models/AccountRule.php
new file mode 100644
index 0000000..e762bee
--- /dev/null
+++ b/modules/User/Models/AccountRule.php
@@ -0,0 +1,43 @@
+hasMany(AccountLog::class, 'rule_id');
+ }
+
+ /**
+ * Notes : 获取可执行次数的文本显示
+ * @Date : 2021/5/21 2:37 下午
+ * @Author : < Jason.C >
+ * @return string
+ */
+ protected function getTriggerTextAttribute(): string
+ {
+ switch ($this->trigger <=> 0) {
+ case -1:
+ return '仅一次';
+ case 0:
+ return '不限制';
+ case 1:
+ return $this->trigger . ' 次/日';
+ default:
+ return '';
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/Identity.php b/modules/User/Models/Identity.php
new file mode 100644
index 0000000..2dc1c21
--- /dev/null
+++ b/modules/User/Models/Identity.php
@@ -0,0 +1,271 @@
+ '原价',
+ 'price' => '开通金额',
+ ];
+
+ const Rules = [
+ 'give_crystal' => '开通赠水滴',
+ 'recommend_rate' => '推荐比例',
+ ];
+
+ const CHANNEL_ONLINE = 1;
+ const CHANNEL_OFFLINE = 2;
+
+ const CHANNELS = [
+ self::CHANNEL_ONLINE => '线上',
+ self::CHANNEL_OFFLINE => '线下'
+ ];
+
+ const EXPERIENCE_YES = 1;
+ const EXPERIENCE_NO = 0;
+
+ const EXPERIENCES = [
+ self::EXPERIENCE_YES => '是',
+ self::EXPERIENCE_NO => '否'
+ ];
+
+ const JOB_YK = 0;
+ const JOB_TY = 1;
+ const JOB_JK = 2;
+ const JOB_NK = 3;
+ const JOB_CS = 4;
+ const JOB_HH = 5;
+
+ const JOBS = [
+ self::JOB_YK => '游客',
+ self::JOB_TY => '体验官',
+ self::JOB_JK => '季卡',
+ self::JOB_NK => '年卡',
+ self::JOB_CS => '创始',
+ self::JOB_HH => '合伙人',
+ ];
+
+ protected $table = 'user_identities';
+
+ protected $casts = [
+ 'conditions' => 'json',
+ 'rules' => 'json',
+ 'rights' => 'json',
+ 'ruleshows' => 'json',
+ ];
+
+ public function setConditionsAttribute($value)
+ {
+ $this->attributes['conditions'] = json_encode(array_values($value));
+ }
+
+ public function setRulesAttribute($value)
+ {
+ $rules = collect($this->rules);
+
+ foreach ($value as &$item) {
+ $info = $rules->where('name', $item['name'])->first();
+ if (! isset($item['icon']) && $info && isset($info['icon'])) {
+ $item['icon'] = $info['icon'];
+ }
+ }
+ $this->attributes['rules'] = json_encode(array_values($value));
+ }
+
+ public function setRuleshowsAttribute($value)
+ {
+ $rules = collect($this->ruleshows);
+
+ foreach ($value as &$item) {
+ $info = $rules->where('name', $item['name'])->first();
+ if (! isset($item['icon']) && $info && isset($info['icon'])) {
+ $item['icon'] = $info['icon'];
+ }
+ }
+ $this->attributes['ruleshows'] = json_encode(array_values($value));
+ }
+
+ public function setRightsAttribute($value)
+ {
+ $rights = collect($this->rights);
+
+ foreach ($value as &$item) {
+ $info = $rights->where('name', $item['name'])->first();
+ if (! isset($item['cover']) && $info && isset($info['cover'])) {
+ $item['cover'] = $info['cover'];
+ }
+ }
+
+ $this->attributes['rights'] = json_encode(array_values($value));
+ }
+
+ /**
+ * Notes: 获取所有规则
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/21 13:03
+ */
+ public function getRules()
+ {
+ $rules = $this->ruleshows;
+ foreach ($rules as $key => $rule) {
+ if (isset($rule['icon'])) {
+ $rules[$key]['cover'] = $this->parseImageUrl($rule['icon']);
+ }
+ $rules[$key]['text'] = Arr::get(config('identity.show_rules'), $rule['name']);
+ }
+ return $rules;
+ }
+
+ /**
+ * Notes: 获取未开通身份时反馈的数据
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/26 14:16
+ * @return array|mixed
+ */
+ public function getNotRules()
+ {
+ $rules = $this->rules;
+ if ($this->job == Identity::JOB_JK) {
+ return [
+ 'give_crystal' => [
+ 'cover' => $this->getRuleIcon('give_crystal'),
+ 'value' => $this->getRule('give_crystal'),
+ 'text' => '赠送水滴'
+ ],
+ 'recommend_coupon' => [
+ 'cover' => $this->getRuleIcon('recommend_coupon'),
+ 'value' => $this->getRule('recommend_coupon'),
+ 'text' => '赠送抵值券'
+ ],
+ 'stock' => [
+ 'value' => $this->stock,
+ 'text' => "赠送{$this->stock}箱水"
+ ],
+ 'year' => [
+ 'value' => $this->years,
+ 'text' => $this->years."个月有效期"
+ ],
+ ];
+ }
+
+ if ($this->job == Identity::JOB_NK) {
+ return [
+ 'stock' => [
+ 'value' => $this->stock,
+ 'text' => "赠送{$this->stock}箱水"
+ ],
+ 'year' => [
+ 'value' => $this->years,
+ 'text' => $this->years."个月有效期"
+ ],
+ ];
+ }
+
+ return $rules;
+ }
+
+
+ /**
+ * Notes : 组内用户
+ *
+ * @Date : 2021/5/6 12:06 下午
+ * @Author : < Jason.C >
+ * @return BelongsToMany
+ */
+ public function users(): BelongsToMany
+ {
+ return $this->belongsToMany(User::class, 'user_identity')
+ ->withTimestamps();
+ }
+
+ /**
+ * Notes : 不同的客服匹配不同的身份
+ *
+ * @Date : 2021/6/07 10:50
+ * @Author : Mr.wang
+ * @return BelongsToMany
+ */
+ public function identities(): BelongsToMany
+ {
+ return $this->belongsToMany(Service::class, 'user_service_identity')
+ ->withTimestamps();
+ }
+
+ /**
+ * 返回身份中的某一个规则
+ *
+ * @param string $key
+ * @param mixed $default
+ * @return mixed|string
+ */
+ public function getRule(string $key = '', $default = '')
+ {
+ $values = collect($this->rules);
+ $value = $values->where('name', $key)->first();
+ if ($value) {
+ return $value['value'];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Notes: 获取规则图标
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/26 14:24
+ * @param string $key
+ * @param $default
+ * @return mixed|string
+ */
+ public function getRuleIcon(string $key = '', $default = '')
+ {
+ $values = collect($this->rules);
+ $value = $values->where('name', $key)->first();
+ if ($value && isset($value['icon'])) {
+ return $this->parseImageUrl($value['icon']);
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * 返回条件中的某一个字段
+ *
+ * @param string $key
+ * @param mixed $default
+ * @return mixed|string
+ */
+ public function getCondition(string $key = '', $default = '')
+ {
+ $values = collect($this->conditions);
+ $value = $values->where('name', $key)->first();
+ if ($value) {
+ return $value['value'];
+ } else {
+ return $default;
+ }
+ }
+
+}
diff --git a/modules/User/Models/IdentityLog.php b/modules/User/Models/IdentityLog.php
new file mode 100644
index 0000000..b4cec38
--- /dev/null
+++ b/modules/User/Models/IdentityLog.php
@@ -0,0 +1,46 @@
+ 'json',
+ ];
+
+ const CHANNEL_AUTO = 'Auto';
+ const CHANNEL_REG = 'Reg';
+
+ const CHANNEL_SYSTEM = 'System';
+
+ const CHANNEL_MAP = [
+ self::CHANNEL_AUTO => '自动变更',
+ self::CHANNEL_REG => '注册默认',
+ self::CHANNEL_SYSTEM => '后台变更',
+ ];
+
+ public function before_identity(): BelongsTo
+ {
+ return $this->belongsTo(Identity::class, 'before')->withDefault([
+ 'name' => '已删除',
+ ]);
+ }
+
+ public function after_identity(): BelongsTo
+ {
+ return $this->belongsTo(Identity::class, 'after')->withDefault([
+ 'name' => '已删除',
+ ]);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/IdentityMiddle.php b/modules/User/Models/IdentityMiddle.php
new file mode 100644
index 0000000..717e714
--- /dev/null
+++ b/modules/User/Models/IdentityMiddle.php
@@ -0,0 +1,36 @@
+identity->order > 1) {
+ $identity->addTimeline();
+ }
+ });
+ }
+
+ public function identity(): BelongsTo
+ {
+ return $this->belongsTo(Identity::class, 'identity_id');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/Order.php b/modules/User/Models/Order.php
new file mode 100644
index 0000000..3462fe6
--- /dev/null
+++ b/modules/User/Models/Order.php
@@ -0,0 +1,96 @@
+ '待审核',
+ self::STATE_SUCCESS => '已支付',
+ self::STATE_REFUND => '已退款',
+ ];
+
+ const TYPE_OPEN = 1;
+ const TYPE_RENEW = 2;
+
+ const TYPES = [
+ self::TYPE_OPEN => '开通',
+ self::TYPE_RENEW => '续费',
+ ];
+
+ const CHANNEL_IDENTITY = 1;
+ const CHANNEL_EXPERIENCE = 2;
+ const CHANNEL_PARTNER = 3;
+
+ const CHANNELS = [
+ self::CHANNEL_IDENTITY => '开通身份',
+ self::CHANNEL_EXPERIENCE => '开通体验官',
+ self::CHANNEL_PARTNER => '开通合伙人',
+ ];
+
+ public $casts = [
+ 'source' => 'json'
+ ];
+
+ public function identity(): BelongsTo
+ {
+ return $this->belongsTo(Identity::class, 'identity_id');
+ }
+
+ /**
+ * Notes: 设置订单支付
+ * @Author: 玄尘
+ * @Date : 2020/11/12 11:19
+ */
+ public function pay()
+ {
+ $this->state = self::STATE_SUCCESS;
+ $this->save();
+
+ event(new UserOrderPaid($this));
+ }
+
+ /**
+ * Notes: 是否可以支付
+ * @Author: 玄尘
+ * @Date : 2021/6/4 10:19
+ * @return bool
+ */
+ public function canPay(): bool
+ {
+ return $this->state == self::STATE_INIT;
+ }
+
+ /**
+ * Notes: 是否可以退款
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/22 13:18
+ * @return bool
+ */
+ public function canRefund(): bool
+ {
+ return $this->isPay();
+ }
+
+
+
+
+}
diff --git a/modules/User/Models/Relation.php b/modules/User/Models/Relation.php
new file mode 100644
index 0000000..774bfea
--- /dev/null
+++ b/modules/User/Models/Relation.php
@@ -0,0 +1,75 @@
+setParentColumn('parent_id');
+ $this->setOrderColumn('created_at');
+ $this->setTitleColumn('user_id');
+ }
+
+ /**
+ * 上级用户
+ *
+ * @return BelongsTo
+ */
+ public function parent(): BelongsTo
+ {
+ return $this->belongsTo(User::class, 'parent_id');
+ }
+
+ /**
+ * Notes: 关联中间表
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/19 13:16
+ */
+ public function identities(): BelongsToMany
+ {
+ return $this->belongsToMany(
+ Identity::class,
+ 'user_identity',
+ 'user_id',
+ 'identity_id',
+ );
+ }
+
+
+ /**
+ * 所有下级用户
+ *
+ * @return HasManyThrough
+ */
+ public function children(): HasManyThrough
+ {
+ return $this->hasManyThrough(
+ User::class,
+ Relation::class,
+ 'parent_id',
+ 'id',
+ 'user_id',
+ 'user_id'
+ );
+ }
+
+}
diff --git a/modules/User/Models/Service.php b/modules/User/Models/Service.php
new file mode 100644
index 0000000..28e8546
--- /dev/null
+++ b/modules/User/Models/Service.php
@@ -0,0 +1,34 @@
+belongsToMany(Identity::class, 'user_service_identity')
+ ->withTimestamps();
+ }
+
+}
diff --git a/modules/User/Models/Sign.php b/modules/User/Models/Sign.php
new file mode 100644
index 0000000..170b3f6
--- /dev/null
+++ b/modules/User/Models/Sign.php
@@ -0,0 +1,61 @@
+ '进行中',
+ self::FINISH_SIGN => '打卡完成',
+ self::FINISH_LOG => '报告完成',
+ ];
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ self::saved(function ($sign) {
+ $params = SignConfig::getParams();
+
+ if ($params['open'] == 1 && $sign->need_case == 0 && $sign->continue_days >= $params['cycle_day']) {
+ $sign->update(['need_case' => 1, 'is_finish' => 1]);
+ }
+
+ });
+ }
+
+ /**
+ * Notes : 获取最新连续签到数
+ *
+ * @Date : 2021/5/28 12:00
+ * @Author : Mr.wang
+ * @return int|mixed
+ */
+ public function getContinueDays()
+ {
+ if ($this->last_sign_at->diffInDays() > 1) {
+ $continue = 1;
+ } else {
+ $continue = $this->continue_days + 1;
+ }
+
+ return $continue;
+ }
+
+}
diff --git a/modules/User/Models/SignBanner.php b/modules/User/Models/SignBanner.php
new file mode 100644
index 0000000..ed7755b
--- /dev/null
+++ b/modules/User/Models/SignBanner.php
@@ -0,0 +1,16 @@
+ '单日',
+ self::TYPE_CONTINUOUS => '连续',
+ self::TYPE_CYCLE => '周期连续',
+ ];
+ const SHOWTYPES_DAY = 'day';
+ const SHOWTYPES_WEEK = 'week';
+ const SHOWTYPES_MONTH = 'month';
+ const SHOWTYPES = [
+ self::SHOWTYPES_DAY => '7天展示',
+ self::SHOWTYPES_WEEK => '一周展示',
+ self::SHOWTYPES_MONTH => '一月展示',
+ ];
+ protected $table = 'user_sign_configs';
+ protected $casts = [
+ 'params' => 'json',
+ 'tasks' => 'json',
+ ];
+
+ public static function getParams($key = '')
+ {
+ $model = SignConfig::find(1);
+ if (! $key) {
+ return collect($model->params);
+ } else {
+ return $model->params[$key] ?? '';
+ }
+ }
+
+ public static function getTasks($key = '')
+ {
+ $model = SignConfig::find(1);
+ if (! $key) {
+ return collect($model->tasks);
+ } else {
+ return $model->tasks[$key] ?? '';
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/SignLog.php b/modules/User/Models/SignLog.php
new file mode 100644
index 0000000..027ca87
--- /dev/null
+++ b/modules/User/Models/SignLog.php
@@ -0,0 +1,30 @@
+ 'date',
+ ];
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ self::created(function ($model) {
+
+ });
+ }
+
+}
diff --git a/modules/User/Models/SignText.php b/modules/User/Models/SignText.php
new file mode 100644
index 0000000..0c806ed
--- /dev/null
+++ b/modules/User/Models/SignText.php
@@ -0,0 +1,13 @@
+ 'json',
+ ];
+
+ public static function boot()
+ {
+ parent::boot();
+
+ self::saved(function ($model) {
+ if ($model->in_use && $model->id) {
+ self::where('id', '<>', $model->id)
+ ->where('in_use', 1)
+ ->update(['in_use' => 0]);
+ }
+ });
+ }
+
+ /**
+ * Notes : 默认网关配置
+ * @Date : 2021/5/27 5:18 下午
+ * @Author : < Jason.C >
+ * @return array
+ */
+ public function getGateway(): array
+ {
+ return SmsGateway::where('slug', $this->default_gateway)->value('configs');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/SmsGateway.php b/modules/User/Models/SmsGateway.php
new file mode 100644
index 0000000..cdd347a
--- /dev/null
+++ b/modules/User/Models/SmsGateway.php
@@ -0,0 +1,19 @@
+ 'json',
+ ];
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/Traits/CertificationTrait.php b/modules/User/Models/Traits/CertificationTrait.php
new file mode 100644
index 0000000..441e4c7
--- /dev/null
+++ b/modules/User/Models/Traits/CertificationTrait.php
@@ -0,0 +1,283 @@
+ocrVerified(Storage::url($keys['front_card']), 'front');
+ if (! $verified) {
+ return [
+ 'code' => 0,
+ 'message' => $this->getErrorMessage(),
+ ];
+ } else {
+ if ($verified['words_result']['姓名']['words'] != $keys['name']) {
+ return [
+ 'code' => 0,
+ 'message' => '正面图片与填写信息不符',
+ ];
+ }
+ if ($verified['words_result']['公民身份号码']['words'] != $keys['idcard']) {
+ return [
+ 'code' => 0,
+ 'message' => '正面图片与填写信息不符',
+ ];
+ }
+ }
+ $verified = $this->ocrVerified(Storage::url($keys['back_card']), 'back');
+
+ if (! $verified) {
+ return [
+ 'code' => 0,
+ 'message' => $this->getErrorMessage(),
+ ];
+ } else {
+ if ($verified['image_status'] != 'normal') {
+ return [
+ 'code' => 0,
+ 'message' => '背面图片与填写信息不符',
+ ];
+ }
+ }
+ }
+ $base = [
+ 'name' => $keys['name'],
+ 'idcard' => $keys['idcard'],
+ ];
+ $data = $this->check($base);
+ if ($data === false) {
+ return [
+ 'code' => 0,
+ 'message' => $this->getErrorMessage(),
+ ];
+ } else {
+ return [
+ 'code' => 1,
+ ];
+ }
+ } else {
+ return [
+ 'code' => 1,
+ ];
+ }
+ }
+
+ /**
+ * Notes : ocr认证
+ *
+ * @Author: Mr.wang
+ * @Date : 2021/12/7 13:54
+ * @param $image
+ * @param $type
+ * @return bool
+ */
+ public function ocrVerified($image, $type): bool
+ {
+ $AppID = config('userCertification.ocr_appid');
+ $SecretKey = config('userCertification.ocr_secretkey');
+
+ if (empty($AppID)) {
+ $this->setErrorMessage('请配置接口AppID');
+
+ return false;
+ }
+ if (empty($SecretKey)) {
+ $this->setErrorMessage('请配置接口SecretKey');
+
+ return false;
+ }
+ $access = $this->getAccess($AppID, $SecretKey);
+ if ($access === false) {
+ $this->setErrorMessage('access_token不正确');
+
+ return false;
+ }
+ $token = $access->access_token;
+ $apiUrl = 'https://aip.baidubce.com/rest/2.0/ocr/v1/idcard';
+ $result = $this->getOcr($apiUrl, $token, $image, $type);
+ if (($result['error_code'] ?? 0) == 100) {
+ $this->setErrorMessage($result['error_msg'] ?? '未知错误');
+
+ return false;
+ } else {
+ if (empty($result['words_result']) || ! isset($result['words_result'])) {
+ $this->setErrorMessage('图片未识别');
+
+ return false;
+ }
+
+ return $result;
+ }
+ }
+
+ protected function getAccess($AppID, $SecretKey)
+ {
+ $authUrl = 'https://aip.baidubce.com/oauth/2.0/token';
+ try {
+ $Client = new Client();
+ $response = $Client->post($authUrl, [
+ 'query' => [
+ 'grant_type' => 'client_credentials',
+ 'client_id' => $AppID,
+ 'client_secret' => $SecretKey,
+ ],
+ ]);
+ $result = json_decode($response->getBody()->getContents());
+
+ return $result;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ protected function getOcr($url, $token, $image, $type)
+ {
+ $url = $url.'?access_token='.$token;
+ $params = [
+ 'url' => $image,
+ 'id_card_side' => $type,
+ ];
+ try {
+ $Client = new Client();
+ $response = $Client->post($url, ['form_params' => $params]);
+ $result = json_decode($response->getBody()->getContents(), true);
+
+ return $result;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->errorMessage;
+ }
+
+ protected function setErrorMessage($message)
+ {
+ $this->errorMessage = $message;
+ }
+
+ /**
+ * Notes : 网络验证
+ *
+ * @Date : 2021/9/25 15:42
+ * @Author : Mr.wang
+ * @param $keys
+ * @return bool|mixed
+ */
+ protected function check($keys): bool
+ {
+ $apiUrl = config('userCertification.url');
+ if (empty($apiUrl)) {
+ $this->setErrorMessage('请配置接口地址');
+
+ return false;
+ }
+ $apiCode = config('userCertification.code');
+ if (empty($apiCode)) {
+ $this->setErrorMessage('请配置接口Code');
+
+ return false;
+ }
+ $this->setParams($keys);
+ $this->setHeaders();
+ $result = $this->dopost($apiUrl);
+ try {
+ if (config('userCertification.type') == 2) {
+ if ($result->code == 0 && $result->message == '成功') {
+ if ($result->result->res == 1) {
+ return true;
+ } else {
+ $this->setErrorMessage('信息'.$result->result->description);
+
+ return false;
+ }
+ } else {
+ $this->setErrorMessage('信息'.$result->result->description);
+
+ return false;
+ }
+ } else {
+ if ($result->code == 200 && $result->success === true) {
+ if ($result->data->result == 0) {
+ return true;
+ } else {
+ $this->setErrorMessage($result->data->desc);
+
+ return false;
+ }
+ } else {
+ $this->setErrorMessage($result->msg);
+
+ return false;
+ }
+ }
+ } catch (\Exception $e) {
+ return $result;
+ }
+ }
+
+ protected function setParams($keys)
+ {
+ $this->params = $keys;
+ }
+
+ protected function setHeaders()
+ {
+ $this->header = [
+ "Authorization" => 'APPCODE '.config('userCertification.code'),
+ "Accept" => "application/json",
+ ];
+ }
+
+ protected function dopost($url)
+ {
+ try {
+ $Client = new Client();
+ $response = $Client->get($url, ['query' => $this->params, 'headers' => $this->header]);
+ // switch (config('usercertification.request_method')) {
+ // case 'get':
+ // $response = $Client->get($url, ['query' => $this->params, 'headers' => $this->header]);
+ // break;
+ // case 'post':
+ // $response = $Client->post($url, ['query' => $this->params, 'headers' => $this->header]);
+ // break;
+ // default:
+ // $this->setErrorMessage('不允许的请求方式');
+ //
+ // return false;
+ // break;
+ // }
+ $result = json_decode($response->getBody()->getContents());
+
+ return $result;
+ } catch (\Exception $e) {
+ preg_match_all('/[\x{4e00}-\x{9fff}]+/u', $e->getmessage(), $cn_name);
+ $this->setErrorMessage($cn_name[0][0]);
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Models/Traits/HasIdentityScopes.php b/modules/User/Models/Traits/HasIdentityScopes.php
new file mode 100644
index 0000000..4af6a39
--- /dev/null
+++ b/modules/User/Models/Traits/HasIdentityScopes.php
@@ -0,0 +1,77 @@
+where('job', Identity::JOB_TY);
+ }
+
+ /**
+ * Notes: 季卡
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/18 15:26
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeJk(Builder $query): Builder
+ {
+ return $query->where('job', Identity::JOB_JK);
+ }
+
+ /**
+ * Notes: 年卡
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/18 15:27
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeNk(Builder $query): Builder
+ {
+ return $query->where('job', Identity::JOB_NK);
+ }
+
+ /**
+ * Notes: 创始
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/18 15:27
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeCs(Builder $query): Builder
+ {
+ return $query->where('job', Identity::JOB_CS);
+ }
+
+ /**
+ * Notes: 合伙人
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/18 15:27
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeHh(Builder $query): Builder
+ {
+ return $query->where('job', Identity::JOB_HH);
+ }
+
+
+}
diff --git a/modules/User/Models/Traits/HasLog.php b/modules/User/Models/Traits/HasLog.php
new file mode 100644
index 0000000..cce3e3f
--- /dev/null
+++ b/modules/User/Models/Traits/HasLog.php
@@ -0,0 +1,44 @@
+hasMany(UserLog::class);
+ }
+
+ /**
+ * Notes:
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/7 14:22
+ * @param $admin
+ * @param $remark
+ */
+ public function addLog($admin, $remark)
+ {
+ $this->logs()->create([
+ 'user_id' => $this->getKey(),
+ 'admin_id' => $admin->getKey(),
+ 'remark' => $remark
+ ]);
+ }
+
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/Traits/HasRelations.php b/modules/User/Models/Traits/HasRelations.php
new file mode 100644
index 0000000..ccf8fa6
--- /dev/null
+++ b/modules/User/Models/Traits/HasRelations.php
@@ -0,0 +1,192 @@
+
+ * @Date : 2020/1/13 5:52 下午
+ */
+ public static function bootHasRelations()
+ {
+ self::created(function ($model) {
+ if (isset($model->parent_id) && is_numeric($model->parent_id) && $model->parent_id != 0) {
+ $parent = User::find($model->parent_id);
+ if ($parent && $model->id != $model->parent_id) {
+ $model->relation()->create([
+ 'parent_id' => $parent->id,
+ 'bloodline' => $parent->relation->bloodline.$parent->id.',',
+ 'layer' => $parent->relation->layer + 1,
+ ]);
+ } else {
+ $model->relation()->create([
+ 'parent_id' => config('user.default_parent_id'),
+ 'bloodline' => config('user.default_parent_id').',',
+ 'layer' => 1,
+ ]);
+ }
+ } else {
+ $model->relation()->create([
+ 'parent_id' => config('user.default_parent_id'),
+ 'bloodline' => config('user.default_parent_id').',',
+ 'layer' => 1,
+ ]);
+ }
+ });
+ }
+
+ /**
+ * Notes: 这个方法,是为了给用户创建事件监听模型使用的
+ * 目的是去除attribute里面的parent_id参数,防止数据库写入错误
+ *
+ * @Author:
+ * @Date : 2020/1/13 5:58 下午
+ * @param int $parentID
+ */
+ protected function setParentIdAttribute(int $parentID)
+ {
+ $this->parent_id = $parentID;
+ }
+
+ /**
+ * Notes: 用户关联关系
+ *
+ * @Author:
+ * @Date : 2020/1/13 5:51 下午
+ * @return HasOne
+ */
+ public function relation(): HasOne
+ {
+ return $this->hasOne(Relation::class)->withDefault();
+ }
+
+ /**
+ * Notes: 上级用户
+ *
+ * @Author:
+ * @Date : 2020/1/13 5:51 下午
+ * @return HasOneThrough
+ */
+ public function parent(): HasOneThrough
+ {
+ return $this->hasOneThrough(
+ User::class,
+ Relation::class,
+ 'user_id',
+ 'id',
+ 'id',
+ 'parent_id'
+ );
+ }
+
+ /**
+ * Notes: 所有下级用户
+ *
+ * @Author:
+ * @Date : 2020/1/13 5:51 下午
+ * @return mixed
+ */
+ public function children(): HasManyThrough
+ {
+ return $this->hasManyThrough(
+ User::class,
+ Relation::class,
+ 'parent_id',
+ 'id',
+ 'id',
+ 'user_id'
+ );
+ }
+
+ /**
+ * 调整隶属
+ *
+ * @param int $parent_id
+ * @return bool
+ * @throws Exception
+ */
+ public function updateParent(int $parent_id = 0): bool
+ {
+ if ($parent_id == $this->id) {
+ throw new Exception('不能绑定自己');
+ }
+ if (Relation::where('user_id', $parent_id)
+ ->where('bloodline', 'like', "%,".$this->id.",%")
+ ->exists()) {
+ throw new Exception('不能绑定自己的下级用户');
+ }
+
+ try {
+ $relation = $this->relation;
+
+ $new_blood = '0,';
+ $new_layer = 1;
+ $new_parent_id = 0;
+
+ $blood = $relation->bloodline;
+ $layer = $relation->layer;
+ $parent = User::find($parent_id);
+ if ($parent) {
+ $new_parent_id = $parent->id;
+ $new_blood = $parent->relation->bloodline.$new_parent_id.',';
+ $new_layer = $parent->relation->layer + 1;
+ }
+ $relation->parent_id = $new_parent_id;
+ $relation->bloodline = $new_blood;
+ $relation->layer = $new_layer;
+ if ($relation->save()) {
+ $diffLayer = $layer - $new_layer;
+ DB::update("UPDATE `user_relations` SET `bloodline`=CONCAT(?,SUBSTRING(bloodline,LENGTH(?)+1)),`layer`=`layer`-? WHERE `bloodline` LIKE ?",
+ [$new_blood, $blood, $diffLayer, "%,".$this->id.",%"]);
+ }
+
+ return true;
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Notes: 获取下级数量
+ *
+ * @Author: 玄尘
+ * @Date : 2021/9/24 11:42
+ */
+ public function getRelationCount(): array
+ {
+ return [
+ 'all' => Relation::query()
+ ->whereIn('layer', [$this->relation->layer + 1, $this->relation->layer + 2])
+ ->where('bloodline', 'like', "%,".$this->id.",%")
+ ->count(),
+ 'one' => Relation::query()
+ ->where('layer', $this->relation->layer + 1)
+ ->where('bloodline', 'like', "%,".$this->id.",%")
+ ->count(),
+ 'two' => Relation::query()
+ ->where('layer', $this->relation->layer + 2)
+ ->where('bloodline', 'like', "%,".$this->id.",%")
+ ->count(),
+ ];
+ }
+
+}
diff --git a/modules/User/Models/Traits/HasSign.php b/modules/User/Models/Traits/HasSign.php
new file mode 100644
index 0000000..8890d1b
--- /dev/null
+++ b/modules/User/Models/Traits/HasSign.php
@@ -0,0 +1,77 @@
+hasOne(Sign::class);
+ }
+
+ /**
+ * Notes : 当前日期是否签到
+ *
+ * @Date : 2022/1/6 13:25
+ * @Author : Mr.wang
+ * @param string $date
+ * @return bool
+ */
+ public function isSign($date = null): bool
+ {
+ $date = $date ?? Carbon::now()->format('Y-m-d');
+ return $this->signLogs()->whereDate('date', $date)->exists();
+ }
+
+ /**
+ * Notes : 用户签到日志
+ *
+ * @Date : 2022/1/6 13:23
+ * @Author : Mr.wang
+ * @return HasMany
+ */
+ public function signLogs(): HasMany
+ {
+ return $this->hasMany(SignLog::class);
+ }
+
+ /**
+ * Notes: 获取签到数据
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 15:35
+ */
+ public function getSignData(): array
+ {
+ $all = Arr::get(SignConfig::getParams(), 'cycle_day', 0);
+ $data = [
+ 'continue' => 0,
+ 'total' => 0,
+ 'all' => $all
+ ];
+ if ($this->sign) {
+ $data = [
+ 'continue' => $this->sign->continue_days,
+ 'total' => $this->sign->counts,
+ 'all' => $all,
+ ];
+ }
+ $data = array_merge($data, [
+ 'text' => '第'.$data['continue'].'/'.$data['all'].'天'
+ ]);
+ return $data;
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Models/Traits/HasStock.php b/modules/User/Models/Traits/HasStock.php
new file mode 100644
index 0000000..5c2a8c9
--- /dev/null
+++ b/modules/User/Models/Traits/HasStock.php
@@ -0,0 +1,153 @@
+hasOne(UserStock::class)->withDefault([
+ 'stock' => 0,
+ 'hold' => 0,
+ 'residue' => 0,
+ ]);
+ }
+
+ /**
+ * Notes: 获取库存
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 13:58
+ */
+ public function getStockData(): array
+ {
+ $identity = $this->identityFirst();
+ $min = 1;
+ if ($identity->job == Identity::JOB_TY) {
+ $min = $identity->stock;
+ }
+
+ $deliver = OrderItem::query()
+ ->whereHas('order', function ($q) {
+ $q->TypeSample()->ByUser($this)->paid();
+ })->sum('qty');
+
+ $deliverd = OrderItem::query()
+ ->whereHas('order', function ($q) {
+ $q->TypeSample()->ByUser($this)->whereIn('state', [
+ Order::STATUS_SIGNED,
+ Order::STATUS_DELIVERED,
+ ]);
+ })->sum('qty');
+
+ return [
+ 'case_id' => $this->case ? $this->case->id : 0,
+ 'stock' => $this->userStock->stock,//总库存
+ 'hold' => $this->userStock->hold,//已发货
+ 'residue' => (int) bcsub($this->userStock->residue, $deliver),//未提货
+ 'deliver' => $deliver,//待发货
+ 'deliverd' => $deliverd,//已发货
+ 'min_pick' => $min,//最少提货数量
+ ];
+ }
+
+ /**
+ * Notes: 增加库存
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/26 16:18
+ */
+ public function addStock($identity_id, $type = null, $addStock = 0)
+ {
+ try {
+ $identity = Identity::find($identity_id);
+
+ $stock = $identity->stock;
+ if ($addStock) {
+ $stock = $addStock;
+ }
+
+ $userStock = $this->userStock;
+ if (! $type) {
+ $type = UserStockLog::TYPE_IN;
+ }
+
+ if (isset($userStock->id)) {
+ $userStock->increment('stock', $stock);
+ } else {
+ $userStock = UserStock::query()->updateOrCreate([
+ 'user_id' => $this->id,
+ ], [
+ 'stock' => $stock,
+ ]);
+ }
+
+ $this->addStockLog($type, $stock, $identity_id);
+
+ return true;
+ } catch (\Exception $exception) {
+ return new \Exception($exception->getMessage());
+ }
+ }
+
+
+ /**
+ * Notes: 增加销量
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 14:44
+ */
+ public function addHold($number)
+ {
+ $this->userStock->increment('hold', $number);
+
+ $this->addStockLog(UserStockLog::TYPE_OUT, -$number);
+
+ }
+
+ /**
+ * Notes: 增加日志
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/2 14:55
+ * @param $type
+ * @param $variable
+ * @param int $identity_id
+ */
+ public function addStockLog($type, $variable, int $identity_id = 0)
+ {
+ $userStock = UserStock::where('user_id', $this->id)->first();
+ UserStockLog::query()
+ ->create([
+ 'user_stock_id' => $userStock->id,
+ 'type' => $type,
+ 'variable' => $variable,
+ 'identity_id' => $identity_id,
+ ]);
+ }
+
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/Traits/HasVipOrders.php b/modules/User/Models/Traits/HasVipOrders.php
new file mode 100644
index 0000000..f5eb36f
--- /dev/null
+++ b/modules/User/Models/Traits/HasVipOrders.php
@@ -0,0 +1,65 @@
+
+ * @return HasMany
+ */
+ public function vipOrders(): HasMany
+ {
+ return $this->hasMany(Order::class);
+ }
+
+ /**
+ * Notes: 开通会员缴费
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/7 13:59
+ */
+ public function getOpenVipPrices($type = 'all')
+ {
+ return $this->vipOrders
+ ->where('state', Order::STATE_SUCCESS)
+ ->where('price', '>', 0)
+ ->pluck('price');
+ }
+
+ /**
+ * Notes: 创建订单
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/7 16:55
+ */
+ public function createOrder($identity_id, $year, $price, $stock, $source = null)
+ {
+ $data = [
+ 'user_id' => $this->id,
+ 'identity_id' => $identity_id,
+ 'year' => $year,
+ 'type' => 1,
+ 'name' => '',
+ 'card_no' => '',
+ 'cover' => '',
+ 'stock' => $stock,
+ 'state' => Order::STATE_INIT,
+ 'price' => $price,
+ 'source' => $source,
+ ];
+
+ $order = Order::create($data);
+ $order->pay();
+ return $order;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/Traits/HasWechat.php b/modules/User/Models/Traits/HasWechat.php
new file mode 100644
index 0000000..39c975a
--- /dev/null
+++ b/modules/User/Models/Traits/HasWechat.php
@@ -0,0 +1,56 @@
+hasOne(UserWechat::class);
+ }
+
+ /**
+ * Notes: 是否关注公众号
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 16:23
+ */
+ public function isOfficialSubscribe(): bool
+ {
+ return $this->wechat ? $this->wechat->isOfficialSubscribe() : false;
+ }
+
+ /**
+ * Notes: 获取openids
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/27 14:51
+ * @return string[]
+ */
+ public function getOpenids()
+ {
+ if ($this->wechat) {
+ $data = $this->wechat->getOpenids();
+ } else {
+ $data = [
+ 'mini' => '',
+ 'official' => '',
+ ];
+ }
+
+ return $data;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/Traits/JoinIdentity.php b/modules/User/Models/Traits/JoinIdentity.php
new file mode 100644
index 0000000..858d8fc
--- /dev/null
+++ b/modules/User/Models/Traits/JoinIdentity.php
@@ -0,0 +1,247 @@
+identityMiddle->count() >= 1) {
+ //续费
+ if ($this->hasIdentity($identity_id)) {
+ $this->renewIdentity($identity_id);
+ } else {
+ //升级
+ self::updateIdentity($identity_id, $channel, $source);
+ }
+ } else {
+ $identity = Identity::find($identity_id);
+ if ($identity) {
+ //未开通此身份
+ if ($this->hasIdentity($identity_id) == false) {
+ $remark = Arr::get($source, 'remark', '加入身份');
+ $res = self::identityLog(0, $identity_id, $channel, $remark, $source);
+ if ($res) {
+ $serial = $source['serial'] ?? '';
+ if ($identity->serial_open && empty($serial)) {
+ $serial = self::getNewSerial($identity->serial_places);
+ }
+
+ $value = [];
+ if ($identity->years) {
+ $value = [
+ 'started_at' => now(),
+ 'ended_at' => now()->addMonths($identity->years),
+ ];
+ }
+
+ $this->identityMiddle()
+ ->updateOrCreate([
+ 'identity_id' => $identity_id,
+ 'serial' => $serial,
+ ], $value);
+
+ event(new UserJoinIdentity($this, $identity));
+ } else {
+ throw new Exception('添加日志失败');
+ }
+ } else {
+ //已开通此身份
+ $this->renewIdentity($identity_id);
+ }
+ } else {
+ throw new Exception('身份信息不存在');
+ }
+
+ }
+ }
+
+ /**
+ * Notes: 续费
+ *
+ * @Author: 玄尘
+ * @Date : 2021/6/8 9:49
+ * @param int $identity_id
+ */
+ public function renewIdentity(int $identity_id)
+ {
+ $identity = Identity::find($identity_id);
+ $before = $this->identityMiddle()->where('identity_id', $identity_id)->first();
+
+ //vip
+ if ($identity->years) {
+ IdentityMiddle::where('user_id', $this->id)
+ ->where('identity_id', $identity_id)
+ ->update([
+ 'ended_at' => Carbon::parse($before->ended_at)->addMonths($identity->years),
+ ]);
+ } else {
+ IdentityMiddle::where('user_id', $this->id)
+ ->where('identity_id', $identity_id)
+ ->update([
+ 'started_at' => null,
+ 'ended_at' => null,
+ ]);
+ }
+
+ event(new UserJoinIdentity($this, $before->identity));
+ }
+
+ /**
+ * 用户身份调整,当多身份关闭时由join触发
+ *
+ * @param int $identity_id
+ * @param string $channel
+ * @param array $source
+ * @throws Exception
+ */
+ public function updateIdentity(
+ int $identity_id = 0,
+ string $channel = IdentityLog::CHANNEL_AUTO,
+ array $source = []
+ ) {
+ $before = $this->identityMiddle()->first();
+ if ($this->identityMiddle()->count() > 1) {
+ $this->identityMiddle()->where('identity_id', '!=', $before->identity_id)->delete();
+ }
+ $identity = Identity::find($identity_id);
+ if ($identity) {
+ $remark = Arr::get($source, 'remark', '身份变更');
+ $res = self::identityLog($before->identity_id, $identity_id, $channel, $remark, $source);
+ if ($res) {
+ $serial = $source['serial'] ?? '';
+ if ($identity->serial_open && empty($serial)) {
+ $serial = self::getNewSerial($identity->serial_places);
+ }
+
+ $data = [
+ 'identity_id' => $identity_id,
+ 'serial' => $serial,
+ 'started_at' => null,
+ 'ended_at' => null,
+ ];
+
+ if ($identity->years) {
+ $data['started_at'] = now();
+ $data['ended_at'] = now()->addMonths($identity->years);
+ }
+
+ IdentityMiddle::where('user_id', $this->id)
+ ->where('identity_id', $before->identity_id)
+ ->update($data);
+
+ event(new UserUpdateIdentity($this, $before->identity, $identity));
+ } else {
+ throw new Exception('调整身份');
+ }
+ } else {
+ throw new Exception('身份信息不存在');
+ }
+ }
+
+ /**
+ * 判断用户是否参与身份
+ *
+ * @param int $identity_id
+ * @return bool
+ */
+ public function hasIdentity(int $identity_id = 0): bool
+ {
+ if ($identity_id) {
+ $res = $this->identityMiddle()->where('identity_id', $identity_id)->first();
+
+ return (bool) $res;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * 用户移除身份
+ *
+ * @param int $identity_id 身份ID
+ * @param string $channel 移除渠道
+ * @param array $source 其他溯源信息
+ * @throws Exception
+ */
+ public function removeIdentity(
+ int $identity_id = 0,
+ string $channel = IdentityLog::CHANNEL_AUTO,
+ array $source = []
+ ) {
+ if ($this->identityMiddle()->where('identity_id', $identity_id)->first()) {
+ $remark = Arr::get($source, 'remark', '身份移除');
+ $res = self::identityLog($identity_id, 0, $channel, $remark, $source);
+ if ($res) {
+ $this->identityMiddle()->where('identity_id', $identity_id)->delete();
+ event(new UserRemoveIdentity($this, Identity::find($identity_id)));
+ } else {
+ throw new Exception('移除记录失败');
+ }
+ } else {
+ throw new Exception('用户不在身份组中');
+ }
+ }
+
+ /**
+ * 生成数据库中部存在的号码
+ *
+ * @return int
+ */
+ public function getNewSerial($places = 8)
+ {
+ try {
+ $min = pow(10, $places - 1);
+ $max = 9 * $min;
+ $query = 'SELECT code FROM (SELECT CEILING(RAND()*'.$max.'+'.$min.') AS code FROM user_identity UNION SELECT CEILING(RAND()*'.$max.'+'.$min.') AS code) AS ss WHERE "code" NOT IN (SELECT serial FROM user_identity where serial !=null) LIMIT 1';
+ $res = DB::select($query);
+
+ return (int) $res[0]->code;
+ } catch (Exception $e) {
+ return '';
+ }
+ }
+
+ public function identityLog(
+ $before = 0,
+ $after = 0,
+ $channel = '',
+ $remark = '',
+ $source = []
+ ): Model {
+ return $this->identity_logs()->create([
+ 'before' => $before,
+ 'after' => $after,
+ 'channel' => $channel,
+ 'remark' => $remark,
+ 'source' => $source,
+ ]);
+ }
+
+}
diff --git a/modules/User/Models/Traits/OrderActions.php b/modules/User/Models/Traits/OrderActions.php
new file mode 100644
index 0000000..f5627db
--- /dev/null
+++ b/modules/User/Models/Traits/OrderActions.php
@@ -0,0 +1,107 @@
+state = self::STATE_SUCCESS;
+ $this->save();
+ event(new UserOrderPaid($this));
+ return true;
+
+ } catch (\Exception $exception) {
+ return $exception->getMessage();
+ }
+
+ }
+
+ /**
+ * Notes: 退款
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/22 13:09
+ */
+ public function refund(): bool
+ {
+ try {
+ $payment = $this->payment;
+ if (! $payment) {
+ throw new Exception("退款失败,未找到支付信息");
+ }
+
+ $refund = $payment->refunds()->create([
+ 'total' => $this->price,
+ ]);
+ //微信支付
+ if ($payment->driver == Payment::DRIVER_WECHAT) {
+
+ $order = [
+ 'out_trade_no' => $payment->trade_id,
+ 'out_refund_no' => $refund->refund_no,
+ 'amount' => [
+ 'refund' => $payment->total * 100,
+ 'total' => $payment->total * 100,
+ 'currency' => 'CNY',
+ ],
+ ];
+
+ $result = app('pay.wechat')->refund($order);
+
+ if (isset($result->code) && $result->code == 'PARAM_ERROR') {
+ throw new Exception("退款失败,".$result->message);
+ }
+
+ $this->update([
+ 'state' => Order::STATE_REFUND
+ ]);
+
+ $refund->update([
+ 'refund_at' => now()
+ ]);
+
+ return true;
+ } elseif ($payment->driver == Payment::DRIVER_ALIPAY) {//支付宝支付
+
+ $order = [
+ 'out_trade_no' => $this->order->order_no,
+ 'refund_amount' => $this->actual_total,
+ ];
+
+ $result = app('pay.alipay')->refund($order);
+
+ if ($result->code != '10000') {
+ throw new Exception("退款失败,".$result->msg);
+ }
+
+ $this->update([
+ 'state' => Order::STATE_REFUND
+ ]);
+
+ $refund->update([
+ 'refund_at' => now()
+ ]);
+
+ return true;
+ } else {
+ throw new Exception("退款失败,未找到支付路径");
+ }
+ } catch (Exception $exception) {
+ throw new Exception($exception->getMessage());
+ }
+ }
+}
diff --git a/modules/User/Models/Traits/WechatAttribute.php b/modules/User/Models/Traits/WechatAttribute.php
new file mode 100644
index 0000000..f2966ab
--- /dev/null
+++ b/modules/User/Models/Traits/WechatAttribute.php
@@ -0,0 +1,77 @@
+official->openid ?? '';
+ }
+
+ /**
+ * Notes: description
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 16:22
+ */
+ public function isOfficialSubscribe(): bool
+ {
+ return $this->official()->where('subscribe', 1)->exists();
+ }
+
+ /**
+ * Notes : 获取微信小程序openid
+ *
+ * @Date : 2021/5/26 17:03
+ * @Author : Mr.wang
+ * @return string
+ */
+ public function getMiniOpenidAttribute(): string
+ {
+ return $this->mini->openid ?? '';
+ }
+
+
+ /**
+ * Notes: 获取用户openid
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/26 13:52
+ * @param $channel
+ * @return mixed
+ */
+ public function getUserOpenid($channel)
+ {
+ $channel = strtolower($channel);
+ if ($channel == 'mp') {
+ return Arr::get($this->getOpenids(), 'official');
+ } else {
+ return Arr::get($this->getOpenids(), 'mini');
+ }
+ }
+
+ /**
+ * Notes: 获取openids
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/26 15:06
+ * @return array
+ */
+ public function getOpenids(): array
+ {
+ return [
+ 'mini' => $this->mini()->value('openid') ?? '',
+ 'official' => $this->official()->value('openid') ?? '',
+ ];
+ }
+
+}
diff --git a/modules/User/Models/User.php b/modules/User/Models/User.php
new file mode 100644
index 0000000..72c41b4
--- /dev/null
+++ b/modules/User/Models/User.php
@@ -0,0 +1,306 @@
+ '正常',
+ self::STATUS_REFUND => '退费',
+ ];
+
+ /**
+ * 禁止写入的字段
+ *
+ * @var array
+ */
+ protected $guarded = [];
+
+ /**
+ * 模型隐藏字段
+ *
+ * @var array
+ */
+ protected $hidden = [
+ 'password',
+ 'remember_token',
+ ];
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ self::created(function ($user) {
+ $user->info()->create([
+ 'nickname' => '用户'.substr($user->username, -4),
+ ]);
+ $user->sign()->create([
+ 'continue_days' => 0,
+ 'counts' => 0,
+ 'last_sign_at' => null,
+ ]);
+ $user->account()->create();
+
+ $defaultIdentity = Identity::where('default', 1)->first();
+ if ($defaultIdentity) {
+ $user->joinIdentity($defaultIdentity->id, IdentityLog::CHANNEL_REG);
+ }
+ });
+ }
+
+ /**
+ * Notes : 用户资料
+ *
+ * @Date : 2021/3/11 5:41 下午
+ * @Author : < Jason.C >
+ * @return HasOne
+ */
+ public function info(): HasOne
+ {
+ return $this->hasOne(UserInfo::class);
+ }
+
+
+ /**
+ * Notes : 用户账户
+ *
+ * @Date : 2021/4/27 11:20 上午
+ * @Author : < Jason.C >
+ * @return HasOne
+ */
+ public function account(): HasOne
+ {
+ return $this->hasOne(Account::class);
+ }
+
+ /**
+ * Notes : 当用户有密码的时候才加密
+ *
+ * @Date : 2021/3/11 1:43 下午
+ * @Author : < Jason.C >
+ * @param $password
+ */
+ public function setPasswordAttribute($password)
+ {
+ if ($password) {
+ $this->attributes['password'] = bcrypt($password);
+ }
+ }
+
+ /**
+ * 第一身份
+ *
+ * @return mixed|null
+ */
+ public function identityFirst()
+ {
+ return $this->identities()->first();
+ }
+
+ /**
+ * Notes : 用户身份
+ *
+ * @Date : 2021/5/6 12:00 下午
+ * @Author : < Jason.C >
+ */
+ public function identities(): BelongsToMany
+ {
+ return $this->belongsToMany(Identity::class, 'user_identity')
+ ->withTimestamps()
+ ->withPivot(['started_at', 'ended_at', 'serial']);
+ }
+
+ /**
+ * 用户中间表关联
+ *
+ * @return HasMany
+ */
+ public function identityMiddle(): HasMany
+ {
+ return $this->hasMany(IdentityMiddle::class);
+ }
+
+ /**
+ * 身份变动表关联
+ *
+ * @return HasMany
+ */
+ public function identity_logs(): HasMany
+ {
+ return $this->hasMany(IdentityLog::class);
+ }
+
+ /**
+ * Notes: 升级缴费订单
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/7 13:57
+ */
+ public function vipOrders(): HasMany
+ {
+ return $this->hasMany(UserOrder::class);
+ }
+
+ /**
+ * 用户个人认证
+ *
+ * @return HasOne
+ */
+ public function certification(): HasOne
+ {
+ return $this->hasOne(UserCertification::class);
+ }
+
+ /**
+ * Notes: 获取状态信息
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 16:30
+ */
+ public function getStateData(): array
+ {
+ $identity = $this->identities->first();
+
+ return [
+ 'isSubscribe' => $this->isOfficialSubscribe(),//关注
+ 'isVip' => $identity ? $identity->order : 0,//是否开会
+ ];
+ }
+
+ /**
+ * Notes: 获取当前状态
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 16:38
+ */
+ public function getNowStatus(): array
+ {
+ $status = $this->getStateData();
+
+ if (! $status['isSubscribe']) {
+ return [
+ 'value' => 'isSubscribe',
+ 'text' => '关注'
+ ];
+ }
+
+ if (! $status['isCase']) {
+ return [
+ 'value' => 'isCase',
+ 'text' => '健康档案'
+ ];
+ }
+
+ if (! $status['isVip']) {
+ return [
+ 'value' => 'isVip',
+ 'text' => '开通会员'
+ ];
+ }
+
+ return [
+ 'value' => 'upCase',
+ 'text' => '上传档案'
+ ];
+
+ }
+
+
+ /**
+ * Notes: 开通季卡的次数
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/21 10:38
+ */
+ public function getOpenJkCount(): int
+ {
+ $jkIdentity = Identity::query()->Jk()->first();
+ $num = 0;
+ if ($jkIdentity) {
+ $num = UserOrder::query()
+ ->byUser($this)
+ ->where('identity_id', $jkIdentity->id)
+ ->where('state', UserOrder::STATE_SUCCESS)
+ ->count();
+ }
+
+ return $num;
+ }
+
+ /**
+ * Notes: description
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/28 13:08
+ */
+ public function getCodeAttribute()
+ {
+ $userIdentity = $this->identityFirst();
+
+ if ($userIdentity && $userIdentity->id > 1) {
+ $invite = Hashids::connection('code')->encode($this->id);
+ } else {
+ $invite = '';
+ }
+
+ $url = Config::get('user.invite_code.url').'?invite='.$invite;
+ return 'data:image/png;base64,'.base64_encode(QrCode::format('png')
+ ->size(100)
+ ->margin(3)
+ ->generate($url));
+ }
+
+}
diff --git a/modules/User/Models/UserCertification.php b/modules/User/Models/UserCertification.php
new file mode 100644
index 0000000..fc152f3
--- /dev/null
+++ b/modules/User/Models/UserCertification.php
@@ -0,0 +1,27 @@
+ 'boolean',
+ ];
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ self::created(function ($certification) {
+ event(new UserCertificationSuccess($certification));
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/UserCertificationConfig.php b/modules/User/Models/UserCertificationConfig.php
new file mode 100644
index 0000000..365a273
--- /dev/null
+++ b/modules/User/Models/UserCertificationConfig.php
@@ -0,0 +1,48 @@
+ '姓名,身份证验证',
+ self::PHONECARD => '姓名,手机号,身份证号验证',
+ ];
+
+ public static function loading()
+ {
+ $config = Cache::rememberForever('userCertification', function () {
+ $conf = self::shown()
+ ->first();
+ Artisan::call('queue:restart');
+
+ return $conf ? $conf->toArray() : '';
+ });
+ config(['userCertification' => $config]);
+ }
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ self::saved(function ($model) {
+ if ($model->status == 1) {
+ Cache::forget('userCertification');
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/modules/User/Models/UserInfo.php b/modules/User/Models/UserInfo.php
new file mode 100644
index 0000000..63d402e
--- /dev/null
+++ b/modules/User/Models/UserInfo.php
@@ -0,0 +1,48 @@
+
+ * @return BelongsTo
+ */
+ public function user(): BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ /**
+ * Notes : 获取头像的实际地址
+ *
+ * @Date : 2021/3/12 10:53 上午
+ * @Author : < Jason.C >
+ * @param $avatar
+ * @return string
+ */
+ public function getAvatarAttribute($avatar): string
+ {
+ if (empty($avatar)) {
+ $avatar = config('user.avatar');
+ }
+
+ if (Str::startsWith($avatar, 'http')) {
+ return $avatar;
+ }
+
+ return $avatar ? Storage::url($avatar) : '';
+ }
+
+}
diff --git a/modules/User/Models/UserLog.php b/modules/User/Models/UserLog.php
new file mode 100644
index 0000000..e4c424f
--- /dev/null
+++ b/modules/User/Models/UserLog.php
@@ -0,0 +1,18 @@
+belongsTo(Administrator::class);
+ }
+}
diff --git a/modules/User/Models/UserStock.php b/modules/User/Models/UserStock.php
new file mode 100644
index 0000000..0d87dfe
--- /dev/null
+++ b/modules/User/Models/UserStock.php
@@ -0,0 +1,93 @@
+ '待提货',
+ self::STATUS_DELIVER => '待发货',
+ self::STATUS_DELIVERED => '待签收',
+ self::STATUS_SIGNED => '已签收',
+ ];
+
+ const STOCK_ORDER_STATUS_MAP = [
+ '待提货' => 'primary',
+ '待发货' => 'success',
+ '待签收' => 'danger',
+ '已签收' => 'info',
+ ];
+
+ public $appends = ['residue'];
+
+ /**
+ * Notes: 获取剩余
+ *
+ * @Author: 玄尘
+ * @Date: 2022/7/29 14:01
+ * @return string
+ */
+ public function getResidueAttribute()
+ {
+ return bcsub($this->stock, $this->hold);
+ }
+
+ /**
+ * Notes: 日志
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/2 14:43
+ * @return HasMany
+ */
+ public function logs(): HasMany
+ {
+ return $this->hasMany(UserStockLog::class);
+ }
+
+ /**
+ * Notes: 获取体验官提货订单状态
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/1 16:40
+ */
+ public function getStockOrderStatusAttribute()
+ {
+ $order = $this->user->orders()->first();
+ if (empty($order)) {
+ return 1;
+ } elseif ($order->state == Order::STATUS_PAID) {
+ return 2;
+ } elseif ($order->state == Order::STATUS_DELIVERED) {
+ return 3;
+ } elseif ($order->state == Order::STATUS_SIGNED) {
+ return 4;
+ }
+ }
+
+
+ /**
+ * Notes: 获取体验官订单名称
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/1 16:45
+ */
+ public function getStockOrderStatusTextAttribute()
+ {
+ return Arr::get(self::STOCK_ORDER_STATUS, $this->stock_order_status, '未知');
+ }
+
+}
diff --git a/modules/User/Models/UserStockLog.php b/modules/User/Models/UserStockLog.php
new file mode 100644
index 0000000..e58fc57
--- /dev/null
+++ b/modules/User/Models/UserStockLog.php
@@ -0,0 +1,53 @@
+ '开通会员',
+ self::TYPE_IN => '续费',
+ self::TYPE_OUT => '提货',
+ ];
+
+ public function identity()
+ {
+ return $this->belongsTo(Identity::class);
+ }
+
+ public function userStock(): BelongsTo
+ {
+ return $this->belongsTo(UserStock::class);
+ }
+
+ public function getTypeTextAttribute(): string
+ {
+ return self::TYPES[$this->type];
+ }
+
+ /**
+ * Notes: description
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/3 10:44
+ * @param Builder $query
+ * @param User $user
+ * @return Builder
+ */
+ public function scopeByUser(Builder $query, User $user): Builder
+ {
+ return $query->whereHas('userStock', function ($q) use ($user) {
+ return $q->ByUser($user);
+ });
+ }
+
+}
diff --git a/modules/User/Models/UserSubscribe.php b/modules/User/Models/UserSubscribe.php
new file mode 100644
index 0000000..1e6c992
--- /dev/null
+++ b/modules/User/Models/UserSubscribe.php
@@ -0,0 +1,11 @@
+ '未知',
+ '1' => '男',
+ '2' => '女',
+ ];
+
+ /**
+ * Notes : 小程序关联
+ *
+ * @Date : 2021/6/10 3:40 下午
+ * @Author : Mr.wang
+ * @return HasOne
+ */
+ public function mini(): HasOne
+ {
+ return $this->hasOne(UserWechatMini::class);
+ }
+
+ /**
+ * Notes : 公众号关联
+ *
+ * @Date : 2021/6/10 3:40 下午
+ * @Author : Mr.wang
+ * @return HasOne
+ */
+ public function official(): HasOne
+ {
+ return $this->hasOne(UserWechatOfficial::class);
+ }
+
+ /**
+ * Notes : app关联
+ *
+ * @Date : 2021/6/10 3:40 下午
+ * @Author : Mr.wang
+ * @return HasOne
+ */
+ public function app(): HasOne
+ {
+ return $this->hasOne(UserWechatApp::class);
+ }
+
+ /**
+ * Notes : 获取性别信息
+ *
+ * @Date : 2021/12/1 14:35
+ * @Author : Mr.wang
+ * @return string
+ */
+ public function getSexAttribute(): string
+ {
+ return self::SEX[$this->sex] ?? '未知';
+ }
+
+}
diff --git a/modules/User/Models/UserWechatApp.php b/modules/User/Models/UserWechatApp.php
new file mode 100644
index 0000000..abb82e8
--- /dev/null
+++ b/modules/User/Models/UserWechatApp.php
@@ -0,0 +1,16 @@
+belongsTo(UserWechat::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/UserWechatMini.php b/modules/User/Models/UserWechatMini.php
new file mode 100644
index 0000000..62b1912
--- /dev/null
+++ b/modules/User/Models/UserWechatMini.php
@@ -0,0 +1,16 @@
+belongsTo(UserWechat::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/UserWechatOfficial.php b/modules/User/Models/UserWechatOfficial.php
new file mode 100644
index 0000000..cda173a
--- /dev/null
+++ b/modules/User/Models/UserWechatOfficial.php
@@ -0,0 +1,19 @@
+belongsTo(UserWechat::class);
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Models/UserWechatSubscribe.php b/modules/User/Models/UserWechatSubscribe.php
new file mode 100644
index 0000000..b15220e
--- /dev/null
+++ b/modules/User/Models/UserWechatSubscribe.php
@@ -0,0 +1,10 @@
+ [
+ UserSignContinueDays::class,
+ ],
+
+ //开通身份
+ UserOrderPaid::class => [
+ UserOrderPaidListeners::class,
+ ],
+
+ UserSignReplenishSuccess::class => [
+ ],
+ ];
+
+}
diff --git a/modules/User/Providers/RouteServiceProvider.php b/modules/User/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..63d66d0
--- /dev/null
+++ b/modules/User/Providers/RouteServiceProvider.php
@@ -0,0 +1,62 @@
+mapAdminRoutes();
+ $this->mapApiRoutes();
+ }
+
+ protected function mapApiRoutes()
+ {
+ Route::as(config('api.route.as'))
+ ->domain(config('api.route.domain'))
+ ->middleware(config('api.route.middleware'))
+ ->namespace($this->moduleNamespace . '\\Api')
+ ->prefix(config('api.route.prefix') . '/user')
+ ->group(module_path($this->moduleName, 'Routes/api.php'));
+ }
+
+ protected function mapAdminRoutes()
+ {
+ Route::as(config('admin.route.as'))
+ ->domain(config('admin.route.domain'))
+ ->middleware(config('admin.route.middleware'))
+ ->namespace($this->moduleNamespace . '\\Admin')
+ ->prefix(config('admin.route.prefix'))
+ ->group(module_path($this->moduleName, 'Routes/admin.php'));
+ }
+
+}
diff --git a/modules/User/Providers/UserServiceProvider.php b/modules/User/Providers/UserServiceProvider.php
new file mode 100644
index 0000000..276a756
--- /dev/null
+++ b/modules/User/Providers/UserServiceProvider.php
@@ -0,0 +1,120 @@
+loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
+
+ $this->registerValidatorExtend();
+
+ if ($this->app->runningInConsole()) {
+ $this->commands($this->runCommands);
+ }
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->registerConfig();
+
+ if (config('user.cache_user')) {
+ Config::set('auth.providers.users.driver', 'cache');
+ Config::set('cache-user', Config::get('user.cache_config'));
+ }
+
+ Config::set('auth.providers.users.model', User::class);
+ Config::set('hashids.connections.code', Config::get('user.invite_code'));
+
+ // 注册短信到容器中
+ $this->app->singleton('sms', function () {
+ return new EasySms(Sms::getConfig());
+ });
+
+ $this->app->register(RouteServiceProvider::class);
+ }
+
+ /**
+ * Register config.
+ *
+ * @return void
+ */
+ protected function registerConfig()
+ {
+ $this->publishes([
+ module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'),
+ module_path($this->moduleName, 'Config/identity.php') => config_path('identity.php'),
+ ], 'config');
+
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
+ );
+ $this->mergeConfigFrom(
+ module_path($this->moduleName, 'Config/identity.php'), 'identity'
+ );
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides(): array
+ {
+ return ['sms'];
+ }
+
+ /**
+ * Notes : 注册短信验证码校验扩展
+ *
+ * @Date : 2021/5/26 5:03 下午
+ * @Author :
+ */
+ public function registerValidatorExtend()
+ {
+ Validator::extend('sms_check', function ($attribute, $code, $parameters) {
+ if (empty($code)) {
+ return false;
+ }
+ $mobileFiled = $parameters[0] ?? 'mobile';
+ $channel = $parameters[1] ?? 'DEFAULT';
+ $mobile = request()->input($mobileFiled);
+
+ return Sms::checkCode($mobile, $code, $channel);
+ });
+ }
+
+}
diff --git a/modules/User/README.md b/modules/User/README.md
new file mode 100644
index 0000000..38cf711
--- /dev/null
+++ b/modules/User/README.md
@@ -0,0 +1,12 @@
+# 用户模块
+
+## 1. 安装
+
+```shell
+composer require overtrue/wechat
+composer require overtrue/easy-sms
+composer require overtrue/socialite
+composer require propaganistas/laravel-phone
+composer require vinkla/hashids
+composer require yangjisen/laravel-cache-provider
+```
diff --git a/modules/User/Renderable/UserLog.php b/modules/User/Renderable/UserLog.php
new file mode 100644
index 0000000..1083dc3
--- /dev/null
+++ b/modules/User/Renderable/UserLog.php
@@ -0,0 +1,31 @@
+logs as $log) {
+ $data[] = [
+ 'admin' => $log->admin->name,
+ 'remark' => $log->remark,
+ 'created_at' => $log->created_at->toDateTimeString(),
+ ];
+ }
+
+
+ $table = new Table(['操作人', '备注', '添加时间'], $data);
+
+ return $table->render();
+
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/Routes/admin.php b/modules/User/Routes/admin.php
new file mode 100644
index 0000000..9ce390d
--- /dev/null
+++ b/modules/User/Routes/admin.php
@@ -0,0 +1,33 @@
+ 'users',
+ 'as' => 'user.',
+], function (Router $router) {
+ $router->get('ajax', 'IndexController@ajax')->name('users.ajax');
+
+ $router->get('accounts', 'AccountController@index');
+ $router->get('accounts/{account}', 'AccountController@show')->name('account.logs');
+ $router->resource('rules', 'RuleController');
+ $router->resource('identities', 'IdentitiesController');
+ $router->resource('identity_logs', 'IdentityLogController');
+ $router->resource('services', 'ServiceController');
+ $router->get('sms', 'SmsController@index');
+ $router->resource('sms/configs', 'SmsConfigController');
+ $router->resource('sms/gateways', 'GatewayController');
+ $router->resource('orders', 'OrderController');
+ $router->resource('certifications/configs', 'CertificationConfigController');
+ $router->resource('certifications', 'CertificationController');
+
+ $router->resource('signs', 'SignConfigController');
+ $router->resource('sign_texts', 'SignTextController');
+ $router->resource('sign_banners', 'SignBannerController');
+ $router->get('stocks', 'StockController@index');
+ $router->get('stocks/{stock}/logs', 'StockLogController@index');
+
+});
+
+Route::resource('users', 'IndexController');
diff --git a/modules/User/Routes/api.php b/modules/User/Routes/api.php
new file mode 100644
index 0000000..5de5c41
--- /dev/null
+++ b/modules/User/Routes/api.php
@@ -0,0 +1,188 @@
+ 'Auth',
+ 'prefix' => 'auth',
+], function (Router $router) {
+ $router->post('login', 'LoginController@index');
+ $router->post('register', 'RegisterController@index');
+ // 获取登录短信验证码
+ $router->post('verify', 'SmsController@send');
+ // 验证码登录
+ $router->post('sms', 'SmsController@login');
+ // 退出登录
+ $router->post('logout', 'LoginController@logout')->middleware('token.guess');
+
+ //获取跳转地址
+ $router->get('get_auth_url', 'WechatController@getAuthUrl');
+ $router->get('get_jssdk', 'WechatController@getJsSdk');
+ $router->get('official_share', 'WechatController@officialShare');
+ $router->get('mini_openid', 'WechatController@getMiniOpenid');
+ $router->get('official_openid', 'WechatController@getOfficialOpenid');
+});
+
+// 社会化登录
+Route::group([
+ 'namespace' => 'Socialite',
+ 'prefix' => 'socialite',
+ 'middleware' => config('api.route.middleware_guess'),
+], function (Router $router) {
+ //微信APP登录
+ $router->post('login/wechat/app', 'WeChatController@app');
+ //微信小程序登录
+ $router->post('login/wechat/mini', 'WeChatController@mini');
+ //微信公众号登陆
+ $router->post('login/wechat/official', 'WeChatController@official');
+ //微信union_id 快速登录
+ $router->post('login/wechat/query', 'WeChatController@query');
+ //UniCloud本机一键登录(后端获取手机号)
+ $router->post('login/unicloud/app', 'UniCloudController@app');
+ //UniCloud本机一键登录(前端获取手机号)
+ $router->post('login/unicloud/query', 'UniCloudController@query');
+ //入库微信用户信息
+ $router->post('login/wechat/add', 'WeChatController@addWechatUser');
+ $router->post('login/wechat/mini/add', 'WeChatController@miniAddWechatUser');
+
+});
+
+Route::group([
+ 'namespace' => 'Certification',
+ 'prefix' => 'certification',
+], function (Router $router) {
+ // 个人认证
+ $router->get('', 'IndexController@index');
+ $router->post('', 'IndexController@store');
+});
+
+Route::group([
+ 'middleware' => config('api.route.middleware_guess'),
+], function (Router $router) {
+ // 用户是否有身份认证
+ $router->get('certified', 'Certification\IndexController@certified');
+ // 获取身份对应的折扣
+ $router->get('identity/rule', 'Identity\IndexController@rule');
+});
+
+Route::group([
+ 'namespace' => 'Identity',
+ 'prefix' => 'identities',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('', 'IndexController@index');
+// $router->get('{identity}', 'IndexController@show');
+ $router->get('create/{identity}', 'IndexController@create');
+ $router->post('create/{identity}', 'IndexController@store');
+ //获取支付数据
+ $router->get('pay/{order}/wechat', 'IndexController@wechat');
+ $router->get('pay/{order}/alipay', 'IndexController@alipay');
+Route::group([
+ 'namespace' => 'Identity',
+ 'prefix' => 'identities',
+ 'middleware' => config('api.route.middleware_guess'),
+], function (Router $router) {
+ $router->get('{identity}', 'IndexController@show');
+});
+
+Route::group([
+ 'namespace' => 'Sign',
+ 'prefix' => 'sign',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('', 'IndexController@index');
+ $router->post('', 'IndexController@sign');
+ // 预留的补签接口
+ $router->post('replenish', 'IndexController@replenish');
+ // 测试日历接口
+ $router->get('date', 'IndexController@date');
+ $router->get('backgrounds', 'IndexController@backgrounds');
+ $router->get('ranks', 'IndexController@rank');
+
+});
+
+// 账户
+Route::group([
+ 'namespace' => 'Account',
+ 'prefix' => 'account',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('score', 'LogController@score');
+ $router->get('balance', 'LogController@balance');
+});
+
+// 用户中心
+Route::group([
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('', 'IndexController@index');
+ // 资料和修改资料
+ $router->get('info', 'IndexController@info');
+ $router->put('{key}', 'IndexController@update');
+ // 我的邀请码
+ $router->get('invite', 'IndexController@invite');
+ // 我的小程序码
+ $router->get('mini_share', 'IndexController@getMiniCode');
+
+ // 绑定邀请码
+ $router->post('bind', 'IndexController@bind');
+ // 我的专属客服
+ $router->get('services', 'Service\IndexController@index');
+});
+
+//设置
+Route::group([
+ 'namespace' => 'Setting',
+ 'prefix' => 'setting',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('', 'IndexController@index');
+ $router->put('{key}', 'IndexController@update');
+});
+
+Route::group([
+ 'namespace' => 'Stock',
+ 'prefix' => 'stock',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ // 个人认证
+ $router->get('', 'IndexController@index');
+ $router->get('pick', 'IndexController@create');
+ $router->post('pick', 'IndexController@Store');
+ $router->get('logs', 'IndexController@logs');
+});
+
+//我的伙伴
+Route::group([
+ 'namespace' => 'Relation',
+ 'prefix' => 'relations',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('', 'IndexController@index');
+});
+
+//排行
+Route::group([
+ 'namespace' => 'Rank',
+ 'prefix' => 'rank',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('total', 'IndexController@total');
+ $router->get('totaluser', 'IndexController@totalUser');
+
+ $router->get('week', 'IndexController@week');
+});
+
+
+
+//我的收藏
+Route::group([
+ 'namespace' => 'Favorite',
+ 'middleware' => config('api.route.middleware_auth'),
+], function (Router $router) {
+ $router->get('favorites', 'IndexController@index');
+});
+
+
diff --git a/modules/User/Rules/IdCardRule.php b/modules/User/Rules/IdCardRule.php
new file mode 100644
index 0000000..bafcec3
--- /dev/null
+++ b/modules/User/Rules/IdCardRule.php
@@ -0,0 +1,68 @@
+
+ * @param string $attribute
+ * @param mixed $value
+ * @return bool
+ */
+ public function passes($attribute, $value): bool
+ {
+ if (strlen($value) !== 18) {
+ return false;
+ }
+
+ return $this->isIdCard($value);
+ }
+
+ private function isIdCard($id): bool
+ {
+ $id = strtoupper($id);
+ $regx = "/(^\d{17}([0-9]|X)$)/";
+ $arr_split = [];
+ if (! preg_match($regx, $id)) {
+ return false;
+ }
+
+ $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/";
+ @preg_match($regx, $id, $arr_split);
+ $dtm_birth = $arr_split[2].'/'.$arr_split[3].'/'.$arr_split[4];
+ if (! strtotime($dtm_birth)) {
+ return false;
+ }
+
+ $arr_int = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
+ $arr_ch = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
+ $sign = 0;
+ for ($i = 0; $i < 17; $i++) {
+ $b = (int) $id[$i];
+ $w = $arr_int[$i];
+ $sign += $b * $w;
+ }
+ $n = $sign % 11;
+ $val_num = $arr_ch[$n];
+
+ return ! ($val_num !== substr($id, 17, 1));
+ }
+
+ /**
+ * 获取校验错误信息
+ *
+ * @return string
+ */
+ public function message(): string
+ {
+ return '身份证号码校验错误';
+ }
+
+}
diff --git a/modules/User/Services/VerificationCode.php b/modules/User/Services/VerificationCode.php
new file mode 100644
index 0000000..99be783
--- /dev/null
+++ b/modules/User/Services/VerificationCode.php
@@ -0,0 +1,91 @@
+config = $config;
+ $this->channel = $channel;
+
+ if ($config['debug']) {
+ $this->code = $config['debug_code'];
+ } else {
+ $this->code = sprintf("%0".$config['length']."d", mt_rand(1, pow(10, $config['length']) - 1));
+ }
+
+ parent::__construct();
+ }
+
+ /**
+ * Notes : 定义直接使用内容发送平台的内容
+ *
+ * @Date : 2021/5/27 9:09 上午
+ * @Author :
+ * @param GatewayInterface|null $gateway
+ * @return string
+ */
+ public function getContent(GatewayInterface $gateway = null): string
+ {
+ return sprintf('验证码%s,您正在登录,若非本人操作,请勿泄露。', $this->code);
+ }
+
+ /**
+ * Notes : 定义使用模板发送方式平台所需要的模板 ID
+ *
+ * @Date : 2021/5/27 9:10 上午
+ * @Author :
+ * @param GatewayInterface|null $gateway
+ * @return string
+ * @throws Exception
+ */
+ public function getTemplate(GatewayInterface $gateway = null): string
+ {
+ $templateId = $this->config['template'][$this->channel] ?? '';
+
+ if (! $templateId) {
+ throw new Exception('不合法的验证通道:'.$this->channel);
+ }
+
+ return $templateId;
+ }
+
+ /**
+ * Notes : 模板参数
+ *
+ * @Date : 2021/5/27 9:10 上午
+ * @Author :
+ * @param GatewayInterface|null $gateway
+ * @return string[]
+ */
+ public function getData(GatewayInterface $gateway = null): array
+ {
+ return [
+ 'code' => $this->code,
+ ];
+ }
+
+}
diff --git a/modules/User/Traits/BelongsToUser.php b/modules/User/Traits/BelongsToUser.php
new file mode 100644
index 0000000..ffebd48
--- /dev/null
+++ b/modules/User/Traits/BelongsToUser.php
@@ -0,0 +1,52 @@
+
+ * @return BelongsTo
+ */
+ public function user(): BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ /**
+ * Notes : 用户 作用域查询
+ *
+ * @Date : 2021/6/21 8:57 上午
+ * @Author :
+ * @param Builder $query
+ * @param User $user
+ * @return Builder
+ */
+ public function scopeByUser(Builder $query, User $user): Builder
+ {
+ return $query->where('user_id', $user->getKey());
+ }
+
+ /**
+ * Notes : 用户ID 作用域查询
+ *
+ * @Date : 2021/6/21 9:04 上午
+ * @Author :
+ * @param Builder $query
+ * @param int $userId
+ * @return Builder
+ */
+ public function scopeByUserId(Builder $query, int $userId): Builder
+ {
+ return $query->where('user_id', $userId);
+ }
+
+}
diff --git a/modules/User/Traits/RankDataTrait.php b/modules/User/Traits/RankDataTrait.php
new file mode 100644
index 0000000..5095de0
--- /dev/null
+++ b/modules/User/Traits/RankDataTrait.php
@@ -0,0 +1,67 @@
+whereBetween('started_at', $timeBetween)
+ ->where('ended_at', '>=', now())
+ ->select('user_id as middle_user_Id',
+ DB::raw('any_value(started_at) as started_at'),
+ DB::raw('any_value(identity_id) as identity_id'))
+ ->groupBy('middle_user_Id');
+
+ $vipRelationJion = DB::table('user_relations')
+ ->leftJoinSub($vipJion, 'vipJion', function ($join) {
+ $join->on('user_relations.user_id', '=', 'vipJion.middle_user_Id');
+ })
+ ->where('identity_id', '>', 1)
+ ->select('parent_id',
+ DB::raw('ifnull(count(user_id),0) as countChild'),
+ DB::raw('max(started_at) as lastOpenTime'))
+ ->groupBy('parent_id');
+
+ return User::query()
+ ->leftJoinSub($vipRelationJion, 'vipRelationJion', function ($join) {
+ $join->on('users.id', '=', 'vipRelationJion.parent_id');
+ })
+ ->where('countChild', '>', 0)
+ ->orderBy('countChild', 'desc')
+ ->orderBy('lastOpenTime', 'asc')
+ ->limit(10)
+ ->get();
+ }
+
+ public function getTotalUser(array $timeBetween)
+ {
+ $relationJion = DB::table('user_relations')
+ ->whereBetween('created_at', $timeBetween)
+ ->select('parent_id',
+ DB::raw('ifnull(count(user_id),0) as countChild'),
+ DB::raw('max(created_at) as lastRegTime')
+ )
+ ->groupBy('parent_id');
+
+ return User::query()
+ ->leftJoinSub($relationJion, 'relationJion', function ($join) {
+ $join->on('users.id', '=', 'relationJion.parent_id');
+ })
+ ->where('countChild', '>', 0)
+ ->orderBy('countChild', 'desc')
+ ->orderBy('lastRegTime', 'asc')
+ ->limit(10)
+ ->get();
+ }
+}
diff --git a/modules/User/Traits/WechatTrait.php b/modules/User/Traits/WechatTrait.php
new file mode 100644
index 0000000..4cbd822
--- /dev/null
+++ b/modules/User/Traits/WechatTrait.php
@@ -0,0 +1,180 @@
+code;
+ if (! $code) {
+ throw new \Exception('缺少code');
+ }
+ $weChat = app('wechat.official_account');
+
+ $weUser = $weChat->oauth->userFromCode(request()->code);
+
+ return $weUser;
+ } catch (\Exception $exception) {
+ throw new \Exception($exception->getMessage());
+ }
+
+ }
+
+ /**
+ * Notes: 获取用户信息
+ *
+ * @Author: 玄尘
+ * @Date: 2022/8/11 11:56
+ * @param $openid
+ * @return mixed
+ */
+ public function getWechatUser($openid)
+ {
+ $weChat = app('wechat.official_account');
+
+ return $weChat->user->get($openid);
+ }
+
+ /**
+ * Notes: 设置已关注列表
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/28 14:18
+ * @param $unionid
+ * @param $openid
+ * @param int $subscribe
+ */
+ public function setUserSubscribe($unionid, $openid, int $subscribe = 1)
+ {
+ UserWechatSubscribe::query()->updateOrCreate([
+ 'unionid' => $unionid,
+ 'openid' => $openid,
+ ], [
+ 'subscribe' => $subscribe,
+ ]);
+ }
+
+ /**
+ * Notes: 入库所有关注列表
+ *
+ * @Author: 玄尘
+ * @Date: 2022/9/28 14:41
+ */
+ public function getAllSubscribeUsers()
+ {
+ $app = app('wechat.official_account');
+ $users = $app->user->list();
+ collect($users->data['openid'])
+ ->split(3)->map(function ($datas) use ($app) {
+ $infos = $app->user->select($datas->toArray());
+ foreach ($infos['user_info_list'] as $info) {
+ $this->setUserSubscribe($info['unionid'], $info['openid'], 1);
+ }
+ });
+ }
+
+ /**
+ * Notes: 用户关注公众号
+ *
+ * @Author: 玄尘
+ * @Date: 2022/11/1 14:19
+ */
+ public function userSubOfficial()
+ {
+ $app = app('wechat.official_account');
+ $user = $app->user->get($this->payload->FromUserName);
+
+ if ($user->unionid) {
+ $wechatUsers = UserWechat::query()
+ ->where('unionid', $user->unionid)
+ ->get();
+ if ($wechatUsers->isNotEmpty()) {
+ foreach ($wechatUsers as $wechatUser) {
+ if ($wechatUser->official) {
+ if ($wechatUser->official->subscribe != 1) {
+ $wechatUser->official->update([
+ 'subscribe' => 1,
+ 'subscribed_at' => Carbon::parse($user->subscribe_time)->toDateTimeString(),
+ ]);
+ }
+ } else {
+ $wechatUser->official()->create([
+ 'openid' => $this->payload->FromUserName,
+ 'subscribe' => 1,
+ 'subscribed_at' => Carbon::parse($user->subscribe_time)->toDateTimeString(),
+ ]);
+ }
+
+ }
+ }
+
+ //插入关注数据
+ $this->setUserSubscribe($user->unionid, $this->payload->FromUserName, 1);
+
+ } else {
+ // 先查找用户是否存在,不存在再注册
+ $officialUsers = UserWechatOfficial::query()
+ ->where('openid', $this->payload->FromUserName)
+ ->get();
+
+ //设置总表uniond
+ if ($officialUsers->isNotEmpty()) {
+ foreach ($officialUsers as $officialUser) {
+ if (! $officialUser->userWechat->unionid && $user->unionid) {
+ $officialUser->userWechat->update([
+ 'unionid' => $user->unionid
+ ]);
+ }
+
+ if ($officialUser->subscribe != 1) {
+ $officialUser->update([
+ 'subscribe' => 1,
+ 'subscribed_at' => Carbon::parse($user->subscribe_time)->toDateTimeString(),
+ ]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Notes: 用户取消关注公众号
+ *
+ * @Author: 玄尘
+ * @Date: 2022/11/1 14:25
+ */
+ public function userUnSubOfficial()
+ {
+ $officialUsers = UserWechatOfficial::where('openid', $this->payload->FromUserName)->get();
+ if ($officialUsers->isNotEmpty()) {
+ foreach ($officialUsers as $officialUser) {
+ $officialUser->update([
+ 'subscribe' => 0,
+ 'subscribed_at' => null,
+ ]);
+
+ //设置取消关注
+ if ($officialUser->userWechat && $officialUser->userWechat->unionid) {
+ $this->setUserSubscribe($officialUser->userWechat->unionid, $officialUser->openid, 0);
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/User/User.php b/modules/User/User.php
new file mode 100644
index 0000000..3978a1c
--- /dev/null
+++ b/modules/User/User.php
@@ -0,0 +1,192 @@
+
+ */
+ public static function install(): void
+ {
+ Artisan::call('migrate', [
+ '--path' => 'modules/User/Database/Migrations',
+ ]);
+
+ self::createAdminMenu();
+ }
+
+ /**
+ * Notes : 创建后台菜单
+ *
+ * @Date : 2021/3/17 9:48 上午
+ * @Author :
+ */
+ protected static function createAdminMenu(): void
+ {
+ SignConfig::updateOrCreate([
+ 'id' => 1,
+ ], [
+ 'params' => [],
+ ]);
+ $menu = config('admin.database.menu_model');
+
+ $main = $menu::create([
+ 'parent_id' => 0,
+ 'order' => 10,
+ 'title' => self::$mainTitle,
+ 'icon' => 'fa-group',
+ ]);
+
+ $main->children()->createMany([
+ [
+ 'order' => 0,
+ 'title' => '用户列表',
+ 'icon' => 'fa-user',
+ 'uri' => 'users',
+ ],
+ [
+ 'order' => 1,
+ 'title' => '用户账户',
+ 'icon' => 'fa-bars',
+ 'uri' => 'users/accounts',
+ ],
+ [
+ 'order' => 2,
+ 'title' => '用户库存',
+ 'icon' => 'fa-bars',
+ 'uri' => 'users/stocks',
+ ],
+ // [
+ // 'order' => 2,
+ // 'title' => '账变规则',
+ // 'icon' => 'fa-bars',
+ // 'uri' => 'users/rules',
+ // ],
+ [
+ 'order' => 3,
+ 'title' => '签到规则管理',
+ 'icon' => 'fa-bars',
+ 'uri' => 'users/signs/1/edit',
+ ],
+ [
+ 'order' => 4,
+ 'title' => '签到展示文字',
+ 'icon' => 'fa-bars',
+ 'uri' => 'users/sign_texts',
+ ],
+ [
+ 'order' => 5,
+ 'title' => '签到背景图片',
+ 'icon' => 'fa-bars',
+ 'uri' => 'users/sign_banners',
+ ],
+ [
+ 'order' => 6,
+ 'title' => '用户身份',
+ 'icon' => 'fa-graduation-cap',
+ 'uri' => 'users/identities',
+ ],
+ [
+ 'order' => 7,
+ 'title' => '身份变动记录',
+ 'icon' => 'fa-graduation-cap',
+ 'uri' => 'users/identity_logs',
+ ],
+ // [
+ // 'order' => 6,
+ // 'title' => '客服中心',
+ // 'icon' => 'fa-group',
+ // 'uri' => 'users/services',
+ // ],
+ [
+ 'order' => 8,
+ 'title' => '升级订单',
+ 'icon' => 'fa-group',
+ 'uri' => 'users/orders',
+ ],
+ ]);
+
+ $subMenu = $main->children()->create([
+ 'order' => 10,
+ 'title' => '短信管理',
+ 'icon' => 'fa-commenting',
+ 'uri' => '',
+ ]);
+
+// $certify = $main->children()->create([
+// 'order' => 8,
+// 'title' => '认证管理',
+// 'icon' => 'fa-certificate',
+// 'uri' => '',
+// ]);
+
+
+ $subMenu->children()->createMany([
+ [
+ 'order' => 1,
+ 'title' => '短信记录',
+ 'icon' => 'fa-mobile',
+ 'uri' => 'users/sms',
+ ],
+ [
+ 'order' => 2,
+ 'title' => '短信配置',
+ 'icon' => 'fa-cogs',
+ 'uri' => 'users/sms/configs',
+ ],
+ [
+ 'order' => 3,
+ 'title' => '短信网关',
+ 'icon' => 'fa-map-signs',
+ 'uri' => 'users/sms/gateways',
+ ],
+ ]);
+
+// $certify->children()->createMany([
+// [
+// 'order' => 1,
+// 'title' => '认证记录',
+// 'icon' => 'fa-align-justify',
+// 'uri' => 'users/certifications',
+// ],
+// [
+// 'order' => 2,
+// 'title' => '认证配置',
+// 'icon' => 'fa-cogs',
+// 'uri' => 'users/certifications/configs',
+// ],
+// ]);
+ }
+
+ /**
+ * Notes : 卸载模块的一些操作
+ *
+ * @Date : 2021/3/12 11:35 上午
+ * @Author :
+ */
+ public static function uninstall(): void
+ {
+ $menu = config('admin.database.menu_model');
+
+ $msgMenu = $menu::where('title', '短信管理')->get();
+ foreach ($msgMenu as $menu) {
+ $menu->delete();
+ }
+
+ $mains = $menu::where('title', self::$mainTitle)->get();
+ foreach ($mains as $menu) {
+ $menu->delete();
+ }
+ }
+
+}
diff --git a/modules/User/composer.json b/modules/User/composer.json
new file mode 100644
index 0000000..bdccd7f
--- /dev/null
+++ b/modules/User/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "uztech/user-module",
+ "description": "",
+ "type": "laravel-module",
+ "authors": [
+ {
+ "name": "Jason.Chen",
+ "email": "chenjxlg@163.com"
+ }
+ ],
+ "require": {
+ "genealabs/laravel-model-caching": "^0.11.3",
+ "overtrue/wechat": "^5.0",
+ "overtrue/easy-sms": "^1.3",
+ "overtrue/socialite": "^3.2",
+ "overtrue/chinese-calendar": "^1.0",
+ "propaganistas/laravel-phone": "^4.3",
+ "vinkla/hashids": "^9.1",
+ "yangjisen/laravel-cache-provider": "^3.0"
+ },
+ "extra": {
+ "module-dir": "modules"
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\User\\": ""
+ }
+ }
+}
diff --git a/modules/User/docs/READMD.md b/modules/User/docs/READMD.md
new file mode 100644
index 0000000..5509ec7
--- /dev/null
+++ b/modules/User/docs/READMD.md
@@ -0,0 +1,13 @@
+# 文档
+
+## 1. 后台
+
+后台AJAX搜索,通过 用户名或昵称获取用户列表的内部接口
+
+```php
+route('admin.user.users.ajax');
+```
+
+## 2. 内部接口
+
+## 3. 接口文档,详见各文档
\ No newline at end of file
diff --git a/modules/User/docs/用户鉴权.md b/modules/User/docs/用户鉴权.md
new file mode 100644
index 0000000..846b5b3
--- /dev/null
+++ b/modules/User/docs/用户鉴权.md
@@ -0,0 +1,2 @@
+# 用户鉴权
+
diff --git a/modules/User/module.json b/modules/User/module.json
new file mode 100644
index 0000000..3aa7736
--- /dev/null
+++ b/modules/User/module.json
@@ -0,0 +1,18 @@
+{
+ "name": "User",
+ "alias": "user",
+ "description": "用户基础模块",
+ "keywords": [],
+ "priority": 0,
+ "providers": [
+ "Modules\\User\\Providers\\UserServiceProvider",
+ "Modules\\User\\Providers\\EventServiceProvider"
+ ],
+ "aliases": {},
+ "files": [],
+ "requires": [],
+ "config": true,
+ "configName": "用户模块",
+ "version": "1.0.0",
+ "author": "Jason.Chen"
+}
diff --git a/modules/User/用户模块.apifox.json b/modules/User/用户模块.apifox.json
new file mode 100644
index 0000000..2d2f9e3
--- /dev/null
+++ b/modules/User/用户模块.apifox.json
@@ -0,0 +1 @@
+{"apifoxProject":"1.0.0","info":{"name":"抖火","description":"","mockRule":{"rules":[],"enableSystemRule":true}},"responseCollection":[],"apiCollection":[{"name":"根目录","parentId":0,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户接口","parentId":0,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"消息中心","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"消息主页","api":{"id":"46914075","method":"get","path":"/api/notifications","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540863","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53036155,"type":"http","path":null,"name":"成功","responseId":117540863,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"全部标记已读","api":{"id":"46914072","method":"put","path":"/api/notifications","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540861","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[{"id":53036153,"type":"http","path":null,"name":"成功","responseId":117540861,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"清空消息","api":{"id":"46914073","method":"delete","path":"/api/notifications","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540862","name":"Successful Operation","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":53036154,"type":"http","path":null,"name":"成功","responseId":117540862,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"分类列表","api":{"id":"46914076","method":"get","path":"/api/notifications/{type}/list","parameters":{"path":[{"name":"type","required":true,"description":"","type":"string","id":"type#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540864","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[{"id":53036156,"type":"http","path":null,"name":"成功","responseId":117540864,"parameters":{"path":[{"id":"type#0","relatedId":"type#0","relatedName":"type","name":"type","value":"","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"消息详情","api":{"id":"46914074","method":"get","path":"/api/notifications/{notification_id}","parameters":{"path":[{"name":"notification_id","required":true,"description":"","type":"string","id":"notification_id#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540865","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53036157,"type":"http","path":null,"name":"成功","responseId":117540865,"parameters":{"path":[{"id":"notification_id#0","relatedId":"notification_id#0","relatedName":"notification_id","name":"notification_id","value":"","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"全部标记已读 (分类)","api":{"id":"46914077","method":"put","path":"/api/notifications/{type}","parameters":{"path":[{"name":"type","required":true,"description":"","type":"string","id":"type#0","example":"SystemNotification"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540866","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[{"id":53036158,"type":"http","path":null,"name":"成功","responseId":117540866,"parameters":{"path":[{"id":"type#0","relatedId":"type#0","relatedName":"type","name":"type","value":"SystemNotification","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"清空消息 (分类)","api":{"id":"46914078","method":"delete","path":"/api/notifications/{type}","parameters":{"path":[{"name":"type","required":true,"description":"","type":"string","id":"type#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540867","name":"Successful Operation","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":36,"cases":[{"id":53036159,"type":"http","path":null,"name":"成功","responseId":117540867,"parameters":{"path":[{"id":"type#0","relatedId":"type#0","relatedName":"type","name":"type","value":"","enable":true}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"未读消息数量","api":{"id":"46914079","method":"get","path":"/api/notifications/counts","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117540868","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":["用户接口/消息中心"],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":42,"cases":[{"id":53036160,"type":"http","path":null,"name":"成功","responseId":117540868,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"登录与注册","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户名密码登录","api":{"id":"46912419","method":"post","path":"/api/user/auth/login","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534018","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"application/x-www-form-urlencoded","parameters":[{"name":"username","required":true,"description":"","type":"text","id":"T2FZ7F6i5f","example":""},{"name":"password","required":true,"description":"","type":"text","id":"k8oOvEn87b"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53034417,"type":"http","path":null,"name":"成功","responseId":117534018,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"username","value":"15663876870","enable":true,"id":"SBuaJ3V5MU","relatedName":"username"},{"name":"password","value":"111111","enable":true,"id":"W6XcQph1tO","relatedName":"password"}]},"preProcessors":[],"postProcessors":[{"type":"commonScript","data":[387699]}],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"uni本机一键登录(服务器获取手机号)","api":{"id":"46912420","method":"post","path":"/api/user/socialite/login/unicloud/app","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534019","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"access_token","required":true,"description":"uni前端获取内容","type":"text","id":"XYGfkloWzH","example":"321"},{"name":"openid","required":true,"description":"uni前端获取内容","type":"text","id":"M1B11ZW2yE","example":"321"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"uni本机一键登录(前端获取手机号)","api":{"id":"46912418","method":"post","path":"/api/user/socialite/login/unicloud/query","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534016","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobile","required":true,"description":"前端获取的手机号","type":"text","id":"awtVowiXai","example":"17645779673"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取登录短信验证码","api":{"id":"46912421","method":"post","path":"/api/user/auth/verify","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534017","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobileNo","required":true,"description":"手机号码","type":"text","id":"81ha5PW5lD"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"验证码登录","api":{"id":"46912427","method":"post","path":"/api/user/auth/sms","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534025","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobileNo","required":true,"description":"手机号","type":"text","id":"GecDdtZXvs"},{"name":"code","required":true,"description":"验证码","type":"text","id":"ykQUsGZhOv"},{"name":"parent_id","required":false,"description":"上级用户","type":"text","id":"Gvf9U3wX1E","example":"10045"}]},"description":"","tags":[],"status":"released","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53034421,"type":"http","path":null,"name":"成功","responseId":117534025,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"mobileNo","value":"14745798066","enable":true,"id":"imNAq03W7C","relatedName":"mobileNo"},{"name":"code","value":"0000","enable":true,"id":"wyPXQRqsvX","relatedName":"code"},{"name":"parent_id","value":"10045","id":"Yg9y03uxwa","relatedName":"parent_id"}]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"APP登录","api":{"id":"46912429","method":"post","path":"/api/user/socialite/login/wechat/app","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534027","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"union_id","required":true,"description":"unionId必须填写","type":"text","id":"N8kmLy1lpZ"},{"name":"open_id","required":false,"description":"","type":"text","id":"JAUyFqnyCy"},{"name":"mobileNo","required":true,"description":"手机号码必须填写","type":"text","id":"EBY5Llip56"},{"name":"code","required":true,"description":"验证码必须填写","type":"text","id":"dsAVqhj9yN"},{"name":"nickname","required":false,"description":"昵称","type":"text","id":"NUvaQyIRCK"},{"name":"avatar","required":false,"description":"头像","type":"text","id":"u1aDFc11Eo"},{"name":"gender","required":false,"description":"性别","type":"text","id":"YUIsw8Ol6F"},{"name":"country","required":false,"description":"国家","type":"text","id":"cteTMMCb05"},{"name":"province","required":false,"description":"省份","type":"text","id":"cvlwg0xjsP"},{"name":"city","required":false,"description":"城市","type":"text","id":"J3iJ7I4g4M"}]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"微信小程序手机号登录","api":{"id":"46912430","method":"post","path":"/api/user/socialite/login/wechat/mini","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"微信code","type":"text","id":"yNjOWwVICn"},{"name":"iv","required":true,"description":"向量","type":"text","id":"DRMIcA6K9v"},{"name":"encryptedData","required":true,"description":"加密串","type":"text","id":"k6XGMCXF6o"},{"name":"nickname","required":false,"description":"微信昵称","type":"text","id":"D0iu5wCYyJ"},{"name":"avatar","required":false,"description":"微信头像","type":"text","id":"6dF4UxZz9X"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534028","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":36,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"身份相关","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"获取身份","api":{"id":"46912433","method":"get","path":"/api/user/identities","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534033","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"finish 条件是否完成\nstock 可开通总数\nsold 已有数量\nresidue 剩余数量\non_line true 可在线开通\nshow_button 是否显示按钮","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53034423,"type":"http","path":null,"name":"成功","responseId":117534033,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取身份详细信息","api":{"id":"46912438","method":"get","path":"/api/user/identities/{identity}","parameters":{"path":[{"name":"identity","required":true,"type":"text","id":"identity#0"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534038","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取可开通身份内容","api":{"id":"46912440","method":"get","path":"/api/user/identities/create/{identity}","parameters":{"path":[{"name":"identity","required":true,"type":"text","id":"identity#0","example":"2"}],"query":[{"name":"year","required":true,"description":"开通几年","type":"text","id":"mTQ2d21Dwc","example":"1"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534040","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"确认开通身份","api":{"id":"46912441","method":"post","path":"/api/user/identities/create/{identity}","parameters":{"path":[{"name":"identity","required":true,"type":"text","id":"identity#0","example":"2"}],"query":[{"name":"year","required":true,"description":"开通年限","type":"text","id":"1lq3bH2pSN","example":"1"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534041","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"开通身份支付(微信)","api":{"id":"46912445","method":"get","path":"/api/user/identities/pay/{order_id}/wechat","parameters":{"path":[{"name":"order_id","required":true,"type":"text","id":"order_id#0","example":"36"}],"query":[{"name":"type","required":true,"description":"支付渠道:miniapp 小程序 mp 公众号 app App","type":"text","id":"M0zVt3wEUm","example":"app"},{"name":"openid","required":true,"description":"用户openid,type=mp时必传","type":"text","id":"sqdUjfCCx0","example":"mp"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534042","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53034424,"type":"http","path":null,"name":"成功","responseId":117534042,"parameters":{"path":[{"id":"order#0","relatedId":"order#0","relatedName":"order","name":"order","value":"36","enable":true}],"query":[{"name":"type","value":"mp","enable":true,"id":"JxDYmeR4hg","relatedName":"type"},{"name":"openid","value":"111","enable":true,"id":"RCejxjJV6g","relatedName":"openid"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"开通身份支付(支付宝)","api":{"id":"46912446","method":"get","path":"/api/user/identities/pay/{order_id}/alipay","parameters":{"path":[{"name":"order_id","required":true,"type":"text","id":"order_id#0","example":"1"}],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534043","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"用户信息","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"我的小程序码","api":{"id":"46914662","method":"get","path":"/api/user/mini_share","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"大小","type":"string","id":"QsJLfs7XCa","example":"300"},{"name":"url","required":true,"description":"跳转地址","type":"string","id":"QjycaeyWZH","example":"pages/mall/index/index"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541884","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"tag ; 1 普通用户 2 大师 3 院士","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53036874,"type":"http","path":null,"name":"成功","responseId":0,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取用户信息","api":{"id":"46912452","method":"get","path":"/api/user/info","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534049","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"我的伙伴","api":{"id":"46914663","method":"get","path":"/api/user/relations","parameters":{"path":[],"query":[{"name":"larer","required":false,"description":"1 直接 2 间接","type":"string","id":"EKdNfxZgTv"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117541885","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"修改用户资料","api":{"id":"46912453","method":"put","path":"/api/user/setting/{key}","parameters":{"path":[{"name":"key","required":true,"type":"text","id":"key#0","example":"nickname"}],"query":[{"name":"value","required":true,"description":"","type":"text","id":"gJETQJf2Jf","example":"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epT7dFGs0RNZERjeAl75GKTibmkrbAIH47dic43toYjTkSLUPegLDPicqGypp3mRVa7TKFBkbhUfaicqQ/132"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534050","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[{"id":53034425,"type":"http","path":null,"name":"上传头像","responseId":117534050,"parameters":{"path":[{"id":"key#0","relatedId":"key#0","relatedName":"key","name":"key","value":"avatar","enable":true}],"query":[{"name":"value","value":"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epT7dFGs0RNZERjeAl75GKTibmkrbAIH47dic43toYjTkSLUPegLDPicqGypp3mRVa7TKFBkbhUfaicqQ/132","enable":true,"id":"UsrrdScZa5","relatedName":"value"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}},{"id":53034426,"type":"http","path":null,"name":"修改昵称","responseId":117534050,"parameters":{"path":[{"id":"key#0","relatedId":"key#0","relatedName":"key","name":"key","value":"nickname","enable":true}],"query":[{"name":"value","value":"玄尘","enable":true,"id":"Jd1drVR9T6","relatedName":"value"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取jssdk","api":{"id":"46914664","method":"get","path":"/api/user/auth/official/jssdk","parameters":{"path":[],"query":[{"name":"url","required":true,"description":"域名:页面的url","type":"string","id":"9CfVBC3Bsz"},{"name":"jsApiList","required":true,"description":"","type":"string","id":"qlDDWLDoED"},{"name":"openTagList","required":false,"description":"","type":"string","id":"aHbb0nKKkN"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541886","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"微信支付、分享、调整到小程序App\njsApiList 查看地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#63\nopenTagList 查看地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html#%E6%A6%82%E8%BF%B0","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":53036875,"type":"http","path":null,"name":"成功","responseId":117541886,"parameters":{"path":[],"query":[{"name":"url","value":"http://www.water.shangkelian.cn/user/code","enable":true,"id":"i1lHZTDvU9","relatedName":"url"},{"name":"jsApiList","enable":true,"id":"bO8fmBb2ma","relatedName":"jsApiList"},{"name":"openTagList","enable":true,"id":"oxz5c7EmLk","relatedName":"openTagList"}],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取邀请码","api":{"id":"46912454","method":"get","path":"/api/user/invite","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534051","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"绑定邀请码","api":{"id":"46912455","method":"post","path":"/api/user/bind","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534052","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"invite","required":false,"description":"","type":"text","id":"XUX77YKZMR"}]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"个人中心设置","api":{"id":"46912456","method":"get","path":"/api/user/setting","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534053","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53034427,"type":"http","path":null,"name":"成功","responseId":117534053,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取小程序openid","api":{"id":"46914666","method":"get","path":"/api/user/auth/mini/openid","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"","type":"string","id":"2nmbNKMVEj"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541888","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":53036877,"type":"http","path":null,"name":"成功","responseId":0,"parameters":{"path":[],"query":[{"name":"url","value":"http://www.gout.shangkelian.cn/user/code","enable":true,"id":"lMEYD90aFt","relatedName":"url"},{"name":"jsApiList","enable":true,"id":"5PatqFaHcn","relatedName":"jsApiList"},{"name":"openTagList","enable":true,"id":"YkKUQsQ5P0","relatedName":"openTagList"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"获取授权页面","api":{"id":"46914667","method":"get","path":"/api/user/auth/official/url","parameters":{"path":[],"query":[{"name":"url","required":true,"description":"域名:页面的url /pages/index","type":"string","id":"pqMoW4te8P"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization"},{"name":"Accept"}]},"responses":[{"id":"117541889","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"用户签到","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户签到","api":{"id":"46912462","method":"post","path":"/api/user/sign","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534059","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"用户签到信息","api":{"id":"46912463","method":"get","path":"/api/user/sign","parameters":{"path":[],"query":[{"name":"days","required":false,"description":"","type":"text","id":"TaEOX6oVrd","example":"7"}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534060","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]},{"name":"用户认证","parentId":9409854,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"auth":{},"items":[{"name":"用户认证","api":{"id":"46912464","method":"post","path":"/api/user/certification","parameters":{"path":[],"query":[{"name":"name","required":true,"description":"姓名","type":"text","id":"nueYaNtusJ","example":" 测试"},{"name":"id_card","required":true,"description":"身份证号","type":"text","id":"ISPMzfvS1f","example":"230102111111111111"},{"name":"front_card","required":true,"description":"身份证正面","type":"text","id":"SnYDZt1OxO","example":""},{"name":"back_card","required":true,"description":"身份证反面","type":"text","id":"A9XW8kuE0r","example":""}],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534061","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":53034428,"type":"http","path":null,"name":"成功","responseId":117534061,"parameters":{"path":[],"query":[{"name":"name","value":" 测试","enable":true,"id":"7PtXSEn8Um","relatedName":"name"},{"name":"id_card","value":"230102198906032138","enable":true,"id":"92kil5yaKX","relatedName":"id_card"},{"name":"front_card","value":"1.jpg","enable":true,"id":"8GSaVo8NbU","relatedName":"front_card"},{"name":"back_card","value":"2.jpg","enable":true,"id":"7w7IDyCq7s","relatedName":"back_card"}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"查看认证信息","api":{"id":"46912465","method":"get","path":"/api/user/certification","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534062","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[{"id":53034429,"type":"http","path":null,"name":"成功","responseId":117534062,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}},{"name":"是否实名认证","api":{"id":"46912466","method":"get","path":"/api/user/certified","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"auth":{},"commonParameters":{},"responses":[{"id":"117534063","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{},"x-apifox-orders":[]}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":53034430,"type":"http","path":null,"name":"成功","responseId":117534063,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[],"auth":{},"advancedSettings":{}}],"mocks":[],"customApiFields":"{}","advancedSettings":{},"mockScript":{},"codeSamples":[]}}]}]}]}],"schemaCollection":[],"environments":[],"commonScripts":[{"name":"登录后保存token","description":"","content":"var jsonData = pm.response.json();\n\npm.environment.set('api_token', jsonData.data.access_token);","stickyOrdering":0,"ordering":0,"id":"387699"}],"globalVariables":[],"commonParameters":{"parameters":{"query":[],"body":[],"cookie":[],"header":[{"name":"Authorization","defaultEnable":true,"type":"string","defaultValue":"Bearer {{api_token}}","description":"Bearer {{api_token}}"},{"name":"Accept","defaultEnable":true,"type":"string","defaultValue":"application/json","description":"application/json"},{"name":"Authorization","defaultEnable":true,"type":"string","defaultValue":"Bearer {{api_token}}","description":"Bearer {{api_token}}"},{"name":"Accept","defaultEnable":true,"type":"string","defaultValue":"application/json","description":"application/json"}]},"id":394503,"projectId":1850830,"creatorId":366036,"editorId":366036,"createdAt":"2022-11-01T05:30:13.000Z","updatedAt":"2022-11-01T05:36:32.000Z","deletedAt":null},"projectSetting":{"apiStatuses":["developing","testing","released","deprecated"],"servers":[{"id":"default","name":"默认服务"}],"preProcessors":[],"postProcessors":[],"auth":{},"initialDisabledMockIds":[],"mockSettings":{},"gateway":[],"language":"zh-CN","id":"1444399","cloudMock":{"security":"free","enable":false,"tokenKey":"apifoxToken"}}}
\ No newline at end of file
diff --git a/modules/pull-all.sh b/modules/pull-all.sh
new file mode 100644
index 0000000..5150557
--- /dev/null
+++ b/modules/pull-all.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+dir=$(ls -d */)
+for i in $dir
+do
+ cd ./$i/ && git pull && cd ..
+done
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..00c6506
--- /dev/null
+++ b/package.json
@@ -0,0 +1,18 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "npm run development",
+ "development": "mix",
+ "watch": "mix watch",
+ "watch-poll": "mix watch -- --watch-options-poll=1000",
+ "hot": "mix watch --hot",
+ "prod": "npm run production",
+ "production": "mix --production"
+ },
+ "devDependencies": {
+ "axios": "^0.21",
+ "laravel-mix": "^6.0.6",
+ "lodash": "^4.17.19",
+ "postcss": "^8.1.14"
+ }
+}
diff --git a/parseComposer.php b/parseComposer.php
new file mode 100644
index 0000000..a6ccb63
--- /dev/null
+++ b/parseComposer.php
@@ -0,0 +1,49 @@
+ $version) {
+ if ($package !== 'php' && substr($package, 0,4) !== 'ext-') {
+ $origin[$package] = $version;
+ }
+}
+
+foreach ($modules as $module) {
+ if ($module == '.' || $module == '..') {
+ continue;
+ }
+
+ if (! is_dir('modules/'.$module)) {
+ continue;
+ }
+
+ $needs = getNeeds(file_get_contents('modules/'.$module.'/composer.json'));
+
+ foreach ($needs as $package => $version) {
+ if (isset($packages[$package])) {
+ if (version_compare($version, $packages[$package]) == 1) {
+ $packages[$package] = $version;
+ }
+ } else {
+ $packages[$package] = $version;
+ }
+ }
+}
+
+$string = '';
+
+foreach (array_diff_key($packages, $origin) as $package => $version) {
+ $string .= $package.':'.$version.' ';
+}
+
+echo $string;
+
+function getNeeds($json): array
+{
+ return json_decode($json, true)['require'];
+}
+
diff --git a/permission.sh b/permission.sh
new file mode 100644
index 0000000..e0770d8
--- /dev/null
+++ b/permission.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+user=www-data
+
+grep '^$user' /etc/passwd | wc -l > /dev/null
+if [ $? -ne 0 ]
+then
+ chown -R www-data:www-data storage bootstrap/cache modules.json
+fi
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..3aec5e2
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,21 @@
+
+
+ Options -MultiViews -Indexes
+
+
+ RewriteEngine On
+
+ # Handle Authorization Header
+ RewriteCond %{HTTP:Authorization} .
+ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
+ # Redirect Trailing Slashes If Not A Folder...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_URI} (.+)/$
+ RewriteRule ^ %1 [L,R=301]
+
+ # Send Requests To Front Controller...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..e69de29
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..a034365
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,55 @@
+make(Kernel::class);
+
+$response = $kernel->handle(
+ $request = Request::capture()
+)->send();
+
+$kernel->terminate($request, $response);
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..eb05362
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/css/bootstrap.min.css b/public/vendor/laravel-admin/AdminLTE/bootstrap/css/bootstrap.min.css
new file mode 100644
index 0000000..a58698c
--- /dev/null
+++ b/public/vendor/laravel-admin/AdminLTE/bootstrap/css/bootstrap.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Bootstrap v3.3.5 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')} .glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .glyphicon-asterisk:before{content:"\2a"} .glyphicon-plus:before{content:"\2b"} .glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"} .glyphicon-minus:before{content:"\2212"} .glyphicon-cloud:before{content:"\2601"} .glyphicon-envelope:before{content:"\2709"} .glyphicon-pencil:before{content:"\270f"} .glyphicon-glass:before{content:"\e001"} .glyphicon-music:before{content:"\e002"} .glyphicon-search:before{content:"\e003"} .glyphicon-heart:before{content:"\e005"} .glyphicon-star:before{content:"\e006"} .glyphicon-star-empty:before{content:"\e007"} .glyphicon-user:before{content:"\e008"} .glyphicon-film:before{content:"\e009"} .glyphicon-th-large:before{content:"\e010"} .glyphicon-th:before{content:"\e011"} .glyphicon-th-list:before{content:"\e012"} .glyphicon-ok:before{content:"\e013"} .glyphicon-remove:before{content:"\e014"} .glyphicon-zoom-in:before{content:"\e015"} .glyphicon-zoom-out:before{content:"\e016"} .glyphicon-off:before{content:"\e017"} .glyphicon-signal:before{content:"\e018"} .glyphicon-cog:before{content:"\e019"} .glyphicon-trash:before{content:"\e020"} .glyphicon-home:before{content:"\e021"} .glyphicon-file:before{content:"\e022"} .glyphicon-time:before{content:"\e023"} .glyphicon-road:before{content:"\e024"} .glyphicon-download-alt:before{content:"\e025"} .glyphicon-download:before{content:"\e026"} .glyphicon-upload:before{content:"\e027"} .glyphicon-inbox:before{content:"\e028"} .glyphicon-play-circle:before{content:"\e029"} .glyphicon-repeat:before{content:"\e030"} .glyphicon-refresh:before{content:"\e031"} .glyphicon-list-alt:before{content:"\e032"} .glyphicon-lock:before{content:"\e033"} .glyphicon-flag:before{content:"\e034"} .glyphicon-headphones:before{content:"\e035"} .glyphicon-volume-off:before{content:"\e036"} .glyphicon-volume-down:before{content:"\e037"} .glyphicon-volume-up:before{content:"\e038"} .glyphicon-qrcode:before{content:"\e039"} .glyphicon-barcode:before{content:"\e040"} .glyphicon-tag:before{content:"\e041"} .glyphicon-tags:before{content:"\e042"} .glyphicon-book:before{content:"\e043"} .glyphicon-bookmark:before{content:"\e044"} .glyphicon-print:before{content:"\e045"} .glyphicon-camera:before{content:"\e046"} .glyphicon-font:before{content:"\e047"} .glyphicon-bold:before{content:"\e048"} .glyphicon-italic:before{content:"\e049"} .glyphicon-text-height:before{content:"\e050"} .glyphicon-text-width:before{content:"\e051"} .glyphicon-align-left:before{content:"\e052"} .glyphicon-align-center:before{content:"\e053"} .glyphicon-align-right:before{content:"\e054"} .glyphicon-align-justify:before{content:"\e055"} .glyphicon-list:before{content:"\e056"} .glyphicon-indent-left:before{content:"\e057"} .glyphicon-indent-right:before{content:"\e058"} .glyphicon-facetime-video:before{content:"\e059"} .glyphicon-picture:before{content:"\e060"} .glyphicon-map-marker:before{content:"\e062"} .glyphicon-adjust:before{content:"\e063"} .glyphicon-tint:before{content:"\e064"} .glyphicon-edit:before{content:"\e065"} .glyphicon-share:before{content:"\e066"} .glyphicon-check:before{content:"\e067"} .glyphicon-move:before{content:"\e068"} .glyphicon-step-backward:before{content:"\e069"} .glyphicon-fast-backward:before{content:"\e070"} .glyphicon-backward:before{content:"\e071"} .glyphicon-play:before{content:"\e072"} .glyphicon-pause:before{content:"\e073"} .glyphicon-stop:before{content:"\e074"} .glyphicon-forward:before{content:"\e075"} .glyphicon-fast-forward:before{content:"\e076"} .glyphicon-step-forward:before{content:"\e077"} .glyphicon-eject:before{content:"\e078"} .glyphicon-chevron-left:before{content:"\e079"} .glyphicon-chevron-right:before{content:"\e080"} .glyphicon-plus-sign:before{content:"\e081"} .glyphicon-minus-sign:before{content:"\e082"} .glyphicon-remove-sign:before{content:"\e083"} .glyphicon-ok-sign:before{content:"\e084"} .glyphicon-question-sign:before{content:"\e085"} .glyphicon-info-sign:before{content:"\e086"} .glyphicon-screenshot:before{content:"\e087"} .glyphicon-remove-circle:before{content:"\e088"} .glyphicon-ok-circle:before{content:"\e089"} .glyphicon-ban-circle:before{content:"\e090"} .glyphicon-arrow-left:before{content:"\e091"} .glyphicon-arrow-right:before{content:"\e092"} .glyphicon-arrow-up:before{content:"\e093"} .glyphicon-arrow-down:before{content:"\e094"} .glyphicon-share-alt:before{content:"\e095"} .glyphicon-resize-full:before{content:"\e096"} .glyphicon-resize-small:before{content:"\e097"} .glyphicon-exclamation-sign:before{content:"\e101"} .glyphicon-gift:before{content:"\e102"} .glyphicon-leaf:before{content:"\e103"} .glyphicon-fire:before{content:"\e104"} .glyphicon-eye-open:before{content:"\e105"} .glyphicon-eye-close:before{content:"\e106"} .glyphicon-warning-sign:before{content:"\e107"} .glyphicon-plane:before{content:"\e108"} .glyphicon-calendar:before{content:"\e109"} .glyphicon-random:before{content:"\e110"} .glyphicon-comment:before{content:"\e111"} .glyphicon-magnet:before{content:"\e112"} .glyphicon-chevron-up:before{content:"\e113"} .glyphicon-chevron-down:before{content:"\e114"} .glyphicon-retweet:before{content:"\e115"} .glyphicon-shopping-cart:before{content:"\e116"} .glyphicon-folder-close:before{content:"\e117"} .glyphicon-folder-open:before{content:"\e118"} .glyphicon-resize-vertical:before{content:"\e119"} .glyphicon-resize-horizontal:before{content:"\e120"} .glyphicon-hdd:before{content:"\e121"} .glyphicon-bullhorn:before{content:"\e122"} .glyphicon-bell:before{content:"\e123"} .glyphicon-certificate:before{content:"\e124"} .glyphicon-thumbs-up:before{content:"\e125"} .glyphicon-thumbs-down:before{content:"\e126"} .glyphicon-hand-right:before{content:"\e127"} .glyphicon-hand-left:before{content:"\e128"} .glyphicon-hand-up:before{content:"\e129"} .glyphicon-hand-down:before{content:"\e130"} .glyphicon-circle-arrow-right:before{content:"\e131"} .glyphicon-circle-arrow-left:before{content:"\e132"} .glyphicon-circle-arrow-up:before{content:"\e133"} .glyphicon-circle-arrow-down:before{content:"\e134"} .glyphicon-globe:before{content:"\e135"} .glyphicon-wrench:before{content:"\e136"} .glyphicon-tasks:before{content:"\e137"} .glyphicon-filter:before{content:"\e138"} .glyphicon-briefcase:before{content:"\e139"} .glyphicon-fullscreen:before{content:"\e140"} .glyphicon-dashboard:before{content:"\e141"} .glyphicon-paperclip:before{content:"\e142"} .glyphicon-heart-empty:before{content:"\e143"} .glyphicon-link:before{content:"\e144"} .glyphicon-phone:before{content:"\e145"} .glyphicon-pushpin:before{content:"\e146"} .glyphicon-usd:before{content:"\e148"} .glyphicon-gbp:before{content:"\e149"} .glyphicon-sort:before{content:"\e150"} .glyphicon-sort-by-alphabet:before{content:"\e151"} .glyphicon-sort-by-alphabet-alt:before{content:"\e152"} .glyphicon-sort-by-order:before{content:"\e153"} .glyphicon-sort-by-order-alt:before{content:"\e154"} .glyphicon-sort-by-attributes:before{content:"\e155"} .glyphicon-sort-by-attributes-alt:before{content:"\e156"} .glyphicon-unchecked:before{content:"\e157"} .glyphicon-expand:before{content:"\e158"} .glyphicon-collapse-down:before{content:"\e159"} .glyphicon-collapse-up:before{content:"\e160"} .glyphicon-log-in:before{content:"\e161"} .glyphicon-flash:before{content:"\e162"} .glyphicon-log-out:before{content:"\e163"} .glyphicon-new-window:before{content:"\e164"} .glyphicon-record:before{content:"\e165"} .glyphicon-save:before{content:"\e166"} .glyphicon-open:before{content:"\e167"} .glyphicon-saved:before{content:"\e168"} .glyphicon-import:before{content:"\e169"} .glyphicon-export:before{content:"\e170"} .glyphicon-send:before{content:"\e171"} .glyphicon-floppy-disk:before{content:"\e172"} .glyphicon-floppy-saved:before{content:"\e173"} .glyphicon-floppy-remove:before{content:"\e174"} .glyphicon-floppy-save:before{content:"\e175"} .glyphicon-floppy-open:before{content:"\e176"} .glyphicon-credit-card:before{content:"\e177"} .glyphicon-transfer:before{content:"\e178"} .glyphicon-cutlery:before{content:"\e179"} .glyphicon-header:before{content:"\e180"} .glyphicon-compressed:before{content:"\e181"} .glyphicon-earphone:before{content:"\e182"} .glyphicon-phone-alt:before{content:"\e183"} .glyphicon-tower:before{content:"\e184"} .glyphicon-stats:before{content:"\e185"} .glyphicon-sd-video:before{content:"\e186"} .glyphicon-hd-video:before{content:"\e187"} .glyphicon-subtitles:before{content:"\e188"} .glyphicon-sound-stereo:before{content:"\e189"} .glyphicon-sound-dolby:before{content:"\e190"} .glyphicon-sound-5-1:before{content:"\e191"} .glyphicon-sound-6-1:before{content:"\e192"} .glyphicon-sound-7-1:before{content:"\e193"} .glyphicon-copyright-mark:before{content:"\e194"} .glyphicon-registration-mark:before{content:"\e195"} .glyphicon-cloud-download:before{content:"\e197"} .glyphicon-cloud-upload:before{content:"\e198"} .glyphicon-tree-conifer:before{content:"\e199"} .glyphicon-tree-deciduous:before{content:"\e200"} .glyphicon-cd:before{content:"\e201"} .glyphicon-save-file:before{content:"\e202"} .glyphicon-open-file:before{content:"\e203"} .glyphicon-level-up:before{content:"\e204"} .glyphicon-copy:before{content:"\e205"} .glyphicon-paste:before{content:"\e206"} .glyphicon-alert:before{content:"\e209"} .glyphicon-equalizer:before{content:"\e210"} .glyphicon-king:before{content:"\e211"} .glyphicon-queen:before{content:"\e212"} .glyphicon-pawn:before{content:"\e213"} .glyphicon-bishop:before{content:"\e214"} .glyphicon-knight:before{content:"\e215"} .glyphicon-baby-formula:before{content:"\e216"} .glyphicon-tent:before{content:"\26fa"} .glyphicon-blackboard:before{content:"\e218"} .glyphicon-bed:before{content:"\e219"} .glyphicon-apple:before{content:"\f8ff"} .glyphicon-erase:before{content:"\e221"} .glyphicon-hourglass:before{content:"\231b"} .glyphicon-lamp:before{content:"\e223"} .glyphicon-duplicate:before{content:"\e224"} .glyphicon-piggy-bank:before{content:"\e225"} .glyphicon-scissors:before{content:"\e226"} .glyphicon-bitcoin:before{content:"\e227"} .glyphicon-btc:before{content:"\e227"} .glyphicon-xbt:before{content:"\e227"} .glyphicon-yen:before{content:"\00a5"} .glyphicon-jpy:before{content:"\00a5"} .glyphicon-ruble:before{content:"\20bd"} .glyphicon-rub:before{content:"\20bd"} .glyphicon-scale:before{content:"\e230"} .glyphicon-ice-lolly:before{content:"\e231"} .glyphicon-ice-lolly-tasted:before{content:"\e232"} .glyphicon-education:before{content:"\e233"} .glyphicon-option-horizontal:before{content:"\e234"} .glyphicon-option-vertical:before{content:"\e235"} .glyphicon-menu-hamburger:before{content:"\e236"} .glyphicon-modal-window:before{content:"\e237"} .glyphicon-oil:before{content:"\e238"} .glyphicon-grain:before{content:"\e239"} .glyphicon-sunglasses:before{content:"\e240"} .glyphicon-text-size:before{content:"\e241"} .glyphicon-text-color:before{content:"\e242"} .glyphicon-text-background:before{content:"\e243"} .glyphicon-object-align-top:before{content:"\e244"} .glyphicon-object-align-bottom:before{content:"\e245"} .glyphicon-object-align-horizontal:before{content:"\e246"} .glyphicon-object-align-left:before{content:"\e247"} .glyphicon-object-align-vertical:before{content:"\e248"} .glyphicon-object-align-right:before{content:"\e249"} .glyphicon-triangle-right:before{content:"\e250"} .glyphicon-triangle-left:before{content:"\e251"} .glyphicon-triangle-bottom:before{content:"\e252"} .glyphicon-triangle-top:before{content:"\e253"} .glyphicon-console:before{content:"\e254"} .glyphicon-superscript:before{content:"\e255"} .glyphicon-subscript:before{content:"\e256"} .glyphicon-menu-left:before{content:"\e257"} .glyphicon-menu-right:before{content:"\e258"} .glyphicon-menu-down:before{content:"\e259"} .glyphicon-menu-up:before{content:"\e260"} *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} :after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)} body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff} button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit} a{color:#337ab7;text-decoration:none} a:focus,a:hover{color:#23527c;text-decoration:underline} a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} figure{margin:0} img{vertical-align:middle} .carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto} .img-rounded{border-radius:6px} .img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out} .img-circle{border-radius:50%} hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee} .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0} .sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} [role=button]{cursor:pointer} .h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit} .h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777} .h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px} .h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%} .h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px} .h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%} .h1,h1{font-size:36px} .h2,h2{font-size:30px} .h3,h3{font-size:24px} .h4,h4{font-size:18px} .h5,h5{font-size:14px} .h6,h6{font-size:12px} p{margin:0 0 10px} .lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}} .small,small{font-size:85%} .mark,mark{padding:.2em;background-color:#fcf8e3} .text-left{text-align:left} .text-right{text-align:right} .text-center{text-align:center} .text-justify{text-align:justify} .text-nowrap{white-space:nowrap} .text-lowercase{text-transform:lowercase} .text-uppercase{text-transform:uppercase} .text-capitalize{text-transform:capitalize} .text-muted{color:#777} .text-primary{color:#337ab7} a.text-primary:focus,a.text-primary:hover{color:#286090} .text-success{color:#3c763d} a.text-success:focus,a.text-success:hover{color:#2b542c} .text-info{color:#31708f} a.text-info:focus,a.text-info:hover{color:#245269} .text-warning{color:#8a6d3b} a.text-warning:focus,a.text-warning:hover{color:#66512c} .text-danger{color:#a94442} a.text-danger:focus,a.text-danger:hover{color:#843534} .bg-primary{color:#fff;background-color:#337ab7} a.bg-primary:focus,a.bg-primary:hover{background-color:#286090} .bg-success{background-color:#dff0d8} a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3} .bg-info{background-color:#d9edf7} a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee} .bg-warning{background-color:#fcf8e3} a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5} .bg-danger{background-color:#f2dede} a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9} .page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee} ol,ul{margin-top:0;margin-bottom:10px} ol ol,ol ul,ul ol,ul ul{margin-bottom:0} .list-unstyled{padding-left:0;list-style:none} .list-inline{padding-left:0;margin-left:-5px;list-style:none} .list-inline>li{display:inline-block;padding-right:5px;padding-left:5px} dl{margin-top:0;margin-bottom:20px} dd,dt{line-height:1.42857143} dt{font-weight:700} dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}} abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777} .initialism{font-size:90%;text-transform:uppercase} blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee} blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0} blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777} blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'} .blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0} .blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''} .blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'} address{margin-bottom:20px;font-style:normal;line-height:1.42857143} code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace} code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px} kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)} kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none} pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px} pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0} .pre-scrollable{max-height:340px;overflow-y:scroll} .container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}} .container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto} .row{margin-right:-15px;margin-left:-15px} .col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px} .col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left} .col-xs-12{width:100%} .col-xs-11{width:91.66666667%} .col-xs-10{width:83.33333333%} .col-xs-9{width:75%} .col-xs-8{width:66.66666667%} .col-xs-7{width:58.33333333%} .col-xs-6{width:50%} .col-xs-5{width:41.66666667%} .col-xs-4{width:33.33333333%} .col-xs-3{width:25%} .col-xs-2{width:16.66666667%} .col-xs-1{width:8.33333333%} .col-xs-pull-12{right:100%} .col-xs-pull-11{right:91.66666667%} .col-xs-pull-10{right:83.33333333%} .col-xs-pull-9{right:75%} .col-xs-pull-8{right:66.66666667%} .col-xs-pull-7{right:58.33333333%} .col-xs-pull-6{right:50%} .col-xs-pull-5{right:41.66666667%} .col-xs-pull-4{right:33.33333333%} .col-xs-pull-3{right:25%} .col-xs-pull-2{right:16.66666667%} .col-xs-pull-1{right:8.33333333%} .col-xs-pull-0{right:auto} .col-xs-push-12{left:100%} .col-xs-push-11{left:91.66666667%} .col-xs-push-10{left:83.33333333%} .col-xs-push-9{left:75%} .col-xs-push-8{left:66.66666667%} .col-xs-push-7{left:58.33333333%} .col-xs-push-6{left:50%} .col-xs-push-5{left:41.66666667%} .col-xs-push-4{left:33.33333333%} .col-xs-push-3{left:25%} .col-xs-push-2{left:16.66666667%} .col-xs-push-1{left:8.33333333%} .col-xs-push-0{left:auto} .col-xs-offset-12{margin-left:100%} .col-xs-offset-11{margin-left:91.66666667%} .col-xs-offset-10{margin-left:83.33333333%} .col-xs-offset-9{margin-left:75%} .col-xs-offset-8{margin-left:66.66666667%} .col-xs-offset-7{margin-left:58.33333333%} .col-xs-offset-6{margin-left:50%} .col-xs-offset-5{margin-left:41.66666667%} .col-xs-offset-4{margin-left:33.33333333%} .col-xs-offset-3{margin-left:25%} .col-xs-offset-2{margin-left:16.66666667%} .col-xs-offset-1{margin-left:8.33333333%} .col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}} table{background-color:transparent} caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left} th{text-align:left} .table{width:100%;max-width:100%;margin-bottom:20px} .table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd} .table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd} .table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0} .table>tbody+tbody{border-top:2px solid #ddd} .table .table{background-color:#fff} .table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px} .table-bordered{border:1px solid #ddd} .table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd} .table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px} .table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9} .table-hover>tbody>tr:hover{background-color:#f5f5f5} table col[class*=col-]{position:static;display:table-column;float:none} table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none} .table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5} .table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8} .table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8} .table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6} .table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7} .table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3} .table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3} .table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc} .table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede} .table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc} .table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}} fieldset{min-width:0;padding:0;margin:0;border:0} legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5} label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700} input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal} input[type=file]{display:block} input[type=range]{display:block;width:100%} select[multiple],select[size]{height:auto} input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555} .form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s} .form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)} .form-control::-moz-placeholder{color:#999;opacity:1} .form-control:-ms-input-placeholder{color:#999} .form-control::-webkit-input-placeholder{color:#999} .form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1} .form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed} textarea.form-control{height:auto} input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}} .form-group{margin-bottom:15px} .checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px} .checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer} .checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px} .checkbox+.checkbox,.radio+.radio{margin-top:-5px} .checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer} .checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px} fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed} .checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed} .checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed} .form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0} .form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0} .input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} select.input-sm{height:30px;line-height:30px} select[multiple].input-sm,textarea.input-sm{height:auto} .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} .form-group-sm select.form-control{height:30px;line-height:30px} .form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto} .form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5} .input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} select.input-lg{height:46px;line-height:46px} select[multiple].input-lg,textarea.input-lg{height:auto} .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} .form-group-lg select.form-control{height:46px;line-height:46px} .form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto} .form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333} .has-feedback{position:relative} .has-feedback .form-control{padding-right:42.5px} .form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none} .form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px} .form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px} .has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d} .has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} .has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168} .has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d} .has-success .form-control-feedback{color:#3c763d} .has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b} .has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} .has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b} .has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b} .has-warning .form-control-feedback{color:#8a6d3b} .has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442} .has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} .has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483} .has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442} .has-error .form-control-feedback{color:#a94442} .has-feedback label~.form-control-feedback{top:25px} .has-feedback label.sr-only~.form-control-feedback{top:0} .help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}} .form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0} .form-horizontal .checkbox,.form-horizontal .radio{min-height:27px} .form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}} .form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}} .btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px} .btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} .btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none} .btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} .btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65} a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none} .btn-default{color:#333;background-color:#fff;border-color:#ccc} .btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c} .btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad} .btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad} .btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c} .btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none} .btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc} .btn-default .badge{color:#fff;background-color:#333} .btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4} .btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40} .btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74} .btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74} .btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40} .btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none} .btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4} .btn-primary .badge{color:#337ab7;background-color:#fff} .btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c} .btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625} .btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439} .btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439} .btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625} .btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none} .btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c} .btn-success .badge{color:#5cb85c;background-color:#fff} .btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da} .btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85} .btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc} .btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc} .btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85} .btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none} .btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da} .btn-info .badge{color:#5bc0de;background-color:#fff} .btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236} .btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d} .btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512} .btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512} .btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d} .btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none} .btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236} .btn-warning .badge{color:#f0ad4e;background-color:#fff} .btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a} .btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19} .btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925} .btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925} .btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19} .btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none} .btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a} .btn-danger .badge{color:#d9534f;background-color:#fff} .btn-link{font-weight:400;color:#337ab7;border-radius:0} .btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none} .btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent} .btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent} .btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none} .btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} .btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} .btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px} .btn-block{display:block;width:100%} .btn-block+.btn-block{margin-top:5px} input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%} .fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear} .fade.in{opacity:1} .collapse{display:none} .collapse.in{display:block} tr.collapse.in{display:table-row} tbody.collapse.in{display:table-row-group} .collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility} .caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent} .dropdown,.dropup{position:relative} .dropdown-toggle:focus{outline:0} .dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)} .dropdown-menu.pull-right{right:0;left:auto} .dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} .dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap} .dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5} .dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0} .dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777} .dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)} .open>.dropdown-menu{display:block} .open>a{outline:0} .dropdown-menu-right{right:0;left:auto} .dropdown-menu-left{right:auto;left:0} .dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap} .dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990} .pull-right>.dropdown-menu{right:0;left:auto} .dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9} .dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}} .btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle} .btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left} .btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2} .btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px} .btn-toolbar{margin-left:-5px} .btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left} .btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px} .btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0} .btn-group>.btn:first-child{margin-left:0} .btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0} .btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0} .btn-group>.btn-group{float:left} .btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0} .btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0} .btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0} .btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0} .btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px} .btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px} .btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} .btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none} .btn .caret{margin-left:0} .btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0} .dropup .btn-lg .caret{border-width:0 5px 5px} .btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%} .btn-group-vertical>.btn-group>.btn{float:none} .btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0} .btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0} .btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0} .btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px} .btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0} .btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0} .btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0} .btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate} .btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%} .btn-group-justified>.btn-group .btn{width:100%} .btn-group-justified>.btn-group .dropdown-menu{left:auto} [data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none} .input-group{position:relative;display:table;border-collapse:separate} .input-group[class*=col-]{float:none;padding-right:0;padding-left:0} .input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0} .input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px} select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto} .input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px} select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto} .input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell} .input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0} .input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle} .input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px} .input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px} .input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px} .input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0} .input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0} .input-group-addon:first-child{border-right:0} .input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0} .input-group-addon:last-child{border-left:0} .input-group-btn{position:relative;font-size:0;white-space:nowrap} .input-group-btn>.btn{position:relative} .input-group-btn>.btn+.btn{margin-left:-1px} .input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2} .input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px} .input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px} .nav{padding-left:0;margin-bottom:0;list-style:none} .nav>li{position:relative;display:block} .nav>li>a{position:relative;display:block;padding:10px 15px} .nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee} .nav>li.disabled>a{color:#777} .nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent} .nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7} .nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} .nav>li>a>img{max-width:none} .nav-tabs{border-bottom:1px solid #ddd} .nav-tabs>li{float:left;margin-bottom:-1px} .nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0} .nav-tabs>li>a:hover{border-color:#eee #eee #ddd} .nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent} .nav-tabs.nav-justified{width:100%;border-bottom:0} .nav-tabs.nav-justified>li{float:none} .nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center} .nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}} .nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px} .nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}} .nav-pills>li{float:left} .nav-pills>li>a{border-radius:4px} .nav-pills>li+li{margin-left:2px} .nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7} .nav-stacked>li{float:none} .nav-stacked>li+li{margin-top:2px;margin-left:0} .nav-justified{width:100%} .nav-justified>li{float:none} .nav-justified>li>a{margin-bottom:5px;text-align:center} .nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}} .nav-tabs-justified{border-bottom:0} .nav-tabs-justified>li>a{margin-right:0;border-radius:4px} .nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}} .tab-content>.tab-pane{display:none} .tab-content>.active{display:block} .nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0} .navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}} .navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)} .navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}} .navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}} .container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}} .navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}} .navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}} .navbar-fixed-top{top:0;border-width:0 0 1px} .navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0} .navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px} .navbar-brand:focus,.navbar-brand:hover{text-decoration:none} .navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}} .navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px} .navbar-toggle:focus{outline:0} .navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px} .navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}} .navbar-nav{margin:7.5px -15px} .navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}} .navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}} .navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0} .navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0} .navbar-btn{margin-top:8px;margin-bottom:8px} .navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px} .navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px} .navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}} .navbar-default{background-color:#f8f8f8;border-color:#e7e7e7} .navbar-default .navbar-brand{color:#777} .navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent} .navbar-default .navbar-text{color:#777} .navbar-default .navbar-nav>li>a{color:#777} .navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent} .navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7} .navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent} .navbar-default .navbar-toggle{border-color:#ddd} .navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd} .navbar-default .navbar-toggle .icon-bar{background-color:#888} .navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7} .navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}} .navbar-default .navbar-link{color:#777} .navbar-default .navbar-link:hover{color:#333} .navbar-default .btn-link{color:#777} .navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333} .navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc} .navbar-inverse{background-color:#222;border-color:#080808} .navbar-inverse .navbar-brand{color:#9d9d9d} .navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent} .navbar-inverse .navbar-text{color:#9d9d9d} .navbar-inverse .navbar-nav>li>a{color:#9d9d9d} .navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent} .navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808} .navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent} .navbar-inverse .navbar-toggle{border-color:#333} .navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333} .navbar-inverse .navbar-toggle .icon-bar{background-color:#fff} .navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010} .navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}} .navbar-inverse .navbar-link{color:#9d9d9d} .navbar-inverse .navbar-link:hover{color:#fff} .navbar-inverse .btn-link{color:#9d9d9d} .navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff} .navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444} .breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px} .breadcrumb>li{display:inline-block} .breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"} .breadcrumb>.active{color:#777} .pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px} .pagination>li{display:inline} .pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd} .pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px} .pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px} .pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd} .pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7} .pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd} .pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333} .pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px} .pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px} .pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5} .pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px} .pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px} .pager{padding-left:0;margin:20px 0;text-align:center;list-style:none} .pager li{display:inline} .pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px} .pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee} .pager .next>a,.pager .next>span{float:right} .pager .previous>a,.pager .previous>span{float:left} .pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff} .label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em} a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer} .label:empty{display:none} .btn .label{position:relative;top:-1px} .label-default{background-color:#777} .label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e} .label-primary{background-color:#337ab7} .label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090} .label-success{background-color:#5cb85c} .label-success[href]:focus,.label-success[href]:hover{background-color:#449d44} .label-info{background-color:#5bc0de} .label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5} .label-warning{background-color:#f0ad4e} .label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f} .label-danger{background-color:#d9534f} .label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c} .badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px} .badge:empty{display:none} .btn .badge{position:relative;top:-1px} .btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px} a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer} .list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff} .list-group-item>.badge{float:right} .list-group-item>.badge+.badge{margin-right:5px} .nav-pills>li>a>.badge{margin-left:3px} .jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee} .jumbotron .h1,.jumbotron h1{color:inherit} .jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200} .jumbotron>hr{border-top-color:#d5d5d5} .container .jumbotron,.container-fluid .jumbotron{border-radius:6px} .jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}} .thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out} .thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto} a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7} .thumbnail .caption{padding:9px;color:#333} .alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px} .alert h4{margin-top:0;color:inherit} .alert .alert-link{font-weight:700} .alert>p,.alert>ul{margin-bottom:0} .alert>p+p{margin-top:5px} .alert-dismissable,.alert-dismissible{padding-right:35px} .alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit} .alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6} .alert-success hr{border-top-color:#c9e2b3} .alert-success .alert-link{color:#2b542c} .alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1} .alert-info hr{border-top-color:#a6e1ec} .alert-info .alert-link{color:#245269} .alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} .alert-warning hr{border-top-color:#f7e1b5} .alert-warning .alert-link{color:#66512c} .alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1} .alert-danger hr{border-top-color:#e4b9c0} .alert-danger .alert-link{color:#843534} @-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}} @-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}} @keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}} .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)} .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease} .progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px} .progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite} .progress-bar-success{background-color:#5cb85c} .progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .progress-bar-info{background-color:#5bc0de} .progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .progress-bar-warning{background-color:#f0ad4e} .progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .progress-bar-danger{background-color:#d9534f} .progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} .media{margin-top:15px} .media:first-child{margin-top:0} .media,.media-body{overflow:hidden;zoom:1} .media-body{width:10000px} .media-object{display:block} .media-object.img-thumbnail{max-width:none} .media-right,.media>.pull-right{padding-left:10px} .media-left,.media>.pull-left{padding-right:10px} .media-body,.media-left,.media-right{display:table-cell;vertical-align:top} .media-middle{vertical-align:middle} .media-bottom{vertical-align:bottom} .media-heading{margin-top:0;margin-bottom:5px} .media-list{padding-left:0;list-style:none} .list-group{padding-left:0;margin-bottom:20px} .list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd} .list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px} .list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px} a.list-group-item,button.list-group-item{color:#555} a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333} a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5} button.list-group-item{width:100%;text-align:left} .list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee} .list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit} .list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777} .list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7} .list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit} .list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef} .list-group-item-success{color:#3c763d;background-color:#dff0d8} a.list-group-item-success,button.list-group-item-success{color:#3c763d} a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit} a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6} a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d} .list-group-item-info{color:#31708f;background-color:#d9edf7} a.list-group-item-info,button.list-group-item-info{color:#31708f} a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit} a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3} a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f} .list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3} a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b} a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit} a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc} a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b} .list-group-item-danger{color:#a94442;background-color:#f2dede} a.list-group-item-danger,button.list-group-item-danger{color:#a94442} a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit} a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc} a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442} .list-group-item-heading{margin-top:0;margin-bottom:5px} .list-group-item-text{margin-bottom:0;line-height:1.3} .panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)} .panel-body{padding:15px} .panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px} .panel-heading>.dropdown .dropdown-toggle{color:inherit} .panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit} .panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit} .panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0} .panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0} .panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px} .panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0} .panel-heading+.list-group .list-group-item:first-child{border-top-width:0} .list-group+.panel-footer{border-top-width:0} .panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0} .panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px} .panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px} .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px} .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px} .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px} .panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px} .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px} .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px} .panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd} .panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0} .panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0} .panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} .panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} .panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0} .panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0} .panel>.table-responsive{margin-bottom:0;border:0} .panel-group{margin-bottom:20px} .panel-group .panel{margin-bottom:0;border-radius:4px} .panel-group .panel+.panel{margin-top:5px} .panel-group .panel-heading{border-bottom:0} .panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd} .panel-group .panel-footer{border-top:0} .panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd} .panel-default{border-color:#ddd} .panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd} .panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd} .panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333} .panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd} .panel-primary{border-color:#337ab7} .panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7} .panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7} .panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff} .panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7} .panel-success{border-color:#d6e9c6} .panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6} .panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6} .panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d} .panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6} .panel-info{border-color:#bce8f1} .panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1} .panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1} .panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f} .panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1} .panel-warning{border-color:#faebcc} .panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} .panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc} .panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b} .panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc} .panel-danger{border-color:#ebccd1} .panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1} .panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1} .panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442} .panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1} .embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden} .embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0} .embed-responsive-16by9{padding-bottom:56.25%} .embed-responsive-4by3{padding-bottom:75%} .well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)} .well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)} .well-lg{padding:24px;border-radius:6px} .well-sm{padding:9px;border-radius:3px} .close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2} .close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5} button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0} .modal-open{overflow:hidden} .modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0} .modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)} .modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)} .modal-open .modal{overflow-x:hidden;overflow-y:auto} .modal-dialog{position:relative;width:auto;margin:10px} .modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)} .modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000} .modal-backdrop.fade{filter:alpha(opacity=0);opacity:0} .modal-backdrop.in{filter:alpha(opacity=50);opacity:.5} .modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5} .modal-header .close{margin-top:-2px} .modal-title{margin:0;line-height:1.42857143} .modal-body{position:relative;padding:15px} .modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5} .modal-footer .btn+.btn{margin-bottom:0;margin-left:5px} .modal-footer .btn-group .btn+.btn{margin-left:-1px} .modal-footer .btn-block+.btn-block{margin-left:0} .modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}} .tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto} .tooltip.in{filter:alpha(opacity=90);opacity:.9} .tooltip.top{padding:5px 0;margin-top:-3px} .tooltip.right{padding:0 5px;margin-left:3px} .tooltip.bottom{padding:5px 0;margin-top:3px} .tooltip.left{padding:0 5px;margin-left:-3px} .tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px} .tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid} .tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000} .tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000} .tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000} .tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000} .tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000} .tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000} .tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000} .tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000} .popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto} .popover.top{margin-top:-10px} .popover.right{margin-left:10px} .popover.bottom{margin-top:10px} .popover.left{margin-left:-10px} .popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0} .popover-content{padding:9px 14px} .popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid} .popover>.arrow{border-width:11px} .popover>.arrow:after{content:"";border-width:10px} .popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0} .popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0} .popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0} .popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0} .popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)} .popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff} .popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)} .popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff} .carousel{position:relative} .carousel-inner{position:relative;width:100%;overflow:hidden} .carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left} .carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}} .carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block} .carousel-inner>.active{left:0} .carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%} .carousel-inner>.next{left:100%} .carousel-inner>.prev{left:-100%} .carousel-inner>.next.left,.carousel-inner>.prev.right{left:0} .carousel-inner>.active.left{left:-100%} .carousel-inner>.active.right{left:100%} .carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5} .carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x} .carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x} .carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9} .carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px} .carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px} .carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px} .carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1} .carousel-control .icon-prev:before{content:'\2039'} .carousel-control .icon-next:before{content:'\203a'} .carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none} .carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px} .carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff} .carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)} .carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}} .btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "} .btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both} .center-block{display:block;margin-right:auto;margin-left:auto} .pull-right{float:right!important} .pull-left{float:left!important} .hide{display:none!important} .show{display:block!important} .invisible{visibility:hidden} .text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0} .hidden{display:none!important} .affix{position:fixed}@-ms-viewport{width:device-width} .visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important} .visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}} .visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}} .visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}} .visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}} .visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}
\ No newline at end of file
diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.eot b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64
GIT binary patch
literal 20127
zcma%hV{j!vx9y2-`@~L8?1^pLwlPU2wr$&<*tR|KBoo`2;LUg6eW-eW-tKDb)vH%`
z^`A!Vd<6hNSRMcX|Cb;E|1qflDggj6Kmr)xA10^t-vIc3*Z+F{r%|K(GyE^?|I{=9
zNq`(c8=wS`0!RZy0g3{M(8^tv41d}oRU?8#IBFtJy*9zAN5dcxqGlMZGL>GG%R#)4J
zDJ2;)4*E1pyHia%>lMv3X7Q`UoFyoB@|xvh^)kOE3)IL&0(G&i;g08s>c%~pHkN&6
z($7!kyv|A2DsV2mq-5Ku)D#$Kn$CzqD-wm5Q*OtEOEZe^&T$xIb0NUL}$)W)Ck`6oter6KcQG9Zcy>lXip)%e&!lQgtQ*N`#abOlytt!&i3fo)cKV
zP0BWmLxS1gQv(r_r|?9>rR0ZeEJPx;Vi|h1!Eo*dohr&^lJgqJZns>&vexP@fs
zkPv93Nyw$-kM5Mw^{@wPU47Y1dSkiHyl3dtHLwV&6Tm1iv{ve;sYA}Z&kmH802s9Z
zyJEn+cfl7yFu#1^#DbtP7k&aR06|n{LnYFYEphKd@dJEq@)s#S)UA&8VJY@S2+{~>
z(4?M();zvayyd^j`@4>xCqH|Au>Sfzb$mEOcD7e4z8pPVRTiMUWiw;|gXHw7LS#U<
zsT(}Z5SJ)CRMXloh$qPnK77w_)ctHmgh}QAe<2S{DU^`!uwptCoq!Owz$u6bF)vnb
zL`bM$%>baN7l#)vtS3y6h*2?xCk
z>w+s)@`O4(4_I{L-!+b%)NZcQ&ND=2lyP+xI#9OzsiY8$c)ys-MI?TG6
zEP6f=vuLo!G>J7F4v|s#lJ+7A`^nEQScH3e?B_jC&{sj>m
zYD?!1z4nDG_Afi$!J(<{>z{~Q)$SaXWjj~%ZvF152Hd^VoG14rFykR=_TO)mCn&K$
z-TfZ!vMBvnToyBoKRkD{3=&=qD|L!vb#jf1f}2338z)e)g>7#NPe!FoaY*jY{f)Bf>ohk-K
z4{>fVS}ZCicCqgLuYR_fYx2;*-4k>kffuywghn?15s1dIOOYfl+XLf5w?wtU2Og*f
z%X5x`H55F6g1>m~%F`655-W1wFJtY>>qNSdVT`M`1Mlh!5Q6#3j={n5#za;!X&^OJ
zgq;d4UJV-F>gg?c3Y?d=kvn3eV)Jb^
zO5vg0G0yN0%}xy#(6oTDSVw8l=_*2k;zTP?+N=*18H5wp`s90K-C67q{W3d8vQGmr
zhpW^>1HEQV2TG#8_P_0q91h8QgHT~8=-Ij5snJ3cj?Jn5_66uV=*pq(j}yHnf$Ft;5VVC?bz%9X31asJeQF2jEa47H#j`
zk&uxf3t?g!tltVP|B#G_UfDD}`<#B#iY^i>oDd-LGF}A@Fno~dR72c&hs6bR
z2F}9(i8+PR%R|~FV$;Ke^Q_E_Bc;$)xN4Ti>Lgg4vaip!%M
z06oxAF_*)LH57w|gCW3SwoEHwjO{}}U=pKhjKSZ{u!K?1zm1q?
zXyA6y@)}_sONiJopF}_}(~}d4FDyp|(@w}Vb;Fl5bZL%{1`}gdw#i{KMjp2@Fb9pg
ziO|u7qP{$kxH$qh8%L+)AvwZNgUT6^zsZq-MRyZid{D?t`f|KzSAD~C?WT3d0rO`0
z=qQ6{)&UXXuHY{9g|P7l_nd-%eh}4%VVaK#Nik*tOu9lBM$<%FS@`NwGEbP0&;Xbo
zObCq=y%a`jSJmx_uTLa{@2@}^&F4c%z6oe-TN&idjv+8E|$FHOvBqg5hT
zMB=7SHq`_-E?5g=()*!V>rIa&LcX(RU}aLm*38U_V$C_g4)7GrW5$GnvTwJZdBmy6
z*X)wi3=R8L=esOhY0a&eH`^fSpUHV8h$J1|o^3fKO|9QzaiKu>yZ9wmRkW?HTkc<*v7i*ylJ#u#j
zD1-n&{B`04oG>0Jn{5PKP*4Qsz{~`VVA3578gA+JUkiPc$Iq!^K|}*p_z3(-c&5z@
zKxmdNpp2&wg&%xL3xZNzG-5Xt7jnI@{?c
z25=M>-VF|;an2Os$Nn%HgQz7m(ujC}Ii0Oesa(y#8>D+P*_m^X##E|h$M6tJr%#=P
zWP*)Px>7z`E~U^2LNCNiy%Z7!!6RI%6fF@#ZY3z`CK91}^J$F!EB0YF1je9hJKU7!S5MnXV{+#K;y
zF~s*H%p@vj&-ru7#(F2L+_;IH46X(z{~HTfcThqD%b{>~u@lSc<+f5#xgt9L7$gSK
ziDJ6D*R%4&YeUB@yu@4+&70MBNTnjRyqMRd+@&lU#rV%0t3OmouhC`mkN}pL>tXin
zY*p)mt=}$EGT2E<4Q>E2`6)gZ`QJhGDNpI}bZL9}m+R>q?l`OzFjW?)Y)P`fUH(_4
zCb?sm1=DD0+Q5v}BW#0n5;Nm(@RTEa3(Y17H2H67La+>ptQHJ@WMy2xRQT$|7l`8c
zYHCxYw2o-rI?(fR2-%}pbs$I%w_&LPYE{4bo}vRoAW>3!SY_zH3`ofx3F1PsQ?&iq
z*BRG>?<6%z=x#`NhlEq{K~&rU7Kc7Y-90aRnoj~rVoKae)L$3^z*Utppk?I`)CX&&
zZ^@Go9fm&fN`b`XY
zt0xE5aw4t@qTg_k=!-5LXU+_~DlW?53!afv6W(k@FPPX-`nA!FBMp7b!ODbL1zh58
z*69I}P_-?qSLKj}JW7gP!la}K@M}L>v?rDD!DY-tu+onu9kLoJz20M4urX_xf2dfZ
zORd9Zp&28_ff=wdMpXi%IiTTNegC}~RLkdYjA39kWqlA?jO~o1`*B&85Hd%VPkYZT
z48MPe62;TOq#c%H(`wX5(Bu>nlh4Fbd*Npasdhh?oRy8a;NB2(eb}6DgwXtx=n}fE
zx67rYw=(s0r?EsPjaya}^Qc-_UT5|*@|$Q}*|>V3O~USkIe6a0_>vd~6kHuP8=m}_
zo2IGKbv;yA+TBtlCpnw)8hDn&eq?26gN$Bh;SdxaS04Fsaih_Cfb98s39xbv)=mS0
z6M<@pM2#pe32w*lYSWG>DYqB95XhgAA)*9dOxHr{t)er0Xugoy)!Vz#2C3FaUMzYl
zCxy{igFB901*R2*F4>grPF}+G`;Yh
zGi@nRjWyG3mR(BVOeBPOF=_&}2IWT%)pqdNAcL{eP`L*^FDv#Rzql5U&Suq_X%JfR_lC!S|y|xd5mQ0{0!G#9hV46S~A`
z0B!{yI-4FZEtol5)mNWXcX(`x&Pc*&gh4k{w%0S#EI>rqqlH2xv7mR=9XNCI$V#NG
z4wb-@u{PfQP;tTbzK>(DF(~bKp3;L1-A*HS!VB)Ae>Acnvde15Anb`h;I&0)aZBS6
z55ZS7mL5Wp!LCt45^{2_70YiI_Py=X{I3>$Px5Ez0ahLQ+
z9EWUWSyzA|+g-Axp*Lx-M{!ReQO07EG7r4^)K(xbj@%ZU=0tBC5shl)1a!ifM5OkF
z0w2xQ-<+r-h1fi7B6waX15|*GGqfva)S)dVcgea`lQ~SQ$KXPR+(3Tn2I2R<0
z9tK`L*pa^+*n%>tZPiqt{_`%v?Bb7CR-!GhMON_Fbs0$#|H}G?rW|{q5fQhvw!FxI
zs-5ZK>hAbnCS#ZQVi5K0X3PjL1JRdQO+&)*!oRCqB{wen60P6!7bGiWn@vD|+E@Xq
zb!!_WiU^I|@1M}Hz6fN-m04x=>Exm{b@>UCW|c8vC`aNbtA@KCHujh^2RWZC}iYhL^<*Z93chIBJYU&w>$CGZDRcHuIgF&oyesDZ#&mA;?wxx4Cm#c0V$xYG?9OL(Smh}#fFuX(K;otJmvRP{h
ze^f-qv;)HKC7geB92_@3a9@MGijS(hNNVd%-rZ;%@F_f7?Fjinbe1(
zn#jQ*jKZTqE+AUTEd3y6t>*=;AO##cmdwU4gc2&rT8l`rtKW2JF<`_M#p>cj+)yCG
zgKF)y8jrfxTjGO&ccm8RU>qn|HxQ7Z#sUo$q)P5H%8iBF$({0Ya51-rA@!It#NHN8MxqK
zrYyl_&=}WVfQ?+ykV4*@F6)=u_~3BebR2G2>>mKaEBPmSW3(qYGGXj??m3L
zHec{@jWCsSD8`xUy0pqT?Sw0oD?AUK*WxZn#D>-$`eI+IT)6ki>ic}W)t$V32^ITD
zR497@LO}S|re%A+#vdv-?fXsQGVnP?QB_d0cGE+U84Q=aM=XrOwGFN3`Lpl@P0fL$
zKN1PqOwojH*($uaQFh8_)H#>Acl&UBSZ>!2W1Dinei`R4dJGX$;~60X=|SG6#jci}
z&t4*dVDR*;+6Y(G{KGj1B2!qjvDYOyPC}%hnPbJ@g(4yBJrViG1#$$X75y+Ul1{%x
zBAuD}Q@w?MFNqF-m39FGpq7RGI?%Bvyyig&oGv)lR>d<`Bqh=p>urib5DE;u$c|$J
zwim~nPb19t?LJZsm{<(Iyyt@~H!a4yywmHKW&=1r5+oj*Fx6c89heW@(2R`i!Uiy*
zp)=`Vr8sR!)KChE-6SEIyi(dvG3<1KoVt>kGV=zZiG7LGonH1+~yOK-`g0)r#+O|Q>)a`I2FVW%wr3lhO(P{ksNQuR!G_d
zeTx(M!%brW_vS9?IF>bzZ2A3mWX-MEaOk^V|4d38{1D|KOlZSjBKrj7Fgf^>JyL0k
zLoI$adZJ0T+8i_Idsuj}C;6jgx9LY#Ukh;!8eJ^B1N}q=Gn4onF*a2vY7~`x$r@rJ
z`*hi&Z2lazgu{&nz>gjd>#eq*IFlXed(%$s5!HRXKNm
zDZld+DwDI`O6hyn2uJ)F^{^;ESf9sjJ)wMSKD~R=DqPBHyP!?cGAvL<1|7K-(=?VO
zGcKcF1spUa+ki<`6K#@QxOTsd847N8WSWztG~?~
z!gUJn>z0O=_)VCE|56hkT~n5xXTp}Ucx$Ii%bQ{5;-a4~I2e|{l9ur#*ghd*hSqO=
z)GD@ev^w&5%k}YYB~!A%3*XbPPU-N6&3Lp1LxyP@|C<{qcn&?l54+zyMk&I3YDT|E
z{lXH-e?C{huu<@~li+73lMOk&k)3s7Asn$t6!PtXJV!RkA`qdo4|OC_a?vR!kE_}k
zK5R9KB%V@R7gt@9=TGL{=#r2gl!@3G;k-6sXp&E4u20DgvbY$iE**Xqj3TyxK>3AU
z!b9}NXuINqt>Htt6fXIy5mj7oZ{A&$XJ&thR5ySE{mkxq_YooME#VCHm2+3D!f`{)
zvR^WSjy_h4v^|!RJV-RaIT2Ctv=)UMMn@fAgjQV$2G+4?&dGA8vK35c-8r)z9Qqa=%k(FU)?iec14<^olkOU3p
zF-6`zHiDKPafKK^USUU+D01>C&Wh{{q?>5m
zGQp|z*+#>IIo=|ae8CtrN@@t~uLFOeT{}vX(IY*;>wAU=u1Qo4c+a&R);$^VCr>;!
zv4L{`lHgc9$BeM)pQ#XA_(Q#=_iSZL4>L~8Hx}NmOC$&*Q*bq|9Aq}rWgFnMDl~d*;7c44GipcpH9PWaBy-G$*MI^F0
z?Tdxir1D<2ui+Q#^c4?uKvq=p>)lq56=Eb|N^qz~w7rsZu)@E4$;~snz+wIxi+980O6M#RmtgLYh@|2}9BiHSpTs
zacjGKvwkUwR3lwTSsCHlwb&*(onU;)$yvdhikonn|B44JMgs*&Lo!jn`6AE>XvBiO
z*LKNX3FVz9yLcsnmL!cRVO_qv=yIM#X|u&}#f%_?Tj0>8)8P_0r0!AjWNw;S44tst
zv+NXY1{zRLf9OYMr6H-z?4CF$Y%MdbpFIN@a-LEnmkcOF>h16cH_;A|e)pJTuCJ4O
zY7!4FxT4>4aFT8a92}84>q0&?46h>&0Vv0p>u~k&qd5$C1A6Q$I4V(5X~6{15;PD@
ze6!s9xh#^QI`J+%8*=^(-!P!@9%~buBmN2VSAp@TOo6}C?az+ALP8~&a0FWZk*F5N
z^8P8IREnN`N0i@>O0?{i-FoFShYbUB`D7O4HB`Im2{yzXmyrg$k>cY6A@>bf7i3n0
z5y&cf2#`zctT>dz+hNF&+d3g;2)U!#vsb-%LC+pqKRTiiSn#FH#e!bVwR1nAf*TG^
z!RKcCy$P>?Sfq6n<%M{T0I8?p@HlgwC!HoWO>~mT+X<{Ylm+$Vtj9};H3$EB}P2wR$3y!TO#$iY8eO-!}+F&jMu4%E6S>m
zB(N4w9O@2=<`WNJay5PwP8javDp~o~xkSbd4t4t8)9jqu@bHmJHq=MV~Pt|(TghCA}fhMS?s-{klV>~=VrT$nsp7mf{?cze~KKOD4
z_1Y!F)*7^W+BBTt1R2h4f1X4Oy2%?=IMhZU8c{qk3xI1=!na*Sg<=A$?K=Y=GUR9@
zQ(ylIm4Lgm>pt#%p`zHxok%vx_=8Fap1|?OM02|N%X-g5_#S~sT@A!x&8k#wVI2lo
z1Uyj{tDQRpb*>c}mjU^gYA9{7mNhFAlM=wZkXcA#MHXWMEs^3>p9X)Oa?dx7b%N*y
zLz@K^%1JaArjgri;8ptNHwz1<0y8tcURSbHsm=26^@CYJ3hwMaEvC7
z3Wi-@AaXIQ)%F6#i@%M>?Mw7$6(kW@?et@wbk-APcvMCC{>iew#vkZej8%9h0JSc?
zCb~K|!9cBU+))^q*co(E^9jRl7gR4Jihyqa(Z(P&ID#TPyysVNL7(^;?Gan!OU>au
zN}miBc&XX-M$mSv%3xs)bh>Jq9#aD_l|zO?I+p4_5qI0Ms*OZyyxA`sXcyiy>-{YN
zA70%HmibZYcHW&YOHk6S&PQ+$rJ3(utuUra3V0~@=_~QZy&nc~)AS>v&<6$gErZC3
zcbC=eVkV4Vu0#}E*r=&{X)Kgq|8MGCh(wsH4geLj@#8EGYa})K2;n
z{1~=ghoz=9TSCxgzr5x3@sQZZ0FZ+t{?klSI_IZa16pSx6*;=O%n!uXVZ@1IL;JEV
zfOS&yyfE9dtS*^jmgt6>jQDOIJM5Gx#Y2eAcC3l^lmoJ{o0T>IHpECTbfYgPI4#LZq0PKqnPCD}_
zyKxz;(`fE0z~nA1s?d{X2!#ZP8wUHzFSOoTWQrk%;wCnBV_3D%3@EC|u$Ao)tO|AO
z$4&aa!wbf}rbNcP{6=ajgg(`p5kTeu$ji20`zw)X1SH*x
zN?T36{d9TY*S896Ijc^!35LLUByY4QO=ARCQ#MMCjudFc7s!z%P$6DESz%zZ#>H|i
zw3Mc@v4~{Eke;FWs`5i@ifeYPh-Sb#vCa#qJPL|&quSKF%sp8*n#t?vIE7kFWjNFh
zJC@u^bRQ^?ra|%39Ux^Dn4I}QICyDKF0mpe+Bk}!lFlqS^WpYm&xwIYxUoS-rJ)N9
z1Tz*6Rl9;x`4lwS1cgW^H_M*)Dt*DX*W?ArBf?-t|1~ge&S}xM0K;U9Ibf{okZHf~
z#4v4qc6s6Zgm8iKch5VMbQc~_V-ZviirnKCi*ouN^c_2lo&-M;YSA>W>>^5tlXObg
zacX$k0=9Tf$Eg+#9k6yV(R5-&F{=DHP8!yvSQ`Y~XRnUx@{O$-bGCksk~3&qH^dqX
zkf+ZZ?Nv5u>LBM@2?k%k&_aUb5Xjqf#!&7%zN#VZwmv65ezo^Y4S#(ed0yUn4tFOB
zh1f1SJ6_s?a{)u6VdwUC!Hv=8`%T9(^c`2hc9nt$(q{Dm2X)dK49ba+KEheQ;7^0)
ziFKw$%EHy_B1)M>=yK^=Z$U-LT36yX>EKT
zvD8IAom2&2?bTmX@_PBR4W|p?6?LQ+&UMzXxqHC5VHzf@Eb1u)kwyfy+NOM8Wa2y@
zNNDL0PE$F;yFyf^jy&RGwDXQwYw6yz>OMWvJt98X@;yr!*RQDBE-
zE*l*u=($Zi1}0-Y4lGaK?J$yQjgb+*ljUvNQ!;QYAoCq@>70=sJ{o{^21^?zT@r~hhf&O;Qiq+
ziGQQLG*D@5;LZ%09mwMiE4Q{IPUx-emo*;a6#DrmWr(zY27d@ezre)Z1BGZdo&pXn
z+);gOFelKDmnjq#8dL7CTiVH)dHOqWi~uE|NM^QI3EqxE6+_n>IW67~UB#J==QOGF
zp_S)c8TJ}uiaEiaER}MyB(grNn=2m&0yztA=!%3xUREyuG_jmadN*D&1nxvjZ6^+2
zORi7iX1iPi$tKasppaR9$a3IUmrrX)m*)fg1>H+$KpqeB*G>AQV((-G{}h=qItj|d
zz~{5@{?&Dab6;0c7!!%Se>w($RmlG7Jlv_zV3Ru8b2rugY0MVPOOYGlokI7%nhIy&
z-B&wE=lh2dtD!F?noD{z^O1~Tq4MhxvchzuT_oF3-t4YyA*MJ*n&+1X3~6quEN
z@m~aEp=b2~mP+}TUP^FmkRS_PDMA{B
zaSy(P=$T~R!yc^Ye0*pl5xcpm_JWI;@-di+nruhqZ4gy7cq-)I&s&Bt3BkgT(Zdjf
zTvvv0)8xzntEtp4iXm}~cT+pi5k{w{(Z@l2XU9lHr4Vy~3ycA_T?V(QS{qwt?v|}k
z_ST!s;C4!jyV5)^6xC#v!o*uS%a-jQ6<
z)>o?z7=+zNNtIz1*F_HJ(w@=`E+T|9TqhC(g7kKDc8z~?RbKQ)LRMn7A1p*PcX2YR
zUAr{);~c7I#3Ssv<0i-Woj0&Z4a!u|@Xt2J1>N-|ED<3$o2V?OwL4oQ%$@!zLamVz
zB)K&Ik^~GOmDAa143{I4?XUk1<3-k{<%?&OID&>Ud%z*Rkt*)mko0RwC2=qFf-^OV
z=d@47?tY=A;=2VAh0mF(3x;!#X!%{|vn;U2XW{(nu5b&8kOr)Kop3-5_xnK5oO_3y
z!EaIb{r%D{7zwtGgFVri4_!yUIGwR(xEV3YWSI_+E}Gdl>TINWsIrfj+7DE?xp+5^
zlr3pM-Cbse*WGKOd3+*Qen^*uHk)+EpH-{u@i%y}Z!YSid<}~kA*IRSk|nf+I1N=2
zIKi+&ej%Al-M5`cP^XU>9A(m7G>58>o|}j0ZWbMg&x`*$B9j#Rnyo0#=BMLdo%=ks
zLa3(2EinQLXQ(3zDe7Bce%Oszu%?8PO648TNst4SMFvj=+{b%)ELyB!0`B?9R6aO{i-63|s@|raSQGL~s)9R#J#duFaTSZ2M{X
z1?YuM*a!!|jP^QJ(hAisJuPOM`8Y-Hzl~%d@latwj}t&0{DNNC+zJARnuQfiN`HQ#
z?boY_2?*q;Qk)LUB)s8(Lz5elaW56p&fDH*AWAq7Zrbeq1!?FBGYHCnFgRu5y1jwD
zc|yBz+UW|X`zDsc{W~8m$sh@VVnZD$lLnKlq@Hg^;ky!}ZuPdKNi2BI70;hrpvaA4+Q_+K)I@|)q1N-H
zrycZU`*YUW``Qi^`bDX-j7j^&bO+-Xg$cz2#i##($uyW{Nl&{DK{=lLWV3|=<&si||2)l=8^8_z+Vho-#5LB0EqQ3v5U#*DF7
zxT)1j^`m+lW}p$>WSIG1eZ>L|YR-@Feu!YNWiw*IZYh03mq+2QVtQ}1ezRJM?0PA<
z;mK(J5@N8>u@<6Y$QAHWNE};rR|)U_&bv8dsnsza7{=zD1VBcxrALqnOf-qW(zzTn
zTAp|pEo#FsQ$~*$j|~Q;$Zy&Liu9OM;VF@#_&*nL!N2hH!Q6l*OeTxq!l>dEc{;Hw
zCQni{iN%jHU*C;?M-VUaXxf0FEJ_G=C8)C-wD!DvhY+qQ#FT3}Th8;GgV&AV94F`D
ztT6=w_Xm8)*)dBnDkZd~UWL|W=Glu!$hc|1w7_7l!3MAt95oIp4Xp{M%clu&TXehO
z+L-1#{mjkpTF@?|w1P98OCky~S%@OR&o75P&ZHvC}Y=(2_{ib(-Al_7aZ^U?s34#H}=
zGfFi5%KnFVCKtdO^>Htpb07#BeCXMDO8U}crpe1Gm`>Q=6qB4i=nLoLZ%p$TY=OcP
z)r}Et-Ed??u~f09d3Nx3bS@ja!fV(Dfa5lXxRs#;8?Y8G+Qvz+iv7fiRkL3liip})
z&G0u8RdEC9c$$rdU53=MH`p!Jn|DHjhOxHK$tW_pw9wCTf0Eo<){HoN=zG!!Gq4z4
z7PwGh)VNPXW-cE#MtofE`-$9~nmmj}m
zlzZscQ2+Jq%gaB9rMgVJkbhup0Ggpb)&L01T=%>n7-?v@I8!Q(p&+!fd+Y^Pu9l+u
zek(_$^HYFVRRIFt@0Fp52g5Q#I`tC3li`;UtDLP*rA{-#Yoa5qp{cD)QYhldihWe+
zG~zuaqLY~$-1sjh2lkbXCX;lq+p~!2Z=76cvuQe*Fl>IFwpUBP+d^&E4BGc{m#l%Kuo6#{XGoRyFc%Hqhf|%nYd<;yiC>tyEyk
z4I+a`(%%Ie=-*n
z-{mg=j&t12)LH3R?@-B1tEb7FLMePI1HK0`Ae@#)KcS%!Qt9p4_fmBl5zhO10n401
zBSfnfJ;?_r{%R)hh}BBNSl=$BiAKbuWrNGQUZ)+0=Mt&5!X*D@yGCSaMNY&@`;^a4
z;v=%D_!K!WXV1!3%4P-M*s%V2b#2jF2bk!)#2GLVuGKd#vNpRMyg`kstw0GQ8@^k^
zuqK5uR<>FeRZ#3{%!|4X!hh7hgirQ@Mwg%%ez8pF!N$xhMNQN((yS(F2-OfduxxKE
zxY#7O(VGfNuLv-ImAw5+h@gwn%!ER;*Q+001;W7W^waWT%@(T+5k!c3A-j)a8y11t
zx4~rSN0s$M8HEOzkcWW4YbKK9GQez2XJ|Nq?TFy;jmGbg;`m&%U4hIiarKmdTHt#l
zL=H;ZHE?fYxKQQXKnC+K!TAU}r086{4m}r()-QaFmU(qWhJlc$eas&y?=H9EYQy8N$8^bni9TpDp
zkA^WRs?KgYgjxX4T6?`SMs$`s3vlut(YU~f2F+id(Rf_)$BIMibk9lACI~LA+i7xn
z%-+=DHV*0TCTJp~-|$VZ@g2vmd*|2QXV;HeTzt530KyK>v&253N1l}bP_J#UjLy4)
zBJili9#-ey8Kj(dxmW^ctorxd;te|xo)%46l%5qE-YhAjP`Cc03vT)vV&GAV%#Cgb
zX~2}uWNvh`2<*AuxuJpq>SyNtZwzuU)r@@dqC@v=Ocd(HnnzytN+M&|Qi#f4Q8D=h
ziE<3ziFW%+!yy(q{il8H44g^5{_+pH60Mx5Z*FgC_3hKxmeJ+wVuX?T#ZfOOD3E4C
zRJsj#wA@3uvwZwHKKGN{{Ag+8^cs?S4N@6(Wkd$CkoCst(Z&hp+l=ffZ?2m%%ffI3
zdV7coR`R+*dPbNx=*ivWeNJK=Iy_vKd`-_Hng{l?hmp=|T3U&epbmgXXWs9ySE|=G
zeQ|^ioL}tveN{s72_&h+F+W;G}?;?_s@h5>DX(rp#eaZ!E=NivgLI
zWykLKev+}sHH41NCRm7W>K+_qdoJ8x9o5Cf!)|qLtF7Izxk*p|fX8UqEY)_sI_45O
zL2u>x=r5xLE%s|d%MO>zU%KV6QKFiEeo12g#bhei4!Hm+`~Fo~4h|BJ)%ENxy9)Up
zOxupSf1QZWun=)gF{L0YWJ<(r0?$bPFANrmphJ>kG`&7E+RgrWQi}ZS#-CQJ*i#8j
zM_A0?w@4Mq@xvk^>QSvEU|VYQoVI=TaOrsLTa`RZfe8{9F~mM{L+C`9YP9?OknLw|
zmkvz>cS6`pF0FYeLdY%>u&XpPj5$*iYkj=m7wMzHqzZ5SG~$i_^f@QEPEC+<2nf-{
zE7W+n%)q$!5@2pBuXMxhUSi*%F>e_g!$T-_`ovjBh(3jK9Q^~OR{)}!0}vdTE^M+m
z9QWsA?xG>EW;U~5gEuKR)Ubfi&YWnXV;3H6Zt^NE725*`;lpSK4HS1sN?{~9a4JkD
z%}23oAovytUKfRN87XTH2c=kq1)O5(fH_M3M-o{{@&~KD`~TRot-gqg7Q2U2o-iiF}K>m?CokhmODaLB
z1p6(6JYGntNOg(s!(>ZU&lzDf+Ur)^Lirm%*}Z>T)9)fAZ9>k(kvnM;ab$ptA=hoh
zVgsVaveXbMpm{|4*d<0>?l_JUFOO8A3xNLQOh%nVXjYI6X8h?a@6kDe5-m&;M0xqx
z+1U$s>(P9P)f0!{z%M@E7|9nn#IWgEx6A6JNJ(7dk`%6$3@!C!l;JK-p2?gg+W|d-
ziEzgk$w7k48NMqg$CM*4O~Abj3+_yUKTyK1p6GDsGEs;}=E_q>^LI-~pym$qhXPJf
z2`!PJDp4l(TTm#|n@bN!j;-FFOM__eLl!6{*}z=)UAcGYloj?bv!-XY1TA6Xz;82J
zLRaF{8ayzGa|}c--}|^xh)xgX>6R(sZD|Z|qX50gu=d`gEwHqC@WYU7{%<5VOnf9+
zB@FX?|UL%`8EIAe!*UdYl|6wRz6Y>(#8x92$#y}wMeE|ZM2X*c}dKJ^4NIf;Fm
zNwzq%QcO?$NR-7`su!*$dlIKo2y(N;qgH@1|8QNo$0wbyyJ2^}$iZ>M{BhBjTdMjK
z>gPEzgX4;g3$rU?jvDeOq`X=>)zdt|jk1Lv3u~bjHI=EGLfIR&+K3ldcc4D&Um&04
z3^F*}WaxR(ZyaB>DlmF_UP@+Q*h$&nsOB#gwLt{1#F4i-{A5J@`>B9@{^i?g_Ce&O
z<<}_We-RUFU&&MHa1#t56u_oM(Ljn7djja!T|gcxSoR=)@?owC*NkDarpBj=W4}=i1@)@L|C)
zQKA+o<(pMVp*Su(`zBC0l1yTa$MRfQ#uby|$mlOMs=G`4J|?apMzKei%jZql#gP@IkOaOjB7MJM=@1j(&!jNnyVkn5;4lvro1!vq
ztXiV8HYj5%)r1PPpIOj)f!>pc^3#LvfZ(hz}C@-3R(Cx7R427*Fwd!XO
z4~j&IkPHcBm0h_|iG;ZNrYdJ4HI!$rSyo&sibmwIgm1|J#g6%>=ML1r!kcEhm(XY&
zD@mIJt;!O%WP7CE&wwE3?1-dt;RTHdm~LvP7K`ccWXkZ0kfFa2S;wGtx_a}S2lslw
z$<4^Jg-n#Ypc(3t2N67Juasu=h)j&UNTPNDil4MQMTlnI81kY46uMH5B^U{~nmc6+
z9>(lGhhvRK9ITfpAD!XQ&BPphL3p8B4PVBN0NF6U49;ZA0Tr75AgGw7(S=Yio+xg_
zepZ*?V#KD;sHH+15ix&yCs0eSB-Z%D%uujlXvT#V$Rz@$+w!u#3GIo*AwMI#Bm^oO
zLr1e}k5W~G0xaO!C%Mb{sarxWZ4%Dn9vG`KHmPC9GWZwOOm11XJp#o0-P-${3m4g(
z6~)X9FXw%Xm~&99tj>a-ri})ZcnsfJtc10F@t9xF5vq6E)X!iUXHq-ohlO`gQdS&k
zZl})3k||u)!_=nNlvMbz%AuIr89l#I$;rG}qvDGiK?xTd5HzMQkw*p$YvFLGyQM!J
zNC^gD!kP{A84nGosi~@MLKqWQNacfs7O$dkZtm4-BZ~iA8xWZPkTK!HpA5zr!9Z&+icfAJ1)NWkTd!-9`NWU>9uXXUr;`Js#NbKFgrNhTcY4GNv*71}}T
zFJh?>=EcbUd2<|fiL+H=wMw8hbX6?+_cl4XnCB#ddwdG>bki*
zt*&6Dy&EIPluL@A3_;R%)shA-tDQA1!Tw4ffBRyy;2n)vm_JV06(4Or&QAOKNZB5f(MVC}&_!B>098R{Simr!UG}?CW1Ah+X+0#~0`X)od
zLYablwmFxN21L))!_zc`IfzWi`5>MxPe(DmjjO1}HHt7TJtAW+VXHt!aKZk>y6PoMsbDXRJnov;D~Ur~2R_7(Xr)aa%wJwZhS3gr7IGgt%@;`jpL@gyc6bGCVx!9CE7NgIbUNZ!Ur1RHror0~
zr(j$^yM4j`#c2KxSP61;(Tk^pe7b~}LWj~SZC=MEpdKf;B@on9=?_n|R|0q;Y*1_@
z>nGq>)&q!;u-8H)WCwtL&7F4vbnnfSAlK1mwnRq2&gZrEr!b1MA
z(3%vAbh3aU-IX`d7b@q`-WiT6eitu}ZH9x#d&qx}?CtDuAXak%5<-P!{a`V=$|XmJ
zUn@4lX6#ulB@a=&-9HG)a>KkH=jE7>&S&N~0X0zD=Q=t|7w;kuh#cU=NN7gBGbQTT
z;?bdSt8V&IIi}sDTzA0dkU}Z-Qvg;RDe8v>468p3*&hbGT1I3hi9hh~Z(!H}{+>eUyF)H&gdrX=k$aB%J6I;6+^^kn1mL+E+?A!A}@xV(Qa@M%HD5C@+-4Mb4lI=Xp=@9+^x+jhtOc
zYgF2aVa(uSR*n(O)e6tf3JEg2xs#dJfhEmi1iOmDYWk|wXNHU?g23^IGKB&yHnsm7
zm_+;p?YpA#N*7vXCkeN2LTNG`{QDa#U3fcFz7SB)83=<8rF)|udrEbrZL$o6W?oDR
zQx!178Ih9B#D9Ko$H(jD{4MME&<|6%MPu|TfOc#E0B}!j^MMpV69D#h2`vsEQ{(?c
zJ3Lh!3&=yS5fWL~;1wCZ?)%nmK`Eqgcu)O6rD^3%ijcxL50^z?OI(LaVDvfL0#zjZ
z2?cPvC$QCzpxpt5jMFp05OxhK0F!Q`rPhDi5)y=-0C}
zIM~ku&S@pl1&0=jl+rlS<4`riV~LC-#pqNde@44MB(j%)On$0Ko(@q?4`1?4149Z_
zZi!5aU@2vM$dHR6WSZpj+VboK+>u-CbNi7*lw4K^ZxxM#24_Yc`jvb9NPVi75L+MlM^U~`;a7`4H0L|TYK>%hfEfXLsu1JGM
zbh|8{wuc7ucV+`Ys1kqxsj`dajwyM;^X^`)#<+a~$WFy8b2t_RS{8yNYKKlnv+>vB
zX(QTf$kqrJ;%I@EwEs{cIcH@Z3|#^S@M+5jsP<^`@8^I4_8MlBb`~cE^n+{{;qW2q
z=p1=&+fUo%T{GhVX@;56kH8K_%?X=;$OTYqW1L*)hzelm^$*?_K;9JyIWhsn4SK(|
zSmXLTUE8VQX{se#8#Rj*lz`xHtT<61V~fb;WZUpu(M)f#;I+2_zR+)y5Jv?l`CxAinx|EY!`IJ*x9_gf_k&Gx2alL!hK
zUWj1T_pk|?iv}4EP#PZvYD_-LpzU!NfcLL%fK&r$W8O1KH9c2&GV~N#T$kaXGvAOl)|T
zuF9%6(i=Y3q?X%VK-D2YIYFPH3f|g$TrXW->&^Ab`WT
z7>Oo!u1u40?jAJ8Hy`bv}qbgs8)cF0&qeVjD?e+3Ggn1Im>K77ZSpbU*08
zfZkIFcv?y)!*B{|>nx@cE{KoutP+seQU?bCGE`tS0GKUO3PN~t=2u7q_6$l;uw^4c
zVu^f{uaqsZ{*a-N?2B8ngrLS8E&s6}Xtv9rR9C^b`@q8*iH)pFzf1|kCfiLw6u{Z%aC
z!X^5CzF6qofFJgklJV3oc|Qc2XdFl+y5M9*P8}A>Kh{
zWRgRwMSZ(?Jw;m%0etU5BsWT-Dj-5F;Q$OQJrQd+lv`i6>MhVo^p*^w6{~=fhe|bN
z*37oV0kji)4an^%3ABbg5RC;CS50@PV5_hKfXjYx+(DqQdKC^JIEMo6X66$qDdLRc
z!YJPSKnbY`#Ht6`g@xGzJmKzzn|abYbP+_Q(v?~~
z96%cd{E0BCsH^0HaWt{y(Cuto4VE7jhB1Z??#UaU(*R&Eo+J`UN+8mcb51F|I|n*J
zJCZ3R*OdyeS9hWkc_mA7-br>3Tw=CX2bl(=TpVt#WP8Bg^vE_9bP&6ccAf3lFMgr`
z{3=h@?Ftb$RTe&@IQtiJfV;O&4fzh)e1>7seG;
z=%mA4@c7{aXeJnhEg2J@Bm;=)j=O=cl#^NNkQ<{r;Bm|8Hg}bJ-S^g4`|itx)~!LN
zXtL}?f1Hs6UQ+f0-X6&TBCW=A4>bU0{rv8C4T!(wD-h>VCK4YJk`6C9$by!fxOYw-
zV#n+0{E(0ttq_#16B}
ze8$E#X9o{B!0vbq#WUwmv5Xz6{(!^~+}sBW{xctdNHL4^vDk!0E}(g|W_q;jR|ZK<
z8w>H-8G{%R#%f!E7cO_^B?yFRKLOH)RT9GJsb+kAKq~}WIF)NRLwKZ^Q;>!2MNa|}
z-mh?=B;*&D{Nd-mQRcfVnHkChI=DRHU4ga%xJ%+QkBd|-d9uRI76@BT(bjsjwS+r)
zvx=lGNLv1?SzZ;P)Gnn>04fO7Culg*?LmbEF0fATG8S@)oJ>NT3pYAXa*vX!eUTDF
ziBrp(QyDqr0ZMTr?4uG_Nqs6f%S0g?h`1vO5fo=5S&u#wI2d4+3hWiolEU!=3_oFo
zfie?+4W#`;1dd#X@g9Yj<53S<6OB!TM8w8})7k-$&q5(smc%;r
z(BlXkTp`C47+%4JA{2X}MIaPbVF!35P#p;u7+fR*46{T+LR8+j25oduCfDzDv6R-hU{TVVo9fz?^N3ShMt!t0NsH)pB
zRK8-S{Dn*y3b|k^*?_B70<2gHt==l7c&cT>r`C#{S}J2;s#d{M)ncW(#Y$C*lByLQ
z&?+{dR7*gpdT~(1;M(FfF==3z`^eW)=5a9RqvF-)2?S-(G
zhS;p(u~_qBum*q}On@$#08}ynd0+spzyVco0%G6;<-i5&016cV5UKzhQ~)fX03|>L
z8ej+HzzgVr6_5ZUpa4HW0Ca!=r1%*}Oo;2no&Zz8DfR)L!@r<5
z2viSZpmvo5XqXyAz{Ms7`7kX>fnr1gi4X~7KpznRT0{Xc5Cfz@43PjBMBoH@z_{~(
z(Wd}IPJ9hH+%)Fc)0!hrV+(A;76rhtI|YHbEDeERV~Ya>SQg^IvlazFkSK(KG9&{q
zkPIR~EeQaaBmwA<20}mBO?)N$(z1@p)5?%}rM|
zGF()~Z&Kx@OIDRI$d0T8;JX@vj3^2%pd_+@l9~a4lntZ;AvUIjqIZbuNTR6@hNJoV
zk4F;ut)LN4ARuyn2M6F~eg-e#UH%2P;8uPGFW^vq1vj8mdIayFOZo(tphk8C7hpT~
z1Fv8?b_LNR3QD9J+!v=p%}#
+
+
\ No newline at end of file
diff --git a/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.ttf b/public/vendor/laravel-admin/AdminLTE/bootstrap/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b
GIT binary patch
literal 45404
zcmd?Sd0-pWwLh*qi$?oCk~i6sWlOeWJC3|4juU5JNSu9hSVACzERcmjLV&P^utNzg
zIE4Kr1=5g!SxTX#Ern9_%4&01rlrW`Z!56xXTGQR4C
z3vR~wXq>NDx$c~e?;ia3YjJ*$!C>69a?2$lLyhpI!CFfJsP=|`8@K0|bbMpWwVUEygg0=0x_)HeHpGSJagJNLA3c!$EuOV>j$wi!
zbo{vZ(s8tl>@!?}dmNHXo)ABy7ohD7_1G-P@SdJWT8*oeyBVYVW9*vn}&VI4q++W;Z+uz=QTK}^C75!`aFYCX#
zf7fC2;o`%!huaTNJAB&VWrx=szU=VLhwnbT`vc<#<`4WI6n_x@AofA~2d90o?1L3w
z9!I|#P*NQ)$#9aASijuw>JRld^-t)Zhmy|i-`Iam|IWkguaMR%lhi4p~cX-9&
zjfbx}yz}s`4-6>D^+6FzihR)Y!GsUy=_MWi_v7y#KmYi-{iZ+s@ekkq!@Wxz!~BQwiI&ti
z>hC&iBe2m(dpNVvSbZe3DVgl(dxHt-k@{xv;&`^c8GJY%&^LpM;}7)B;5Qg5J^E${
z7z~k8eWOucjX6)7q1a%EVtmnND8cclz8R1=X4W@D8IDeUGXxEWe&p>Z*voO0u_2!!
zj3dT(Ki+4E;uykKi*yr?w6!BW2FD55PD6SMj`OfBLwXL5EA-9KjpMo4*5Eqs^>4&>
z8PezAcn!9jk-h-Oo!E9EjX8W6@EkTHeI<@AY{f|5fMW<-Ez-z)xCvW3()Z#x0oydB
zzm4MzY^NdpIF9qMp-jU;99LjlgY@@s+=z`}_%V*xV7nRV*Kwrx-i`FzI0BZ#yOI8#
z!SDeNA5b6u9!Imj89v0(g$;dT_y|Yz!3V`i{{_dez8U@##|X9A};s^7vEd!3AcdyVlhVk$v?$O442KIM1-wX^R{U7`JW&lPr3N(%kXfXT_`7w^?
z=#ntx`tTF|N$UT?pELvw7T*2;=Q-x@KmDUIbLyXZ>f5=y7z1DT<7>Bp0k;eItHF?1
zErzhlD2B$Tm|^7DrxnTYm-tgg`Mt4Eivp5{r$o9e)8(fXBO4g|G^6Xy?y$SM*&V52
z6SR*%`%DZC^w(gOWQL?6DRoI*hBNT)xW9sxvmi@!vI^!mI$3kvAMmR_q#SGn3zRb_
zGe$=;Tv3dXN~9XuIHow*NEU4y&u}FcZEZoSlXb9IBOA}!@J3uovp}yerhPMaiI8|SDhvWVr
z^BE&yx6e3&RYqIg;mYVZ*3#A-cDJ;#ms4txEmwm@g^s`BB}KmSr7K+ruIoKs=s|gOXP|2
zb1!)87h9?(+1^QRWb(Vo8+@G=o24gyuzF3ytfsKjTHZJ}o{YznGcTDm!s)DRnmOX}
z3pPL4wExoN$kyc2>#J`k+<67sy-VsfbQ-1u+HkyFR?9G`9r6g4*8!(!c65Be-5hUg
zZHY$M0k(Yd+DT1*8)G(q)1&tDl=g9H7!bZTOvEEFnBOk_K=DXF(d4JOaH
zI}*A3jGmy{gR>s}EQzyJa_q_?TYPNXRU1O;fcV_&TQZhd{@*8Tgpraf~nT0BYktu*n{a~ub^UUqQPyr~yBY{k2O
zgV)honv{B_CqY|*S~3up%Wn%7i*_>Lu|%5~j)}rQLT1ZN?5%QN`LTJ}vA!EE=1`So
z!$$Mv?6T)xk)H8JTrZ~m)oNXxS}pwPd#);<*>zWsYoL6iK!gRSBB{JCgB28C#E{T?
z5VOCMW^;h~eMke(w6vLlKvm!!TyIf;k*RtK)|Q>_@nY#J%=h%aVb)?Ni_By)XNxY)E3`|}_u}fn+Kp^3p4RbhFUBRtGsDyx9Eolg77iWN
z2iH-}CiM!pfYDIn7;i#Ui1KG01{3D<{e}uWTdlX4Vr*nsb^>l0%{O?0L9tP|KGw8w
z+T5F}md>3qDZQ_IVkQ|BzuN08uN?SsVt$~wcHO4pB9~ykFTJO3g<4X({-Tm1w{Ufo
zI03<6KK`ZjqVyQ(>{_aMxu7Zm^ck&~)Q84MOsQ-XS~{6j>0lTl@lMtfWjj;PT{nlZ
zIn0YL?kK7CYJa)(8?unZ)j8L(O}%$5S#lTcq{rr5_gqqtZ@*0Yw4}OdjL*kBv+>+@
z&*24U=y{Nl58qJyW1vTwqsvs=VRAzojm&V
zEn6=WzdL1y+^}%Vg!ap>x%%nFi=V#wn#
zUuheBR@*KS)5Mn0`f=3fMwR|#-rPMQJg(fW*5e`7xO&^UUH{L(U8D$JtI!ac!g(Ze89<`UiO@L+)^D
zjPk2_Ie0p~4|LiI?-+pHXuRaZKG$%zVT0jn!yTvvM^jlcp`|VSHRt-G@_&~<4&qW@
z?b#zIN)G(}L|60jer*P7#KCu*Af;{mpWWvYK$@Squ|n-Vtfgr@ZOmR5Xpl;0q~VILmjk$$mgp+`<2jP
z@+nW5Oap%fF4nFwnVwR7rpFaOdmnfB$-rkO6T3#w^|*rft~acgCP|ZkgA6PHD#Of|
zY%E!3tXtsWS`udLsE7cSE8g@p$ceu*tI71V31uA7jwmXUCT7+Cu3uv|W>ZwD{&O4Nfjjvl43N#A$|FWxId!
z%=X!HSiQ-#4nS&smww~iXRn<-`&zc)nR~js?|Ei-cei$^$KsqtxNDZvl1oavXK#Pz
zT&%Wln^Y5M95w=vJxj0a-ko_iQt(LTX_5x#*QfQLtPil;kkR|kz}`*xHiLWr35ajx
zHRL-QQv$|PK-$ges|NHw8k6v?&d;{A$*q15hz9{}-`e6ys1EQ1oNNKDFGQ0xA!x^(
zkG*-ueZT(GukSnK&Bs=4+w|(kuWs5V_2#3`!;f}q?>xU5IgoMl^DNf+Xd<=sl2XvkqviJ>d?+G@Z5nxxd5Sqd$*ENUB_mb8Z+7CyyU
zA6mDQ&e+S~w49csl*UePzY;^K)Fbs^%?7;+hFc(xz#mWoek4_&QvmT7Fe)*{h-9R4
zqyXuN5{)HdQ6yVi#tRUO#M%;pL>rQxN~6yoZ)*{{!?jU)RD*oOxDoTjVh6iNmhWNC
zB5_{R=o{qvxEvi(khbRS`FOXmOO|&Dj$&~>*oo)bZz%lPhEA@
zQ;;w5eu5^%i;)w?T&*=UaK?*|U3~{0tC`rvfEsRPgR~16;~{_S2&=E{fE2=c>{+y}
zx1*NTv-*zO^px5TA|B```#NetKg`19O!BK*-#~wDM@KEllk^nfQ2quy25G%)l72<>
zzL$^{DDM#jKt?<>m;!?E2p0l12`j+QJjr{Lx*47Nq(v6i3M&*P{jkZB{xR?NOSPN%
zU>I+~d_ny=pX??qjF*E78>}Mgts@_yn`)C`wN-He_!OyE+gRI?-a>Om>Vh~3OX5+&
z6MX*d1`SkdXwvb7KH&=31RCC|&H!aA1g_=ZY0hP)-Wm6?A7SG0*|$mC7N^SSBh@MG
z9?V0tv_sE>X==yV{)^LsygK2=$Mo_0N!JCOU?r}rmWdHD%$h~~G3;bt`lH&
zAuOOZ=G1Mih**0>lB5x+r)X^8mz!0K{SScj4|a=s^VhUEp#2M=^#WRqe?T&H9GnWa
zYOq{+gBn9Q0e0*Zu>C(BAX=I-Af9wIFhCW6_>TsIH$d>|{fIrs&BX?2G>GvFc=<8`
zVJ`#^knMU~65dWGgXcht`Kb>{V2oo%<{NK|iH+R^|Gx%q+env#Js*(EBT3V0=w4F@W+oLFsA)l7Qy8mx_;6Vrk;F2RjKFvmeq}
zro&>@b^(?f))OoQ#^#s)tRL>b0gzhRYRG}EU%wr9GjQ#~Rpo|RSkeik^p9x2+=rUr}vfnQoeFAlv=oX%YqbLpvyvcZ3l$B
z5bo;hDd(fjT;9o7g9xUg3|#?wU2#BJ0G&W1#wn?mfNR{O7bq747tc~mM%m%t+7YN}^tMa24O4@w<|$lk@pGx!;%pKiq&mZB
z?3h<&w>un8r?Xua6(@Txu~Za9tI@|C4#!dmHMzDF_-_~Jolztm=e)@vG11bZQAs!tFvd9{C;oxC7VfWq377Y(LR^X_TyX9bn$)I765l=rJ%9uXcjggX*r?u
zk|0!db_*1$&i8>d&G3C}A`{Fun_1J;Vx0gk7P_}8KBZDowr*8$@X?W6v^LYmNWI)lN92yQ;tDpN
zOUdS-W4JZUjwF-X#w0r;97;i(l}ZZT$DRd4u#?pf^e2yaFo
zbm>I@5}#8FjsmigM8w_f#m4fEP~r~_?OWB%SGWcn$ThnJ@Y`ZI-O&Qs#Y14To(
zWAl>9Gw7#}eT(!c%D0m>5D8**a@h;sLW=6_AsT5v1Sd_T-C4pgu_kvc?7+X&n_fct
znkHy(_LExh=N%o3I-q#f$F4QJpy>jZBW
zRF7?EhqTGk)w&Koi}QQY3sVh?@e-Z3C9)P!(hMhxmXLC
zF_+ZSTQU`Gqx@o(~B$dbr
zHlEUKoK&`2gl>zKXlEi8w6}`X3kh3as1~sX5@^`X_nYl}hlbpeeVlj#2sv)CIMe%b
zBs7f|37f8qq}gA~Is9gj&=te^wN8ma?;vF)7gce;&sZ64!7LqpR!fy)?4cEZposQ8
zf;rZF7Q>YMF1~eQ|Z*!5j0DuA=`~VG$Gg6B?Om1
z6fM@`Ck-K*k(eJ)Kvysb8sccsFf@7~3vfnC=<$q+VNv)FyVh6ZsWw}*vs>%k3$)9|
zR9ek-@pA23qswe1io)(Vz!vS1o*XEN*LhVYOq#T`;rDkgt86T@O`23xW~;W_#ZS|x
zvwx-XMb7_!hIte-#JNpFxskMMpo2OYhHRr0Yn8d^(jh3-+!CNs0K2B!1dL$9UuAD=
zQ%7Ae(Y@}%Cd~!`h|wAdm$2WoZ(iA1(a_-1?znZ%8h72o&Mm*4x8Ta<4++;Yr6|}u
zW8$p&izhdqF=m8$)HyS2J6cKyo;Yvb>DTfx4`4R{
zPSODe9E|uflE<`xTO=r>u~u=NuyB&H!(2a8vwh!jP!yfE3N>IiO1jI>7e&3rR#RO3_}G23W?gwDHgSgekzQ^PU&G5z&}V5GO?
zfg#*72*$DP1T8i`S7=P;bQ8lYF9_@8^C(|;9v8ZaK2GnWz4$Th2a0$)XTiaxNWfdq
z;yNi9veH!j)ba$9pke8`y2^63BP
zIyYKj^7;2don3se!P&%I2jzFf|LA&tQ=NDs{r9fIi-F{-yiG-}@2`VR^-LIFN8BC4
z&?*IvLiGHH5>NY(Z^CL_A;yISNdq58}=u~9!Ia7
zm7MkDiK~lsfLpvmPMo!0$keA$`%Tm`>Fx9JpG^EfEb(;}%5}B4Dw!O3BCkf$$W-dF
z$BupUPgLpHvr<<+QcNX*w@+Rz&VQz)Uh!j4|DYeKm5IC05T$KqVV3Y|MSXom+Jn8c
zgUEaFW1McGi^44xoG*b0JWE4T`vka7qTo#dcS4RauUpE{O!ZQ?r=-MlY#;VBzhHGU
zS@kCaZ*H73XX6~HtHd*4qr2h}Pf0Re@!WOyvres_9l2!AhPiV$@O2sX>$21)-3i+_
z*sHO4Ika^!&2utZ@5%VbpH(m2wE3qOPn-I5Tbnt&yn9{k*eMr3^u6zG-~PSr(w$p>
zw)x^a*8Ru$PE+{&)%VQUvAKKiWiwvc{`|GqK2K|ZMy^Tv3g|zENL86z7i<c
zW`W>zV1u}X%P;Ajn+>A)2iXZbJ5YB_r>K-h5g^N=LkN^h0Y6dPFfSBh(L`G$D%7c`
z&0RXDv$}c7#w*7!x^LUes_|V*=bd&aP+KFi((tG*gakSR+FA26%{QJdB5G1F=UuU&koU*^zQA=cEN9}Vd?OEh|
zgzbFf1?@LlPkcXH$;YZe`WEJ3si6&R2MRb}LYK&zK9WRD=kY-JMPUurX-t4(Wy{%`
zZ@0WM2+IqPa9D(^*+MXw2NWwSX-_WdF0nMWpEhAyotIgqu5Y$wA=zfuXJ0Y2lL3#ji26-P3Z?-&0^KBc*`T$+8+cqp`%g0WB
zTH9L)FZ&t073H4?t=(U6{8B+uRW_J_n*vW|p`DugT^3xe8Tomh^d}0k^G7$3wLgP&
zn)vTWiMA&=bR8lX9H=uh4G04R6>C&Zjnx_f@MMY!6HK5v$T%vaFm;E8q=`w2Y}ucJ
zkz~dKGqv9$E80NTtnx|Rf_)|3wxpnY6nh3U9<)fv2-vhQ6v=WhKO@~@X57N-`7Ppc
zF;I7)eL?RN23FmGh0s;Z#+p)}-TgTJE%&>{W+}C`^-sy{gTm<$>rR
z-X7F%MB9Sf%6o7A%ZHReD4R;imU6<9h81{%avv}hqugeaf=~^3A=x(Om6Lku-Pn9i
zC;LP%Q7Xw*0`Kg1)X~nAsUfdV%HWrpr8dZRpd-#%)c#Fu^mqo|^b{9Mam`^Zw_@j@
zR&ZdBr3?@<@%4Z-%LT&RLgDUFs4a(CTah_5x4X`xDRugi#vI-cw*^{ncwMtA4NKjByYBza)Y$hozZCpuxL{IP&=tw6ZO52WY3|iwGf&IJCn+u(>icK
zZB1~bWXCmwAUz|^<&ysd#*!DSp8}DLNbl5lRFat4NkvItxy;9tpp9~|@
z;JctShv^Iq4(z+y7^j&I?GCdKMVg&jCwtCkc4*@O7HY*veGDBtAIn*JgD$QftP}8=
zxFAdF=(S>Ra6(4slk#h%b?EOU-96TIX$Jbfl*_7IY-|R%H
zF8u|~hYS-YwWt5+^!uGcnKL~jM;)ObZ#q68ZkA?}CzV-%6_vPIdzh_wHT_$mM%vws9lxUj;E@#1UX?WO2R^41(X!nk$+2oJGr!sgcbn1f^yl1
z#pbPB&Bf;1&2+?};Jg5qgD1{4_|%X#s48rOLE!vx3@ktstyBsDQWwDz4GYlcgu$UJ
zp|z_32yN72T*oT$SF8<}>e;FN^X&vWNCz>b2W0rwK#<1#kbV)Cf`vN-F$&knLo5T&
z8!sO-*^x4=kJ$L&*h%rQ@49l?7_9IG99~xJDDil00<${~D&;kiqRQqeW5*22A`8I2
z(^@`qZoF7_`CO_e;8#qF!&g>UY;wD5MxWU>azoo=E{kW(GU#pbOi%XAn%?W{b>-bTt&2?G=E&BnK9m0zs{qr$*&g8afR_x`B~o
zd#dxPpaap;I=>1j8=9Oj)i}s@V}oXhP*{R|@DAQXzQJekJnmuQ;vL90_)H_nD1g6e
zS1H#dzg)U&6$fz0g%|jxDdz|FQN{KJ&Yx0vfuzAFewJjv`pdMRpY-wU`-Y6WQnJ(@
zGVb!-8DRJZvHnRFiR3PG3Tu^nCn(CcZHh7hQvyd7i6Q3&ot86XI{jo%WZqCPcTR0<
zMRg$ZE=PQx66ovJDvI_JChN~k@L^Pyxv#?X^<)-TS5gk`M~d<~j%!UOWG;ZMi1af<
z+86U0=sm!qAVJAIqqU`Qs1uJhQJA&n@9F1PUrYuW!-~IT>l$I!#5dBaiAK}RUufjg{$#GdQBkxF1=KU2E@N=i^;xgG2Y4|{H>s`
z$t`k8c-8`fS7Yfb1FM#)vPKVE4Uf(Pk&%HLe
z%^4L>@Z^9Z{ZOX<^e)~adVRkKJDanJ6VBC_m@6qUq_WF@Epw>AYqf%r6qDzQ~AEJ!jtUvLp^CcqZ^G-;Kz3T;O4WG45Z
zFhrluCxlY`M+OKr2SeI697btH7Kj`O>A!+2DTEQ=48cR>Gg2^5uqp(+y5Sl09MRl*
zp|28!v*wvMd_~e2DdKDMMQ|({HMn3D%%ATEecGG8V9>`JeL)T0KG}=}6K8NiSN5W<
z79-ZdYWRUb`T}(b{RjN8>?M~opnSRl$$^gT`B27kMym5LNHu-k;A;VF8R(HtDYJHS
zU7;L{a@`>jd0svOYKbwzq+pWSC(C~SPgG~nWR3pBA8@OICK$Cy#U`kS$I;?|^-SBC
zBFkoO8Z^%8Fc-@X!KebF2Ob3%`8zlVHj6H;^(m7J35(_bS;cZPd}TY~qixY{MhykQ
zV&7u7s%E=?i`}Ax-7dB0ih47w*7!@GBt<*7ImM|_mYS|9_K7CH+i}?*#o~a&tF-?C
zlynEu1DmiAbGurEX2Flfy$wEVk7AU;`k#=IQE*6DMWafTL|9-vT0qs{A3mmZGzOyN
zcM9#Rgo7WgB_ujU+?Q@Ql?V-!E=jbypS+*chI&zA+C_3_@aJal}!Q54?qsL0In({Ly
zjH;e+_SK8yi0NQB%TO+Dl77jp#2pMGtwsgaC>K!)NimXG3;m7y`W+&<(ZaV>N*K$j
zLL~I+6ouPk6_(iO>61cIsinx`5}DcKSaHjYkkMuDoVl>mKO<4$F