From 5b8901281cb0f7d54fa2a965dd88345471acd4ab Mon Sep 17 00:00:00 2001 From: xuanchen <122383162@qq.com> Date: Thu, 12 Jan 2023 14:47:38 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B5=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Area/AreaCodeController.php | 26 + app/Api/Controllers/Area/IndexController.php | 22 +- app/Api/Resources/Area/AreaCodeCollection.php | 3 +- app/Api/Resources/Area/AreaCodeResource.php | 19 + app/Api/Resources/Area/AreaFullResource.php | 23 + app/Api/Routes/area.php | 12 +- app/Models/Area.php | 7 +- app/Models/AreaCode.php | 17 +- app/Traits/HasStatus.php | 21 +- 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 | 105 + .../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/Routes/admin.php | 17 + modules/Cms/Routes/api.php | 35 + modules/Cms/Traits/HasTags.php | 25 + modules/Cms/composer.json | 34 + modules/Cms/module.json | 15 + ...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/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/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 + ..._add_area_code_id_to_mall_orders_table.php | 32 + .../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 | 408 ++ 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 | 48 + .../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 | 277 ++ .../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 | 141 + .../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 | 50 + .../Controllers/Api/OrderBuyController.php | 197 + .../Http/Controllers/Api/OrderController.php | 287 ++ .../Http/Controllers/Api/PayController.php | 136 + .../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 | 50 + 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 | 34 + .../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 | 24 + .../Resources/Api/Order/OrderItemResource.php | 26 + .../Api/Order/OrderLogisticResource.php | 22 + .../Resources/Api/Order/OrderPayResource.php | 27 + .../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 | 313 ++ modules/Mall/Models/OrderExpress.php | 48 + modules/Mall/Models/OrderItem.php | 130 + 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 | 206 + modules/Mall/Models/Traits/HasRegion.php | 69 + modules/Mall/Models/Traits/OrderActions.php | 185 + 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 + .../views/admin/order/detail.blade.php | 271 ++ .../views/admin/stock_order/detail.blade.php | 271 ++ modules/Mall/Routes/admin.php | 87 + modules/Mall/Routes/agent.php | 14 + modules/Mall/Routes/api.php | 125 + 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 | 129 + 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/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/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/Storage/Config/config.php | 45 + ...0_00_000000_create_file_storages_table.php | 36 + .../Http/Controllers/OssController.php | 198 + modules/Storage/Models/Storage.php | 12 + .../Providers/RouteServiceProvider.php | 51 + .../Providers/StorageServiceProvider.php | 69 + modules/Storage/Routes/api.php | 12 + modules/Storage/Storage.php | 34 + modules/Storage/composer.json | 34 + 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 | 125 + .../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/.styleci.yml | 17 + modules/User/Config/config.php | 128 + modules/User/Config/identity.php | 26 + .../Console/Commands/AutoRemindUserCase.php | 57 + .../Console/Commands/AutoRemindUserSign.php | 70 + .../Console/Commands/UserIdentityOver.php | 57 + modules/User/Console/Kernel.php | 15 + ..._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 | 55 + ...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 | 44 + ..._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_banners_table.php | 37 + ..._000000_create_user_sign_configs_table.php | 33 + ..._00_000000_create_user_sign_logs_table.php | 37 + ...00_000000_create_user_sign_texts_table.php | 37 + ...0_00_00_000000_create_user_signs_table.php | 40 + ...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 | 36 + ...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 + ..._08_09_134110_create_user_perves_table.php | 38 + ...144_create_user_identity_coupons_table.php | 36 + ...085224_add_star_to_user_identity_table.php | 32 + ...09_07_140701_add_status_to_users_table.php | 32 + ...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 + ...22_09_16_143020_add_tag_to_users_table.php | 32 + ...09_20_151841_create_user_invites_table.php | 39 + ...28_142001_create_user_subscribes_table.php | 34 + ...9_30_093417_add_channel_to_users_table.php | 32 + ...9_30_100124_create_user_channels_table.php | 38 + ..._add_user_id_to_mall_order_items_table.php | 32 + .../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 + .../Controllers/Admin/Actions/AllotCard.php | 75 + .../Actions/Certification/ConfigPublish.php | 26 + .../Admin/Actions/Certification/Replicate.php | 33 + .../Admin/Actions/JoinIdentity.php | 70 + .../Admin/Actions/LinkJoinCsVip.php | 20 + .../Http/Controllers/Admin/Actions/Pay.php | 25 + .../Http/Controllers/Admin/Actions/Refund.php | 25 + .../Admin/Actions/RemoveIdentity.php | 48 + .../Http/Controllers/Admin/Actions/SetTag.php | 41 + .../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/ChannelController.php | 81 + .../Controllers/Admin/GatewayController.php | 42 + .../Admin/IdentitiesController.php | 226 ++ .../Admin/IdentityLogController.php | 60 + .../Controllers/Admin/IndexController.php | 179 + .../Controllers/Admin/InviteController.php | 115 + .../Controllers/Admin/JoinCsVIpController.php | 166 + .../Controllers/Admin/OrderController.php | 98 + .../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 | 132 + .../Controllers/Api/Auth/WechatController.php | 154 + .../Api/Certification/IndexController.php | 90 + .../Api/Favorite/IndexController.php | 45 + .../Api/Identity/IndexController.php | 449 +++ .../Api/Identity/PartnerController.php | 105 + .../Http/Controllers/Api/IndexController.php | 198 + .../Api/Invite/IndexController.php | 42 + .../Controllers/Api/Rank/IndexController.php | 109 + .../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 | 489 +++ .../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 + .../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 | 26 + .../Resources/Invite/UserInviteCollection.php | 20 + .../Resources/Invite/UserInviteResource.php | 23 + .../User/Http/Resources/RelationResource.php | 24 + .../Resources/Sign/SignBannerResource.php | 16 + .../Http/Resources/Sign/SignTextResource.php | 18 + .../Resources/UserCertificationResource.php | 28 + .../Resources/UserIdentityBaseResource.php | 21 + .../Http/Resources/UserIdentityResource.php | 80 + .../Resources/UserIdentityRightsResource.php | 35 + .../Http/Resources/UserInfoBaseResource.php | 26 + .../User/Http/Resources/UserInfoResource.php | 55 + .../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 | 280 ++ modules/User/Models/IdentityLog.php | 46 + modules/User/Models/IdentityMiddle.php | 36 + modules/User/Models/Order.php | 98 + 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 ++ modules/User/Models/Traits/HasChannel.php | 14 + .../User/Models/Traits/HasIdentityScopes.php | 77 + modules/User/Models/Traits/HasInvite.php | 14 + modules/User/Models/Traits/HasLog.php | 44 + modules/User/Models/Traits/HasPerf.php | 72 + modules/User/Models/Traits/HasRelations.php | 284 ++ 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 | 276 ++ modules/User/Models/Traits/OrderActions.php | 110 + .../User/Models/Traits/WechatAttribute.php | 79 + modules/User/Models/User.php | 312 ++ modules/User/Models/UserCertification.php | 27 + .../User/Models/UserCertificationConfig.php | 48 + modules/User/Models/UserChannel.php | 28 + modules/User/Models/UserIdentityCoupon.php | 23 + modules/User/Models/UserInfo.php | 67 + modules/User/Models/UserInvite.php | 53 + modules/User/Models/UserLog.php | 18 + modules/User/Models/UserPerf.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 | 72 + modules/User/Models/UserWechatApp.php | 16 + modules/User/Models/UserWechatMini.php | 16 + modules/User/Models/UserWechatOfficial.php | 19 + .../User/Providers/EventServiceProvider.php | 30 + .../User/Providers/RouteServiceProvider.php | 62 + .../User/Providers/UserServiceProvider.php | 125 + modules/User/Renderable/UserLog.php | 31 + modules/User/Routes/admin.php | 38 + modules/User/Routes/api.php | 202 + modules/User/Rules/IdCardRule.php | 68 + modules/User/Services/VerificationCode.php | 91 + modules/User/Traits/BelongsToUser.php | 52 + modules/User/Traits/RankDataTrait.php | 120 + modules/User/Traits/WechatTrait.php | 89 + modules/User/User.php | 198 + 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/Withdraw/.gitignore | 2 + modules/Withdraw/Config/config.php | 13 + .../0000_00_00_000000_create_banks_table.php | 36 + ...21_09_26_115830_create_withdraws_table.php | 44 + ...021_09_26_120338_create_accounts_table.php | 39 + ...2021_09_26_130107_create_configs_table.php | 36 + .../2021_09_27_085100_create_logs_table.php | 36 + ...03_142359_create_alipay_accounts_table.php | 37 + modules/Withdraw/Events/WithdrawAuditPass.php | 8 + .../Withdraw/Events/WithdrawAuditReject.php | 8 + modules/Withdraw/Events/WithdrawEvent.php | 25 + .../Controllers/Admin/AccountController.php | 46 + .../Admin/Actions/WithdrawAudit.php | 48 + .../Http/Controllers/Admin/BankController.php | 50 + .../Controllers/Admin/ConfigController.php | 40 + .../Controllers/Admin/WithdrawController.php | 74 + .../Controllers/Api/AccountController.php | 144 + .../Http/Controllers/Api/IndexController.php | 155 + .../Http/Requests/UserBankAccountRequest.php | 35 + .../Requests/UserEditBankAccountRequest.php | 35 + .../Http/Requests/WithdrawRequest.php | 35 + .../Account/UserAlipayAccountCollection.php | 21 + .../Account/UserAlipayAccountResource.php | 21 + .../Account/UserBankAccountCollection.php | 21 + .../Account/UserBankAccountResource.php | 27 + .../Http/Resources/Bank/BankResource.php | 18 + .../Resources/Withdraw/WithdrawCollection.php | 21 + .../Resources/Withdraw/WithdrawResource.php | 30 + modules/Withdraw/Models/Account.php | 15 + modules/Withdraw/Models/AlipayAccount.php | 23 + modules/Withdraw/Models/Bank.php | 23 + modules/Withdraw/Models/Config.php | 23 + modules/Withdraw/Models/Log.php | 12 + .../Withdraw/Models/Traits/BelongsToBank.php | 22 + .../Models/Traits/WithdrawActions.php | 326 ++ modules/Withdraw/Models/Withdraw.php | 126 + .../Providers/RouteServiceProvider.php | 78 + .../Providers/WithdrawServiceProvider.php | 77 + modules/Withdraw/README.md | 9 + modules/Withdraw/Resources/assets/js/app.js | 0 .../Withdraw/Resources/assets/sass/app.scss | 0 .../Withdraw/Resources/views/index.blade.php | 9 + .../Resources/views/layouts/master.blade.php | 19 + modules/Withdraw/Routes/admin.php | 13 + modules/Withdraw/Routes/api.php | 15 + modules/Withdraw/Selectable/AccountAble.php | 49 + modules/Withdraw/Traits/HasWithdraws.php | 50 + modules/Withdraw/Withdraw.php | 164 + modules/Withdraw/composer.json | 21 + modules/Withdraw/module.json | 15 + modules/Withdraw/提现模块 v3.apifox.json | 1 + 626 files changed, 39326 insertions(+), 12 deletions(-) create mode 100644 app/Api/Controllers/Area/AreaCodeController.php create mode 100644 app/Api/Resources/Area/AreaCodeResource.php create mode 100644 app/Api/Resources/Area/AreaFullResource.php 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/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/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/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/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/Migrations/2023_01_12_134329_add_area_code_id_to_mall_orders_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/OrderPayResource.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/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/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/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/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/Models/Storage.php create mode 100644 modules/Storage/Providers/RouteServiceProvider.php create mode 100644 modules/Storage/Providers/StorageServiceProvider.php 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/.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/AutoRemindUserCase.php create mode 100644 modules/User/Console/Commands/AutoRemindUserSign.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_banners_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_sign_texts_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_08_09_134110_create_user_perves_table.php create mode 100644 modules/User/Database/Migrations/2022_08_19_161144_create_user_identity_coupons_table.php create mode 100644 modules/User/Database/Migrations/2022_08_31_085224_add_star_to_user_identity_table.php create mode 100644 modules/User/Database/Migrations/2022_09_07_140701_add_status_to_users_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_16_143020_add_tag_to_users_table.php create mode 100644 modules/User/Database/Migrations/2022_09_20_151841_create_user_invites_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_30_093417_add_channel_to_users_table.php create mode 100644 modules/User/Database/Migrations/2022_09_30_100124_create_user_channels_table.php create mode 100644 modules/User/Database/Migrations/2022_12_31_201255_add_user_id_to_mall_order_items_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/AllotCard.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/LinkJoinCsVip.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/SetTag.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/ChannelController.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/InviteController.php create mode 100644 modules/User/Http/Controllers/Admin/JoinCsVIpController.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/Identity/PartnerController.php create mode 100644 modules/User/Http/Controllers/Api/IndexController.php create mode 100644 modules/User/Http/Controllers/Api/Invite/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/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/Invite/UserInviteCollection.php create mode 100644 modules/User/Http/Resources/Invite/UserInviteResource.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/HasChannel.php create mode 100644 modules/User/Models/Traits/HasIdentityScopes.php create mode 100644 modules/User/Models/Traits/HasInvite.php create mode 100644 modules/User/Models/Traits/HasLog.php create mode 100644 modules/User/Models/Traits/HasPerf.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/UserChannel.php create mode 100644 modules/User/Models/UserIdentityCoupon.php create mode 100644 modules/User/Models/UserInfo.php create mode 100644 modules/User/Models/UserInvite.php create mode 100644 modules/User/Models/UserLog.php create mode 100644 modules/User/Models/UserPerf.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/Providers/EventServiceProvider.php create mode 100644 modules/User/Providers/RouteServiceProvider.php create mode 100644 modules/User/Providers/UserServiceProvider.php 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/Withdraw/.gitignore create mode 100644 modules/Withdraw/Config/config.php create mode 100644 modules/Withdraw/Database/Migrations/0000_00_00_000000_create_banks_table.php create mode 100644 modules/Withdraw/Database/Migrations/2021_09_26_115830_create_withdraws_table.php create mode 100644 modules/Withdraw/Database/Migrations/2021_09_26_120338_create_accounts_table.php create mode 100644 modules/Withdraw/Database/Migrations/2021_09_26_130107_create_configs_table.php create mode 100644 modules/Withdraw/Database/Migrations/2021_09_27_085100_create_logs_table.php create mode 100644 modules/Withdraw/Database/Migrations/2021_11_03_142359_create_alipay_accounts_table.php create mode 100644 modules/Withdraw/Events/WithdrawAuditPass.php create mode 100644 modules/Withdraw/Events/WithdrawAuditReject.php create mode 100644 modules/Withdraw/Events/WithdrawEvent.php create mode 100644 modules/Withdraw/Http/Controllers/Admin/AccountController.php create mode 100644 modules/Withdraw/Http/Controllers/Admin/Actions/WithdrawAudit.php create mode 100644 modules/Withdraw/Http/Controllers/Admin/BankController.php create mode 100644 modules/Withdraw/Http/Controllers/Admin/ConfigController.php create mode 100644 modules/Withdraw/Http/Controllers/Admin/WithdrawController.php create mode 100644 modules/Withdraw/Http/Controllers/Api/AccountController.php create mode 100644 modules/Withdraw/Http/Controllers/Api/IndexController.php create mode 100644 modules/Withdraw/Http/Requests/UserBankAccountRequest.php create mode 100644 modules/Withdraw/Http/Requests/UserEditBankAccountRequest.php create mode 100644 modules/Withdraw/Http/Requests/WithdrawRequest.php create mode 100644 modules/Withdraw/Http/Resources/Account/UserAlipayAccountCollection.php create mode 100644 modules/Withdraw/Http/Resources/Account/UserAlipayAccountResource.php create mode 100644 modules/Withdraw/Http/Resources/Account/UserBankAccountCollection.php create mode 100644 modules/Withdraw/Http/Resources/Account/UserBankAccountResource.php create mode 100644 modules/Withdraw/Http/Resources/Bank/BankResource.php create mode 100644 modules/Withdraw/Http/Resources/Withdraw/WithdrawCollection.php create mode 100644 modules/Withdraw/Http/Resources/Withdraw/WithdrawResource.php create mode 100644 modules/Withdraw/Models/Account.php create mode 100644 modules/Withdraw/Models/AlipayAccount.php create mode 100644 modules/Withdraw/Models/Bank.php create mode 100644 modules/Withdraw/Models/Config.php create mode 100644 modules/Withdraw/Models/Log.php create mode 100644 modules/Withdraw/Models/Traits/BelongsToBank.php create mode 100644 modules/Withdraw/Models/Traits/WithdrawActions.php create mode 100644 modules/Withdraw/Models/Withdraw.php create mode 100644 modules/Withdraw/Providers/RouteServiceProvider.php create mode 100644 modules/Withdraw/Providers/WithdrawServiceProvider.php create mode 100644 modules/Withdraw/README.md create mode 100644 modules/Withdraw/Resources/assets/js/app.js create mode 100644 modules/Withdraw/Resources/assets/sass/app.scss create mode 100644 modules/Withdraw/Resources/views/index.blade.php create mode 100644 modules/Withdraw/Resources/views/layouts/master.blade.php create mode 100644 modules/Withdraw/Routes/admin.php create mode 100644 modules/Withdraw/Routes/api.php create mode 100644 modules/Withdraw/Selectable/AccountAble.php create mode 100644 modules/Withdraw/Traits/HasWithdraws.php create mode 100644 modules/Withdraw/Withdraw.php create mode 100644 modules/Withdraw/composer.json create mode 100644 modules/Withdraw/module.json create mode 100644 modules/Withdraw/提现模块 v3.apifox.json diff --git a/app/Api/Controllers/Area/AreaCodeController.php b/app/Api/Controllers/Area/AreaCodeController.php new file mode 100644 index 0000000..340c4c9 --- /dev/null +++ b/app/Api/Controllers/Area/AreaCodeController.php @@ -0,0 +1,26 @@ +success(new AreaCodeResource($code)); + } + +} diff --git a/app/Api/Controllers/Area/IndexController.php b/app/Api/Controllers/Area/IndexController.php index 0cf7c5e..53079af 100644 --- a/app/Api/Controllers/Area/IndexController.php +++ b/app/Api/Controllers/Area/IndexController.php @@ -39,16 +39,34 @@ class IndexController extends Controller * @Date: 2023/1/11 15:42 * @param Area $area */ - public function codes(Area $area, Request $request) + public function codes(Request $request) { $status = $request->status ?? ''; + $area = Area::query() + ->whereHas('areaClerks', function ($q) { + $q->where('user_id', Api::userId()); + }) + ->first(); + $codes = $area->areaCodes() ->when($status, function ($q) use ($status) { $q->where('status', $status); }) ->paginate(); - return $this->success(new AreaCodeCollection($codes)); + + $release = $area->areaCodes()->count(); + $data = [ + 'count' => [ + 'all' => $area->stocks()->sum('amount'), + 'stock' => $area->stock, + 'release' => $release, + 'unrelease' => bcsub($area->stock, $release), + ], + 'codes' => new AreaCodeCollection($codes) + ]; + + return $this->success($data); } /** diff --git a/app/Api/Resources/Area/AreaCodeCollection.php b/app/Api/Resources/Area/AreaCodeCollection.php index 606518b..0f6b278 100644 --- a/app/Api/Resources/Area/AreaCodeCollection.php +++ b/app/Api/Resources/Area/AreaCodeCollection.php @@ -14,7 +14,8 @@ class AreaCodeCollection extends BaseCollection 'data' => $this->collection->map(function ($info) { return [ 'area_code' => $info->id, - 'user' => new UserBaseResource($info->user), + 'user' => $info->user ? new UserBaseResource($info->user) : "", + 'code' => $info->code, 'status' => [ 'status' => $info->status, 'text' => $info->status_text, diff --git a/app/Api/Resources/Area/AreaCodeResource.php b/app/Api/Resources/Area/AreaCodeResource.php new file mode 100644 index 0000000..d8755df --- /dev/null +++ b/app/Api/Resources/Area/AreaCodeResource.php @@ -0,0 +1,19 @@ + $this->id, + 'code' => $this->code, + 'area' => new AreaFullResource($this->area), + ]; + } + +} diff --git a/app/Api/Resources/Area/AreaFullResource.php b/app/Api/Resources/Area/AreaFullResource.php new file mode 100644 index 0000000..b10d974 --- /dev/null +++ b/app/Api/Resources/Area/AreaFullResource.php @@ -0,0 +1,23 @@ + $this->id, + 'title' => $this->title, + 'stock' => $this->stock, + 'province' => $this->province->name, + 'city' => $this->city->name, + 'district' => $this->district->name, + 'full_address' => $this->getFullAddress(), + ]; + } + +} diff --git a/app/Api/Routes/area.php b/app/Api/Routes/area.php index cb005e4..2c390c5 100644 --- a/app/Api/Routes/area.php +++ b/app/Api/Routes/area.php @@ -8,7 +8,15 @@ Route::group([ 'middleware' => config('api.route.middleware_auth'), ], function (Router $router) { $router->get('areas', 'IndexController@index'); - $router->get('areas/{area}/codes', 'IndexController@codes'); - $router->get('areas/{area}/generate', 'IndexController@generate'); + $router->get('areas/codes', 'IndexController@codes'); + $router->get('areas/generate', 'IndexController@generate'); +}); + + +Route::group([ + 'namespace' => 'Area', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('areas/{code}/show', 'AreaCodeController@show'); }); diff --git a/app/Models/Area.php b/app/Models/Area.php index a3bd69f..4f8938b 100644 --- a/app/Models/Area.php +++ b/app/Models/Area.php @@ -121,8 +121,11 @@ class Area extends Model if (! $num) { throw new \Exception('缺少数量'); } - if ($this->stock < $num) { - throw new \Exception('可生成数量不足,您还可以释放'.$this->stock); + + $canStockNum = bcsub($this->stock, $this->areaCodes()->count()); + + if ($canStockNum < $num) { + throw new \Exception('可生成数量不足,您还可以释放'.$canStockNum); } while ($num > 0) { diff --git a/app/Models/AreaCode.php b/app/Models/AreaCode.php index 89d92e0..8765d0d 100644 --- a/app/Models/AreaCode.php +++ b/app/Models/AreaCode.php @@ -16,10 +16,11 @@ class AreaCode extends Model const STATUS_USED = 1; const STATUS = [ - self::STATUS_INIT => '未使用', - self::STATUS_USED => '已使用', + self::STATUS_INIT => '未提取', + self::STATUS_USED => '已提取', ]; + /** * Notes: 生成人-管理人 * @@ -32,5 +33,17 @@ class AreaCode extends Model return $this->belongsTo(User::class); } + /** + * Notes: 设置使用 + * + * @Author: 玄尘 + * @Date: 2023/1/12 14:25 + */ + public function used() + { + $this->status = self::STATUS_USED; + $this->save(); + } + } diff --git a/app/Traits/HasStatus.php b/app/Traits/HasStatus.php index 763e116..56c266a 100644 --- a/app/Traits/HasStatus.php +++ b/app/Traits/HasStatus.php @@ -9,6 +9,7 @@ trait HasStatus /** * Notes : 获取状态字段,主模型可配置 $status_field + * * @Date : 2021/3/16 4:34 下午 * @Author : < Jason.C > * @return string @@ -20,22 +21,34 @@ trait HasStatus /** * Notes : 获取各状态的名称 + * * @Date : 2021/5/27 11:50 上午 * @Author : < Jason.C > * @return string[] */ protected function getStatusMap(): array { - return isset($this->status_map) && !empty($this->status_map) ? $this->status_map : [ + $status_map = [ 0 => '待审核', 1 => '正常', 2 => '驳回', 3 => '关闭', ]; + + $statusData = constant(get_class($this)."::STATUS"); + + if (isset($this->status_map) && ! empty($this->status_map)) { + $status_map = $this->status_map; + } elseif ($statusData) { + $status_map = $statusData; + } + + return $status_map; } /** * 正常显示的数据 + * * @Author: * @Date :2021-04-09 * @param \Illuminate\Database\Eloquent\Builder $query @@ -48,6 +61,7 @@ trait HasStatus /** * 不显示的数据 + * * @Author : * @Date :2021-04-09 * @param \Illuminate\Database\Eloquent\Builder $query @@ -60,10 +74,11 @@ trait HasStatus /** * Notes : 状态查询 + * * @Date : 2021/6/28 10:25 上午 * @Author : < Jason.C > * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $status + * @param int $status * @return \Illuminate\Database\Eloquent\Builder */ public function scopeOfStatus(Builder $query, int $status): Builder @@ -73,6 +88,7 @@ trait HasStatus /** * Notes : 获取状态的文本信息 + * * @Date : 2021/4/25 2:10 下午 * @Author : < Jason.C > * @return string @@ -80,7 +96,6 @@ trait HasStatus public function getStatusTextAttribute(): string { $map = $this->getStatusMap(); - return $map[$this->{$this->getStatusField()}] ?? '未知'; } 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..1a9a258 --- /dev/null +++ b/modules/Cms/Http/Controllers/Api/PageController.php @@ -0,0 +1,105 @@ + + * @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)); + } + + /** + * Notes: 授权书 + * + * @Author: 玄尘 + * @Date: 2022/12/30 14:16 + * @return \Illuminate\Http\JsonResponse + */ + public function letter() + { + $info = Page::where('slug', 'letter')->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/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..e0d36cc --- /dev/null +++ b/modules/Cms/Routes/api.php @@ -0,0 +1,35 @@ + '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/letter', 'PageController@letter'); + $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/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/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/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/Migrations/2023_01_12_134329_add_area_code_id_to_mall_orders_table.php b/modules/Mall/Database/Migrations/2023_01_12_134329_add_area_code_id_to_mall_orders_table.php new file mode 100644 index 0000000..a959bd4 --- /dev/null +++ b/modules/Mall/Database/Migrations/2023_01_12_134329_add_area_code_id_to_mall_orders_table.php @@ -0,0 +1,32 @@ +unsignedBigInteger('area_code_id')->nullable()->after('id')->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mall_orders', function (Blueprint $table) { + + }); + } +} 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..9f7e46e --- /dev/null +++ b/modules/Mall/Facades/Order.php @@ -0,0 +1,408 @@ +user_id = $user->getAuthIdentifier();; + + return $this; + } + + + /** + * Notes: 提货码 + * + * @Author: 玄尘 + * @Date: 2023/1/12 14:19 + * @param AreaCode $areaCode + * @return $this + */ + public function areaCode(AreaCode $areaCode): Order + { + $this->area_code_id = $areaCode->getKey(); + + 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, + 'area_code_id' => $this->area_code_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([ + 'user_id' => $order->user_id ?? 0, + '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 && $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..4d14195 --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/Action/Order/Delivered.php @@ -0,0 +1,48 @@ +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 = Express::query()->pluck('name', 'id'); + + $this->select('type', '方式') + ->options([ + OrderExpress::TYPE_LOGISTICS => '物流', + ]) + ->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..fe91b61 --- /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..fc4853a --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/OrderController.php @@ -0,0 +1,277 @@ +header('订单列表')->body($this->grid()); + } + + /** + * 已支付 + * + * @param Content $content [description] + * @return Content [type] [description] + * @author 玄尘 2020-03-03 + */ + public function paid(Content $content) + { + return $content->header('待发货')->body($this->grid(Order::STATUS_PAID)); + } + + /** + * 已发货 + * + * @param Content $content [description] + * @return \Encore\Admin\Layout\Content [type] [description] + * @author 玄尘 2020-03-03 + */ + public function delivered(Content $content) + { + return $content->header('已发货')->body($this->grid(Order::STATUS_DELIVERED)); + } + + protected function grid($state = ''): Grid + { + $grid = new Grid(new Order()); + + $grid->model() + ->when($state, function ($q) use ($state) { + $q->where('state', $state); + }) + ->whereIn('type', [Order::TYPE_NORMAL, Order::TYPE_SCORE]) + ->with(['shop', 'items', 'user']) + ->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('user.username', '下单用户') + ->display(function () { + return $this->user->username."({$this->user->info->nickname})"; + }); + + if ($state == Order::STATUS_PAID) { + $grid->column('收货地址') + ->display(function () { + return $this->express ? $this->express->getAllAddress() : ''; + }); + + } else { + $grid->column('type', '订单类型')->using(Order::TYPE_MAP)->label(); + $grid->column('expired_at', '过期时间')->sortable(); + } + + + $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('paid_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..1e96afa --- /dev/null +++ b/modules/Mall/Http/Controllers/Admin/StockOrderController.php @@ -0,0 +1,141 @@ +model() + ->where('type', Order::TYPE_SAMPLE) + ->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..93fac3e --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/IndexController.php @@ -0,0 +1,50 @@ + + * @return JsonResponse + */ + public function index(): JsonResponse + { + $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_NORMAL) + ->get(); + + + $user = Api::user(); + + $data = [ + 'user' => $user ? new UserBaseResource($user) : '', + 'show_goods' => $show_goods ? new GoodsBaseResource($show_goods) : '', + 'goods' => GoodsBaseResource::collection($goods), + ]; + + 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..3663aad --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/OrderBuyController.php @@ -0,0 +1,197 @@ +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; + + $detail = collect(); + $user = Api::user(); + $areaCode = $user->info->areaCode; + if (! $areaCode) { + return $this->failed('您没有提货码不可参与此活动'); + } + if ($areaCode->status != AreaCode::STATUS_INIT) { + return $this->failed('提货码已被使用,不可重复使用'); + } + +// $address_id = $request->address_id; +// 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, null, $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 = [ + 'areaCode' => new AreaCodeResource($areaCode), + 'detail' => $items, + 'amount' => floatval(bcadd($amount, $freight, 2)), + 'freight' => $freight, + 'weight' => $weight, + 'user' => $user ? new UserBaseResource($user) : '', + ]; + + 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(Request $request) + { + $validator = Validator::make($request->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()); + } + + $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; + + $areaCode = $user->info->areaCode; + if (! $areaCode) { + return $this->failed('您没有提货码不可参与此活动'); + } + if ($areaCode->status != AreaCode::STATUS_INIT) { + return $this->failed('提货码已被使用,不可重复使用'); + } + +// $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, null, $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) + ->areaCode($areaCode) + ->type($type) + ->source(['share_user_id' => $share_user_id,]) + ->create(); + + foreach ($orders as $order) { + if ($type == OrderModel::TYPE_SAMPLE) { + $order->pay(); + $order->areaCode->used(); + } + } + + return $this->success([ + 'order_no' => $orders->first()->order_no, + 'total' => $orders->first()->total, + 'score' => $user->account->score, + ]); + } + +} \ 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..b0bda1e --- /dev/null +++ b/modules/Mall/Http/Controllers/Api/PayController.php @@ -0,0 +1,136 @@ +success(new OrderPayResource($order)); + } + + /** + * Notes: 微信支付 + * + * @Author: 玄尘 + * @Date : 2021/5/20 9:11 + * @param \Modules\Mall\Models\Order $order + * @return mixed + * @throws \Exception + */ + public function wechat(Request $request, Order $order) + { + $user = Api::user(); + $gateway = $request->type ?? 'mp'; + $openid = $request->openid ?? ''; + + + if (! $order->can('pay')) { + return $this->failed('支付失败,订单不可支付'); + } + + if ($order->user()->isNot($user)) { + return $this->failed('支付失败,您没有权限支付'); + } + + + if (! $openid) { + $openid = $user->wechat->getUserOpenid($gateway); + } + + $extends = [ + 'notify_url' => route('api.payment.notify.wechat'), + 'payer' => [ + 'openid' => $openid, + ], + ]; + + $total = $order->total; + $payment = $order->createWechatPayment($user, $total, $gateway); + + $notify = $payment->getPaymentParams('商品下单', $extends); + + $data = [ + 'wechat' => $notify->toJson(), + 'total' => $payment->total, + ]; + return $this->success($data); + } + + /** + * 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..f9bce4f --- /dev/null +++ b/modules/Mall/Http/Exporter/OrderExporter.php @@ -0,0 +1,50 @@ +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..ad72ac5 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Goods/GoodsBaseResource.php @@ -0,0 +1,34 @@ + $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, + ], + 'skus' => SkuResource::collection($this->skus), + '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..2b1cf34 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/OrderExpressResource.php @@ -0,0 +1,24 @@ + $this->id, + 'name' => $this->name, + 'mobile' => $this->mobile, + 'express_name' => $this->express_id ? $this->express->name : '', + 'express_no' => $this->express_no, + 'deliver_at' => $this->deliver_at, + 'receive_at' => $this->receive_at, + '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/OrderPayResource.php b/modules/Mall/Http/Resources/Api/Order/OrderPayResource.php new file mode 100644 index 0000000..a295c48 --- /dev/null +++ b/modules/Mall/Http/Resources/Api/Order/OrderPayResource.php @@ -0,0 +1,27 @@ + $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, + 'items' => OrderItemResource::collection($this->items), + 'remark' => (string) $this->remark, + 'created_at' => (string) $this->created_at, + ]; + } + +} 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(), + ]; + } + + /** + * Notes: 提货码 + * + * @Author: 玄尘 + * @Date: 2023/1/12 14:24 + * @return BelongsTo + */ + public function areaCode(): BelongsTo + { + return $this->belongsTo(AreaCode::class); + } + +} 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..36526e2 --- /dev/null +++ b/modules/Mall/Models/OrderItem.php @@ -0,0 +1,130 @@ + '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..cfafb30 --- /dev/null +++ b/modules/Mall/Models/Traits/GoodsAttribute.php @@ -0,0 +1,206 @@ +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 + { + $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..8843545 --- /dev/null +++ b/modules/Mall/Models/Traits/HasRegion.php @@ -0,0 +1,69 @@ + + * @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; + } + + public function getAllAddress() + { + return $this->name.' '.$this->mobile.' '.$this->getFullAddress(); + } + +} diff --git a/modules/Mall/Models/Traits/OrderActions.php b/modules/Mall/Models/Traits/OrderActions.php new file mode 100644 index 0000000..52b525e --- /dev/null +++ b/modules/Mall/Models/Traits/OrderActions.php @@ -0,0 +1,185 @@ + + * @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)); + + 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/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 @@ +
+
+

订单编号:{{ $order->order_no }}

+ +
+ + + +
+
    + @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 }}
+
+
+
+
+ +
+
+
+
+

商品信息

+
+
+ + + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + + + @foreach ($order->items as $item) + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + @endforeach + + +
id商品名称属性数量单价水晶退款/退货
{{ $item->id }}{{ $item->source['goods_name'] }}{{ $item->source['unit']??'---' }}{{ $item->qty }}{{ $item->price }}{{ $item->source['score'] }}{{ $item->isRefund()?'是':'否' }}
+
+
+
+
+ @if ($order->express) +
+
+
+
+

物流信息

+
+
+ + + + + + + + + @if($order->express->express_no) + + + @endif + + + + + + + + + + + @if($order->express->express_no) + + + @endif + + + +
Id类型收件人电话地址物流公司物流单号经办人
{{ $order->express->id }}{{ $order->express->type_text }}{{ $order->express->name }}{{ $order->express->mobile }}{{ $order->express->getFullAddress() }}物流公司:{{$order->express->express->name}}物流单号:{{$order->express->express_no }}{{ $order->express->person }}
+
+
+
+
+ @endif + + @if ($order->payments->isNotEmpty()) +
+
+
+
+

支付信息

+
+
+ + + + + + + + + + + + + + @foreach ($order->payments as $payment) + + + + + + + + + + @endforeach + +
Id支付方式支付金额(元)流水号支付单号状态支付时间
{{ $payment->id }}{{ $payment->driver_text }}{{ $payment->total }}{{ $payment->trade_id }}{{ $payment->transaction_id }}{{ $payment->state_text }}{{ $payment->created_at }}
+
+
+
+
+ @endif + + @if ($order->refunds->isNotEmpty()) +
+
+
+
+

退款/货信息

+
+
+ + + + + + + + + + + + + @foreach ($order->refunds as $refund) + + + + + + + + + @endforeach + +
退款/货单号申请退款金额实退金额状态申请时间退款时间
{{ $refund->refund_no }}{{ $refund->refund_total }}{{ $refund->actual_total }}{{ $refund->state_text }}{{ $refund->created_at }}{{ $refund->refunded_at }}
+
+
+
+
+ @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 @@ +
+
+

订单编号:{{ $order->order_no }}

+ +
+ + + +
+
    + @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 }}
+
+
+
+
+ +
+
+
+
+

商品信息

+
+
+ + + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + + + @foreach ($order->items as $item) + + + + {{-- --}} + + + {{-- --}} + {{-- --}} + + @endforeach + + +
id商品名称属性数量单价水晶退款/退货
{{ $item->id }}{{ $item->source['goods_name'] }}{{ $item->source['unit']??'---' }}{{ $item->qty }}{{ $item->price }}{{ $item->source['score'] }}{{ $item->isRefund()?'是':'否' }}
+
+
+
+
+ @if ($order->express) +
+
+
+
+

物流信息

+
+
+ + + + + + + + + @if($order->express->express_no) + + + @endif + + + + + + + + + + + @if($order->express->express_no) + + + @endif + + + +
Id类型收件人电话地址物流公司物流单号经办人
{{ $order->express->id }}{{ $order->express->type_text }}{{ $order->express->name }}{{ $order->express->mobile }}{{ $order->express->getFullAddress() }}物流公司:{{$order->express->express->name}}物流单号:{{$order->express->express_no }}{{ $order->express->person }}
+
+
+
+
+ @endif + + @if ($order->payments->isNotEmpty()) +
+
+
+
+

支付信息

+
+
+ + + + + + + + + + + + + + @foreach ($order->payments as $payment) + + + + + + + + + + @endforeach + +
Id支付方式支付金额(元)流水号支付单号状态支付时间
{{ $payment->id }}{{ $payment->driver_text }}{{ $payment->total }}{{ $payment->trade_id }}{{ $payment->transaction_id }}{{ $payment->state_text }}{{ $payment->created_at }}
+
+
+
+
+ @endif + + @if ($order->refunds->isNotEmpty()) +
+
+
+
+

退款/货信息

+
+
+ + + + + + + + + + + + + @foreach ($order->refunds as $refund) + + + + + + + + + @endforeach + +
退款/货单号申请退款金额实退金额状态申请时间退款时间
{{ $refund->refund_no }}{{ $refund->refund_total }}{{ $refund->actual_total }}{{ $refund->state_text }}{{ $refund->created_at }}{{ $refund->refunded_at }}
+
+
+
+
+ @endif + +
diff --git a/modules/Mall/Routes/admin.php b/modules/Mall/Routes/admin.php new file mode 100644 index 0000000..91bdebe --- /dev/null +++ b/modules/Mall/Routes/admin.php @@ -0,0 +1,87 @@ + '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->get('orders/paid', 'OrderController@paid'); + $router->get('orders/delivered', 'OrderController@delivered'); + $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..68d9447 --- /dev/null +++ b/modules/Mall/Routes/api.php @@ -0,0 +1,125 @@ + '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}', 'PayController@index')->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..b381ff0 --- /dev/null +++ b/modules/Mall/Traits/HasOrders.php @@ -0,0 +1,129 @@ + + * @return HasMany + */ + public function orders(): HasMany + { + return $this->hasMany(Order::class); + } + + public function orderItems(): HasMany + { + return $this->hasMany(OrderItem::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 '不可提货'; + } + } + + /** + * Notes: 获取订单数据 + * + * @Author: 玄尘 + * @Date: 2022/12/30 20:57 + */ + public function getOrderCount(): array + { + return [ + 'all' => $this->orders()->common()->count(), + 'paid' => $this->orders()->paid()->count(), + 'unpay' => $this->orders()->unpay()->count(), + 'delivered' => $this->orders()->delivered()->count(), + ]; + } + + /** + * Notes: 获取捐赠数量 + * + * @Author: 玄尘 + * @Date: 2022/12/31 17:14 + */ + public function getJzCount() + { + return OrderItem::query() + ->whereHas('order', function ($q) { + $q->where('type', Order::TYPE_NORMAL) + ->where('user_id', $this->id) + ->whereIn('state', [ + Order::STATUS_PAID, + Order::STATUS_DELIVERED, + Order::STATUS_SIGNED, + Order::STATUS_COMPLETED, + ]); + })->sum('qty'); + } + + + /** + * Notes: 获取所有捐赠数 + * + * @Author: 玄尘 + * @Date: 2022/12/31 18:03 + * @return int|mixed + */ + public function getALlJzCount() + { + return OrderItem::query() + ->whereHas('order', function ($q) { + $q->where('type', Order::TYPE_NORMAL) + ->whereIn('state', [ + Order::STATUS_PAID, + Order::STATUS_DELIVERED, + Order::STATUS_SIGNED, + Order::STATUS_COMPLETED, + ]); + })->sum('qty'); + } + + /** + * Notes: 剩余 + * + * @Author: 玄尘 + * @Date: 2023/1/3 10:55 + */ + public function getResidue() + { + $hasAllJzCOunt = $this->getALlJzCount(); + + return bcsub(1000, $hasAllJzCOunt); + } + +} \ 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/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/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/Storage/Config/config.php b/modules/Storage/Config/config.php new file mode 100644 index 0000000..003944d --- /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'), // 使用 ssl 这里设置如: https://oss-cn-beijing.aliyuncs.com + 'bucket' => env('OSS_BUCKET'), + 'isCName' => env('OSS_IS_CNAME', false), + // 如果 isCname 为 false,endpoint 应配置 oss 提供的域名如:`oss-cn-beijing.aliyuncs.com`,否则为自定义域名,,cname 或 cdn 请自行到阿里 oss 后台配置并绑定 bucket + // 如果有更多的 bucket 需要切换,就添加所有bucket,默认的 bucket 填写到上面,不要加到 buckets 中 + '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), + ], + ], + // 以下是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..1915d97 --- /dev/null +++ b/modules/Storage/Http/Controllers/OssController.php @@ -0,0 +1,198 @@ + + * @param \Illuminate\Http\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 = Config::get('storage.upload_path').date('/Y/m/d'); + $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 \Illuminate\Http\Request $request + * @return mixed + */ + public function uploads(Request $request) + { + $fullFile = []; + $files = []; + if ($request->file()) { + $i = 1; + foreach ($request->file() as $key => $upload) { + $size = File::size($upload->path()); + + if ($size > Config::get('storage.max_upload_size')) { + $message = '上传失败,图片大小超过5M'; + + 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 = Config::get('storage.upload_path').date('/Y/m/d'); + $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; + $i++; + } + + return $this->success([ + 'path' => $files, + 'url' => $fullFile, + ]); + } else { + return $this->failed('没有图片'); + } + } + + /** + * Notes : STS 授权直传 + * + * @Date : 2021/4/25 5:24 下午 + * @Author : + * @return mixed + */ + public function sts() + { + $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..c5e4ae5 --- /dev/null +++ b/modules/Storage/Providers/StorageServiceProvider.php @@ -0,0 +1,69 @@ +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/Routes/api.php b/modules/Storage/Routes/api.php new file mode 100644 index 0000000..6eee763 --- /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', 'OssController@sts'); +}); \ No newline at end of file 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..f31e0d1 --- /dev/null +++ b/modules/Storage/composer.json @@ -0,0 +1,34 @@ +{ + "name": "uztech/storage-storage", + "description": "文件存储模块", + "type": "laravel-module", + "authors": [ + { + "name": "Jason.Chen", + "email": "chenjxlg@163.com" + } + ], + "require": { + "php": "^7.3|^8.0", + "alibabacloud/sts": "^1.8", + "encore/laravel-admin": "^1.8", + "iidestiny/laravel-filesystem-oss": "^2.1", + "jasonc/api": "^5.0.0", + "joshbrw/laravel-module-installer": "^2.0", + "laravel/framework": "^8.5", + "nwidart/laravel-modules": "^8.2" + }, + "extra": { + "module-dir": "modules", + "laravel": { + "providers": [], + "aliases": { + } + } + }, + "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..619beb0 --- /dev/null +++ b/modules/Task/Http/Controllers/Admin/TaskController.php @@ -0,0 +1,125 @@ +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); + + + $status = [ + 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'], + ]; + $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/.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..e7afbbc --- /dev/null +++ b/modules/User/Config/config.php @@ -0,0 +1,128 @@ + 'User', + + //h5设置 + 'web' => [ + 'base' => env('USER_WEB_BASE', '') + ], + //体验官配置 + 'experience' => [ + 'end_at' => '2022-08-31',//截止时间 + 'service' => [ + 'bank' => '中国银行深圳证券交易所支行', + 'bank_no' => '762773656978', + ], + ], + + //身份id + 'identities' => [ + 'experience' => 2, + 'jk' => 3, + 'nk' => 4, + 'cs' => 5, + 'hhr' => 6, + ], + + /* + |-------------------------------------------------------------------------- + | 后台是否显示新增用户按钮 + |-------------------------------------------------------------------------- + */ + 'create_user_by_admin' => env('USER_CREATE_BY_ADMIN', true), + + /* + |-------------------------------------------------------------------------- + | 后台是否显示新增用户按钮 + |-------------------------------------------------------------------------- + */ + 'edit_user_by_admin' => env('USER_EDIT_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' => [ + 'url' => env('USER_INVITE_URL', ''), + '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..c3b171a --- /dev/null +++ b/modules/User/Config/identity.php @@ -0,0 +1,26 @@ + false, + 'conditions' => [ + 'price' => '开通金额', + 'cost' => '原价', + ], + 'rules' => [ + + ], + 'show_rules' => [ + 'open_get_goods' => '获得产品', + ], + 'stars' => [ + '1' => '1星', + '2' => '2星', + '3' => '3星', + '4' => '4星', + '5' => '5星', + ] +]; \ No newline at end of file diff --git a/modules/User/Console/Commands/AutoRemindUserCase.php b/modules/User/Console/Commands/AutoRemindUserCase.php new file mode 100644 index 0000000..ba255ac --- /dev/null +++ b/modules/User/Console/Commands/AutoRemindUserCase.php @@ -0,0 +1,57 @@ +whereHas('user', function ($q) { + $q->whereHas('identityMiddle', function ($q) { + $q->whereIn('identity_id', [2, 3, 4, 5, 6]); + }); + }) + ->where('need_case', 1) + ->where('is_finish', 1) + ->chunkById(1000, function ($signs) { + foreach ($signs as $key => $sign) { + $sign->user->notify(new SystemUpdateCase()); + } + }); + } +} diff --git a/modules/User/Console/Commands/AutoRemindUserSign.php b/modules/User/Console/Commands/AutoRemindUserSign.php new file mode 100644 index 0000000..781afb7 --- /dev/null +++ b/modules/User/Console/Commands/AutoRemindUserSign.php @@ -0,0 +1,70 @@ +ty()->first(); + if ($tyIdentity) { + User::query() + ->with('sign') + ->whereHas('identityMiddle', function ($q) { + $q->whereIn('identity_id', [2, 3, 4, 5, 6]); + }) + ->whereHas('sign', function ($q) { + $q->where('is_finish', 0); + }) + ->where(function ($query) { + $query->whereDoesntHave('signLogs') + ->orWhereHas('signLogs', function ($q) { + $q->whereDate('date', '<>', now()->format('Y-m-d'))->orWhere; + }); + }) + ->chunkById(1000, function ($users) { + foreach ($users as $user) { + $user->notify(new SystemRemindUserSign()); + } + }); + } + + } + +} 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..baaa5d4 --- /dev/null +++ b/modules/User/Console/Kernel.php @@ -0,0 +1,15 @@ +command('xuanchen:auto-set-user-identity-over')->everyMinute(); + } + +} \ 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..94d9619 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_identities_table.php @@ -0,0 +1,55 @@ +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..f3d8cd7 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_orders_table.php @@ -0,0 +1,44 @@ +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_banners_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_banners_table.php new file mode 100644 index 0000000..437ca18 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_banners_table.php @@ -0,0 +1,37 @@ +id(); + $table->boolean('type')->default(1); + $table->string('title')->nullable(); + $table->string('cover')->nullable(); + $table->boolean('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_sign_banners'); + } + +} 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_sign_texts_table.php b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_texts_table.php new file mode 100644 index 0000000..feb5c24 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_sign_texts_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('title')->nullable(); + $table->string('description')->nullable(); + $table->string('sub_description')->nullable(); + $table->boolean('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_sign_texts'); + } + +} 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..a6d25ec --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_user_signs_table.php @@ -0,0 +1,40 @@ +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..e528061 --- /dev/null +++ b/modules/User/Database/Migrations/0000_00_00_000000_create_users_table.php @@ -0,0 +1,36 @@ +id()->startingValue(10000); + $table->string('username')->unique(); + $table->string('password')->nullable(); + $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_08_09_134110_create_user_perves_table.php b/modules/User/Database/Migrations/2022_08_09_134110_create_user_perves_table.php new file mode 100644 index 0000000..f12edd6 --- /dev/null +++ b/modules/User/Database/Migrations/2022_08_09_134110_create_user_perves_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedInteger('user_id'); + $table->morphs('order'); + $table->unsignedInteger('layer'); + $table->unsignedInteger('parent_id'); + $table->longText('bloodline'); + $table->unsignedDecimal('perf', 12, 2)->default(0); + $table->string('remark')->nullable()->comment('描述'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_perves'); + } +} diff --git a/modules/User/Database/Migrations/2022_08_19_161144_create_user_identity_coupons_table.php b/modules/User/Database/Migrations/2022_08_19_161144_create_user_identity_coupons_table.php new file mode 100644 index 0000000..5de2da8 --- /dev/null +++ b/modules/User/Database/Migrations/2022_08_19_161144_create_user_identity_coupons_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('identity_id'); + $table->unsignedBigInteger('source_id')->comment('来源用户'); + $table->tinyInteger('type')->comment('获取优惠券类型'); + $table->string('code')->comment('优惠券编号'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_identity_coupons'); + } +} diff --git a/modules/User/Database/Migrations/2022_08_31_085224_add_star_to_user_identity_table.php b/modules/User/Database/Migrations/2022_08_31_085224_add_star_to_user_identity_table.php new file mode 100644 index 0000000..9aef2b7 --- /dev/null +++ b/modules/User/Database/Migrations/2022_08_31_085224_add_star_to_user_identity_table.php @@ -0,0 +1,32 @@ +tinyInteger('star')->default(0)->after('serial'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('user_identity', function (Blueprint $table) { + + }); + } +} diff --git a/modules/User/Database/Migrations/2022_09_07_140701_add_status_to_users_table.php b/modules/User/Database/Migrations/2022_09_07_140701_add_status_to_users_table.php new file mode 100644 index 0000000..b078757 --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_07_140701_add_status_to_users_table.php @@ -0,0 +1,32 @@ +boolean('status')->default(1)->comment('状态')->after('password'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + + }); + } +} 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_16_143020_add_tag_to_users_table.php b/modules/User/Database/Migrations/2022_09_16_143020_add_tag_to_users_table.php new file mode 100644 index 0000000..ab8c5aa --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_16_143020_add_tag_to_users_table.php @@ -0,0 +1,32 @@ +boolean('tag')->default(1)->comment('标签')->after('status'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + + }); + } +} diff --git a/modules/User/Database/Migrations/2022_09_20_151841_create_user_invites_table.php b/modules/User/Database/Migrations/2022_09_20_151841_create_user_invites_table.php new file mode 100644 index 0000000..5e53635 --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_20_151841_create_user_invites_table.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('user_id')->index()->comment('归属人'); + $table->unsignedBigInteger('active_id')->index()->comment('激活人'); + $table->string('number'); + $table->string('code'); + $table->boolean('status')->default(1)->comment('状态'); + $table->timestamp('start_at')->nullable(); + $table->timestamp('end_at')->nullable(); + $table->timestamp('actived_at')->nullable()->comment('激活时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_invites'); + } +} 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_30_093417_add_channel_to_users_table.php b/modules/User/Database/Migrations/2022_09_30_093417_add_channel_to_users_table.php new file mode 100644 index 0000000..fd96bd7 --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_30_093417_add_channel_to_users_table.php @@ -0,0 +1,32 @@ +integer('channel_id')->nullable()->after('tag')->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + + }); + } +} diff --git a/modules/User/Database/Migrations/2022_09_30_100124_create_user_channels_table.php b/modules/User/Database/Migrations/2022_09_30_100124_create_user_channels_table.php new file mode 100644 index 0000000..ae4e4c2 --- /dev/null +++ b/modules/User/Database/Migrations/2022_09_30_100124_create_user_channels_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('name')->comment('渠道名称'); + $table->string('cover')->nullable()->comment('封面'); + $table->string('code')->unique()->comment('渠道码'); + $table->string('mini_code')->nullable()->comment('小程序码'); + $table->string('mini_path')->nullable()->comment('小程序路径'); + $table->string('official_code')->nullable()->comment('h5码'); + $table->boolean('status')->default(1); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_channels'); + } +} diff --git a/modules/User/Database/Migrations/2022_12_31_201255_add_user_id_to_mall_order_items_table.php b/modules/User/Database/Migrations/2022_12_31_201255_add_user_id_to_mall_order_items_table.php new file mode 100644 index 0000000..8402de8 --- /dev/null +++ b/modules/User/Database/Migrations/2022_12_31_201255_add_user_id_to_mall_order_items_table.php @@ -0,0 +1,32 @@ +unsignedBigInteger('user_id')->index()->after('id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mall_order_items', function (Blueprint $table) { + + }); + } +} 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..28e6905 --- /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..2ac3765 --- /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->isExperience()) { + $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->isExperience()) { + $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/AllotCard.php b/modules/User/Http/Controllers/Admin/Actions/AllotCard.php new file mode 100644 index 0000000..9072754 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/AllotCard.php @@ -0,0 +1,75 @@ +validate([ + 'startNum' => 'required|integer|min:1|lte:endNum', + 'endNum' => 'required|integer|min:1', + ], [ + 'startNum.required' => '开始号码必须填写', + 'startNum.integer' => '开始号码必须是整数', + 'startNum.min' => '开始号码最小为1', + 'startNum.lte' => '开始号码要小于结束号码', + 'endNum.required' => '结束号码必须填写', + 'endNum.integer' => '结束号码必须是整数', + 'endNum.min' => '结束号码最小为1', + ]); + + $response = $this->response(); + $startNum = $request->startNum ?: 0; + $endNum = $request->endNum ?: 0; + $user_id = $request->user_id; + + +// $startNum = sprintf("%'.08d", $startNum); +// $endNum = sprintf("%'.08d", $endNum++); + + + $isActive = UserInvite::whereBetween('id', [$startNum, $endNum])->where('status', '!=', 1)->value('code'); + if ($isActive) { + $response->status = false; + return $response->error($isActive.'不可分配'); + } + + UserInvite::whereBetween('id', [$startNum, $endNum])->update([ + 'user_id' => $user_id, + 'status' => UserInvite::STATUS_ALLOT, + ]); + + return $response->success('卡分配完毕')->refresh(); + } + + public function form() + { + $users = User::leftJoin('user_infos as info', 'users.id', '=', 'info.user_id') +// ->whereHas('identities', function ($q) { +// $q->where('id', 6); +// }) + ->select('id', DB::raw('CONCAT(username, " [", info.nickname, "]") as text')) + ->pluck('text', 'id'); + + $this->select('user_id', '会员') + ->options($users) + ->required(); + $this->text('startNum', '开始')->rules('required|integer|min:1'); + $this->text('endNum', '结束')->rules('required|integer|min:1'); + + } + + public function html() + { + return "分配激活码"; + } +} 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..a1675ad --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/JoinIdentity.php @@ -0,0 +1,70 @@ +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/LinkJoinCsVip.php b/modules/User/Http/Controllers/Admin/Actions/LinkJoinCsVip.php new file mode 100644 index 0000000..ddf721e --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/LinkJoinCsVip.php @@ -0,0 +1,20 @@ +row->id.'/join'); + } + +} \ 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..efc2e3e --- /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/SetTag.php b/modules/User/Http/Controllers/Admin/Actions/SetTag.php new file mode 100644 index 0000000..a9f6cea --- /dev/null +++ b/modules/User/Http/Controllers/Admin/Actions/SetTag.php @@ -0,0 +1,41 @@ +tag; + + $user->update([ + 'tag' => $tag + ]); + + return $this->response()->success('加入标签成功')->refresh(); + } catch (Exception $e) { + return $this->response()->error($e->getMessage())->refresh(); + } + } + + public function form(User $user) + { + $this->select('tag', '标签') + ->options(User::TAGS) + ->required(); + $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/ChannelController.php b/modules/User/Http/Controllers/Admin/ChannelController.php new file mode 100644 index 0000000..9155a76 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/ChannelController.php @@ -0,0 +1,81 @@ +disableFilter(); + + $grid->column('name', '名称'); + $grid->column('code', '渠道码'); + $grid->column('mini_path', '小程序地址'); + $grid->column('mini_code', '小程序码')->display(function () { + return Storage::url($this->mini_code); + })->image(); + $grid->column('official_code', 'h5二维码')->display(function () { + return Storage::url($this->official_code); + })->image(); + + $grid->column('status', '状态')->bool(); + + return $grid; + } + + protected function form(): Form + { + $form = new Form(new UserChannel()); + + $form->text('name', '名称')->required(); + $this->cover($form); + $form->text('code', '渠道码')->required(); + $form->text('mini_path', '小程序路径')->required(); + + $form->switch('status', '显示')->default(1); + + $form->saved(function (Form $form) { + $url = 'channel/code'; + $info = $form->model(); + + //h5 + $base64_img = $info->official_code_base64; + preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_img, $res); + $base64_img = base64_decode(str_replace($res[1], '', $base64_img)); + $h5Name = 'official_code_'.$info->id.'.png'; + $H5path = $url.'/'.$h5Name; + Storage::put($H5path, $base64_img); + $data['official_code'] = $H5path; + + //小程序 + $app = app('wechat.mini_program'); + $arr['channel'] = $info->code; + $str = $info->mini_path.'?'.http_build_query($arr); + $response = $app->app_code->getQrCode($str); + + if ($response instanceof StreamResponse) { + $file = $response->saveAs(storage_path('app/public/'.$url), 'mini_code_'.$info->id.'.png'); + $data['mini_code'] = $url.'/'.$file; + } + + $form->model()->update($data); + + }); + return $form; + } + +} \ 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..9d918ee --- /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..4c164b4 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/IndexController.php @@ -0,0 +1,179 @@ + + * @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) { + if (! config('user.edit_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('tag', '标签') + ->using(User::TAGS) + ->label([ + User::TAG_USER => 'primary', + User::TAG_WALKER => 'success', + User::TAG_ACADEMY => 'danger', + ]); +// $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('star', '星级') +// ->display(function () { +// $data = []; +// foreach ($this->identities as $identity) { +// $data[] = $identity->getOriginal('pivot_star').' 星'; +// } +// +// 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/InviteController.php b/modules/User/Http/Controllers/Admin/InviteController.php new file mode 100644 index 0000000..b945dae --- /dev/null +++ b/modules/User/Http/Controllers/Admin/InviteController.php @@ -0,0 +1,115 @@ +disableCreateButton(); + $grid->disableRowSelector(); + $grid->disableActions(); + $grid->disableColumnSelector(); + + $grid->tools(function (Grid\Tools $tools) { + $tools->append(new AllotCard()); + }); + + $grid->header(function ($query) { + $form = new Form(); + $form->action(admin_url('/users/invites/createCard')); +// $form->hidden('_token')->default(csrf_token()); + $form->disableReset(); + $form->text('num', '生成数量')->rules('required|integer|min:1'); + + $box = new Box('生成激活码', $form->render()); + $box->collapsable(); + $box->style('success'); + $box->solid(); + return $box->render(); + }); + + + $grid->model()->orderBy('id', 'desc'); + + $grid->column('id', '序号')->sortable(); +// $grid->column('number', '编号')->sortable(); + $grid->column('归属')->display(function () { + return ($this->user->username ?? '---')."
".($this->user->info->nickname ?? '---'); + }); + + $grid->column('code', '激活码'); + $grid->column('status', '状态') + ->using(UserInvite::STATUS) + ->label([ + 1 => 'warning', + 2 => 'success', + 3 => 'info', + ]); + + $grid->column('激活用户')->display(function () { + return ($this->activeUser->username ?? '---')."
".($this->activeUser->info->nickname ?? '---'); + + }); + $grid->column('actived_at', '激活时间'); + $grid->column('created_at', '创建时间'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('code', '激活码'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->like('user.username', '归属'); + }); + $filter->column(1 / 3, function (Grid\Filter $filter) { + $filter->equal('status', '状态')->select(UserInvite::STATUS); + }); + + }); + + return $grid; + } + + public function createCard(Request $request) + { + $request->validate([ + 'num' => 'required|integer|min:1', + ], [ + 'num.required' => '数量必须填写', + 'num.integer' => '数量必须是整数', + 'num.min' => '数量最小为1', + ]); + + $num = $request->num; + $codeStart = (UserInvite::max('id') ?? 0) + 1; + + $data = []; + while ($num > 0) { + $data[] = [ + 'number' => sprintf("%'.08d", $codeStart++), + 'code' => Str::random(14), + 'status' => UserInvite::STATUS_INIT, + 'created_at' => now(), + 'updated_at' => now(), + ]; + --$num; + } + UserInvite::insert($data); + admin_success('成功生成', $request->num.'个码已经生成。'); + return back(); + } + +} diff --git a/modules/User/Http/Controllers/Admin/JoinCsVIpController.php b/modules/User/Http/Controllers/Admin/JoinCsVIpController.php new file mode 100644 index 0000000..381bfa4 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/JoinCsVIpController.php @@ -0,0 +1,166 @@ +title; + } + + /** + * Create interface. + * + * @param Content $content + * @return Content + */ + public function create(Content $content, User $user): Content + { + return $content + ->title($this->title()) + ->description($this->description['create'] ?? trans('admin.create')) + ->body($this->form($user)); + } + + public function form($user): Form + { + $channel = config('account.sms.template.balance'); + $form = new Form(new User); + + $form->tools(function (Form\Tools $tools) { + // 去掉`列表`按钮 + $tools->disableList(); + }); + + $form->display('联名卡会员账户')->default($user->account->drill); + $form->display('用户昵称')->default($user->info->nickname)->readonly(); + $form->display('手机号')->default($user->username)->readonly(); + $form->select('rule_id', '规则') + ->options(function () { + return AccountRule::query() + ->whereIn('name', [ + 'system_drill_in', + ]) + ->get() + ->pluck('longname', 'id'); + }) + ->required(); + + $form->text('mobile', '手机号') + ->value(config('account.sms.mobile')) + ->disable() + ->required(); + + $form->text('amount', '数值')->required(); + $form->hidden('user_id', '用户id')->value($user->id); + $form->text('remark', '备注')->help('告知用户调整原因')->required(); + + $form->html(view('admin.tools.sms', compact('channel')), '验证码'); + $form->setAction('/admin/user/csvip'); + + return $form; + } + + /** + * Notes: 设置积分 + * + * @Author: 玄尘 + * @Date : 2021/4/26 13:52 + * @param \Illuminate\Http\Request $request + */ + public function setIdentity(Request $request) + { + $validator = \Validator::make($request->all(), [ + 'user_id' => 'required', + 'rule_id' => 'required', + 'amount' => 'required|integer', + 'code' => 'required', + ], [ + 'user_id.required' => '缺少操作的用户', + 'rule_id.required' => '缺少规则id', + 'amount.required' => '缺少增加/减少的数值', + 'amount.integer' => '数值必须是整数', + 'code.required' => '缺少短信验证码', + ]); + + if ($validator->fails()) { + return $this->backErrorMessage($validator->errors()->first()); + } + + $amount = $request->amount; + $channel = $request->channel ?? config('account.sms.template.default'); + + $user = User::find($request->user_id); + + $res = \Sms::check(config('account.sms.mobile'), $request->code, $channel); + + if ($res != true) { + return $this->backErrorMessage('验证码校验失败'); + } + + $rule = AccountRule::find($request->rule_id); + if (! $rule) { + return $this->backErrorMessage('规则未找到'); + } + + if (in_array($rule->name, config('account.system_set_score.in'))) { + $res = $user->account->rule($rule->id, $amount, false, [ + 'expired_at' => now()->addYear(), + 'remark' => '后台操作增加余额', + ]); + } elseif (in_array($rule->name, config('account.system_set_score.out'))) { + if ($user->account->{$rule->type} < $amount) { + return $this->backErrorMessage('账户余额不足。扣除失败'); + } + + $res = $user->account->rule($rule->id, -$amount, false, [ + 'remark' => '后台操作扣除余额', + ]); + } else { + return $this->backErrorMessage('规则出错'); + } + + if ($res === true) { + admin_toastr('操作完成'); + + return redirect()->to('/admin/users?username='.$user->username); + } + + return $this->backErrorMessage('操作失败'.$res); + } + + public function backErrorMessage($message) + { + $error = new MessageBag([ + 'title' => '错误', + 'message' => $message, + ]); + + return back()->withInput()->with(compact('error')); + } + +} diff --git a/modules/User/Http/Controllers/Admin/OrderController.php b/modules/User/Http/Controllers/Admin/OrderController.php new file mode 100644 index 0000000..375e4d0 --- /dev/null +++ b/modules/User/Http/Controllers/Admin/OrderController.php @@ -0,0 +1,98 @@ +model()->latest(); + $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('coupon', '优惠券') + ->display(function () { + if ($this->useCouponLog) { + return $this->useCouponLog->couponGrant->code; + } else { + return '---'; + } + }); + $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..c82a0c7 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Auth/SmsController.php @@ -0,0 +1,132 @@ + + * @param SmsRequest $request + * @return JsonResponse + */ + public function send(SmsRequest $request): JsonResponse + { + $mobile = $request->mobileNo; + + try { + Sms::sendVerificationCode($mobile); + + if (Arr::get(Sms::getConfig(), 'debug')) { + $message = '短信发送成功,测试短信码'.Arr::get(Sms::getConfig(), 'debug_code'); + } else { + $message = '短信发送成功'; + } + + return $this->success($message); +// +// $isExists = User::where('username', $mobile)->exists(); +// +// return $this->success([ +// 'new' => ! $isExists, +// 'message' => $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 ?? '';//渠道 + $name = $request->name ?? '';//姓名 + $delivery_code = $request->delivery_code ?? '';//提货码 + + $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]; + } + + $check = Sms::checkCode($mobileNo, $code); + if ($check == false) { + return $this->failed('验证码不正确', 422); + } + + $user = User::firstOrCreate([ + 'username' => $mobileNo, + ], [ + 'parent_id' => $parent, + 'channel_id' => $channel ? $channel->id : null, + 'password' => 111111, + ]); + + $is_new = $user->wasRecentlyCreated; + + $message = ''; + if ($is_new) { + if ($user->parent && $parent && $user->parent->id != $parent) { + $message = "您已与用户{$user->parent->info->nickname}绑定隶属关系,此次邀请码无效"; + } + } + + if ($name) { + $user->info->update([ + 'nickname' => $name + ]); + } + + $token = Api::login($user); + + event(new UserLoginSuccess($user, $request, '手机验证码')); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + 'message' => $message, + 'invite' => Hashids::connection('code')->encode($user->id), + '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..2ca4826 --- /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 \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\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 $this->success([ + 'openid' => $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..9d3c7ae --- /dev/null +++ b/modules/User/Http/Controllers/Api/Identity/IndexController.php @@ -0,0 +1,449 @@ +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; + + + $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; + } + + } + } + + if (in_array($identity->job, [Identity::JOB_JK, Identity::JOB_NK])) { + $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; + } + + $coupon_price = 0; + + $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) { + return $this->success('提交成功,请等待后台审核'); + } else { + return $this->failed('创建订单失败,请稍后再试'); + } + } + + /** + * Notes: 微信支付 + * + * @Author: 玄尘 + * @Date : 2021/6/7 14:42 + * @param Order $order + * @param Request $request + * @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)); + } + + /** + * Notes: 激活码激活 + * + * @Author: 玄尘 + * @Date: 2022/9/21 10:06 + * @param Order $order + * @param Request $request + * @return JsonResponse|mixed + */ + public function invite(Order $order, Request $request) + { + $code = $request->code; + + $user = Api::user(); + info($user->id); + + if (! $order->canPay()) { + return $this->failed('支付失败,订单不可支付'); + } + info($order->toJson()); + + if ($order->user()->isNot($user)) { + return $this->failed('支付失败,您没有权限支付'); + } + + $invite = UserInvite::where('code', $code)->first(); + if (! $invite) { + return $this->failed('未找到激活码信息'); + } + + if ($invite->status != UserInvite::STATUS_ALLOT) { + return $this->failed('激活码状态不对'); + } + + try { + $order->pay(); + $invite->use($user->id); + return $this->success('激活成功'); + } catch (\Exception $exception) { + return $this->failed($exception->getMessage()); + } + + + } + +} diff --git a/modules/User/Http/Controllers/Api/Identity/PartnerController.php b/modules/User/Http/Controllers/Api/Identity/PartnerController.php new file mode 100644 index 0000000..3c20b53 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Identity/PartnerController.php @@ -0,0 +1,105 @@ +where('identity_id', 0)->first(); + $value = true; + if ($order) { + $value = false; + + if ($order->state == Order::STATE_INIT) { + $text = '等待审核'; + } else { + $text = '您已开通'; + } + } + $data = [ + 'activity' => $activityInfo, + 'status' => [ + 'value' => $value, + 'text' => $text, + ] + ]; + + return $this->success($data); + + } + + /** + * Notes: 开通会员 + * + * @Author: 玄尘 + * @Date : 2021/6/4 10:02 + * @param Request $request + * @return JsonResponse + */ + public function store(Request $request): JsonResponse + { + $validator = Validator::make($request->all(), [ + 'name' => 'required', + 'card_no' => 'required|numeric', + 'cover' => 'required', + ], [ + 'name.required' => '缺少姓名', + 'card_no.required' => '缺少银行卡号', + 'card_no.numeric' => '银行卡号只能是数字', + 'cover.required' => '缺少打款凭证', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + $user = Api::user(); + + $data = [ + 'user_id' => $user->id, + 'identity_id' => 0, + 'year' => 1, + 'type' => 1, + 'name' => $request->name, + 'card_no' => $request->card_no, + 'cover' => $request->cover, + 'state' => Order::STATE_INIT, + 'price' => 0, + ]; + + $order = Order::create($data); + + if ($order) { + return $this->success('提交成功,请等待后台审核'); + } else { + return $this->failed('创建订单失败,请稍后再试'); + } + } + +} diff --git a/modules/User/Http/Controllers/Api/IndexController.php b/modules/User/Http/Controllers/Api/IndexController.php new file mode 100644 index 0000000..28d80d0 --- /dev/null +++ b/modules/User/Http/Controllers/Api/IndexController.php @@ -0,0 +1,198 @@ + + * @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(); + $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([ + 'code' => $code, + 'invite' => $invite, + 'children' => $user->children()->count(), + 'user_info' => new UserInfoBaseResource($user), + ]); + + } + + /** + * 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); + } + + /** + * 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('绑定失败'); + } + } + +} diff --git a/modules/User/Http/Controllers/Api/Invite/IndexController.php b/modules/User/Http/Controllers/Api/Invite/IndexController.php new file mode 100644 index 0000000..a1c2fe8 --- /dev/null +++ b/modules/User/Http/Controllers/Api/Invite/IndexController.php @@ -0,0 +1,42 @@ +status ?? 1; + $lists = UserInvite::query() + ->ByUser(Api::user()) + ->where('status', $status) + ->paginate(); + + return $this->success(new UserInviteCollection($lists)); + } + +} 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..35b88df --- /dev/null +++ b/modules/User/Http/Controllers/Api/Rank/IndexController.php @@ -0,0 +1,109 @@ +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/12/31 18:23 + */ + public function merit() + { + $users = $this->getAllUserJz(); + return $this->success($users); + } + + /** + * 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') + ->oldest('id') + ->get() + ->where('children_count', '>', 0) + ->map(function ($info) use ($user) { + $children_count = $info->children_count; + return [ + 'user' => [ + 'user_id' => $info->id, + 'username' => substr_replace($info->username, '****', -4, 4), + 'nickname' => $info->info->nickname_text ?? '', + 'avatar' => $info->info->avatar ?? '', + ], + 'total' => $children_count > 0 ? $children_count : '-', + ]; + }) + ->pad(10, [ + 'user' => [ + 'user_id' => 0, + 'username' => '-', + 'nickname' => '-', + 'avatar' => '-', + ], + 'total' => '-', + ]); + + $users = $users->toArray(); + + return $this->success($users); + } + +} 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..223c8e6 --- /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..3e6205e --- /dev/null +++ b/modules/User/Http/Controllers/Api/Socialite/WeChatController.php @@ -0,0 +1,489 @@ +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; + $weChat = app('wechat.mini_program'); + $session = $weChat->auth->session($code); + if ($session->errcode) { + return $this->failed($session->errmsg); + } + + try { + $decryptedData = $weChat->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()); + } + } + + /** + * 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()); + } + } + + /** + * Notes: 小程序登录加注册 + * + * @Author: 玄尘 + * @Date: 2023/1/12 8:53 + * @param Request $request + * @return JsonResponse|mixed + */ + public function miniLoginAndReg(Request $request) + { + $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; + $invite_code = $request->invite ?? '';//推荐码 + + $weChat = app('wechat.mini_program'); + $session = $weChat->auth->session($code); + + if (isset($session->errcode)) { + return $this->failed($session->errmsg); + } + + $decryptedData = $weChat->encryptor->decryptData( + $session->session_key, + $request->iv, + $request->encryptedData + ); + + $user = User::query()->where('username', $decryptedData['phoneNumber'])->first(); + if (! $user) { + $parent = 0; + if ($invite_code) { + $invite = Hashids::connection('code')->decode($invite_code); + + if (empty($invite)) { + return $this->failed('邀请码不正确'); + } + $parent = $invite[0]; + } + $user = User::query() + ->firstOrCreate([ + 'username' => $decryptedData['phoneNumber'], + ], [ + 'parent_id' => $parent, + 'password' => 111111, + ]); + + + } + + $token = Api::login($user); + + return $this->success([ + 'token_type' => 'Bearer', + 'access_token' => $token, + ]); + } catch (\Exception $e) { + return $this->failed($e->getMessage()); + } + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date: 2023/1/12 10:48 + */ + public function updateUserData(Request $request) + { + $user = Api::user(); + + $delivery_code = $request->delivery_code ?? '';//提货码 + $name = $request->name ?? '';//提货码 + $validator = \Validator::make($request->all(), [ + 'delivery_code' => 'required', + 'name' => 'required', + ], [ + 'delivery_code.required' => '缺少提货码', + 'name.required' => '缺少姓名', + ]); + + if ($validator->fails()) { + return $this->failed($validator->errors()->first()); + } + + if ($name) { + $user->info->update([ + 'nickname' => $name + ]); + } + + if ($delivery_code) { + if (! $user->info->delivery_code) { + $areaCode = AreaCode::query()->where('code', $delivery_code)->first(); + if ($areaCode->user_id) { + return $this->failed('当前提货码已被别人使用'); + } + + $user->info->update([ + 'delivery_code' => $delivery_code + ]); + $areaCode->update([ + 'user_id' => $user->id + ]); + } else { + if ($user->info->delivery_code && $user->info->delivery_code != $delivery_code) { + return $this->failed('您已经绑定过提货码,不能重复绑定'); + } + } + } + + return $this->success('更新成功'); + + + } +} 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..8671701 --- /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/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..20d153c --- /dev/null +++ b/modules/User/Http/Resources/IdentityMiddleResource.php @@ -0,0 +1,26 @@ +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/Invite/UserInviteCollection.php b/modules/User/Http/Resources/Invite/UserInviteCollection.php new file mode 100644 index 0000000..e3e35ad --- /dev/null +++ b/modules/User/Http/Resources/Invite/UserInviteCollection.php @@ -0,0 +1,20 @@ + $this->collection->map(function ($info) { + return new UserInviteResource($info); + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/User/Http/Resources/Invite/UserInviteResource.php b/modules/User/Http/Resources/Invite/UserInviteResource.php new file mode 100644 index 0000000..8913f14 --- /dev/null +++ b/modules/User/Http/Resources/Invite/UserInviteResource.php @@ -0,0 +1,23 @@ + $this->id, + 'number' => $this->number, + 'code' => $this->code, + 'user' => $this->user ? new UserBaseResource($this->user) : '', + 'activeUser' => $this->activeUser ? new UserBaseResource($this->activeUser) : '', + 'actived_at' => $this->actived_at ? $this->actived_at->format('Y-m-d') : '', + ]; + } + +} \ 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..f0ddf4f --- /dev/null +++ b/modules/User/Http/Resources/UserIdentityBaseResource.php @@ -0,0 +1,21 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover_url, + 'order' => $this->order, + 'is_experience' => (bool) $this->isExperience(), + ]; + } + +} diff --git a/modules/User/Http/Resources/UserIdentityResource.php b/modules/User/Http/Resources/UserIdentityResource.php new file mode 100644 index 0000000..213cf69 --- /dev/null +++ b/modules/User/Http/Resources/UserIdentityResource.php @@ -0,0 +1,80 @@ +getCondition('price', '0'); + $cost = $this->getCondition('cost', '0'); + $identityName = $this->name; + + 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; + } + + } + } 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 ?? "", + 'cost' => floatval($cost),//开通金额 + 'price' => floatval($price),//开通金额 + 'is_experience' => (bool) $this->isExperience(), + '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..8cea792 --- /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..92e95f3 --- /dev/null +++ b/modules/User/Http/Resources/UserInfoResource.php @@ -0,0 +1,55 @@ +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(), + 'created_at' => (string) $this->created_at, + 'status' => $this->getStateData(), + 'nowStatus' => $this->getNowStatus(), + 'canPick' => $this->canPick(), + 'count' => [ + 'relation' => $this->getRelationCount(),//下级 + 'orders' => $this->getOrderCount(), + '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..c463008 --- /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..91d7cba --- /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..1e30110 --- /dev/null +++ b/modules/User/Models/Identity.php @@ -0,0 +1,280 @@ + '原价', + '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_MONTH = 2; + const JOB_JK = 3; + const JOB_NK = 4; + + const JOBS = [ + self::JOB_YK => '游客', + self::JOB_MONTH => '月卡', + self::JOB_JK => '季卡', + self::JOB_NK => '年卡', + ]; + + 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; + } + } + + /** + * Notes: 是否是体验官 + * + * @Author: 玄尘 + * @Date: 2022/8/18 9:52 + */ + public function isExperience(): bool + { + return $this->job == self::JOB_TY; + } + +} 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..661bc87 --- /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..18adfcb --- /dev/null +++ b/modules/User/Models/Order.php @@ -0,0 +1,98 @@ + '待审核', + 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 : 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(); + } + + /** + * Notes: 后台空升创建升级单 + * + * @Author: 玄尘 + * @Date: 2022/8/30 15:04 + * @param $user + * @param $year + * @param $price + */ + public function addOrder($user, $year, $price) + { + + } + + +} 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/HasChannel.php b/modules/User/Models/Traits/HasChannel.php new file mode 100644 index 0000000..66b42f2 --- /dev/null +++ b/modules/User/Models/Traits/HasChannel.php @@ -0,0 +1,14 @@ +belongsTo(UserChannel::class); + } + +} \ 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/HasInvite.php b/modules/User/Models/Traits/HasInvite.php new file mode 100644 index 0000000..569f03e --- /dev/null +++ b/modules/User/Models/Traits/HasInvite.php @@ -0,0 +1,14 @@ +hasMany(UserInvite::class); + } + +} \ No newline at end of file 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/HasPerf.php b/modules/User/Models/Traits/HasPerf.php new file mode 100644 index 0000000..f796610 --- /dev/null +++ b/modules/User/Models/Traits/HasPerf.php @@ -0,0 +1,72 @@ +hasMany(UserPerf::class); + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2022/8/30 14:59 + * @param $perf 业绩 + * @param $order 升级订单 + * @param $remark + */ + public function addPerf($perf = 0, $order, $remark = '订单获得') + { + if ($perf) { + $this->perf()->create([ + 'order_type' => $order->getMorphClass(), + 'order_id' => $order->id, + 'layer' => $this->relation->layer, + 'parent_id' => $this->relation->parent_id, + 'bloodline' => $this->relation->bloodline, + 'perf' => $perf, + 'remark' => $remark, + ]); + } + } + + /** + * Notes: 推荐业绩 + * + * @Author: 玄尘 + * @Date : 2022/8/30 14:59 + * @return mixed + */ + public function recommendPerf() + { + return UserPerf::where('user_id', $this->id) + ->orWhere('parent_id', $this->id) + ->sum('perf'); + } + + /** + * Notes: 总业绩 + * + * @Author: 玄尘 + * @Date : 2022/8/30 15:00 + * @return mixed + */ + public function allPerf(array $timeBetween = null) + { + return UserPerf::where(function ($query) { + $query->where('user_id', $this->id) + ->orWhere('bloodline', 'like', '%,'.$this->id.',%'); + }) + ->when($timeBetween, function ($query) use ($timeBetween) { + $query->whereBetween('created_at', $timeBetween); + }) + ->sum('perf'); + } + +} diff --git a/modules/User/Models/Traits/HasRelations.php b/modules/User/Models/Traits/HasRelations.php new file mode 100644 index 0000000..31d452f --- /dev/null +++ b/modules/User/Models/Traits/HasRelations.php @@ -0,0 +1,284 @@ + + * @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' + ); + } + + /** + * Notes: 获取直推季卡会员数 + * + * @Author: 玄尘 + * @Date: 2022/8/19 14:44 + */ + public function getJkChildrenCount(): int + { + $identity = Identity::query()->Jk()->first(); + if ($identity) { + return $this->children() + ->whereHas('identityMiddle', function ($q) use ($identity) { + $q->where('identity_id', $identity->id); + }) + ->count() ?? 0; + } else { + return 0; + } + } + + /** + * Notes: 获取直推合伙人数量 + * + * @Author: 玄尘 + * @Date: 2022/8/31 9:07 + * @return int + */ + public function getHhChildrenCount(): int + { + $identity = Identity::query()->Hh()->first(); + if ($identity) { + return $this->children() + ->whereHas('identityMiddle', function ($q) use ($identity) { + $q->where('identity_id', $identity->id); + }) + ->count() ?? 0; + } else { + return 0; + } + + } + + + /** + * Notes: 获取直推年卡会员数 + * + * @Author: 玄尘 + * @Date: 2022/8/19 14:48 + * @return int + */ + public function getNkChildrenCount(): int + { + $identity = Identity::query()->Nk()->first(); + if ($identity) { + return $this->children() + ->whereHas('identityMiddle', function ($q) use ($identity) { + $q->where('identity_id', $identity->id); + }) + ->count() ?? 0; + } else { + return 0; + } + + } + + /** + * Notes: 获取上级合伙人 + * + * @Author: 玄尘 + * @Date: 2022/8/19 13:13 + */ + public function getParentHh() + { + $users = []; + $parent = $this->parent; + + while ($parent) { + if ($parent) { + if ($parent->identityFirst()->job == Identity::JOB_HH) { + array_push($users, $parent); + } + $parent = $parent->parent; + } else { + break; + } + } + + return $users; + } + + /** + * 调整隶属 + * + * @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..7ddc5ec --- /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..537c55e --- /dev/null +++ b/modules/User/Models/Traits/JoinIdentity.php @@ -0,0 +1,276 @@ +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, + ]); + } + + /** + * Notes: 检查合伙人星级 + * + * @Author: 玄尘 + * @Date: 2022/8/31 8:57 + */ + public function checkStar() + { + if ($this->identityFirst()->job == Identity::JOB_HH) { + $hhChildren = $this->getHhChildrenCount(); + $allPerf = $this->allPerf(); + $levels = ['five' => 5, 'four' => 4, 'three' => 3, 'two' => 2, 'one' => 1]; + + foreach ($levels as $key => $level) { + $nHhCount = app('Conf_user')[$key.'_star_count'] ?? 0; + $nPerformance = app('Conf_user')[$key.'_star_performance'] ?? 0; + + if ($hhChildren >= $nHhCount && $allPerf >= $nPerformance) { + $this->identityMiddle()->update([ + 'star' => $level + ]); + break; + } + } + } + + + } + +} diff --git a/modules/User/Models/Traits/OrderActions.php b/modules/User/Models/Traits/OrderActions.php new file mode 100644 index 0000000..a3462fb --- /dev/null +++ b/modules/User/Models/Traits/OrderActions.php @@ -0,0 +1,110 @@ +state = self::STATE_SUCCESS; + $this->save(); + if ($this->useCouponLog) { + $this->user->verification($this->useCouponLog->couponGrant->code, Coupon::USE_WAY_ONLINE, $this); + } + 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..42b9b7f --- /dev/null +++ b/modules/User/Models/Traits/WechatAttribute.php @@ -0,0 +1,79 @@ +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..49ba6db --- /dev/null +++ b/modules/User/Models/User.php @@ -0,0 +1,312 @@ + '正常', + self::STATUS_REFUND => '退费', + ]; + const TAG_USER = 1; + const TAG_WALKER = 2; + const TAG_ACADEMY = 3; + + const TAGS = [ + self::TAG_USER => '用户', + self::TAG_WALKER => '上师', + self::TAG_ACADEMY => '院士', + ]; + + + /** + * 禁止写入的字段 + * + * @var array + */ + protected $guarded = []; + + + /** + * 模型隐藏字段 + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + protected $appends = [ + 'show_name' + ]; + + 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', 'star']); + } + + /** + * 用户中间表关联 + * + * @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,//是否开会 + 'isCase' => (bool) $this->case,//是否有档案 + ]; + } + + /** + * 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)); + } + + public function getShowNameAttribute(): string + { + return sprintf("%s (%s)", $this->username, $this->info->nickname); + } + + +} 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/UserChannel.php b/modules/User/Models/UserChannel.php new file mode 100644 index 0000000..58b03af --- /dev/null +++ b/modules/User/Models/UserChannel.php @@ -0,0 +1,28 @@ +code; + + return 'data:image/png;base64,'.base64_encode(QrCode::format('png') + ->size(300) + ->margin(3) + ->generate($url)); + } + + public function users(): HasMany + { + return $this->hasMany(User::class,'channel_id'); + } +} diff --git a/modules/User/Models/UserIdentityCoupon.php b/modules/User/Models/UserIdentityCoupon.php new file mode 100644 index 0000000..31d33b9 --- /dev/null +++ b/modules/User/Models/UserIdentityCoupon.php @@ -0,0 +1,23 @@ +belongsTo(Identity::class, 'identity_id'); + } + + public function sourceUser(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } +} diff --git a/modules/User/Models/UserInfo.php b/modules/User/Models/UserInfo.php new file mode 100644 index 0000000..9bc19e9 --- /dev/null +++ b/modules/User/Models/UserInfo.php @@ -0,0 +1,67 @@ + + * @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) : ''; + } + + public function getNicknameTextAttribute(): string + { + $lenth = Str::length($this->nickname); + return Str::substr($this->nickname, 0, bcsub($lenth, 1)).'*'; + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date: 2023/1/12 14:12 + */ + public function areaCode(): HasOne + { + return $this->hasOne(AreaCode::class,'code','delivery_code'); + } + +} diff --git a/modules/User/Models/UserInvite.php b/modules/User/Models/UserInvite.php new file mode 100644 index 0000000..eceeada --- /dev/null +++ b/modules/User/Models/UserInvite.php @@ -0,0 +1,53 @@ + '未分配', + self::STATUS_ALLOT => '已分配', + self::STATUS_USED => '已激活', + ]; + + protected $dates = [ + 'actived_at', + ]; + + public function activeUser(): BelongsTo + { + return $this->belongsTo(User::class, 'active_id'); + } + + public function getStatusTextAttribute() + { + return self::STATUS[$this->status] ?? '未知'; + } + + /** + * Notes: 激活 + * + * @Author: 玄尘 + * @Date: 2022/9/21 10:11 + * @param $user_id + */ + public function use($user_id) + { + $this->update([ + 'active_id' => $user_id, + 'actived_at' => now(), + 'status' => self::STATUS_USED, + ]); + } +} 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/UserPerf.php b/modules/User/Models/UserPerf.php new file mode 100644 index 0000000..ee29674 --- /dev/null +++ b/modules/User/Models/UserPerf.php @@ -0,0 +1,18 @@ +morphTo(); + } +} 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/Providers/EventServiceProvider.php b/modules/User/Providers/EventServiceProvider.php new file mode 100644 index 0000000..81621a3 --- /dev/null +++ b/modules/User/Providers/EventServiceProvider.php @@ -0,0 +1,30 @@ + [ + UserSignContinueDays::class, + ], + + //开通身份 + UserOrderPaid::class => [ + UserOrderPaidListeners::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..c4564bf --- /dev/null +++ b/modules/User/Providers/UserServiceProvider.php @@ -0,0 +1,125 @@ +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/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..9f07b24 --- /dev/null +++ b/modules/User/Routes/admin.php @@ -0,0 +1,38 @@ + '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'); + + //邀请码管理 + $router->post('invites/createCard', 'InviteController@createCard'); + $router->resource('invites', 'InviteController'); + //渠道管理 + $router->resource('channels', 'ChannelController'); +}); + +Route::resource('users', 'IndexController'); diff --git a/modules/User/Routes/api.php b/modules/User/Routes/api.php new file mode 100644 index 0000000..a6c9be5 --- /dev/null +++ b/modules/User/Routes/api.php @@ -0,0 +1,202 @@ + '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'); + $router->post('login/wechat/mini/reg_login', 'WeChatController@miniLoginAndReg'); + $router->post('login/wechat/mini/update', 'WeChatController@updateUserData'); + +}); + +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'); + $router->post('pay/{order}/invite', 'IndexController@invite'); + //合伙人 + $router->get('partner/create', 'PartnerController@create'); + $router->post('partner', 'PartnerController@store'); +}); +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_guess'), +], function (Router $router) { + $router->get('total', 'IndexController@total'); + $router->get('totaluser', 'IndexController@totalUser'); + + $router->get('merit', 'IndexController@merit');//功德榜 + $router->get('week', 'IndexController@week'); +}); + +//激活码 +Route::group([ + 'namespace' => 'Invite', + 'middleware' => config('api.route.middleware_auth'), +], function (Router $router) { + $router->get('invites', 'IndexController@index'); +}); + +//我的收藏 +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..255aed1 --- /dev/null +++ b/modules/User/Traits/RankDataTrait.php @@ -0,0 +1,120 @@ +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(); + } + + + /** + * Notes: 功德榜 + * + * @Author: 玄尘 + * @Date: 2022/12/31 18:25 + */ + public function getAllUserJz() + { + return User::query() + ->withSum([ + 'orderItems' => function (Builder $query) { + $query->whereHas('order', function ($q) { + $q->whereIn('state', [ + Order::STATUS_PAID, + Order::STATUS_DELIVERED, + Order::STATUS_SIGNED, + Order::STATUS_COMPLETED, + ]); + }); + } + ], 'qty') + ->latest('order_items_sum_qty') + ->oldest('id') + ->take(10) + ->get() + ->where('order_items_sum_qty', '>', 0) + ->map(function ($info) { + + return [ + 'user' => [ + 'user_id' => $info->id, + 'username' => substr_replace($info->username, '****', -4, 4), + 'nickname' => $info->info->nickname_text ?? '', + 'avatar' => $info->info->avatar ?? '', + ], + 'total' => $info->order_items_sum_qty > 0 ? $info->order_items_sum_qty : '-', + ]; + }) + ->pad(10, [ + 'user' => [ + 'user_id' => 0, + 'username' => '-', + 'nickname' => '-', + 'avatar' => '-', + ], + 'total' => '-', + ]); + + } +} diff --git a/modules/User/Traits/WechatTrait.php b/modules/User/Traits/WechatTrait.php new file mode 100644 index 0000000..c2dd392 --- /dev/null +++ b/modules/User/Traits/WechatTrait.php @@ -0,0 +1,89 @@ +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, $subscribe = 1) + { + UserSubscribe::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); + } + }); + } + +} \ No newline at end of file diff --git a/modules/User/User.php b/modules/User/User.php new file mode 100644 index 0000000..81b5403 --- /dev/null +++ b/modules/User/User.php @@ -0,0 +1,198 @@ + + */ + 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', + ], + // [ + // 'order' => 9, + // 'title' => '渠道管理', + // 'icon' => 'fa-group', + // 'uri' => 'users/channels', + // ], + ]); + + $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..41ee35e --- /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":[],"items":[{"name":"登录与注册","parentId":961594,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[{"name":"用户名密码登录","api":{"id":"6368635","method":"post","path":"/api/user/auth/login","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911855","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"application/x-www-form-urlencoded","parameters":[{"name":"username","required":true,"description":"","sampleValue":"","type":"text"},{"name":"password","required":true,"description":"","type":"text"}]},"description":"","tags":[],"status":"released","serverId":"","ordering":0,"cases":[{"id":6794737,"name":"成功","responseId":6911855,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"username","value":"15663876870","enable":true},{"name":"password","value":"111111","enable":true}]},"preProcessors":[],"postProcessors":[{"type":"commonScript","data":[348731]}]},{"id":6794738,"name":"商家登录","responseId":6911855,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"username","value":"18946139658","enable":true},{"name":"password","value":"111111","enable":true}]},"preProcessors":[],"postProcessors":[{"type":"commonScript","data":[348731]}]},{"id":6794739,"name":"测试E货买家登录","responseId":6911855,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"username","value":"18704601510","enable":true},{"name":"password","value":"123456","enable":true}]},"preProcessors":[],"postProcessors":[{"type":"commonScript","data":[348731]}]},{"id":6794740,"name":"员工登录","responseId":6911855,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"username","value":"13836142496","enable":true},{"name":"password","value":"111111","enable":true}]},"preProcessors":[],"postProcessors":[{"type":"commonScript","data":[348731]}]}],"mocks":[]}},{"name":"uni本机一键登录(服务器获取手机号)","api":{"id":"6368636","method":"post","path":"/api/user/socialite/login/unicloud/app","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911856","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"access_token","required":true,"description":"uni前端获取内容","sampleValue":"321","type":"text"},{"name":"openid","required":true,"description":"uni前端获取内容","sampleValue":"321","type":"text"}]},"description":"","tags":[],"status":"released","serverId":"","ordering":6,"cases":[],"mocks":[]}},{"name":"uni本机一键登录(前端获取手机号)","api":{"id":"6368637","method":"post","path":"/api/user/socialite/login/unicloud/query","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911857","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobile","required":true,"description":"前端获取的手机号","sampleValue":"17645779673","type":"text"}]},"description":"","tags":[],"status":"released","serverId":"","ordering":12,"cases":[],"mocks":[]}},{"name":"获取登录短信验证码","api":{"id":"6368638","method":"post","path":"/api/user/auth/verify","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911858","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobileNo","required":true,"description":"手机号码","type":"text"}]},"description":"","tags":[],"status":"released","serverId":"","ordering":18,"cases":[],"mocks":[]}},{"name":"验证码登录","api":{"id":"6368639","method":"post","path":"/api/user/auth/sms","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911859","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"mobileNo","required":true,"description":"手机号","type":"text"},{"name":"code","required":true,"description":"验证码","type":"text"},{"name":"parent_id","required":false,"description":"上级用户","sampleValue":"10045","type":"text"}]},"description":"","tags":[],"status":"released","serverId":"","ordering":24,"cases":[{"id":6794741,"name":"成功","responseId":6911859,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[{"name":"mobileNo","value":"14745798066","enable":true},{"name":"code","value":"0000","enable":true},{"name":"parent_id","value":"10045"}]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"APP登录","api":{"id":"6368640","method":"post","path":"/api/user/socialite/login/wechat/app","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911860","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"union_id","required":true,"description":"unionId必须填写","type":"text"},{"name":"open_id","required":false,"description":"","type":"text"},{"name":"mobileNo","required":true,"description":"手机号码必须填写","type":"text"},{"name":"code","required":true,"description":"验证码必须填写","type":"text"},{"name":"nickname","required":false,"description":"昵称","type":"text"},{"name":"avatar","required":false,"description":"头像","type":"text"},{"name":"gender","required":false,"description":"性别","type":"text"},{"name":"country","required":false,"description":"国家","type":"text"},{"name":"province","required":false,"description":"省份","type":"text"},{"name":"city","required":false,"description":"城市","type":"text"}]},"description":"","tags":[],"status":"developing","serverId":"","ordering":30,"cases":[],"mocks":[]}},{"name":"微信小程序手机号登录","api":{"id":"6368641","method":"post","path":"/api/user/socialite/login/wechat/mini","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"微信code","type":"text"},{"name":"iv","required":true,"description":"向量","type":"text"},{"name":"encryptedData","required":true,"description":"加密串","type":"text"},{"name":"nickname","required":false,"description":"微信昵称","type":"text"},{"name":"avatar","required":false,"description":"微信头像","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911861","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":36,"cases":[],"mocks":[]}},{"name":"小程序获取微信用户openid","api":{"id":"6368642","method":"get","path":"/api/user/socialite/login/wechat/openid","parameters":{"path":[],"query":[{"name":"code","required":true,"description":"","sampleValue":"dsadsa","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911862","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":42,"cases":[{"id":6794742,"name":"成功","responseId":6911862,"parameters":{"path":[],"query":[{"name":"code","value":"dsadsa","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}}]},{"name":"身份相关","parentId":961594,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[{"name":"获取身份","api":{"id":"6368643","method":"get","path":"/api/user/identities","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911863","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"finish 条件是否完成\nstock 可开通总数\nsold 已有数量\nresidue 剩余数量\non_line true 可在线开通\nshow_button 是否显示按钮","tags":[],"status":"developing","serverId":"","ordering":0,"cases":[{"id":6794743,"name":"成功","responseId":6911863,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"获取身份详细信息","api":{"id":"6368644","method":"get","path":"/api/user/identities/{identity}","parameters":{"path":[{"name":"identity","required":true,"type":"text"}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911864","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":6,"cases":[],"mocks":[]}},{"name":"获取可开通身份内容","api":{"id":"6368645","method":"get","path":"/api/user/identities/create/{identity}","parameters":{"path":[{"name":"identity","required":true,"sampleValue":"2","type":"text"}],"query":[{"name":"year","required":true,"description":"开通几年","sampleValue":"1","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911865","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":12,"cases":[],"mocks":[]}},{"name":"确认开通身份","api":{"id":"6368646","method":"post","path":"/api/user/identities/create/{identity}","parameters":{"path":[{"name":"identity","required":true,"sampleValue":"2","type":"text"}],"query":[{"name":"year","required":true,"description":"开通年限","sampleValue":"1","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911866","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":18,"cases":[],"mocks":[]}},{"name":"开通身份支付(微信)","api":{"id":"6368647","method":"get","path":"/api/user/identities/pay/{order_id}/wechat","parameters":{"path":[{"name":"order_id","required":true,"sampleValue":"36","type":"text"}],"query":[{"name":"type","required":true,"description":"支付渠道:miniapp 小程序 mp 公众号 app App","sampleValue":"app","type":"text"},{"name":"openid","required":true,"description":"用户openid,type=mp时必传","sampleValue":"mp","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911867","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":24,"cases":[{"id":6794744,"name":"成功","responseId":6911867,"parameters":{"path":[{"name":"order","value":"36","enable":true}],"query":[{"name":"type","value":"mp","enable":true},{"name":"openid","value":"111","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"开通身份支付(支付宝)","api":{"id":"6368648","method":"get","path":"/api/user/identities/pay/{order_id}/alipay","parameters":{"path":[{"name":"order_id","required":true,"sampleValue":"1","type":"text"}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911868","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":30,"cases":[],"mocks":[]}}]},{"name":"用户信息","parentId":961594,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[{"name":"获取用户信息","api":{"id":"6368649","method":"get","path":"/api/user/info","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911869","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":0,"cases":[],"mocks":[]}},{"name":"修改用户资料","api":{"id":"6368650","method":"put","path":"/api/user/setting/{key}","parameters":{"path":[{"name":"key","required":true,"sampleValue":"nickname","type":"text"}],"query":[{"name":"value","required":true,"description":"","sampleValue":"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epT7dFGs0RNZERjeAl75GKTibmkrbAIH47dic43toYjTkSLUPegLDPicqGypp3mRVa7TKFBkbhUfaicqQ/132","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911870","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":6,"cases":[{"id":6794745,"name":"上传头像","responseId":6911870,"parameters":{"path":[{"name":"key","value":"avatar","enable":true}],"query":[{"name":"value","value":"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epT7dFGs0RNZERjeAl75GKTibmkrbAIH47dic43toYjTkSLUPegLDPicqGypp3mRVa7TKFBkbhUfaicqQ/132","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]},{"id":6794746,"name":"修改昵称","responseId":6911870,"parameters":{"path":[{"name":"key","value":"nickname","enable":true}],"query":[{"name":"value","value":"玄尘","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"获取邀请码","api":{"id":"6368651","method":"get","path":"/api/user/invite","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911871","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":12,"cases":[],"mocks":[]}},{"name":"绑定邀请码","api":{"id":"6368652","method":"post","path":"/api/user/bind","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911872","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"multipart/form-data","parameters":[{"name":"invite","required":false,"description":"","type":"text"}]},"description":"","tags":[],"status":"developing","serverId":"","ordering":18,"cases":[],"mocks":[]}},{"name":"个人中心设置","api":{"id":"6368653","method":"get","path":"/api/user/setting","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911873","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":24,"cases":[{"id":6794747,"name":"成功","responseId":6911873,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}}]},{"name":"用户签到","parentId":961594,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[{"name":"用户签到","api":{"id":"6368654","method":"post","path":"/api/user/sign","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911874","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":0,"cases":[],"mocks":[]}},{"name":"用户签到信息","api":{"id":"6368655","method":"get","path":"/api/user/sign","parameters":{"path":[],"query":[{"name":"days","required":false,"description":"","sampleValue":"7","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911875","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":6,"cases":[],"mocks":[]}}]},{"name":"用户认证","parentId":961594,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[{"name":"用户认证","api":{"id":"6368656","method":"post","path":"/api/user/certification","parameters":{"path":[],"query":[{"name":"name","required":true,"description":"姓名","sampleValue":" 测试","type":"text"},{"name":"id_card","required":true,"description":"身份证号","sampleValue":"230102111111111111","type":"text"},{"name":"front_card","required":true,"description":"身份证正面","sampleValue":"","type":"text"},{"name":"back_card","required":true,"description":"身份证反面","sampleValue":"","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911876","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":0,"cases":[{"id":6794748,"name":"成功","responseId":6911876,"parameters":{"path":[],"query":[{"name":"name","value":" 测试","enable":true},{"name":"id_card","value":"230102198906032138","enable":true},{"name":"front_card","value":"1.jpg","enable":true},{"name":"back_card","value":"2.jpg","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"查看认证信息","api":{"id":"6368657","method":"get","path":"/api/user/certification","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911877","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":6,"cases":[{"id":6794749,"name":"成功","responseId":6911877,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"是否实名认证","api":{"id":"6368658","method":"get","path":"/api/user/certified","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911878","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":12,"cases":[{"id":6794750,"name":"成功","responseId":6911878,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}}]},{"name":"推广二维码","api":{"id":"6368634","method":"get","path":"/api/user/invite","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"6911854","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","ordering":0,"cases":[{"id":6794736,"name":"成功","responseId":6911854,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}}]}],"socketCollection":[],"schemaCollection":[{"name":"默认分组","items":[]}],"apiTestCaseCollection":[{"name":"默认分组","children":[],"items":[]}],"apiTestSuiteCollection":[],"environments":[{"baseUrl":"http://127.0.0.1:4523/mock/437099","baseUrls":{"default":"http://127.0.0.1:4523/mock/undefined"},"parameters":{},"variables":[],"name":"Mock 服务","type":"mock","visibility":"protected","ordering":0,"id":"628081"}],"commonScripts":[{"name":"登录后保存token","description":"","content":"var jsonData = pm.response.json();\n\npm.environment.set('api_token', jsonData.data.access_token);","id":"348731"}],"databaseConnections":[],"globalVariables":[],"commonParameters":null} \ No newline at end of file diff --git a/modules/Withdraw/.gitignore b/modules/Withdraw/.gitignore new file mode 100644 index 0000000..3ce5adb --- /dev/null +++ b/modules/Withdraw/.gitignore @@ -0,0 +1,2 @@ +.idea +vendor diff --git a/modules/Withdraw/Config/config.php b/modules/Withdraw/Config/config.php new file mode 100644 index 0000000..7e2d19c --- /dev/null +++ b/modules/Withdraw/Config/config.php @@ -0,0 +1,13 @@ + '提现模块', + 'is_chain' => false,//提现账户是否是区块链,true 是 false 否 + 'account' => 'coins',//提现账户 + 'withdraw_no_counter_length' => 8, + 'model' => [ + 'account_rules' => Modules\User\Models\AccountRule::class,//规则表 + 'chain_money' => App\Models\DayDataChain::class,//记录金额表 + ], + +]; diff --git a/modules/Withdraw/Database/Migrations/0000_00_00_000000_create_banks_table.php b/modules/Withdraw/Database/Migrations/0000_00_00_000000_create_banks_table.php new file mode 100644 index 0000000..2980e82 --- /dev/null +++ b/modules/Withdraw/Database/Migrations/0000_00_00_000000_create_banks_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('name'); + $table->string('cover')->nullable(); + $table->boolean('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('withdraw_banks'); + } + +} diff --git a/modules/Withdraw/Database/Migrations/2021_09_26_115830_create_withdraws_table.php b/modules/Withdraw/Database/Migrations/2021_09_26_115830_create_withdraws_table.php new file mode 100644 index 0000000..ef622f2 --- /dev/null +++ b/modules/Withdraw/Database/Migrations/2021_09_26_115830_create_withdraws_table.php @@ -0,0 +1,44 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('bank_account_id')->nullable()->index(); + $table->unsignedBigInteger('alipay_account_id')->nullable()->index(); + $table->string('withdraw_no', 32)->comment('提现单号'); + $table->decimal('amount')->unsigned(0)->comment('提现金额'); + $table->decimal('tax')->unsigned()->comment('手续费'); + $table->decimal('take')->unsigned()->comment('实际到账金额'); + $table->boolean('status'); + $table->boolean('type'); + $table->json('source')->nullable(); + $table->timestamp('paid_at')->nullable()->comment('付款成功时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('withdraws'); + } + +} diff --git a/modules/Withdraw/Database/Migrations/2021_09_26_120338_create_accounts_table.php b/modules/Withdraw/Database/Migrations/2021_09_26_120338_create_accounts_table.php new file mode 100644 index 0000000..f4af787 --- /dev/null +++ b/modules/Withdraw/Database/Migrations/2021_09_26_120338_create_accounts_table.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('bank_id'); + $table->string('name')->comment('收款人姓名'); + $table->string('no')->comment('银行卡号'); + $table->string('branch_name')->nullable()->comment('支行名称'); + $table->string('mobile')->comment('收款人手机号'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('withdraw_bank_accounts'); + } + +} diff --git a/modules/Withdraw/Database/Migrations/2021_09_26_130107_create_configs_table.php b/modules/Withdraw/Database/Migrations/2021_09_26_130107_create_configs_table.php new file mode 100644 index 0000000..1e220f3 --- /dev/null +++ b/modules/Withdraw/Database/Migrations/2021_09_26_130107_create_configs_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('name', 100); + $table->string('slug', 100); + $table->string('value', 100); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('withdraw_configs'); + } + +} diff --git a/modules/Withdraw/Database/Migrations/2021_09_27_085100_create_logs_table.php b/modules/Withdraw/Database/Migrations/2021_09_27_085100_create_logs_table.php new file mode 100644 index 0000000..1d30043 --- /dev/null +++ b/modules/Withdraw/Database/Migrations/2021_09_27_085100_create_logs_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('withdraw_id')->index(); + $table->boolean('status'); + $table->string('remark')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('withdraw_logs'); + } + +} diff --git a/modules/Withdraw/Database/Migrations/2021_11_03_142359_create_alipay_accounts_table.php b/modules/Withdraw/Database/Migrations/2021_11_03_142359_create_alipay_accounts_table.php new file mode 100644 index 0000000..dcac629 --- /dev/null +++ b/modules/Withdraw/Database/Migrations/2021_11_03_142359_create_alipay_accounts_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('user_id')->index(); + $table->text('name')->comment('支付宝姓名'); + $table->text('identity_type')->comment('标识类型:ALIPAY_USER_ID 支付宝的会员ID,ALIPAY_LOGON_ID:支付宝登录号,支持邮箱和手机号格式'); + $table->text('identity')->comment('支付宝账号'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('withdraw_alipay_accounts'); + } + +} diff --git a/modules/Withdraw/Events/WithdrawAuditPass.php b/modules/Withdraw/Events/WithdrawAuditPass.php new file mode 100644 index 0000000..46bfc17 --- /dev/null +++ b/modules/Withdraw/Events/WithdrawAuditPass.php @@ -0,0 +1,8 @@ +withdraw = $withdraw; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Controllers/Admin/AccountController.php b/modules/Withdraw/Http/Controllers/Admin/AccountController.php new file mode 100644 index 0000000..bc61581 --- /dev/null +++ b/modules/Withdraw/Http/Controllers/Admin/AccountController.php @@ -0,0 +1,46 @@ +model()->with(['user.info']); + $grid->disableCreateButton(); + $grid->actions(function ($actions) { + $actions->disableDelete(); + $actions->disableView(); + }); + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('user.username', '用户账号'); + $filter->like('no', '卡号'); + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('user.info.nickname', '用户昵称'); + }); + }); + + $grid->column('id', '#ID#'); + $grid->column('用户')->display(function () { + return $this->user->username."({$this->user->info->nickname})"; + }); + + $grid->column('name', '提款人'); + $grid->column('bank.name', '开户行'); + $grid->column('no', '卡号'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Controllers/Admin/Actions/WithdrawAudit.php b/modules/Withdraw/Http/Controllers/Admin/Actions/WithdrawAudit.php new file mode 100644 index 0000000..5c886c9 --- /dev/null +++ b/modules/Withdraw/Http/Controllers/Admin/Actions/WithdrawAudit.php @@ -0,0 +1,48 @@ +status; + $remark = $request->remark; + + try { + if ($status == Withdraw::STATUS_PASS) { + $withdraw->pass($remark); + } + if ($status == Withdraw::STATUS_REJECT) { + $withdraw->reject($remark); + } + + return $this->response()->success('操作成功')->refresh(); + } catch (\Exception $e) { + return $this->response()->error($e->getMessage())->refresh(); + } + + } + + public function form(Model $model) + { + $this->select('status', '状态') + ->options([ + Withdraw::STATUS_PASS => '通过', + Withdraw::STATUS_REJECT => '驳回', + ]) + ->required(); + + $this->text('remark', '说明'); + } + +} diff --git a/modules/Withdraw/Http/Controllers/Admin/BankController.php b/modules/Withdraw/Http/Controllers/Admin/BankController.php new file mode 100644 index 0000000..d43f5b4 --- /dev/null +++ b/modules/Withdraw/Http/Controllers/Admin/BankController.php @@ -0,0 +1,50 @@ +filter(function (Grid\Filter $filter) { + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('name', '银行名称'); + }); + }); + $grid->column('id', '#ID#'); + $grid->column('cover', '图标')->image('', 60, 60); + $grid->column('name', '银行名称'); + $grid->column('status', '状态')->switch([ + 'on' => ['value' => 1, 'text' => '打开', 'color' => 'success'], + 'off' => ['value' => 0, 'text' => '关闭', 'color' => 'danger'], + ]); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form() + { + $form = new Form(new Bank()); + $this->cover($form); + $form->text('name', '银行名称')->required(); + + $form->switch('status', '是否开启')->default(1); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Controllers/Admin/ConfigController.php b/modules/Withdraw/Http/Controllers/Admin/ConfigController.php new file mode 100644 index 0000000..33f77bc --- /dev/null +++ b/modules/Withdraw/Http/Controllers/Admin/ConfigController.php @@ -0,0 +1,40 @@ +disableFilter(); + + $grid->column('id', '#ID#'); + $grid->column('name', '名称'); + $grid->column('slug', '别名'); + $grid->column('value', '值'); + $grid->column('created_at', '创建时间'); + + return $grid; + } + + public function form(): Form + { + $form = new Form(new Config()); + $form->text('name', '名称')->required(); + $form->text('slug', '别名')->required(); + $form->text('value', '值')->required(); + + return $form; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Controllers/Admin/WithdrawController.php b/modules/Withdraw/Http/Controllers/Admin/WithdrawController.php new file mode 100644 index 0000000..eb155b4 --- /dev/null +++ b/modules/Withdraw/Http/Controllers/Admin/WithdrawController.php @@ -0,0 +1,74 @@ +model()->with(['user.info'])->latest(); + + $grid->disableCreateButton(); + $grid->actions(function ($actions) { + $actions->disableDelete(); + $actions->disableView(); + $actions->disableEdit(); + if ($actions->row->canAudit()) { + $actions->add(new WithdrawAudit()); + } + }); + + $grid->filter(function (Grid\Filter $filter) { + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('user.username', '账户'); + $filter->equal('status', '状态')->select(Withdraw::STATUS); + $filter->like('bankAccount.branch_name', '开户行'); + }); + $filter->column(1 / 2, function (Grid\Filter $filter) { + $filter->like('user.info.nickname', '昵称'); + $filter->like('bankAccount.no', '卡号'); + }); + + }); + + $grid->column('id', 'ID'); + $grid->column('用户')->display(function () { + return $this->user->username."({$this->user->info->nickname})"; + }); + $grid->column('提现账号') + ->display(function () { + return '查看'; + }) + ->modal('提现账号', AccountAble::class); + $grid->column('amount', '提现金额'); + $grid->column('tax', '手续费'); + $grid->column('take', '实到金额'); + $grid->column('type', '提现类型') + ->using(Withdraw::TYPES) + ->label(Withdraw::TYPES_LABEL); + + $grid->column('status', '状态') + ->using(Withdraw::STATUS) + ->label([ + Withdraw::STATUS_INIT => 'info', + Withdraw::STATUS_PASS => 'success', + Withdraw::STATUS_REJECT => 'danger', + ]); + $grid->column('paid_at', '审核时间'); + $grid->column('source', '备份数据')->hide(); + $grid->column('created_at', '申请时间'); + + return $grid; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Controllers/Api/AccountController.php b/modules/Withdraw/Http/Controllers/Api/AccountController.php new file mode 100644 index 0000000..6f4c576 --- /dev/null +++ b/modules/Withdraw/Http/Controllers/Api/AccountController.php @@ -0,0 +1,144 @@ +where('user_id', $user->id)->paginate(); + + return $this->success(new UserBankAccountCollection($lists)); + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2021/9/26 14:09 + * @return JsonResponse + */ + public function create(): JsonResponse + { + $banks = Bank::where('status', Bank::STATUS_OPEN)->get(); + + $data = [ + 'banks' => BankResource::collection($banks), + ]; + + return $this->success($data); + } + + /*** + * Notes: 提交 + * + * @Author: 玄尘 + * @Date : 2021/9/26 14:11 + * @param UserBankAccountRequest $request + * @return JsonResponse|mixed + */ + public function store(UserBankAccountRequest $request) + { + $user = Api::user(); + + try { + $user->bankAccounts()->create([ + 'name' => $request->name, + 'mobile' => $request->mobile, + 'no' => $request->no, + 'branch_name' => $request->branch_name ?? '', + 'bank_id' => $request->bank_id, + ]); + + return $this->success('添加成功'); + } catch (\Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2021/9/26 14:23 + * @param Account $account + */ + public function edit(Account $account): JsonResponse + { + $banks = Bank::where('status', Bank::STATUS_OPEN)->get(); + + $data = [ + 'banks' => BankResource::collection($banks), + 'info' => new UserBankAccountResource($account), + ]; + + return $this->success($data); + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2021/9/26 14:26 + * @param UserEditBankAccountRequest $request + * @param Account $account + * @return JsonResponse|mixed + */ + public function update(UserEditBankAccountRequest $request, Account $account) + { + try { + $account->update([ + 'name' => $request->name, + 'mobile' => $request->mobile, + 'no' => $request->no, + 'branch_name' => $request->branch_name, + 'bank_id' => $request->bank_id, + ]); + + return $this->success('编辑成功'); + } catch (\Exception $exception) { + return $this->failed($exception->getMessage()); + } + } + + /** + * Notes: 删除 + * + * @Author: 玄尘 + * @Date : 2021/9/26 15:02 + * @param Account $account + * @return JsonResponse + */ + public function destroy(Account $account): JsonResponse + { + $user = Api::user(); + if ($account->user->isNot($user)) { + return $this->failed('您没有权限删除'); + } + $account->delete(); + + return $this->success('删除成功'); + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Controllers/Api/IndexController.php b/modules/Withdraw/Http/Controllers/Api/IndexController.php new file mode 100644 index 0000000..145f489 --- /dev/null +++ b/modules/Withdraw/Http/Controllers/Api/IndexController.php @@ -0,0 +1,155 @@ +type ?? 'day'; + $date = $request->date ?? Carbon::now()->format('Y-m-d'); + + $time = Carbon::parse($date); + + switch ($type) { + case 'month': + $timeBetween = [ + $time->startOfMonth()->toDateTimeString(), + $time->endOfMonth()->toDateTimeString(), + ]; + break; + case 'year': + $timeBetween = [ + $time->startOfYear()->toDateTimeString(), + $time->endOfYear()->toDateTimeString(), + ]; + break; + case 'day': + default: + $timeBetween = [ + $time->startOfDay()->toDateTimeString(), + $time->endOfDay()->toDateTimeString(), + ]; + break; + } + + $lists = $user->withdraws() + ->whereBetween('created_at', $timeBetween) + ->latest() + ->paginate(); + + $is_chain = config('withdraw.is_chain', false); + + if ($is_chain) { + $all = floatval($user->getStoneFormatBalance()); + } else { + $account = config('withdraw.account', 'coins'); + $all = $user->account->{$account}; + } + $data = [ + 'all' => $all, + 'lists' => new WithdrawCollection($lists), + ]; + + return $this->success($data); + } + + /** + * Notes: 提现前置 + * + * @Author: 玄尘 + * @Date : 2021/1/21 14:21 + */ + public function create(Request $request): JsonResponse + { + $user = Api::user(); + $is_chain = config('withdraw.is_chain', false); + $bank_account_id = $request->bank_account_id; + $alipay_account_id = $request->alipay_account_id; + if ($bank_account_id) { + $bank_account = $user->bankAccounts()->where('id', $bank_account_id)->first(); + } else { + $bank_account = $user->bankAccounts()->first(); + } + if ($alipay_account_id) { + $alipay_account = $user->alipayAccounts()->where('id', $alipay_account_id)->first(); + } else { + $alipay_account = $user->alipayAccounts()->first(); + } + + $data = [ + 'tax' => Config::getValue('tax', 0), + 'bank_account' => $bank_account ? new UserBankAccountResource($bank_account) : '', + 'alipay_account' => $alipay_account ? new UserAlipayAccountResource($alipay_account) : '', + 'bank_accounts' => UserBankAccountResource::collection($user->bankAccounts), + 'alipay_accounts' => UserAlipayAccountResource::collection($user->alipayAccounts), + 'is_chain' => config('withdraw.is_chain'), + 'certification' => (bool) $user->certification, + 'types' => Withdraw::TYPES, + ]; + + if ($is_chain) { + $data = array_merge($data, [ + 'balance' => floatval($user->getStoneFormatBalance()),//账户余额 + 'cost' => (new DayDataChain())->getStoneValue(),//现金价值 + ]); + } else { + $account = config('withdraw.account'); + $data = array_merge($data, [ + 'balance' => $user->account->{$account},//账户余额 + 'cost' => 1, + ]); + } + + return $this->success($data); + } + + /** + * Notes: 提现 + * + * @Author: 玄尘 + * @Date : 2021/1/21 14:27 + * @param WithdrawRequest $request + * @return JsonResponse|mixed + */ + public function store(WithdrawRequest $request) + { + $user = Api::user(); + + try { +// if (! $user->certification) { +// throw new \Exception('未进行个人认证不可提现'); +// } + + Withdraw::createInfo($user, $request); + + return $this->success('申请提现成功,等待管理员审核'); + } catch (\Exception $e) { + return $this->failed($e->getmessage()); + } + + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Requests/UserBankAccountRequest.php b/modules/Withdraw/Http/Requests/UserBankAccountRequest.php new file mode 100644 index 0000000..1dbca34 --- /dev/null +++ b/modules/Withdraw/Http/Requests/UserBankAccountRequest.php @@ -0,0 +1,35 @@ + 'required', + 'no' => ['required', 'unique:withdraw_bank_accounts,no', 'digits_between:16,19'], +// 'branch_name' => 'required', + 'mobile' => 'required', + 'bank_id' => 'required', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '收款人姓名必须填写', + 'no.required' => '银行卡号必须填写', + 'no.unique' => '此银行卡号已提交过', + 'no.integer' => '银行卡号必须是数字', + 'no.digits_between' => '银行卡号长度必须在:min ~ :max之间', + 'branch_name.required' => '支行名称必须填写', + 'mobile.required' => '收款人手机号必须填写', + 'bank_id.required' => '银行必须选择', + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Requests/UserEditBankAccountRequest.php b/modules/Withdraw/Http/Requests/UserEditBankAccountRequest.php new file mode 100644 index 0000000..cb63781 --- /dev/null +++ b/modules/Withdraw/Http/Requests/UserEditBankAccountRequest.php @@ -0,0 +1,35 @@ + 'required', + 'no' => ['required', 'digits_between:16,19'], +// 'branch_name' => 'required', + 'mobile' => 'required', + 'bank_id' => 'required', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '收款人姓名必须填写', + 'no.required' => '银行卡号必须填写', + 'no.unique' => '此银行卡号已提交过', + 'no.integer' => '银行卡号必须是数字', + 'no.digits_between' => '银行卡号长度必须在:min ~ :max之间', + 'branch_name.required' => '支行名称必须填写', + 'mobile.required' => '收款人手机号必须填写', + 'bank_id.required' => '银行必须选择', + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Requests/WithdrawRequest.php b/modules/Withdraw/Http/Requests/WithdrawRequest.php new file mode 100644 index 0000000..8a4ca4a --- /dev/null +++ b/modules/Withdraw/Http/Requests/WithdrawRequest.php @@ -0,0 +1,35 @@ + ['required_if:type,1'], + 'name' => ['required_if:type,2'], + 'identity' => ['required_if:type,2'], + 'amount' => 'required|numeric', + 'type' => 'required|numeric', + ]; + } + + public function messages(): array + { + return [ + 'bank_account_id.required_if' => '提现账户必须选择', + 'amount.required' => '提现金额必须选择', + 'amount.numeric' => '提现金额必须填写', + 'type.required' => '提现类型必须选择', + 'type.numeric' => '提现类型必须填写', + 'name.required_if' => '提现人必须填写', + 'identity.required_if' => '支付宝账号必须填写', + + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Resources/Account/UserAlipayAccountCollection.php b/modules/Withdraw/Http/Resources/Account/UserAlipayAccountCollection.php new file mode 100644 index 0000000..36d8399 --- /dev/null +++ b/modules/Withdraw/Http/Resources/Account/UserAlipayAccountCollection.php @@ -0,0 +1,21 @@ + $this->collection->map(function ($info) { + return new UserAlipayAccountResource($info); + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Resources/Account/UserAlipayAccountResource.php b/modules/Withdraw/Http/Resources/Account/UserAlipayAccountResource.php new file mode 100644 index 0000000..e9576c1 --- /dev/null +++ b/modules/Withdraw/Http/Resources/Account/UserAlipayAccountResource.php @@ -0,0 +1,21 @@ + $this->id, + 'name' => $this->name, + 'identity_type' => $this->identity_type, + 'identity' => $this->identity, + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Resources/Account/UserBankAccountCollection.php b/modules/Withdraw/Http/Resources/Account/UserBankAccountCollection.php new file mode 100644 index 0000000..e0f304c --- /dev/null +++ b/modules/Withdraw/Http/Resources/Account/UserBankAccountCollection.php @@ -0,0 +1,21 @@ + $this->collection->map(function ($info) { + return new UserBankAccountResource($info); + }), + 'page' => $this->page(), + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Resources/Account/UserBankAccountResource.php b/modules/Withdraw/Http/Resources/Account/UserBankAccountResource.php new file mode 100644 index 0000000..1babf44 --- /dev/null +++ b/modules/Withdraw/Http/Resources/Account/UserBankAccountResource.php @@ -0,0 +1,27 @@ + $this->id, + 'bank' => [ + 'id' => $this->bank_id, + 'name' => $this->bank->name, + 'cover' => $this->bank->cover_url, + ], + 'name' => $this->name, + 'no' => $this->no, + 'branch_name' => $this->branch_name, + 'mobile' => $this->mobile, + 'created_at' => (string) $this->created_at, + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Resources/Bank/BankResource.php b/modules/Withdraw/Http/Resources/Bank/BankResource.php new file mode 100644 index 0000000..1199b0e --- /dev/null +++ b/modules/Withdraw/Http/Resources/Bank/BankResource.php @@ -0,0 +1,18 @@ + $this->id, + 'name' => $this->name, + ]; + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Http/Resources/Withdraw/WithdrawCollection.php b/modules/Withdraw/Http/Resources/Withdraw/WithdrawCollection.php new file mode 100644 index 0000000..f097654 --- /dev/null +++ b/modules/Withdraw/Http/Resources/Withdraw/WithdrawCollection.php @@ -0,0 +1,21 @@ + $this->collection->map(function ($info) { + return new WithdrawResource($info); + }), + 'page' => $this->page(), + ]; + } + +} diff --git a/modules/Withdraw/Http/Resources/Withdraw/WithdrawResource.php b/modules/Withdraw/Http/Resources/Withdraw/WithdrawResource.php new file mode 100644 index 0000000..81752bf --- /dev/null +++ b/modules/Withdraw/Http/Resources/Withdraw/WithdrawResource.php @@ -0,0 +1,30 @@ + $this->id, + 'status' => [ + 'status' => $this->status, + 'status_text' => $this->status_text, + ], + 'amount' => $this->amount, + 'tax' => $this->tax, + 'take' => $this->take, + 'payment_at' => (string) $this->payment_at, + 'way' => '红包提现', + 'quantity' => $this->source[config('withdraw.account')] ?? '', + 'source' => $this->source, + + 'create_at' => (string) $this->created_at, + ]; + } + +} diff --git a/modules/Withdraw/Models/Account.php b/modules/Withdraw/Models/Account.php new file mode 100644 index 0000000..54a76a5 --- /dev/null +++ b/modules/Withdraw/Models/Account.php @@ -0,0 +1,15 @@ + '支付宝的会员ID', + self::TYPE_LOGONID => '支付宝登录号,支持邮箱和手机号格式', + ]; + +} diff --git a/modules/Withdraw/Models/Bank.php b/modules/Withdraw/Models/Bank.php new file mode 100644 index 0000000..c5ad603 --- /dev/null +++ b/modules/Withdraw/Models/Bank.php @@ -0,0 +1,23 @@ + '开启', + self::STATUS_CLOSE => '关闭', + ]; + +} diff --git a/modules/Withdraw/Models/Config.php b/modules/Withdraw/Models/Config.php new file mode 100644 index 0000000..2e65a5d --- /dev/null +++ b/modules/Withdraw/Models/Config.php @@ -0,0 +1,23 @@ +where('slug', $slug)->first(); + + if ($info) { + return $info->value; + } else { + return $default; + } + } + +} diff --git a/modules/Withdraw/Models/Log.php b/modules/Withdraw/Models/Log.php new file mode 100644 index 0000000..0293d7b --- /dev/null +++ b/modules/Withdraw/Models/Log.php @@ -0,0 +1,12 @@ +belongsTo(Bank::class); + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Models/Traits/WithdrawActions.php b/modules/Withdraw/Models/Traits/WithdrawActions.php new file mode 100644 index 0000000..d2b81ac --- /dev/null +++ b/modules/Withdraw/Models/Traits/WithdrawActions.php @@ -0,0 +1,326 @@ +canAudit()) { + throw new \Exception('该提现记录已经被审核'); + } + + try { + if ($this->type == self::TYPE_ALIPAY) { + + if (! $this->alipayAccount) { + throw new \Exception('未找到支付宝账户'); + } + + $allPlugins = Pay::alipay() + ->mergeCommonPlugins([TransUniTransferPlugin::class]); + + $params = [ + 'out_biz_no' => $this->withdraw_no, + 'trans_amount' => $this->take, + 'payee_info' => [ + 'identity' => $this->alipayAccount->identity, + 'identity_type' => $this->alipayAccount->identity_type, + 'name' => $this->alipayAccount->name, + ], + ]; + + $res = Pay::alipay($this->getPayConfig('alipay'))->pay($allPlugins, $params); + if ($res->code != 10000) { + throw new \Exception($res->sub_msg); + } + + } + + DB::transaction(function () use ($remark) { + $this->status = Withdraw::STATUS_PASS; + $this->paid_at = now(); + $this->save(); + }); + + $this->addLog(Withdraw::STATUS_INIT, $remark); + + event(new WithdrawAuditPass($this)); + + return true; + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + /** + * Notes: 驳回 + * + * @Author: 玄尘 + * @Date : 2021/9/26 15:21 + * @param string $remark + * @return bool + * @throws \Exception + */ + public function reject(string $remark = ''): bool + { + if (! $this->canAudit()) { + throw new \Exception('该提现记录已经被审核'); + } + + try { + DB::transaction(function () use ($remark) { + $this->status = Withdraw::STATUS_REJECT; + $this->paid_at = now(); + $this->save(); + + $is_chain = config('withdraw.is_chain', false); + + if (! $is_chain) { + $this->user->account->rule('withdraw_in', $this->amount, false, [ + 'withdraw_id' => $this->id, + ]); + } else { + $hash = $this->rejectPick(); + $source = [ + 'reject_hash' => $hash, + ]; + + $this->update(['source' => $source]); + } + + $this->addLog(Withdraw::STATUS_REJECT, $remark); + + event(new WithdrawAuditReject($this)); + + }); + + return true; + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + /** + * Notes: 添加记录 + * + * @Author: 玄尘 + * @Date : 2021/9/27 8:56 + * @param $status + * @param string $remark + * @return bool + */ + public function addLog($status, string $remark = ''): bool + { + $this->logs()->create([ + 'status' => $status, + 'remark' => $remark, + ]); + + return true; + } + + /** + * Notes: 创建提现信息 + * + * @Author: 玄尘 + * @Date : 2021/9/27 10:14 + */ + public static function createInfo($user, $params): bool + { + try { + DB::transaction(function () use ($user, $params) { + + $type = $params->type ?? 0; + $bank_account_id = $params->bank_account_id ?? 0; + $amount = $params->amount; + + $account_name = config('withdraw.account'); + $source = [ + $account_name => $amount, + ]; + + $cash = $amount; + + $is_chain = config('withdraw.is_chain', false); + + $balance = $is_chain ? $user->getStoneBalance() : $user->account->{$account_name}; + + if ($amount > $balance) { + throw new \Exception('提现失败,余额不足'); + } + + if ($is_chain) { + $cost = (new DayDataChain())->getStoneValue(); + $cash = bcmul($cost, $amount, 2); + $source = array_merge($source, ['cost' => $cost,]); + } + + $withdraw_min = Config::getValue('min', 1); + + if ($withdraw_min > 0 && $cash < $withdraw_min) { + throw new \Exception("提现失败,提现金额必须大于等于{$withdraw_min}"); + } + + $tax_value = Config::getValue('tax', 0); + + $tax = $tax_value ? round($cash * $tax_value / 100, 2) : 0; + $take = $cash - $tax; + + if ($type == Withdraw::TYPE_BANK) { + $bank_account = Account::find($bank_account_id); + if (! $bank_account) { + throw new \Exception('提现失败,未找到提现账户信息'); + } + + if ($bank_account->user->isNot($user)) { + throw new \Exception('提现失败,您不可使用此提现账户'); + } + + $info = $user->withdraws()->create([ + 'bank_account_id' => $bank_account_id, + 'amount' => $cash, + 'tax' => $tax, //手续费 + 'take' => $take, //实到金额 + 'source' => $source, + 'type' => Withdraw::TYPE_BANK, + ]); + } else { + $alipay_account = $user->alipayAccounts()->create([ + 'name' => $params->name, + 'identity' => $params->identity, + 'identity_type' => 'ALIPAY_LOGON_ID', + ]); + + $info = $user->withdraws()->create([ + 'alipay_account_id' => $alipay_account->id, + 'amount' => $cash, + 'tax' => $tax, //手续费 + 'take' => $take, //实到金额 + 'source' => $source, + 'type' => Withdraw::TYPE_ALIPAY, + ]); + } + + if (! $is_chain) { + $user->account->rule('withdraw_out', -$cash, false, [ + 'withdraw_id' => $info->id, + ]); + } else { + $hash = $info->pick();//转到代持账户 + $source = array_merge($source, ['hash' => $hash,]); + + $info->update(['source' => $source]); + } + }); + + return true; + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + /** + * Notes: 申请提现时将提现金额转入代持账户 + * + * @Author: 玄尘 + * @Date : 2021/9/27 10:34 + * @return string + * @throws \Exception + */ + public function pick(): string + { + $to_address = Config::getValue('withdraw_addr', ''); + + if (! $to_address) { + throw new \Exception('提现失败,缺少代持账户信息,请管理人员配置代持账户。'); + } + + $number = $this->source['coins'] ?? 0; + if (! $number) { + throw new \Exception('提现数据错误'); + } + + $from_address = $this->user->blockChain->address; + $balance = coinsFormat(Chain33::Balance()->get($from_address)['balance']); + + if ($number > $balance) { + throw new \Exception('提现失败,数量不足'); + } + + try { + $fromAddr = Address::where('address', $from_address)->first(); + + return Chain33::Transaction() + ->coins($to_address, $number * 1e8, $fromAddr->private_key, 0, '用户提现'); + + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2021/9/27 14:45 + * @return string + * @throws \Exception + */ + public function rejectPick(): string + { + $from_address = Config::getValue('withdraw_addr', ''); + + if (! $from_address) { + throw new \Exception('提现失败,缺少代持账户信息,请管理人员配置代持账户。'); + } + + $number = $this->source['coins'] ?? 0; + if (! $number) { + throw new \Exception('提现数据错误'); + } + + $to_address = $this->user->blockChain->address; + $balance = coinsFormat(Chain33::Balance()->get($from_address)['balance']); + + if ($number > $balance) { + throw new \Exception('驳回失败,代持账户数量不足'); + } + + try { + $fromAddr = Address::where('address', $from_address)->first(); + + return Chain33::Transaction() + ->coins($to_address, $number * 1e8, $fromAddr->private_key, 0, '驳回提现'); + + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + +} \ No newline at end of file diff --git a/modules/Withdraw/Models/Withdraw.php b/modules/Withdraw/Models/Withdraw.php new file mode 100644 index 0000000..c8ee7e9 --- /dev/null +++ b/modules/Withdraw/Models/Withdraw.php @@ -0,0 +1,126 @@ + '待审核', + self::STATUS_PASS => '通过', + self::STATUS_REJECT => '驳回', + ]; + + const TYPE_BANK = 1; + const TYPE_ALIPAY = 2; + + const TYPES = [ + self::TYPE_BANK => '银行卡', + self::TYPE_ALIPAY => '支付宝', + ]; + + const TYPES_LABEL = [ + self::TYPE_BANK => 'primary', + self::TYPE_ALIPAY => 'success', + ]; + + protected $casts = [ + 'source' => 'json', + ]; + + public static function boot() + { + parent::boot(); + + self::creating(function ($model) { + $time = explode(' ', microtime()); + + $counter = $model->whereDate('created_at', Carbon::today())->count() + 1; + + $len = config('withdraw.withdraw_no_counter_length'); + + $len = $len < 6 ? 6 : $len; + $len = $len > 16 ? 16 : $len; + + $model->withdraw_no = date('YmdHis'). + sprintf('%06d', $time[0] * 1e6). + sprintf('%0'.$len.'d', $counter); + + $model->status = self::STATUS_INIT; + }); + } + + /** + * Notes: 关联银行账号 + * + * @Author: 玄尘 + * @Date : 2021/9/27 8:39 + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function bankAccount(): BelongsTo + { + return $this->belongsTo(Account::class); + } + + /** + * Notes: 支付宝账号 + * + * @Author: 玄尘 + * @Date : 2021/11/3 15:58 + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function alipayAccount(): BelongsTo + { + return $this->belongsTo(AlipayAccount::class); + } + + /** + * Notes: 提现状态 + * + * @Author: 玄尘 + * @Date : 2021/9/26 15:06 + * @return string + */ + public function getStatusTextAttribute(): string + { + return self::STATUS[$this->status] ?? '未知'; + } + + /** + * Notes: 是否可以审核 + * + * @Author: 玄尘 + * @Date : 2021/9/26 15:06 + * @return bool + */ + public function canAudit(): bool + { + return $this->status == self::STATUS_INIT; + } + + /** + * Notes: 操作记录 + * + * @Author: 玄尘 + * @Date : 2021/9/27 8:53 + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function logs(): HasMany + { + return $this->hasMany(Log::class); + } + +} diff --git a/modules/Withdraw/Providers/RouteServiceProvider.php b/modules/Withdraw/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..7ae1c02 --- /dev/null +++ b/modules/Withdraw/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').'/withdraws') + ->group(module_path($this->moduleName, 'Routes/api.php')); + } + +} diff --git a/modules/Withdraw/Providers/WithdrawServiceProvider.php b/modules/Withdraw/Providers/WithdrawServiceProvider.php new file mode 100644 index 0000000..6106710 --- /dev/null +++ b/modules/Withdraw/Providers/WithdrawServiceProvider.php @@ -0,0 +1,77 @@ +registerConfig(); + $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations')); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->app->register(RouteServiceProvider::class); + + $models = config('withdraw.model'); + if ($models) { + foreach ($models as $key => $model) { + $this->app->bind('withdraw.model.'.$key, function ($app) use ($model) { + return (new $model); + }); + } + } + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower.'.php'), + ], 'withdraw-config'); + + $this->mergeConfigFrom( + module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } + +} diff --git a/modules/Withdraw/README.md b/modules/Withdraw/README.md new file mode 100644 index 0000000..34be6ee --- /dev/null +++ b/modules/Withdraw/README.md @@ -0,0 +1,9 @@ +# 提现模块 + +> 1、先发布资源 + +> 2、启用模块 + +> 3、修改配置里的 account 提现账户 + +> 4、修改提现账变规则对应的账户 diff --git a/modules/Withdraw/Resources/assets/js/app.js b/modules/Withdraw/Resources/assets/js/app.js new file mode 100644 index 0000000..e69de29 diff --git a/modules/Withdraw/Resources/assets/sass/app.scss b/modules/Withdraw/Resources/assets/sass/app.scss new file mode 100644 index 0000000..e69de29 diff --git a/modules/Withdraw/Resources/views/index.blade.php b/modules/Withdraw/Resources/views/index.blade.php new file mode 100644 index 0000000..cf8ed5d --- /dev/null +++ b/modules/Withdraw/Resources/views/index.blade.php @@ -0,0 +1,9 @@ +@extends('withdraw::layouts.master') + +@section('content') +

Hello World

+ +

+ This view is loaded from module: {!! config('withdraw.name') !!} +

+@endsection diff --git a/modules/Withdraw/Resources/views/layouts/master.blade.php b/modules/Withdraw/Resources/views/layouts/master.blade.php new file mode 100644 index 0000000..2db20c8 --- /dev/null +++ b/modules/Withdraw/Resources/views/layouts/master.blade.php @@ -0,0 +1,19 @@ + + + + + + + Module Withdraw + + {{-- Laravel Mix - CSS File --}} + {{-- --}} + + + +@yield('content') + +{{-- Laravel Mix - JS File --}} +{{-- --}} + + diff --git a/modules/Withdraw/Routes/admin.php b/modules/Withdraw/Routes/admin.php new file mode 100644 index 0000000..79fee1d --- /dev/null +++ b/modules/Withdraw/Routes/admin.php @@ -0,0 +1,13 @@ + 'withdraws', +], function (Router $router) { + $router->resource('info', 'WithdrawController'); //提现管理 + $router->resource('banks', 'BankController'); //银行管理 + $router->resource('configs', 'ConfigController'); //配置信息 + $router->resource('accounts', 'AccountController'); //账户列表 +}); diff --git a/modules/Withdraw/Routes/api.php b/modules/Withdraw/Routes/api.php new file mode 100644 index 0000000..d405671 --- /dev/null +++ b/modules/Withdraw/Routes/api.php @@ -0,0 +1,15 @@ + config('coupon.route.middleware_auth'), +], function (Router $router) { + + $router->resource('accounts', 'AccountController'); //银行账户 + $router->resource('index', 'IndexController'); //提现记录 + +}); + + diff --git a/modules/Withdraw/Selectable/AccountAble.php b/modules/Withdraw/Selectable/AccountAble.php new file mode 100644 index 0000000..8740956 --- /dev/null +++ b/modules/Withdraw/Selectable/AccountAble.php @@ -0,0 +1,49 @@ +type == Withdraw::TYPE_BANK) { + $account = $withdraw->bankAccount; + $data = [ + $account->name, + $account->bank->name, + $account->no, + ]; + $header = ['提款人', '开户行', '卡号']; + $table = new Table($header, [$data]); + + return $table->render(); + } elseif ($withdraw->type == Withdraw::TYPE_ALIPAY) { + + $account = $withdraw->alipayAccount; + + $table = new Table( + [ + '姓名', + '支付宝账号', + ], + [ + [ + $account->name, + $account->identity, + ], + ] + ); + + return $table->render(); + } + + } + +} diff --git a/modules/Withdraw/Traits/HasWithdraws.php b/modules/Withdraw/Traits/HasWithdraws.php new file mode 100644 index 0000000..2b0eb80 --- /dev/null +++ b/modules/Withdraw/Traits/HasWithdraws.php @@ -0,0 +1,50 @@ +hasMany(Withdraw::class); + + } + + /** + * Notes: description + * + * @Author: 玄尘 + * @Date : 2021/11/3 14:42 + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function bankAccounts(): HasMany + { + return $this->hasMany(Account::class); + } + + /** + * Notes: 支付宝账号 + * + * @Author: 玄尘 + * @Date : 2021/11/3 14:41 + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function alipayAccounts(): HasMany + { + return $this->hasMany(AlipayAccount::class); + } + +} diff --git a/modules/Withdraw/Withdraw.php b/modules/Withdraw/Withdraw.php new file mode 100644 index 0000000..3fec667 --- /dev/null +++ b/modules/Withdraw/Withdraw.php @@ -0,0 +1,164 @@ + 'modules/Withdraw/Database/Migrations', + ]); + + self::createAdminMenu(); + self::addDefaultData(); + + } + + 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' => 1, + 'title' => '提现列表', + 'icon' => 'fa-align-left', + 'uri' => 'withdraws/info', + ]); + + $main->children() + ->create([ + 'order' => 2, + 'title' => '配置', + 'icon' => 'fa-align-left', + 'uri' => 'withdraws/configs', + ]); + + $main->children() + ->create([ + 'order' => 3, + 'title' => '银行列表', + 'icon' => 'fa-align-left', + 'uri' => 'withdraws/banks', + ]); + + $main->children() + ->create([ + 'order' => 4, + 'title' => '银行账户列表', + 'icon' => 'fa-align-left', + 'uri' => 'withdraws/accounts', + ]); + } + + } + + /** + * 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(); + } + + } + + public static function addDefaultData() + { + self::setAccountRule(); + self::setDefaultConfig(); + } + + public static function setDefaultConfig() + { + $configs_data = [ + ['name' => '手续费', 'slug' => 'tax', 'value' => 0,], + ['name' => '最少提现金额', 'slug' => 'min', 'value' => 1,], + ['name' => '提现代持账户', 'slug' => 'withdraw_addr', 'value' => 0,], + ]; + + foreach ($configs_data as $data) { + $info = Config::query()->where('slug', $data['slug'])->first(); + if (! $info) { + Config::query()->create($data); + } + } + } + + public static function setAccountRule() + { + $account_rules = config('withdraw.model.account_rules'); + + if (Schema::hasTable((new $account_rules)->getTable())) { + $account = config('withdraw.account'); + $rules_data = [ + [ + 'title' => '提现', + 'name' => 'withdraw_out', + 'type' => $account, + 'variable' => 0, + 'trigger' => 0, + 'deductions' => 1, + 'remark' => '提现扣除相应账户', + ], + [ + 'title' => '提现驳回', + 'name' => 'withdraw_in', + 'type' => $account, + 'variable' => 0, + 'trigger' => 0, + 'deductions' => 1, + 'remark' => '提现驳回返回提现金额', + ], + ]; + + foreach ($rules_data as $data) { + $info = (new $account_rules)::query()->where('name', $data['name'])->first(); + if (! $info) { + (new $account_rules)::query()->create($data); + } else { + if ($info->type != $account) { + $info->update([ + 'type' => $account, + ]); + } + } + } + } + } + +} \ No newline at end of file diff --git a/modules/Withdraw/composer.json b/modules/Withdraw/composer.json new file mode 100644 index 0000000..aeaffe9 --- /dev/null +++ b/modules/Withdraw/composer.json @@ -0,0 +1,21 @@ +{ + "name": "xuanchen/withdraw-module", + "description": "", + "type": "laravel-module", + "authors": [ + { + "name": "Chen.Xuan", + "email": "122383162@qq.com" + } + ], + "require": { + }, + "extra": { + "module-dir": "modules" + }, + "autoload": { + "psr-4": { + "Modules\\Withdraw\\": "" + } + } +} diff --git a/modules/Withdraw/module.json b/modules/Withdraw/module.json new file mode 100644 index 0000000..b5ea7d3 --- /dev/null +++ b/modules/Withdraw/module.json @@ -0,0 +1,15 @@ +{ + "name": "Withdraw", + "alias": "withdraw", + "description": "提现", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Withdraw\\Providers\\WithdrawServiceProvider" + ], + "aliases": {}, + "files": [], + "version": "1.0.0", + "author": "玄尘", + "requires": [] +} diff --git a/modules/Withdraw/提现模块 v3.apifox.json b/modules/Withdraw/提现模块 v3.apifox.json new file mode 100644 index 0000000..494e886 --- /dev/null +++ b/modules/Withdraw/提现模块 v3.apifox.json @@ -0,0 +1 @@ +{"apifoxProject":"1.0.0","info":{"name":"测试","description":"","mockRule":{"rules":[],"enableSystemRule":true}},"responseCollection":[],"apiCollection":[{"name":"根目录","parentId":0,"serverId":"","description":null,"preProcessors":[],"postProcessors":[],"items":[{"name":"默认分类","parentId":0,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[]},{"name":"提现模块 v3","parentId":0,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[{"name":"银行账户","parentId":1376058,"serverId":"","description":"","preProcessors":[],"postProcessors":[],"items":[{"name":"我的银行账户","api":{"id":"7858181","method":"get","path":"/api/withdraws/accounts","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914032","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[],"mocks":[]}},{"name":"添加账户前置","api":{"id":"7858182","method":"get","path":"/api/withdraws/accounts/create","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914033","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[],"mocks":[]}},{"name":"添加账户","api":{"id":"7858183","method":"post","path":"/api/withdraws/accounts","parameters":{"path":[],"query":[{"name":"bank_id","required":true,"description":"银行","type":"text"},{"name":"name","required":true,"description":"收款人姓名","sampleValue":"","type":"text"},{"name":"branch_name","required":true,"description":"支行名称","type":"text"},{"name":"no","required":true,"description":"银行卡号","type":"text"},{"name":"mobile","required":true,"description":"收款人手机号","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914034","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":8419368,"name":"成功","responseId":10914034,"parameters":{"path":[],"query":[{"name":"bank_id","value":"1","enable":true},{"name":"name","value":"徐永宾","enable":true},{"name":"branch_name","value":"XXX工商银行","enable":true},{"name":"no","value":"6212263588888659456","enable":true},{"name":"mobile","value":"15663876870","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"编辑前置","api":{"id":"7858184","method":"get","path":"/api/withdraws/accounts/{bank_account_id}/edit","parameters":{"path":[{"name":"bank_account_id","required":true,"sampleValue":"1","type":"text"}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914035","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":18,"cases":[],"mocks":[]}},{"name":"编辑-提交","api":{"id":"7858185","method":"put","path":"/api/withdraws/accounts/{bank_account_id}","parameters":{"path":[{"name":"bank_account_id","required":true,"type":"text"}],"query":[{"name":"bank_id","required":true,"description":"银行","type":"text"},{"name":"name","required":true,"description":"收款人姓名","sampleValue":"","type":"text"},{"name":"branch_name","required":true,"description":"支行名称","type":"text"},{"name":"no","required":true,"description":"银行卡号","type":"text"},{"name":"mobile","required":true,"description":"收款人手机号","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914036","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":24,"cases":[{"id":8419369,"name":"成功","responseId":10914036,"parameters":{"path":[],"query":[{"name":"bank_id","value":"1","enable":true},{"name":"name","value":"徐永宾","enable":true},{"name":"branch_name","value":"XXX工商银行","enable":true},{"name":"no","value":"62122695698564856","enable":true},{"name":"mobile","value":"15663876870","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"删除","api":{"id":"7858186","method":"delete","path":"/api/withdraws/accounts/{bank_account_id}","parameters":{"path":[{"name":"bank_account_id","required":true,"type":"text"}],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914037","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":30,"cases":[],"mocks":[]}}]},{"name":"我的提现","api":{"id":"7858178","method":"get","path":"/api/withdraws/index","parameters":{"path":[],"query":[{"name":"type","required":true,"description":"天 day 月month 年 year","sampleValue":"year","type":"text"},{"name":"date","required":true,"description":"","type":"text"}],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914029","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":0,"cases":[{"id":8419366,"name":"成功","responseId":10914029,"parameters":{"path":[],"query":[{"name":"type","value":"year","enable":true},{"name":"date","enable":true}],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"提现前置","api":{"id":"7858179","method":"get","path":"/api/withdraws/index/create","parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"responses":[{"id":"10914030","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":6,"cases":[{"id":8419367,"name":"成功","responseId":10914030,"parameters":{"path":[],"query":[],"cookie":[],"header":[]},"commonParameters":{},"requestBody":{"parameters":[]},"preProcessors":[],"postProcessors":[]}],"mocks":[]}},{"name":"提现","api":{"id":"7858180","method":"post","path":"/api/withdraws/index","parameters":{"path":[],"query":[{"name":"bank_account_id","required":true,"description":"","sampleValue":"23","type":"text"},{"name":"amount","required":true,"description":"","type":"text"},{"required":true,"description":"1 银行卡提现 2 支付宝提现","name":"type","sampleValue":"1"},{"required":false,"description":"支付宝姓名:type =2 必填","name":"name"},{"required":false,"description":"支付宝账号:type =2 必填","name":"identity"}],"cookie":[],"header":[]},"commonParameters":{"query":[],"body":[],"cookie":[],"header":[]},"responses":[{"id":"10914031","name":"成功","code":200,"contentType":"json","jsonSchema":{"type":"object","properties":{}}}],"responseExamples":[],"requestBody":{"type":"none","parameters":[]},"description":"","tags":[],"status":"developing","serverId":"","operationId":"","sourceUrl":"","ordering":12,"cases":[{"id":8419402,"name":"成功","responseId":10914031,"parameters":{"query":[{"name":"bank_account_id","value":"23","enable":true},{"name":"amount","enable":true},{"name":"type","value":"1","enable":true},{"name":"name","enable":true},{"name":"identity","enable":true}]},"commonParameters":{"query":[],"body":[],"header":[],"cookie":[]},"requestBody":{"parameters":[],"type":"none"},"preProcessors":[],"postProcessors":[]}],"mocks":[]}}]}]}],"socketCollection":[],"docCollection":[],"schemaCollection":[{"name":"默认分类","items":[]},{"name":"默认分组","items":[]}],"apiTestCaseCollection":[{"name":"默认分类","children":[],"items":[]},{"name":"默认分组","children":[],"items":[]}],"apiTestSuiteCollection":[],"environments":[{"baseUrl":"http://127.0.0.1:4523/mock/429954","baseUrls":{"default":"http://127.0.0.1:4523/mock/undefined"},"parameters":{},"variables":[],"name":"Mock 服务","type":"mock","visibility":"protected","ordering":0,"id":"601712"}],"commonScripts":[{"name":"登录后保存token","description":"","content":"var jsonData = pm.response.json();\n\npm.environment.set('api_token', jsonData.data.access_token);","id":"348840"}],"databaseConnections":[],"globalVariables":[],"commonParameters":null,"projectSetting":{"apiStatuses":["developing","testing","released","deprecated"],"servers":[{"id":"default","name":"默认服务"}],"preProcessors":[],"postProcessors":[],"id":"374225"}} \ No newline at end of file