浏览代码

Split User Routes and Adjust Admin Routes

BrettonYe 9 月之前
父节点
当前提交
60a55dcfd1
共有 44 个文件被更改,包括 707 次插入595 次删除
  1. 1 1
      app/Console/Commands/TaskDaily.php
  2. 63 0
      app/Http/Controllers/Admin/InviteController.php
  3. 18 0
      app/Http/Controllers/Admin/SystemController.php
  4. 3 3
      app/Http/Controllers/Admin/TicketController.php
  5. 0 75
      app/Http/Controllers/AdminController.php
  6. 4 4
      app/Http/Controllers/OAuthController.php
  7. 5 5
      app/Http/Controllers/User/AffiliateController.php
  8. 46 0
      app/Http/Controllers/User/ArticleController.php
  9. 46 0
      app/Http/Controllers/User/InviteController.php
  10. 50 0
      app/Http/Controllers/User/InvoiceController.php
  11. 40 0
      app/Http/Controllers/User/NodeController.php
  12. 125 0
      app/Http/Controllers/User/ShopController.php
  13. 86 0
      app/Http/Controllers/User/TicketController.php
  14. 33 370
      app/Http/Controllers/UserController.php
  15. 1 1
      app/Notifications/PaymentReceived.php
  16. 1 1
      app/Utils/Payments/CodePay.php
  17. 1 1
      app/Utils/Payments/EPay.php
  18. 1 1
      app/Utils/Payments/PayBeaver.php
  19. 2 2
      app/Utils/Payments/PayPal.php
  20. 3 3
      app/Utils/Payments/Stripe.php
  21. 2 2
      database/seeders/LabelSeeder.php
  22. 1 1
      resources/views/admin/article/show.blade.php
  23. 7 7
      resources/views/admin/config/common.blade.php
  24. 1 1
      resources/views/admin/logs/order.blade.php
  25. 20 9
      resources/views/admin/user/export.blade.php
  26. 4 4
      resources/views/admin/user/index.blade.php
  27. 4 4
      resources/views/admin/user/info.blade.php
  28. 2 2
      resources/views/user/buy.blade.php
  29. 1 1
      resources/views/user/components/notifications/paymentReceived.blade.php
  30. 2 2
      resources/views/user/components/payment/default.blade.php
  31. 1 1
      resources/views/user/components/payment/manual.blade.php
  32. 4 4
      resources/views/user/index.blade.php
  33. 1 1
      resources/views/user/invite.blade.php
  34. 2 2
      resources/views/user/invoices.blade.php
  35. 1 1
      resources/views/user/knowledge.blade.php
  36. 17 17
      resources/views/user/layouts.blade.php
  37. 1 2
      resources/views/user/nodeList.blade.php
  38. 4 3
      resources/views/user/profile.blade.php
  39. 1 1
      resources/views/user/referral.blade.php
  40. 31 20
      resources/views/user/replyTicket.blade.php
  41. 3 3
      resources/views/user/services.blade.php
  42. 2 3
      resources/views/user/tickets.blade.php
  43. 11 10
      routes/admin.php
  44. 55 27
      routes/user.php

+ 1 - 1
app/Console/Commands/TaskDaily.php

@@ -78,7 +78,7 @@ class TaskDaily extends Command
         })->where('updated_at', '<=', now()->subHours($closeTicketsHours))->chunk(config('tasks.chunk'), function ($tickets) use ($closeTicketsHours) {
             $tickets->each(function ($ticket) use ($closeTicketsHours) {
                 if ($ticket->close()) {
-                    $ticket->user->notify(new TicketClosed($ticket->id, $ticket->title, route('replyTicket', ['id' => $ticket->id]),
+                    $ticket->user->notify(new TicketClosed($ticket->id, $ticket->title, route('ticket.edit', $ticket),
                         __('You have not responded this ticket in :num hours, System has closed your ticket.', ['num' => $closeTicketsHours])));
                 }
             });

+ 63 - 0
app/Http/Controllers/Admin/InviteController.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\Invite;
+use Illuminate\Http\JsonResponse;
+use Log;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+use Response;
+use Str;
+
+class InviteController extends Controller
+{
+    public function index(): \Illuminate\Http\Response
+    { // 邀请码列表
+        return Response::view('admin.aff.invite', [
+            'inviteList' => Invite::with(['invitee:id,username', 'inviter:id,username'])->orderBy('status')->orderByDesc('id')->paginate(15)->appends(request('page')),
+        ]);
+    }
+
+    public function create(): JsonResponse
+    { // 生成邀请码
+        for ($i = 0; $i < 10; $i++) {
+            $obj = new Invite;
+            $obj->code = strtoupper(substr(md5(microtime().Str::random(6)), 8, 12));
+            $obj->dateline = date('Y-m-d H:i:s', strtotime(sysConfig('admin_invite_days').' days'));
+            $obj->save();
+        }
+
+        return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.generate')])]);
+    }
+
+    public function export(): void
+    { // 导出邀请码
+        $inviteList = Invite::whereStatus(0)->orderBy('id')->get();
+        $filename = trans('user.invite.attribute').'_'.date('Ymd').'.xlsx';
+
+        $spreadsheet = new Spreadsheet;
+        $spreadsheet->getProperties()->setCreator('ProxyPanel')->setLastModifiedBy('ProxyPanel')->setTitle(trans('user.invite.attribute'))->setSubject(trans('user.invite.attribute'));
+        $spreadsheet->setActiveSheetIndex(0);
+        $sheet = $spreadsheet->getActiveSheet();
+        $sheet->setTitle(trans('user.invite.attribute'));
+        $sheet->fromArray([trans('user.invite.attribute'), trans('common.available_date')]);
+
+        foreach ($inviteList as $k => $vo) {
+            $sheet->fromArray([$vo->code, $vo->dateline], null, 'A'.($k + 2));
+        }
+
+        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // 输出07Excel文件
+        //header('Content-Type:application/vnd.ms-excel'); // 输出Excel03版本文件
+        header('Content-Disposition: attachment;filename="'.$filename.'"');
+        header('Cache-Control: max-age=0');
+        try {
+            $writer = new Xlsx($spreadsheet);
+            $writer->save('php://output');
+        } catch (Exception $e) {
+            Log::error(trans('common.error_action_item', ['action' => trans('common.export'), 'attribute' => trans('user.invite.attribute')]).': '.$e->getMessage());
+        }
+    }
+}

+ 18 - 0
app/Http/Controllers/Admin/SystemController.php

@@ -13,6 +13,11 @@ use App\Channels\WeChatChannel;
 use App\Http\Controllers\Controller;
 use App\Http\Requests\Admin\SystemRequest;
 use App\Models\Config;
+use App\Models\Country;
+use App\Models\GoodsCategory;
+use App\Models\Label;
+use App\Models\Level;
+use App\Models\SsConfig;
 use App\Notifications\Custom;
 use App\Services\TelegramService;
 use App\Utils\DDNS;
@@ -246,4 +251,17 @@ class SystemController extends Controller
 
         return Response::json(['status' => 'success', 'message' => trans('admin.system.notification.test.success')]);
     }
+
+    public function common(): View
+    {
+        return view('admin.config.common', [
+            'methods' => SsConfig::type(1)->get(),
+            'protocols' => SsConfig::type(2)->get(),
+            'categories' => GoodsCategory::all(),
+            'obfsList' => SsConfig::type(3)->get(),
+            'countries' => Country::all(),
+            'levels' => Level::all(),
+            'labels' => Label::with('nodes')->get(),
+        ]);
+    }
 }

+ 3 - 3
app/Http/Controllers/Admin/TicketController.php

@@ -43,7 +43,7 @@ class TicketController extends Controller
         }
 
         if ($ticket = Ticket::create(['user_id' => $user->id, 'admin_id' => auth()->id(), 'title' => $data['title'], 'content' => clean($data['content'])])) {
-            $user->notify(new TicketCreated($ticket, route('replyTicket', ['id' => $ticket->id])));
+            $user->notify(new TicketCreated($ticket, route('ticket.edit', $ticket)));
 
             return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.create')])]);
         }
@@ -75,7 +75,7 @@ class TicketController extends Controller
 
             // 通知用户
             if (sysConfig('ticket_replied_notification')) {
-                $ticket->user->notify(new TicketReplied($reply, route('replyTicket', ['id' => $ticket->id]), true));
+                $ticket->user->notify(new TicketReplied($reply, route('ticket.edit', $ticket), true));
             }
 
             return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('user.ticket.reply')])]);
@@ -92,7 +92,7 @@ class TicketController extends Controller
         }
         // 通知用户
         if (sysConfig('ticket_closed_notification')) {
-            $ticket->user->notify(new TicketClosed($ticket->id, $ticket->title, route('replyTicket', ['id' => $ticket->id]), \request('reason'), true));
+            $ticket->user->notify(new TicketClosed($ticket->id, $ticket->title, route('ticket.edit', $ticket), \request('reason'), true));
         }
 
         return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.close')])]);

+ 0 - 75
app/Http/Controllers/AdminController.php

@@ -2,29 +2,17 @@
 
 namespace App\Http\Controllers;
 
-use App\Models\Country;
-use App\Models\GoodsCategory;
-use App\Models\Invite;
-use App\Models\Label;
-use App\Models\Level;
 use App\Models\Node;
 use App\Models\NodeDailyDataFlow;
 use App\Models\NodeHourlyDataFlow;
 use App\Models\Order;
 use App\Models\ReferralApply;
 use App\Models\ReferralLog;
-use App\Models\SsConfig;
 use App\Models\User;
 use App\Models\UserHourlyDataFlow;
 use Cache;
 use DB;
-use Illuminate\Http\JsonResponse;
-use Log;
-use PhpOffice\PhpSpreadsheet\Exception;
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
-use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 use Response;
-use Str;
 
 class AdminController extends Controller
 {
@@ -77,67 +65,4 @@ class AdminController extends Controller
             'todaySuccessOrder' => Order::whereIn('status', [2, 3])->whereDate('created_at', $today)->count(),
         ]);
     }
-
-    // 邀请码列表
-    public function inviteList()
-    {
-        return view('admin.aff.invite', [
-            'inviteList' => Invite::with(['invitee:id,username', 'inviter:id,username'])->orderBy('status')->orderByDesc('id')->paginate(15)->appends(request('page')),
-        ]);
-    }
-
-    // 生成邀请码
-    public function makeInvite(): JsonResponse
-    {
-        for ($i = 0; $i < 10; $i++) {
-            $obj = new Invite;
-            $obj->code = strtoupper(substr(md5(microtime().Str::random(6)), 8, 12));
-            $obj->dateline = date('Y-m-d H:i:s', strtotime(sysConfig('admin_invite_days').' days'));
-            $obj->save();
-        }
-
-        return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.generate')])]);
-    }
-
-    // 导出邀请码
-    public function exportInvite(): void
-    {
-        $inviteList = Invite::whereStatus(0)->orderBy('id')->get();
-        $filename = trans('user.invite.attribute').'_'.date('Ymd').'.xlsx';
-
-        $spreadsheet = new Spreadsheet;
-        $spreadsheet->getProperties()->setCreator('ProxyPanel')->setLastModifiedBy('ProxyPanel')->setTitle(trans('user.invite.attribute'))->setSubject(trans('user.invite.attribute'));
-        $spreadsheet->setActiveSheetIndex(0);
-        $sheet = $spreadsheet->getActiveSheet();
-        $sheet->setTitle(trans('user.invite.attribute'));
-        $sheet->fromArray([trans('user.invite.attribute'), trans('common.available_date')]);
-
-        foreach ($inviteList as $k => $vo) {
-            $sheet->fromArray([$vo->code, $vo->dateline], null, 'A'.($k + 2));
-        }
-
-        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // 输出07Excel文件
-        //header('Content-Type:application/vnd.ms-excel'); // 输出Excel03版本文件
-        header('Content-Disposition: attachment;filename="'.$filename.'"');
-        header('Cache-Control: max-age=0');
-        try {
-            $writer = new Xlsx($spreadsheet);
-            $writer->save('php://output');
-        } catch (Exception $e) {
-            Log::error(trans('common.error_action_item', ['action' => trans('common.export'), 'attribute' => trans('user.invite.attribute')]).': '.$e->getMessage());
-        }
-    }
-
-    public function config()
-    {
-        return view('admin.config.common', [
-            'methods' => SsConfig::type(1)->get(),
-            'protocols' => SsConfig::type(2)->get(),
-            'categories' => GoodsCategory::all(),
-            'obfsList' => SsConfig::type(3)->get(),
-            'countries' => Country::all(),
-            'levels' => Level::all(),
-            'labels' => Label::with('nodes')->get(),
-        ]);
-    }
 }

+ 4 - 4
app/Http/Controllers/OAuthController.php

@@ -18,10 +18,10 @@ class OAuthController extends Controller
         $user = Auth::user();
 
         if ($user && $user->userAuths()->whereType($provider)->delete()) {
-            return redirect()->route('profile')->with('successMsg', trans('common.success_item', ['attribute' => trans('user.oauth.unbind')]));
+            return redirect()->back()->with('successMsg', trans('common.success_item', ['attribute' => trans('user.oauth.unbind')]));
         }
 
-        return redirect()->route('profile')->withErrors(trans('common.failed_item', ['attribute' => trans('user.oauth.unbind')]));
+        return redirect()->back()->withErrors(trans('common.failed_item', ['attribute' => trans('user.oauth.unbind')]));
     }
 
     public function bind(string $provider): RedirectResponse
@@ -36,7 +36,7 @@ class OAuthController extends Controller
         $user = Auth::user();
 
         if (! $user) {
-            return redirect()->route('profile')->withErrors(trans('common.failed_item', ['attribute' => trans('user.oauth.bind')]));
+            return redirect()->back()->withErrors(trans('common.failed_item', ['attribute' => trans('user.oauth.bind')]));
         }
 
         return $this->bindLogic($provider, $user, $authInfo);
@@ -60,7 +60,7 @@ class OAuthController extends Controller
             $message = trans('common.success_item', ['attribute' => trans('user.oauth.bind')]);
         }
 
-        return redirect()->route('profile')->with('successMsg', $message);
+        return redirect()->back()->with('successMsg', $message);
     }
 
     public function register(string $provider): RedirectResponse

+ 5 - 5
app/Http/Controllers/User/AffiliateController.php

@@ -15,7 +15,7 @@ use Response;
 class AffiliateController extends Controller
 {
     // 推广返利
-    public function referral()
+    public function index()
     {
         if (ReferralLog::uid()->doesntExist() && Order::uid()->whereStatus(2)->doesntExist()) {
             return Response::view('auth.error', ['message' => trans('user.purchase.required').'<a class="btn btn-sm btn-danger" href="/">'.trans('common.back').'</a>'], 402);
@@ -35,7 +35,7 @@ class AffiliateController extends Controller
     }
 
     // 申请提现
-    public function extractMoney(): JsonResponse
+    public function withdraw(): JsonResponse
     {
         // 判断账户是否过期
         if (Auth::getUser()->expiration_date < date('Y-m-d')) {
@@ -48,8 +48,8 @@ class AffiliateController extends Controller
         }
 
         // 校验可以提现金额是否超过系统设置的阀值
-        $commission = ReferralLog::uid()->whereStatus(0)->sum('commission');
-        $commission /= 100;
+        $referrals = ReferralLog::uid()->whereStatus(0)->get();
+        $commission = $referrals->sum('commission');
         if ($commission < sysConfig('referral_money')) {
             return Response::json([
                 'status' => 'fail', 'title' => trans('common.failed_item', ['attribute' => trans('common.request')]), 'message' => trans('user.referral.msg.unfulfilled', ['amount' => Helpers::getPriceTag(sysConfig('referral_money'))]),
@@ -60,7 +60,7 @@ class AffiliateController extends Controller
         $ref->user_id = Auth::id();
         $ref->before = $commission;
         $ref->amount = $commission;
-        $ref->link_logs = ReferralLog::uid()->whereStatus(0)->pluck('id')->toArray();
+        $ref->link_logs = $referrals->pluck('id')->toArray();
         if ($ref->save()) {
             return Response::json(['status' => 'success', 'title' => trans('common.success_item', ['attribute' => trans('common.request')]), 'message' => trans('user.referral.msg.wait')]);
         }

+ 46 - 0
app/Http/Controllers/User/ArticleController.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Controllers\User;
+
+use App\Http\Controllers\Controller;
+use App\Models\Article;
+use App\Models\Node;
+use App\Services\ArticleService;
+use Illuminate\Http\JsonResponse;
+
+class ArticleController extends Controller
+{
+    public function index()
+    { // 帮助中心
+        $data = [];
+        if (Node::whereType(0)->whereStatus(1)->exists()) {
+            $data[] = 'ss';
+        }
+        if (Node::whereIn('type', [1, 4])->whereStatus(1)->exists()) {
+            $data[] = 'ssr';
+        }
+        if (Node::whereType(2)->whereStatus(1)->exists()) {
+            $data[] = 'v2';
+        }
+        if (Node::whereType(3)->whereStatus(1)->exists()) {
+            $data[] = 'trojan';
+        }
+
+        $subscribe = auth()->user()->subscribe;
+
+        return view('user.knowledge', [
+            'subType' => $data,
+            'subUrl' => route('sub', $subscribe->code),
+            'subStatus' => $subscribe->status,
+            'subMsg' => $subscribe->ban_desc,
+            'knowledges' => Article::type(1)->lang()->orderByDesc('sort')->latest()->get()->groupBy('category'),
+        ]);
+    }
+
+    public function show(Article $article): JsonResponse
+    { // 公告详情
+        $articleService = new ArticleService($article);
+
+        return response()->json(['title' => $article->title, 'content' => $articleService->getContent()]);
+    }
+}

+ 46 - 0
app/Http/Controllers/User/InviteController.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Controllers\User;
+
+use App\Http\Controllers\Controller;
+use App\Models\Invite;
+use App\Models\Order;
+use Illuminate\Http\JsonResponse;
+use Response;
+use Str;
+
+class InviteController extends Controller
+{
+    public function index(): \Illuminate\Http\Response
+    { // 邀请页面
+        if (Order::uid()->active()->where('origin_amount', '>', 0)->doesntExist()) {
+            return Response::view('auth.error', ['message' => trans('user.purchase.required').' <a class="btn btn-sm btn-danger" href="/">'.trans('common.back').'</a>'], 402);
+        }
+
+        return Response::view('user.invite', [
+            'num' => auth()->user()->invite_num, // 还可以生成的邀请码数量
+            'inviteList' => Invite::uid()->with('invitee')->paginate(10), // 邀请码列表
+            'referral_traffic' => formatBytes(sysConfig('referral_traffic'), 'MiB'),
+            'referral_percent' => sysConfig('referral_percent'),
+        ]);
+    }
+
+    public function store(): JsonResponse
+    {  // 生成邀请码
+        $user = auth()->user();
+        if ($user->invite_num <= 0) {
+            return Response::json(['status' => 'fail', 'message' => trans('user.invite.generate_failed')]);
+        }
+        $invite = $user->invites()->create([
+            'code' => strtoupper(mb_substr(md5(microtime().Str::random()), 8, 12)),
+            'dateline' => date('Y-m-d H:i:s', strtotime(sysConfig('user_invite_days').' days')),
+        ]);
+        if ($invite) {
+            $user->decrement('invite_num');
+
+            return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.generate')])]);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.generate')])]);
+    }
+}

+ 50 - 0
app/Http/Controllers/User/InvoiceController.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Http\Controllers\User;
+
+use App\Http\Controllers\Controller;
+use App\Models\Order;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Response;
+
+class InvoiceController extends Controller
+{
+    public function index(Request $request): \Illuminate\Http\Response
+    { // 订单列表
+        return Response::view('user.invoices', [
+            'orderList' => auth()->user()->orders()->with(['goods', 'payment'])->orderByDesc('id')->paginate(10)->appends($request->except('page')),
+            'prepaidPlan' => Order::userPrepay()->exists(),
+        ]);
+    }
+
+    public function show(string $sn): \Illuminate\Http\Response
+    { // 订单明细
+        return Response::view('user.invoiceDetail', ['order' => Order::uid()->whereSn($sn)->with(['goods', 'coupon'])->firstOrFail()]);
+    }
+
+    public function activate(): JsonResponse
+    { // 激活套餐
+        $activePlan = Order::userActivePlan()->first();
+        if ($activePlan) {
+            if ($activePlan->expired()) { // 关闭先前套餐后,新套餐自动运行
+                if (Order::userActivePlan()->exists()) {
+                    return Response::json(['status' => 'success', 'message' => trans('common.active_item', ['attribute' => trans('common.success')])]);
+                }
+
+                return Response::json(['status' => 'success', 'message' => trans('common.close')]);
+            }
+        } else {
+            $prepaidPlan = Order::userPrepay()->first();
+            if ($prepaidPlan) { // 关闭先前套餐后,新套餐自动运行
+                if ($prepaidPlan->complete()) {
+                    return Response::json(['status' => 'success', 'message' => trans('common.active_item', ['attribute' => trans('common.success')])]);
+                }
+
+                return Response::json(['status' => 'success', 'message' => trans('common.close')]);
+            }
+        }
+
+        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.close')])]);
+    }
+}

+ 40 - 0
app/Http/Controllers/User/NodeController.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Http\Controllers\User;
+
+use App\Http\Controllers\Controller;
+use App\Models\Node;
+use App\Models\NodeHeartbeat;
+use App\Services\ProxyService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Response;
+
+class NodeController extends Controller
+{
+    public function index(): \Illuminate\Http\Response
+    { // 节点列表
+        $user = auth()->user();
+
+        // 获取当前用户可用节点
+        $nodeList = $user->nodes()->whereIn('is_display', [1, 3])->with(['labels', 'level_table'])->get();
+        $onlineNode = NodeHeartbeat::recently()->distinct()->pluck('node_id')->toArray();
+        foreach ($nodeList as $node) {
+            // 节点在线状态
+            $node->offline = ! in_array($node->id, $onlineNode, true);
+        }
+
+        return Response::view('user.nodeList', [
+            'nodesGeo' => $nodeList->pluck('name', 'geo')->toArray(),
+            'nodeList' => $nodeList,
+        ]);
+    }
+
+    public function show(Request $request, Node $node): JsonResponse
+    { // 节点详细
+        $proxyServer = new ProxyService;
+        $server = $proxyServer->getProxyConfig($node);
+
+        return Response::json(['status' => 'success', 'data' => $proxyServer->getUserProxyConfig($server, $request->input('type') !== 'text'), 'title' => $server['type']]);
+    }
+}

+ 125 - 0
app/Http/Controllers/User/ShopController.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace App\Http\Controllers\User;
+
+use App\Http\Controllers\Controller;
+use App\Models\Coupon;
+use App\Models\Goods;
+use App\Models\Node;
+use App\Models\Order;
+use App\Services\CouponService;
+use App\Utils\Helpers;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Validation\Rule;
+use Response;
+use Validator;
+
+class ShopController extends Controller
+{
+    public function index(): \Illuminate\Http\Response
+    { // 商品列表
+        $user = auth()->user();
+        // 余额充值商品,只取10个
+        $renewOrder = Order::userActivePlan($user->id)->first();
+        $renewPrice = $renewOrder->goods->renew ?? 0;
+        // 有重置日时按照重置日为标准,否则就以过期日为标准
+        $dataPlusDays = $user->reset_time ?? $user->expired_at;
+
+        $goodsList = Goods::whereStatus(1)->where('type', '<=', '2')->orderByDesc('type')->orderByDesc('sort')->get();
+
+        if ($user && $nodes = $user->userGroup) {
+            $nodes = $nodes->nodes();
+        } else {
+            $nodes = Node::all();
+        }
+        foreach ($goodsList as $goods) {
+            $goods->node_count = $nodes->where('level', '<=', $goods->level)->count();
+            $goods->node_countries = $nodes->where('level', '<=', $goods->level)->pluck('country_code')->unique();
+        }
+
+        return Response::view('user.services', [
+            'chargeGoodsList' => Goods::type(3)->orderBy('price')->get(),
+            'goodsList' => $goodsList,
+            'renewTraffic' => $renewPrice ? Helpers::getPriceTag($renewPrice) : 0,
+            'dataPlusDays' => $dataPlusDays > date('Y-m-d') ? $dataPlusDays->diffInDays() : 0,
+        ]);
+    }
+
+    public function resetTraffic(): JsonResponse
+    { // 重置流量
+        $user = auth()->user();
+        $order = Order::userActivePlan()->firstOrFail();
+        $renewCost = $order->goods->renew;
+        if ($user->credit < $renewCost) {
+            return Response::json(['status' => 'fail', 'message' => trans('user.payment.insufficient_balance')]);
+        }
+
+        $user->update(['u' => 0, 'd' => 0]);
+
+        // 记录余额操作日志
+        Helpers::addUserCreditLog($user->id, null, $user->credit, $user->credit - $renewCost, -1 * $renewCost, 'The user manually reset the data.');
+
+        // 扣余额
+        $user->updateCredit(-$renewCost);
+
+        return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.reset')])]);
+    }
+
+    public function checkBonus(Request $request, Goods $good): JsonResponse
+    { // 兑换优惠券码
+        $coupon_sn = $request->input('coupon_sn');
+
+        if (empty($coupon_sn)) {
+            return Response::json(['status' => 'fail', 'title' => trans('common.failed'), 'message' => trans('user.coupon.error.unknown')]);
+        }
+
+        $coupon = (new CouponService($coupon_sn))->search($good); // 检查券合规性
+
+        if (! $coupon instanceof Coupon) {
+            return $coupon;
+        }
+
+        $data = [
+            'name' => $coupon->name,
+            'type' => $coupon->type,
+            'value' => $coupon->type === 2 ? $coupon->value : Helpers::getPriceTag($coupon->value),
+        ];
+
+        return Response::json(['status' => 'success', 'data' => $data, 'message' => trans('common.applied', ['attribute' => trans('model.coupon.attribute')])]);
+    }
+
+    public function show(Goods $good): \Illuminate\Http\Response
+    { // 显示服务详细
+        $user = auth()->user();
+        // 有重置日时按照重置日为标准,否则就以过期日为标准
+        $dataPlusDays = $user->reset_time ?? $user->expired_at;
+
+        return Response::view('user.buy', [
+            'dataPlusDays' => $dataPlusDays > date('Y-m-d') ? $dataPlusDays->diffInDays() : 0,
+            'activePlan' => Order::userActivePlan()->exists(),
+            'goods' => $good,
+        ]);
+    }
+
+    public function charge(Request $request): JsonResponse
+    {
+        $validator = Validator::make($request->all(), [
+            'coupon_sn' => [
+                'required', Rule::exists('coupon', 'sn')->where(static function ($query) {
+                    $query->whereType(3)->whereStatus(0);
+                }),
+            ],
+        ]);
+
+        if ($validator->fails()) {
+            return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
+        }
+
+        if ((new CouponService($request->input('coupon_sn')))->charge()) {
+            return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('user.recharge')])]);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('user.recharge')])]);
+    }
+}

+ 86 - 0
app/Http/Controllers/User/TicketController.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace App\Http\Controllers\User;
+
+use App\Http\Controllers\Controller;
+use App\Models\Ticket;
+use App\Models\User;
+use App\Notifications\TicketCreated;
+use App\Notifications\TicketReplied;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Notification;
+use Response;
+
+class TicketController extends Controller
+{
+    public function index(Request $request)
+    { // 工单
+        return view('user.tickets', [
+            'tickets' => auth()->user()->tickets()->latest()->paginate(10)->appends($request->except('page')),
+        ]);
+    }
+
+    public function store(Request $request): ?JsonResponse
+    { // 添加工单
+        $user = auth()->user();
+        $title = $request->input('title');
+        $content = substr(str_replace(['atob', 'eval'], '', clean($request->input('content'))), 0, 300);
+
+        if (empty($title) || empty($content)) {
+            return Response::json([
+                'status' => 'fail', 'message' => trans('validation.required', ['attribute' => ucfirst(trans('validation.attributes.title')).'&'.ucfirst(trans('validation.attributes.content'))]),
+            ]);
+        }
+
+        if ($ticket = $user->tickets()->create(compact('title', 'content'))) {
+            // 通知相关管理员
+            Notification::send(User::find(1), new TicketCreated($ticket, route('admin.ticket.edit', $ticket)));
+        }
+
+        return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.submit')])]);
+    }
+
+    public function edit(Ticket $ticket)
+    { // 回复工单
+        return view('user.replyTicket', [
+            'ticket' => $ticket,
+            'replyList' => $ticket->reply()->with('ticket:id,status', 'admin:id,username,qq', 'user:id,username,qq')->oldest()->get(),
+        ]);
+    }
+
+    public function reply(Request $request, Ticket $ticket)
+    {
+        $content = substr(str_replace(['atob', 'eval'], '', clean($request->input('content'))), 0, 300);
+
+        if (empty($content)) {
+            return Response::json([
+                'status' => 'fail', 'message' => trans('validation.required', ['attribute' => ucfirst(trans('validation.attributes.title')).'&'.ucfirst(trans('validation.attributes.content'))]),
+            ]);
+        }
+
+        $reply = $ticket->reply()->create(['user_id' => auth()->id(), 'content' => $content]);
+        if ($reply) {
+            // 重新打开工单
+            if (in_array($ticket->status, [1, 2], true)) {
+                $ticket->update(['status' => 0]);
+            }
+
+            // 通知相关管理员
+            Notification::send(User::find(1), new TicketReplied($reply, route('admin.ticket.edit', $ticket)));
+
+            return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('user.ticket.reply')])]);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('user.ticket.reply')])]);
+    }
+
+    public function close(Ticket $ticket): JsonResponse
+    { // 关闭工单
+        if ($ticket->close()) {
+            return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.close')])]);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.close')])]);
+    }
+}

+ 33 - 370
app/Http/Controllers/UserController.php

@@ -4,19 +4,6 @@ namespace App\Http\Controllers;
 
 use App\Helpers\DataChart;
 use App\Models\Article;
-use App\Models\Coupon;
-use App\Models\Goods;
-use App\Models\Invite;
-use App\Models\Node;
-use App\Models\NodeHeartbeat;
-use App\Models\Order;
-use App\Models\Ticket;
-use App\Models\User;
-use App\Notifications\TicketCreated;
-use App\Notifications\TicketReplied;
-use App\Services\ArticleService;
-use App\Services\CouponService;
-use App\Services\ProxyService;
 use App\Services\UserService;
 use App\Utils\Helpers;
 use Cache;
@@ -26,14 +13,11 @@ use Hash;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
-use Illuminate\Validation\Rule;
 use Log;
-use Notification;
 use Redirect;
 use Response;
 use Session;
 use Str;
-use Validator;
 
 class UserController extends Controller
 {
@@ -108,365 +92,65 @@ class UserController extends Controller
         return Response::json(['status' => 'success', 'message' => trans('user.home.attendance.success', ['data' => formatBytes($traffic)])]);
     }
 
-    // 节点列表
-    public function nodeList(Request $request)
-    {
-        $user = auth()->user();
-        if ($request->isMethod('POST')) {
-            $proxyServer = new ProxyService;
-            $server = $proxyServer->getProxyConfig(Node::findOrFail($request->input('id')));
-
-            return Response::json(['status' => 'success', 'data' => $proxyServer->getUserProxyConfig($server, $request->input('type') !== 'text'), 'title' => $server['type']]);
-        }
-
-        // 获取当前用户可用节点
-        $nodeList = $user->nodes()->whereIn('is_display', [1, 3])->with(['labels', 'level_table'])->get();
-        $onlineNode = NodeHeartbeat::recently()->distinct()->pluck('node_id')->toArray();
-        foreach ($nodeList as $node) {
-            // 节点在线状态
-            $node->offline = ! in_array($node->id, $onlineNode, true);
-        }
-
-        return view('user.nodeList', [
-            'nodesGeo' => $nodeList->pluck('name', 'geo')->toArray(),
-            'nodeList' => $nodeList,
-        ]);
-    }
-
-    public function article(Article $article): JsonResponse
-    { // 公告详情
-        $articleService = new ArticleService($article);
-
-        return response()->json(['title' => $article->title, 'content' => $articleService->getContent()]);
-    }
-
     // 修改个人资料
-    public function profile(Request $request)
+    public function profile()
     {
         $user = auth()->user();
-        if ($request->isMethod('POST')) {
-            // 修改密码
-            if ($request->has(['password', 'new_password'])) {
-                $data = $request->only(['password', 'new_password']);
-
-                if (! Hash::check($data['password'], $user->password)) {
-                    return Redirect::back()->withErrors(trans('auth.password.reset.error.wrong'));
-                }
-
-                if (Hash::check($data['new_password'], $user->password)) {
-                    return Redirect::back()->withErrors(trans('auth.password.reset.error.same'));
-                }
-
-                // 演示环境禁止改管理员密码
-                if ($user->id === 1 && config('app.env') === 'demo') {
-                    return Redirect::back()->withErrors(trans('auth.password.reset.error.demo'));
-                }
-
-                if (! $user->update(['password' => $data['new_password']])) {
-                    return Redirect::back()->withErrors(trans('common.failed_item', ['attribute' => trans('common.update')]));
-                }
-
-                return Redirect::back()->with('successMsg', trans('common.success_item', ['attribute' => trans('common.update')]));
-                // 修改代理密码
-            }
-
-            if ($request->has('passwd')) {
-                $passwd = $request->input('passwd');
-                if (! $user->update(['passwd' => $passwd])) {
-                    return Redirect::back()->withErrors(trans('common.failed_item', ['attribute' => trans('common.update')]));
-                }
-
-                return Redirect::back()->with('successMsg', trans('common.success_item', ['attribute' => trans('common.update')]));
-            }
-
-            // 修改联系方式
-            if ($request->has(['nickname', 'wechat', 'qq'])) {
-                $data = $request->only(['nickname', 'wechat', 'qq']);
-                if (empty($data['nickname'])) {
-                    return Redirect::back()->withErrors(trans('validation.required', ['attribute' => trans('model.user.nickname')]));
-                }
-
-                if (! $user->update($data)) {
-                    return Redirect::back()->withErrors(trans('common.failed_item', ['attribute' => trans('common.update')]));
-                }
-            }
-
-            return Redirect::back()->with('successMsg', trans('common.success_item', ['attribute' => trans('common.update')]));
-        }
         $auth = $user->userAuths()->pluck('type')->toArray();
 
         return view('user.profile', compact('auth'));
     }
 
-    // 商品列表
-    public function services()
-    {
-        $user = auth()->user();
-        // 余额充值商品,只取10个
-        $renewOrder = Order::userActivePlan($user->id)->first();
-        $renewPrice = $renewOrder->goods->renew ?? 0;
-        // 有重置日时按照重置日为标准,否则就以过期日为标准
-        $dataPlusDays = $user->reset_time ?? $user->expired_at;
-
-        $goodsList = Goods::whereStatus(1)->where('type', '<=', '2')->orderByDesc('type')->orderByDesc('sort')->get();
-
-        if ($user && $nodes = $user->userGroup) {
-            $nodes = $nodes->nodes();
-        } else {
-            $nodes = Node::all();
-        }
-        foreach ($goodsList as $goods) {
-            $goods->node_count = $nodes->where('level', '<=', $goods->level)->where('status', 1)->count();
-            $goods->node_countries = $nodes->where('level', '<=', $goods->level)->where('status', 1)->pluck('country_code')->unique();
-        }
-
-        return view('user.services', [
-            'chargeGoodsList' => Goods::type(3)->orderBy('price')->get(),
-            'goodsList' => $goodsList,
-            'renewTraffic' => $renewPrice ? Helpers::getPriceTag($renewPrice) : 0,
-            'dataPlusDays' => $dataPlusDays > date('Y-m-d') ? $dataPlusDays->diffInDays() : 0,
-        ]);
-    }
-
-    //重置流量
-    public function resetUserTraffic(): ?JsonResponse
+    public function updateProfile(Request $request): RedirectResponse
     {
         $user = auth()->user();
-        $order = Order::userActivePlan()->firstOrFail();
-        $renewCost = $order->goods->renew;
-        if ($user->credit < $renewCost) {
-            return Response::json(['status' => 'fail', 'message' => trans('user.payment.insufficient_balance')]);
-        }
-
-        $user->update(['u' => 0, 'd' => 0]);
-
-        // 记录余额操作日志
-        Helpers::addUserCreditLog($user->id, null, $user->credit, $user->credit - $renewCost, -1 * $renewCost, 'The user manually reset the data.');
-
-        // 扣余额
-        $user->updateCredit(-$renewCost);
-
-        return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.reset')])]);
-    }
+        // 修改密码
+        if ($request->has(['password', 'new_password'])) {
+            $data = $request->only(['password', 'new_password']);
 
-    // 工单
-    public function ticketList(Request $request)
-    {
-        return view('user.ticketList', [
-            'tickets' => auth()->user()->tickets()->latest()->paginate(10)->appends($request->except('page')),
-        ]);
-    }
-
-    // 订单
-    public function invoices(Request $request)
-    {
-        return view('user.invoices', [
-            'orderList' => auth()->user()->orders()->with(['goods', 'payment'])->orderByDesc('id')->paginate(10)->appends($request->except('page')),
-            'prepaidPlan' => Order::userPrepay()->exists(),
-        ]);
-    }
-
-    public function closePlan(): JsonResponse
-    {
-        $activePlan = Order::userActivePlan()->first();
-        if ($activePlan) {
-            if ($activePlan->expired()) { // 关闭先前套餐后,新套餐自动运行
-                if (Order::userActivePlan()->exists()) {
-                    return Response::json(['status' => 'success', 'message' => trans('common.active_item', ['attribute' => trans('common.success')])]);
-                }
-
-                return Response::json(['status' => 'success', 'message' => trans('common.close')]);
+            if (! Hash::check($data['password'], $user->password)) {
+                return Redirect::back()->withErrors(trans('auth.password.reset.error.wrong'));
             }
-        } else {
-            $prepaidPlan = Order::userPrepay()->first();
-            if ($prepaidPlan) { // 关闭先前套餐后,新套餐自动运行
-                if ($prepaidPlan->complete()) {
-                    return Response::json(['status' => 'success', 'message' => trans('common.active_item', ['attribute' => trans('common.success')])]);
-                }
-
-                return Response::json(['status' => 'success', 'message' => trans('common.close')]);
-            }
-        }
-
-        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.close')])]);
-    }
-
-    // 订单明细
-    public function invoiceDetail($sn)
-    {
-        return view('user.invoiceDetail', ['order' => Order::uid()->whereSn($sn)->with(['goods', 'coupon', 'payment'])->firstOrFail()]);
-    }
-
-    // 添加工单
-    public function createTicket(Request $request): ?JsonResponse
-    {
-        $user = auth()->user();
-        $title = $request->input('title');
-        $content = substr(str_replace(['atob', 'eval'], '', clean($request->input('content'))), 0, 300);
-
-        if (empty($title) || empty($content)) {
-            return Response::json([
-                'status' => 'fail', 'message' => trans('validation.required', ['attribute' => ucfirst(trans('validation.attributes.title')).'&'.ucfirst(trans('validation.attributes.content'))]),
-            ]);
-        }
-
-        if ($ticket = $user->tickets()->create(compact('title', 'content'))) {
-            // 通知相关管理员
-            Notification::send(User::find(1), new TicketCreated($ticket, route('admin.ticket.edit', $ticket)));
-        }
 
-        return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.submit')])]);
-    }
-
-    // 回复工单
-    public function replyTicket(Request $request)
-    {
-        $id = $request->input('id');
-
-        $ticket = Ticket::uid()->with('user')->whereId($id)->firstOrFail();
-
-        if ($request->isMethod('POST')) {
-            $content = substr(str_replace(['atob', 'eval'], '', clean($request->input('content'))), 0, 300);
-
-            if (empty($content)) {
-                return Response::json([
-                    'status' => 'fail', 'message' => trans('validation.required', ['attribute' => ucfirst(trans('validation.attributes.title')).'&'.ucfirst(trans('validation.attributes.content'))]),
-                ]);
+            if (Hash::check($data['new_password'], $user->password)) {
+                return Redirect::back()->withErrors(trans('auth.password.reset.error.same'));
             }
 
-            $reply = $ticket->reply()->create(['user_id' => auth()->id(), 'content' => $content]);
-            if ($reply) {
-                // 重新打开工单
-                if ($ticket->status === 2) {
-                    $ticket->update(['status' => 0]);
-                }
-
-                // 通知相关管理员
-                Notification::send(User::find(1), new TicketReplied($reply, route('admin.ticket.edit', $ticket)));
-
-                return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('user.ticket.reply')])]);
+            // 演示环境禁止改管理员密码
+            if ($user->id === 1 && config('app.env') === 'demo') {
+                return Redirect::back()->withErrors(trans('auth.password.reset.error.demo'));
             }
 
-            return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('user.ticket.reply')])]);
-        }
-
-        return view('user.replyTicket', [
-            'ticket' => $ticket,
-            'replyList' => $ticket->reply()->with('ticket:id,status', 'admin:id,username,qq', 'user:id,username,qq')->oldest()->get(),
-        ]);
-    }
-
-    // 关闭工单
-    public function closeTicket(Request $request): ?JsonResponse
-    {
-        $id = $request->input('id');
-
-        if (Ticket::uid()->whereId($id)->firstOrFail()->close()) {
-            return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.close')])]);
-        }
-
-        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.close')])]);
-    }
-
-    // 邀请码
-    public function invite()
-    {
-        if (Order::uid()->active()->where('origin_amount', '>', 0)->doesntExist()) {
-            return Response::view('auth.error', ['message' => trans('user.purchase.required').' <a class="btn btn-sm btn-danger" href="/">'.trans('common.back').'</a>'], 402);
-        }
-
-        return view('user.invite', [
-            'num' => auth()->user()->invite_num, // 还可以生成的邀请码数量
-            'inviteList' => Invite::uid()->with(['invitee', 'inviter'])->paginate(10), // 邀请码列表
-            'referral_traffic' => formatBytes(sysConfig('referral_traffic'), 'MiB'),
-            'referral_percent' => sysConfig('referral_percent'),
-        ]);
-    }
-
-    // 生成邀请码
-    public function makeInvite(): JsonResponse
-    {
-        $user = auth()->user();
-        if ($user->invite_num <= 0) {
-            return Response::json(['status' => 'fail', 'message' => trans('user.invite.generate_failed')]);
-        }
-        $invite = $user->invites()->create([
-            'code' => strtoupper(mb_substr(md5(microtime().Str::random()), 8, 12)),
-            'dateline' => date('Y-m-d H:i:s', strtotime(sysConfig('user_invite_days').' days')),
-        ]);
-        if ($invite) {
-            $user->decrement('invite_num');
-
-            return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('common.generate')])]);
-        }
-
-        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.generate')])]);
-    }
-
-    // 使用优惠券
-    public function redeemCoupon(Request $request, Goods $good): JsonResponse
-    {
-        $coupon_sn = $request->input('coupon_sn');
+            if (! $user->update(['password' => $data['new_password']])) {
+                return Redirect::back()->withErrors(trans('common.failed_item', ['attribute' => trans('common.update')]));
+            }
 
-        if (empty($coupon_sn)) {
-            return Response::json(['status' => 'fail', 'title' => trans('common.failed'), 'message' => trans('user.coupon.error.unknown')]);
+            return Redirect::back()->with('successMsg', trans('common.success_item', ['attribute' => trans('common.update')]));
+            // 修改代理密码
         }
 
-        $coupon = (new CouponService($coupon_sn))->search($good); // 检查券合规性
+        if ($request->has('passwd')) {
+            $passwd = $request->input('passwd');
+            if (! $user->update(['passwd' => $passwd])) {
+                return Redirect::back()->withErrors(trans('common.failed_item', ['attribute' => trans('common.update')]));
+            }
 
-        if (! $coupon instanceof Coupon) {
-            return $coupon;
+            return Redirect::back()->with('successMsg', trans('common.success_item', ['attribute' => trans('common.update')]));
         }
 
-        $data = [
-            'name' => $coupon->name,
-            'type' => $coupon->type,
-            'value' => $coupon->type === 2 ? $coupon->value : Helpers::getPriceTag($coupon->value),
-        ];
-
-        return Response::json(['status' => 'success', 'data' => $data, 'message' => trans('common.applied', ['attribute' => trans('model.coupon.attribute')])]);
-    }
-
-    // 购买服务
-    public function buy(Goods $good)
-    {
-        $user = auth()->user();
-        // 有重置日时按照重置日为标准,否则就以过期日为标准
-        $dataPlusDays = $user->reset_time ?? $user->expired_at;
-
-        return view('user.buy', [
-            'dataPlusDays' => $dataPlusDays > date('Y-m-d') ? $dataPlusDays->diffInDays() : 0,
-            'activePlan' => Order::userActivePlan()->exists(),
-            'goods' => $good,
-        ]);
-    }
+        // 修改联系方式
+        if ($request->has(['nickname', 'wechat', 'qq'])) {
+            $data = $request->only(['nickname', 'wechat', 'qq']);
+            if (empty($data['nickname'])) {
+                return Redirect::back()->withErrors(trans('validation.required', ['attribute' => trans('model.user.nickname')]));
+            }
 
-    // 帮助中心
-    public function knowledge()
-    {
-        $data = [];
-        if (Node::whereType(0)->whereStatus(1)->exists()) {
-            $data[] = 'ss';
-        }
-        if (Node::whereIn('type', [1, 4])->whereStatus(1)->exists()) {
-            $data[] = 'ssr';
-        }
-        if (Node::whereType(2)->whereStatus(1)->exists()) {
-            $data[] = 'v2';
-        }
-        if (Node::whereType(3)->whereStatus(1)->exists()) {
-            $data[] = 'trojan';
+            if (! $user->update($data)) {
+                return Redirect::back()->withErrors(trans('common.failed_item', ['attribute' => trans('common.update')]));
+            }
         }
 
-        $subscribe = auth()->user()->subscribe;
-
-        return view('user.knowledge', [
-            'subType' => $data,
-            'subUrl' => route('sub', $subscribe->code),
-            'subStatus' => $subscribe->status,
-            'subMsg' => $subscribe->ban_desc,
-            'knowledges' => Article::type(1)->lang()->orderByDesc('sort')->latest()->get()->groupBy('category'),
-        ]);
+        return Redirect::back()->with('successMsg', trans('common.success_item', ['attribute' => trans('common.update')]));
     }
 
     public function exchangeSubscribe(): ?JsonResponse
@@ -508,27 +192,6 @@ class UserController extends Controller
         return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('common.toggle')])]);
     }
 
-    public function charge(Request $request): ?JsonResponse
-    {
-        $validator = Validator::make($request->all(), [
-            'coupon_sn' => [
-                'required', Rule::exists('coupon', 'sn')->where(static function ($query) {
-                    $query->whereType(3)->whereStatus(0);
-                }),
-            ],
-        ]);
-
-        if ($validator->fails()) {
-            return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
-        }
-
-        if ((new CouponService($request->input('coupon_sn')))->charge()) {
-            return Response::json(['status' => 'success', 'message' => trans('common.success_item', ['attribute' => trans('user.recharge')])]);
-        }
-
-        return Response::json(['status' => 'fail', 'message' => trans('common.failed_item', ['attribute' => trans('user.recharge')])]);
-    }
-
     public function switchCurrency(string $code): RedirectResponse
     { // 切换语言
         Session::put('currency', $code);

+ 1 - 1
app/Notifications/PaymentReceived.php

@@ -24,7 +24,7 @@ class PaymentReceived extends Notification implements ShouldQueue
     public function toMail($notifiable): MailMessage
     {
         return (new MailMessage)->subject(__('Payment Received'))->line(__('Payment for #:sn has been received! Total amount: :amount.', ['sn' => $this->sn, 'amount' => $this->amountWithSign]))->action(__('Invoice Detail'),
-            route('invoiceInfo', $this->sn));
+            route('invoice.show', $this->sn));
     }
 
     public function toDataBase($notifiable): array

+ 1 - 1
app/Utils/Payments/CodePay.php

@@ -25,7 +25,7 @@ class CodePay extends PaymentService implements Gateway
             'page' => 1,
             'outTime' => 900,
             'notify_url' => route('payment.notify', ['method' => 'codepay']),
-            'return_url' => route('invoice'),
+            'return_url' => route('invoice.index'),
         ];
         $data['sign'] = PaymentHelper::aliStyleSign($data, sysConfig('codepay_key'));
 

+ 1 - 1
app/Utils/Payments/EPay.php

@@ -22,7 +22,7 @@ class EPay extends PaymentService implements Gateway
             'pid' => sysConfig('epay_mch_id'),
             'type' => [1 => 'alipay', 2 => 'qqpay', 3 => 'wxpay'][$request->input('type')] ?? 'alipay',
             'notify_url' => route('payment.notify', ['method' => 'epay']),
-            'return_url' => route('invoice'),
+            'return_url' => route('invoice.index'),
             'out_trade_no' => $payment->trade_no,
             'name' => sysConfig('subject_name') ?: sysConfig('website_name'),
             'money' => $payment->amount,

+ 1 - 1
app/Utils/Payments/PayBeaver.php

@@ -39,7 +39,7 @@ class PayBeaver extends PaymentService implements Gateway
             'merchant_order_id' => $payment->trade_no,
             'price_amount' => $payment->amount * 110,
             'notify_url' => route('payment.notify', ['method' => 'paybeaver']),
-            'return_url' => route('invoice'),
+            'return_url' => route('invoice.index'),
         ]);
 
         if (! isset($result['message']) && isset($result['data']['pay_url'])) {

+ 2 - 2
app/Utils/Payments/PayPal.php

@@ -48,7 +48,7 @@ class PayPal extends PaymentService implements Gateway
             'intent' => 'CAPTURE',
             'application_context' => [
                 'return_url' => route('payment.notify', ['method' => 'paypal']),
-                'cancel_url' => route('invoice'),
+                'cancel_url' => route('invoice.index'),
             ],
             'purchase_units' => [
                 0 => [
@@ -98,7 +98,7 @@ class PayPal extends PaymentService implements Gateway
             ],
             'invoice_description' => $trade_no,
             'return_url' => route('payment.notify', ['method' => 'paypal']),
-            'cancel_url' => route('invoice'),
+            'cancel_url' => route('invoice.index'),
             'total' => $amount,
         ];
     }

+ 3 - 3
app/Utils/Payments/Stripe.php

@@ -41,7 +41,7 @@ class Stripe extends PaymentService implements Gateway
                     'identifier' => '',
                 ],
                 'redirect' => [
-                    'return_url' => route('invoice'),
+                    'return_url' => route('invoice.index'),
                 ],
             ]);
             if ($type === 3) {
@@ -99,8 +99,8 @@ class Stripe extends PaymentService implements Gateway
                 ],
             ],
             'mode' => 'payment',
-            'success_url' => route('invoice'),
-            'cancel_url' => route('invoice'),
+            'success_url' => route('invoice.index'),
+            'cancel_url' => route('invoice.index'),
             'client_reference_id' => $tradeNo,
             'customer_email' => Auth::getUser()->email,
         ];

+ 2 - 2
database/seeders/LabelSeeder.php

@@ -19,12 +19,12 @@ class LabelSeeder extends Seeder
         'Happyon',
         'AbemeTV',
         'DMM',
-        'NicoNico',
+        'Niconico',
         'Pixiv',
         'TVer',
         'TVB',
         'HBO Go',
-        'BiliBili港澳台',
+        'Bilibili 港澳台',
         '動畫瘋',
         '四季線上影視',
         'LINE TV',

+ 1 - 1
resources/views/admin/article/show.blade.php

@@ -41,7 +41,7 @@
             if (!document.getElementById("article_B" + id).innerHTML) {
                 $.ajax({
                     method: "GET",
-                    url: '{{ route('article', '') }}/' + id,
+                    url: '{{ route('admin.article.show', '') }}/' + id,
                     beforeSend: function() {
                         $("#loading_article").show();
                     },

+ 7 - 7
resources/views/admin/config/common.blade.php

@@ -37,7 +37,7 @@
                             <button class="btn btn-primary float-right mb-10" data-toggle="modal" data-target="#add_config_modal">
                                 <i class="icon wb-plus"></i>
                             </button>
-                            <table class="text-md-center" data-toggle="table" data-height="700" data-virtual-scroll="true" data-mobile-responsive="true">
+                            <table class="text-md-center" data-toggle="table" data-height="700" data-mobile-responsive="true">
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ ucfirst(trans('validation.attributes.name')) }}</th>
@@ -71,7 +71,7 @@
                             <button class="btn btn-primary float-right mb-10" data-toggle="modal" data-target="#add_config_modal">
                                 <i class="icon wb-plus"></i>
                             </button>
-                            <table class="text-md-center" data-toggle="table" data-height="700" data-virtual-scroll="true" data-mobile-responsive="true">
+                            <table class="text-md-center" data-toggle="table" data-height="700" data-mobile-responsive="true">
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ ucfirst(trans('validation.attributes.name')) }}</th>
@@ -105,7 +105,7 @@
                             <button class="btn btn-primary float-right mb-10" data-toggle="modal" data-target="#add_config_modal">
                                 <i class="icon wb-plus"></i>
                             </button>
-                            <table class="text-md-center" data-toggle="table" data-height="700" data-virtual-scroll="true" data-mobile-responsive="true">
+                            <table class="text-md-center" data-toggle="table" data-height="700" data-mobile-responsive="true">
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ ucfirst(trans('validation.attributes.name')) }}</th>
@@ -139,7 +139,7 @@
                             <button class="btn btn-primary float-right mb-10" data-toggle="modal" data-target="#add_level_modal">
                                 <i class="icon wb-plus"></i>
                             </button>
-                            <table class="text-md-center" data-toggle="table" data-height="700" data-virtual-scroll="true" data-mobile-responsive="true">
+                            <table class="text-md-center" data-toggle="table" data-height="700" data-mobile-responsive="true">
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ trans('model.common.level') }}</th>
@@ -176,7 +176,7 @@
                             <button class="btn btn-primary float-right mb-10" data-toggle="modal" data-target="#add_category_modal">
                                 <i class="icon wb-plus"></i>
                             </button>
-                            <table class="text-md-center" data-toggle="table" data-height="700" data-virtual-scroll="true" data-mobile-responsive="true">
+                            <table class="text-md-center" data-toggle="table" data-height="700" data-mobile-responsive="true">
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ ucfirst(trans('validation.attributes.name')) }}</th>
@@ -213,7 +213,7 @@
                             <button class="btn btn-primary float-right mb-10" data-toggle="modal" data-target="#add_country_modal">
                                 <i class="icon wb-plus"></i>
                             </button>
-                            <table class="text-md-center" data-toggle="table" data-height="700" data-virtual-scroll="true" data-mobile-responsive="true">
+                            <table class="text-md-center" data-toggle="table" data-height="700" data-mobile-responsive="true">
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ trans('model.country.icon') }}</th>
@@ -253,7 +253,7 @@
                             <button class="btn btn-primary float-right mb-10" data-toggle="modal" data-target="#add_label_modal">
                                 <i class="icon wb-plus"></i>
                             </button>
-                            <table class="text-md-center" data-toggle="table" data-height="700" data-virtual-scroll="true" data-mobile-responsive="true">
+                            <table class="text-md-center" data-toggle="table" data-height="700" data-mobile-responsive="true">
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ ucfirst(trans('validation.attributes.name')) }}</th>

+ 1 - 1
resources/views/admin/logs/order.blade.php

@@ -61,7 +61,7 @@
                         <select class="form-control show-tick" id="status" name="status[]" data-plugin="selectpicker" data-style="btn-outline btn-primary"
                                 title="{{ trans('model.order.status') }}" multiple>
                             <option value="-1">{{ trans('common.order.status.canceled') }}</option>
-                            <option value="0">{{ trans('common.payment.status.wait') }}</option>
+                            <option value="0">{{ trans('common.status.payment_pending') }}</option>
                             <option value="1">{{ trans('common.order.status.review') }}</option>
                             <option value="2">
                                 {{ trans('common.order.status.completed') . '/' . trans('common.status.expire') . '/' . trans('common.order.status.ongoing') }}

+ 20 - 9
resources/views/admin/user/export.blade.php

@@ -49,14 +49,14 @@
                                 <td>
                                     @can('admin.user.exportProxy')
                                         <div class="btn-group">
-                                            <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','code')"><i
-                                                   class="fa-solid fa-code"></i>
+                                            <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','code')">
+                                                <i class="fa-solid fa-code" id="code{{ $node->id }}"></i>
                                             </button>
-                                            <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','qrcode')"><i
-                                                   class="fa-solid fa-qrcode"></i>
+                                            <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','qrcode')">
+                                                <i class="fa-solid fa-qrcode" id="qrcode{{ $node->id }}"></i>
                                             </button>
-                                            <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','text')"><i
-                                                   class="fa-solid fa-list"></i>
+                                            <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','text')">
+                                                <i class="fa-solid fa-list" id="text{{ $node->id }}"></i>
                                             </button>
                                         </div>
                                     @endcan
@@ -86,12 +86,19 @@
     @can('admin.user.exportProxy')
         <script>
             function getInfo(id, type) {
-                $.post("{{ route('admin.user.exportProxy', $user) }}", {
+                const oldClass = $('#' + type + id).attr('class');
+                $.ajax({
+                    method: 'POST',
+                    url: '{{ route('admin.user.exportProxy', $user) }}',
+                    data: {
                         _token: '{{ csrf_token() }}',
                         id: id,
                         type: type
                     },
-                    function(ret) {
+                    beforeSend: function() {
+                        $('#' + type + id).removeAttr('class').addClass('icon wb-loop icon-spin');
+                    },
+                    success: function(ret) {
                         if (ret.status === 'success') {
                             switch (type) {
                                 case 'code':
@@ -131,7 +138,11 @@
                                     });
                             }
                         }
-                    });
+                    },
+                    complete: function() {
+                        $('#' + type + id).removeAttr('class').addClass(oldClass);
+                    },
+                });
             }
 
             function Download() {

+ 4 - 4
resources/views/admin/user/index.blade.php

@@ -55,16 +55,16 @@
                     <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4">
                         <select class="form-control" id="user_group_id" name="user_group_id" data-plugin="selectpicker" data-style="btn-outline btn-primary"
                                 title="{{ trans('model.user_group.attribute') }}">
-                            @foreach ($userGroups as $key => $group)
-                                <option value="{{ $key }}">{{ $group }}</option>
+                            @foreach ($userGroups as $id => $name)
+                                <option value="{{ $id }}">{{ $name }}</option>
                             @endforeach
                         </select>
                     </div>
                     <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4">
                         <select class="form-control" id="level" name="level" data-plugin="selectpicker" data-style="btn-outline btn-primary"
                                 title="{{ trans('model.common.level') }}">
-                            @foreach ($levels as $key => $level)
-                                <option value="{{ $key }}">{{ $level }}</option>
+                            @foreach ($levels as $level => $name)
+                                <option value="{{ $level }}">{{ $name }}</option>
                             @endforeach
                         </select>
                     </div>

+ 4 - 4
resources/views/admin/user/info.blade.php

@@ -46,8 +46,8 @@
                                 <label class="col-md-2 col-sm-3 col-form-label" for="level">{{ trans('model.common.level') }}</label>
                                 <div class="col-xl-4 col-sm-8">
                                     <select class="form-control" id="level" name="level" data-plugin="selectpicker" data-style="btn-outline btn-primary">
-                                        @foreach ($levels as $level)
-                                            <option value="{{ $level->level }}">{{ $level->name }}</option>
+                                        @foreach ($levels as $level => $name)
+                                            <option value="{{ $level }}">{{ $name }}</option>
                                         @endforeach
                                     </select>
                                 </div>
@@ -57,8 +57,8 @@
                                 <div class="col-xl-4 col-sm-8">
                                     <select class="form-control" id="group" name="group" data-plugin="selectpicker" data-style="btn-outline btn-primary"
                                             title="{{ trans('common.none') }}">
-                                        @foreach ($userGroups as $group)
-                                            <option value="{{ $group->id }}">{{ $group->name }}</option>
+                                        @foreach ($userGroups as $id => $name)
+                                            <option value="{{ $id }}">{{ $name }}</option>
                                         @endforeach
                                     </select>
                                 </div>

+ 2 - 2
resources/views/user/buy.blade.php

@@ -79,7 +79,7 @@
             const sign = tag[1];
             $.ajax({
                 method: 'POST',
-                url: '{{ route('redeemCoupon', $goods) }}',
+                url: '{{ route('shop.coupon.check', $goods) }}',
                 dataType: 'json',
                 data: {
                     _token: '{{ csrf_token() }}',
@@ -181,7 +181,7 @@
                                 timer: 1000,
                                 showConfirmButton: false
                             }).
-                            then(() => window.location.href = '{{ route('invoice') }}');
+                            then(() => window.location.href = '{{ route('invoice.index') }}');
                         }
                         if (ret.data) {
                             window.location.href = '{{ route('orderDetail', '') }}/' + ret.data;

+ 1 - 1
resources/views/user/components/notifications/paymentReceived.blade.php

@@ -1,4 +1,4 @@
-<a class="list-group-item dropdown-item" href="{{ route('invoiceInfo', $notification->data['sn']) }}" role="menuitem">
+<a class="list-group-item dropdown-item" href="{{ route('invoice.show', $notification->data['sn']) }}" role="menuitem">
     <div class="media">
         <div class="pr-10">
             <i class="icon wb-order bg-primary-600 white icon-circle" aria-hidden="true"></i>

+ 2 - 2
resources/views/user/components/payment/default.blade.php

@@ -72,7 +72,7 @@
                             timer: 1500,
                             showConfirmButton: false
                         }).then(() => {
-                            window.location.href = '{{ route('invoice') }}';
+                            window.location.href = '{{ route('invoice.index') }}';
                         });
                     } else if (ret.status === 'error') {
                         swal.fire({
@@ -81,7 +81,7 @@
                             timer: 1500,
                             showConfirmButton: false
                         }).then(() => {
-                            window.location.href = '{{ route('invoice') }}';
+                            window.location.href = '{{ route('invoice.index') }}';
                         });
                     }
                 },

+ 1 - 1
resources/views/user/components/payment/manual.blade.php

@@ -224,7 +224,7 @@
                         swal.fire({
                             text: ret.message,
                             icon: 'success',
-                        }).then(() => window.location.href = '{{ route('invoice') }}');
+                        }).then(() => window.location.href = '{{ route('invoice.index') }}');
                     } else {
                         swal.fire({
                             title: ret.message,

+ 4 - 4
resources/views/user/index.blade.php

@@ -34,7 +34,7 @@
                                 <p class="mb-15 mr-15 text-right">{{ trans('common.more') }}
                                     <code>{{ trans('user.attribute.node') }}</code>
                                 </p>
-                                <a class="btn btn-block btn-danger" href="{{ route('shop') }}">{{ trans('user.purchase.promotion') }}</a>
+                                <a class="btn btn-block btn-danger" href="{{ route('shop.index') }}">{{ trans('user.purchase.promotion') }}</a>
                             @elseif(Auth::user()->enable)
                                 <i class="wb-check green-400 font-size-40 mr-10" aria-hidden="true"></i>
                                 <span class="font-size-40 font-weight-100">{{ trans('common.status.normal') }}</span>
@@ -106,7 +106,7 @@
                             @else
                                 <span class="font-size-40 font-weight-100">{{ trans('common.status.expire') }}</span>
                                 <br />
-                                <a class="btn btn-danger" href="{{ route('shop') }}">{{ trans('user.shop.buy') }}</a>
+                                <a class="btn btn-danger" href="{{ route('shop.index') }}">{{ trans('user.shop.buy') }}</a>
                             @endif
                         </div>
                     </div>
@@ -204,7 +204,7 @@
                                 <i class="font-size-40 wb-wrench"></i>
                                 <h4 class="card-title">{{ trans('user.clients') }}</h4>
                                 <p class="card-text">{{ trans('common.download') . ' & ' . trans('user.tutorials') }}</p>
-                                <a class="btn btn-primary mb-10" href="{{ route('knowledge') }}">{{ trans('common.goto') }}</a>
+                                <a class="btn btn-primary mb-10" href="{{ route('knowledge.index') }}">{{ trans('common.goto') }}</a>
                             </div>
                         </div>
                     </div>
@@ -386,7 +386,7 @@
                     _token: '{{ csrf_token() }}'
                 }, function(ret) {
                     if (ret.status === 'success') {
-                        swal.fire(ret.title, ret.message, 'success');
+                        swal.fire(ret.title, ret.message, 'success').then(() => window.location.reload());
                     } else {
                         swal.fire(ret.title, ret.message, 'error');
                     }

+ 1 - 1
resources/views/user/invite.blade.php

@@ -87,7 +87,7 @@
             $.ajax({
                 method: 'POST',
                 dataType: 'json',
-                url: '{{ route('createInvite') }}',
+                url: '{{ route('invite.store') }}',
                 data: {
                     _token: '{{ csrf_token() }}'
                 },

+ 2 - 2
resources/views/user/invoices.blade.php

@@ -33,7 +33,7 @@
                         @foreach ($orderList as $order)
                             <tr>
                                 <td>{{ $loop->iteration }}</td>
-                                <td><a href="/invoice/{{ $order->sn }}" target="_blank">{{ $order->sn }}</a></td>
+                                <td><a href="{{ route('invoice.show', $order->sn) }}" target="_blank">{{ $order->sn }}</a></td>
                                 <td>{{ $order->goods->name ?? trans('user.recharge_credit') }}</td>
                                 <td>{{ $order->pay_way === 1 ? trans('user.shop.pay_credit') : trans('user.shop.pay_online') }}</td>
                                 <td>{{ $order->amount_tag }}</td>
@@ -92,7 +92,7 @@
                 if (result.value) {
                     $.ajax({
                         method: 'POST',
-                        url: '{{ route('cancelPlan') }}',
+                        url: '{{ route('invoice.activate') }}',
                         dataType: 'json',
                         data: {
                             _token: '{{ csrf_token() }}'

+ 1 - 1
resources/views/user/knowledge.blade.php

@@ -137,7 +137,7 @@
             if (!document.getElementById("load_article_" + id).innerHTML) {
                 $.ajax({
                     method: "GET",
-                    url: '{{ route('article', '') }}/' + id,
+                    url: '{{ route('knowledge.show', '') }}/' + id,
                     beforeSend: function() {
                         $("#loading_article").show();
                     },

+ 17 - 17
resources/views/user/layouts.blade.php

@@ -81,7 +81,7 @@
                                     {{ trans('user.menu.admin_dashboard') }}
                                 </a>
                             @endcan
-                            <a class="dropdown-item" href="{{ route('profile') }}" role="menuitem">
+                            <a class="dropdown-item" href="{{ route('profile.show') }}" role="menuitem">
                                 <i class="icon wb-settings" aria-hidden="true"></i>
                                 {{ trans('user.menu.profile') }}
                             </a>
@@ -99,32 +99,32 @@
     <div class="site-menubar {{ config('theme.sidebar') }}">
         <div class="site-menubar-body">
             <ul class="site-menu" data-plugin="menu">
-                <li class="site-menu-item {{ request()->routeIs('home', 'profile', 'article') ? 'active open' : '' }}">
+                <li class="site-menu-item {{ request()->routeIs('home') ? 'active open' : '' }}">
                     <a href="{{ route('home') }}">
                         <i class="site-menu-icon wb-home" aria-hidden="true"></i>
                         <span class="site-menu-title">{{ trans('user.menu.home') }}</span>
                     </a>
                 </li>
-                <li class="site-menu-item {{ request()->routeIs('shop', 'buy', 'orderDetail') ? 'active open' : '' }}">
-                    <a href="{{ route('shop') }}">
+                <li class="site-menu-item {{ request()->routeIs('shop.*') ? 'active open' : '' }}">
+                    <a href="{{ route('shop.index') }}">
                         <i class="site-menu-icon wb-shopping-cart" aria-hidden="true"></i>
                         <span class="site-menu-title">{{ trans('user.menu.shop') }}</span>
                     </a>
                 </li>
-                <li class="site-menu-item {{ request()->routeIs('node') ? 'active open' : '' }}">
-                    <a href="{{ route('node') }}">
+                <li class="site-menu-item {{ request()->routeIs('node.*') ? 'active open' : '' }}">
+                    <a href="{{ route('node.index') }}">
                         <i class="site-menu-icon wb-cloud" aria-hidden="true"></i>
                         <span class="site-menu-title">{{ trans('user.menu.nodes') }}</span>
                     </a>
                 </li>
-                <li class="site-menu-item {{ request()->routeIs('knowledge') ? 'active open' : '' }}">
-                    <a href="{{ route('knowledge') }}">
+                <li class="site-menu-item {{ request()->routeIs('knowledge.*') ? 'active open' : '' }}">
+                    <a href="{{ route('knowledge.index') }}">
                         <i class="site-menu-icon wb-info-circle" aria-hidden="true"></i>
                         <span class="site-menu-title">{{ trans('user.menu.help') }}</span>
                     </a>
                 </li>
-                <li class="site-menu-item {{ request()->routeIs('profile') ? 'active open' : '' }}">
-                    <a href="{{ route('profile') }}">
+                <li class="site-menu-item {{ request()->routeIs('profile.*') ? 'active open' : '' }}">
+                    <a href="{{ route('profile.show') }}">
                         <i class="site-menu-icon wb-settings" aria-hidden="true"></i>
                         <span class="site-menu-title">{{ trans('user.menu.profile') }}</span>
                     </a>
@@ -132,8 +132,8 @@
                 @php
                     $openTicket = auth()->user()->tickets()->where('status', '<>', 2)->count();
                 @endphp
-                <li class="site-menu-item {{ request()->routeIs('ticket', 'replyTicket') ? 'active open' : '' }}">
-                    <a href="{{ route('ticket') }}">
+                <li class="site-menu-item {{ request()->routeIs('ticket.*') ? 'active open' : '' }}">
+                    <a href="{{ route('ticket.index') }}">
                         <i class="site-menu-icon wb-chat-working" aria-hidden="true"></i>
                         <span class="site-menu-title">{{ trans('user.menu.tickets') }}</span>
                         @if ($openTicket > 0)
@@ -143,16 +143,16 @@
                         @endif
                     </a>
                 </li>
-                <li class="site-menu-item {{ request()->routeIs('invoice', 'invoiceInfo') ? 'active open' : '' }}">
-                    <a href="{{ route('invoice') }}">
+                <li class="site-menu-item {{ request()->routeIs('invoice.*') ? 'active open' : '' }}">
+                    <a href="{{ route('invoice.index') }}">
                         <i class="site-menu-icon wb-bookmark" aria-hidden="true"></i>
                         <span class="site-menu-title">{{ trans('user.menu.invoices') }}</span>
                     </a>
                 </li>
                 @if (ReferralLog::uid()->exists() || Order::uid()->whereStatus(2)->exists())
                     @if (sysConfig('is_invite_register'))
-                        <li class="site-menu-item {{ request()->routeIs('invite') ? 'active open' : '' }}">
-                            <a href="{{ route('invite') }}">
+                        <li class="site-menu-item {{ request()->routeIs('invite.*') ? 'active open' : '' }}">
+                            <a href="{{ route('invite.index') }}">
                                 <i class="site-menu-icon wb-extension" aria-hidden="true"></i>
                                 <span class="site-menu-title">{{ trans('user.menu.invites') }}</span>
                             </a>
@@ -160,7 +160,7 @@
                     @endif
                     @if (sysConfig('referral_status'))
                         <li class="site-menu-item {{ request()->routeIs('commission') ? 'active open' : '' }}">
-                            <a href="{{ route('commission') }}">
+                            <a href="{{ route('referral.index') }}">
                                 <i class="site-menu-icon wb-star-outline" aria-hidden="true"></i>
                                 <span class="site-menu-title">{{ trans('user.menu.promotion') }}</span>
                             </a>

+ 1 - 2
resources/views/user/nodeList.blade.php

@@ -171,10 +171,9 @@
             const oldClass = $('#' + type + id).attr('class');
             $.ajax({
                 method: 'POST',
-                url: '{{ route('node') }}',
+                url: '{{ route('node.show', '') }}/' + id,
                 data: {
                     _token: '{{ csrf_token() }}',
-                    id: id,
                     type: type
                 },
                 beforeSend: function() {

+ 4 - 3
resources/views/user/profile.blade.php

@@ -96,7 +96,8 @@
                         </ul>
                         <div class="tab-content py-10">
                             <div class="tab-pane active animation-slide-left" id="account" role="tabpanel">
-                                <form class="form-horizontal" action="{{ route('profile') }}" method="post" enctype="multipart/form-data" autocomplete="off">
+                                <form class="form-horizontal" action="{{ route('profile.update') }}" method="post" enctype="multipart/form-data"
+                                      autocomplete="off">
                                     @csrf
                                     <div class="form-group row">
                                         <label class="col-md-5 col-form-label" for="password">{{ trans('auth.password.original') }}</label>
@@ -112,7 +113,7 @@
                                 </form>
                             </div>
                             <div class="tab-pane animation-slide-left" id="contact" role="tabpanel">
-                                <form class="form-horizontal" action="{{ route('profile') }}" method="post" enctype="multipart/form-data">
+                                <form class="form-horizontal" action="{{ route('profile.update') }}" method="post" enctype="multipart/form-data">
                                     @csrf
                                     <div class="form-group row">
                                         <label class="col-md-5 col-form-label" for="nickname">{{ trans('model.user.nickname') }}</label>
@@ -135,7 +136,7 @@
                                 </form>
                             </div>
                             <div class="tab-pane animation-slide-left" id="proxy" role="tabpanel">
-                                <form class="form-horizontal" action="{{ route('profile') }}" method="post" enctype="multipart/form-data">
+                                <form class="form-horizontal" action="{{ route('profile.update') }}" method="post" enctype="multipart/form-data">
                                     @csrf
                                     <div class="form-group row">
                                         <label class="col-md-5 col-form-label" for="passwd"> {{ trans('user.account.connect_password') }} </label>

+ 1 - 1
resources/views/user/referral.blade.php

@@ -200,7 +200,7 @@
 
         // 申请提现
         function extractMoney() {
-            $.post('{{ route('applyCommission') }}', {
+            $.post('{{ route('referral.withdraw') }}', {
                 _token: '{{ csrf_token() }}'
             }, function(ret) {
                 if (ret.status === 'success') {

+ 31 - 20
resources/views/user/replyTicket.blade.php

@@ -59,12 +59,11 @@
             }).then((result) => {
                 if (result.value) {
                     $.ajax({
-                        method: 'POST',
-                        url: '{{ route('closeTicket') }}',
+                        method: 'PATCH',
+                        url: '{{ route('ticket.close', $ticket) }}',
                         async: true,
                         data: {
                             _token: '{{ csrf_token() }}',
-                            id: '{{ $ticket->id }}'
                         },
                         dataType: 'json',
                         success: function(ret) {
@@ -72,7 +71,7 @@
                                 title: ret.message,
                                 icon: 'success',
                                 timer: 1300,
-                            }).then(() => window.location.href = '{{ route('ticket') }}');
+                            }).then(() => window.location.href = '{{ route('ticket.index') }}');
                         },
                         error: function() {
                             swal.fire({
@@ -106,24 +105,36 @@
                 confirmButtonText: '{{ trans('common.confirm') }}',
             }).then((result) => {
                 if (result.value) {
-                    $.post('{{ route('replyTicket') }}', {
-                        _token: '{{ csrf_token() }}',
-                        id: '{{ $ticket->id }}',
-                        content: content,
-                    }, function(ret) {
-                        if (ret.status === 'success') {
-                            swal.fire({
-                                title: ret.message,
-                                icon: 'success',
-                                timer: 1000,
-                                showConfirmButton: false,
-                            }).then(() => window.location.reload());
-                        } else {
+                    $.ajax({
+                        method: 'PUT',
+                        url: '{{ route('ticket.reply', $ticket) }}',
+                        async: true,
+                        data: {
+                            _token: '{{ csrf_token() }}',
+                            content: content,
+                        },
+                        dataType: 'json',
+                        success: function(ret) {
+                            if (ret.status === 'success') {
+                                swal.fire({
+                                    title: ret.message,
+                                    icon: 'success',
+                                    timer: 1000,
+                                    showConfirmButton: false,
+                                }).then(() => window.location.reload());
+                            } else {
+                                swal.fire({
+                                    title: ret.message,
+                                    icon: 'error'
+                                }).then(() => window.location.reload());
+                            }
+                        },
+                        error: function() {
                             swal.fire({
-                                title: ret.message,
+                                title: '{{ trans('user.ticket.error') }}',
                                 icon: 'error'
-                            }).then(() => window.location.reload());
-                        }
+                            });
+                        },
                     });
                 }
             });

+ 3 - 3
resources/views/user/services.blade.php

@@ -85,7 +85,7 @@
                                             {!! $goods->info !!}
                                         </ul>
                                         <div class="pricing-footer text-center bg-blue-grey-100">
-                                            <a class="btn btn-lg btn-primary" href="{{ route('buy', $goods) }}"> {{ trans('user.shop.buy') }}</a>
+                                            <a class="btn btn-lg btn-primary" href="{{ route('shop.show', $goods) }}"> {{ trans('user.shop.buy') }}</a>
                                         </div>
                                     </div>
                                 </div>
@@ -183,7 +183,7 @@
                 confirmButtonText: '{{ trans('common.confirm') }}',
             }).then((result) => {
                 if (result.value) {
-                    $.post('{{ route('resetTraffic') }}', {
+                    $.post('{{ route('shop.resetTraffic') }}', {
                         _token: '{{ csrf_token() }}'
                     }, function(ret) {
                         if (ret.status === 'success') {
@@ -260,7 +260,7 @@
 
                 $.ajax({
                     method: 'POST',
-                    url: '{{ route('recharge') }}',
+                    url: '{{ route('shop.coupon.redeem') }}',
                     data: {
                         _token: '{{ csrf_token() }}',
                         coupon_sn: charge_coupon

+ 2 - 3
resources/views/user/ticketList.blade.php → resources/views/user/tickets.blade.php

@@ -37,8 +37,7 @@
                                             <td>{{ $ticket->title }}</td>
                                             <td>{!! $ticket->status_label !!}</td>
                                             <td>
-                                                <a class="btn btn-animate btn-animate-vertical btn-outline-info"
-                                                   href="{{ route('replyTicket', ['id' => $ticket->id]) }}">
+                                                <a class="btn btn-animate btn-animate-vertical btn-outline-info" href="{{ route('ticket.edit', $ticket) }}">
                                                     <span>
                                                         @if ($ticket->status === 2)
                                                             <i class="icon wb-eye" aria-hidden="true" style="left: 40%"> </i>{{ trans('common.view') }}
@@ -161,7 +160,7 @@
                 confirmButtonText: '{{ trans('common.confirm') }}',
             }).then((result) => {
                 if (result.value) {
-                    $.post('{{ route('openTicket') }}', {
+                    $.post('{{ route('ticket.store') }}', {
                         _token: '{{ csrf_token() }}',
                         title: title,
                         content: content,

+ 11 - 10
routes/admin.php

@@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\Config\LabelController;
 use App\Http\Controllers\Admin\Config\LevelController;
 use App\Http\Controllers\Admin\Config\SsConfigController;
 use App\Http\Controllers\Admin\CouponController;
+use App\Http\Controllers\Admin\InviteController;
 use App\Http\Controllers\Admin\LogsController;
 use App\Http\Controllers\Admin\MarketingController;
 use App\Http\Controllers\Admin\NodeAuthController;
@@ -30,14 +31,7 @@ use App\Http\Controllers\AdminController;
 use App\Utils\Payments\EPay;
 
 Route::prefix('admin')->name('admin.')->group(function () {
-    Route::controller(AdminController::class)->group(function () {
-        Route::get('/', 'index')->name('index'); // 后台首页
-        Route::get('config', 'config')->name('config.index'); // 系统通用配置
-        Route::get('invite', 'inviteList')->name('invite.index'); // 邀请码列表
-        Route::post('invite', 'makeInvite')->name('invite.create'); // 生成邀请码
-        Route::get('Invite/export', 'exportInvite')->name('invite.export'); // 导出邀请码
-    });
-    Route::get('epayInfo', [EPay::class, 'queryInfo'])->name('test.epay'); // 易支付信息
+    Route::get('/', [AdminController::class, 'index'])->name('index'); // 后台首页
 
     Route::resource('user', UserController::class)->except('show');
     Route::name('user.')->group(function () {
@@ -58,8 +52,8 @@ Route::prefix('admin')->name('admin.')->group(function () {
 
     Route::prefix('subscribe')->name('subscribe.')->controller(SubscribeController::class)->group(function () {
         Route::get('/', 'index')->name('index'); // 订阅码列表
-        Route::get('log/{id}', 'subscribeLog')->name('log'); // 订阅码记录
-        Route::post('set/{subscribe}', 'setSubscribeStatus')->name('set'); // 启用禁用用户的订阅
+        Route::get('log/{userSubscribe}', 'subscribeLog')->name('log'); // 订阅码记录
+        Route::post('set/{userSubscribe}', 'setSubscribeStatus')->name('set'); // 启用禁用用户的订阅
     });
 
     Route::resource('ticket', TicketController::class)->except('create', 'show');
@@ -91,6 +85,11 @@ Route::prefix('admin')->name('admin.')->group(function () {
     Route::get('coupon/export', [CouponController::class, 'exportCoupon'])->name('coupon.export'); // 导出优惠券
     Route::resource('coupon', CouponController::class)->except('edit', 'update'); // 优惠券
 
+    Route::prefix('invite')->name('invite.')->controller(InviteController::class)->group(function () {
+        Route::get('/', 'index')->name('index'); // 邀请码列表
+        Route::post('/', 'generate')->name('create'); // 生成邀请码
+        Route::get('/export', 'export')->name('export'); // 导出邀请码
+    });
     Route::prefix('aff')->name('aff.')->controller(AffiliateController::class)->group(function () {
         Route::get('/', 'index')->name('index'); // 提现申请列表
         Route::get('rebate', 'rebate')->name('rebate'); // 返利流水记录
@@ -146,5 +145,7 @@ Route::prefix('admin')->name('admin.')->group(function () {
         Route::post('setExtend', 'setExtend')->name('system.extend'); // 设置logo图片文件
         Route::post('setConfig', 'setConfig')->name('system.update'); // 设置某个配置项
         Route::post('sendTestNotification', 'sendTestNotification')->name('test.notify'); //推送通知测试
+        Route::get('config', 'common')->name('config.index'); // 系统通用配置
     });
+    Route::get('epayInfo', [EPay::class, 'queryInfo'])->name('test.epay'); // 易支付信息
 });

+ 55 - 27
routes/user.php

@@ -2,6 +2,12 @@
 
 use App\Http\Controllers\PaymentController;
 use App\Http\Controllers\User\AffiliateController;
+use App\Http\Controllers\User\ArticleController;
+use App\Http\Controllers\User\InviteController;
+use App\Http\Controllers\User\InvoiceController;
+use App\Http\Controllers\User\NodeController;
+use App\Http\Controllers\User\ShopController;
+use App\Http\Controllers\User\TicketController;
 use App\Http\Controllers\UserController;
 use App\Utils\Avatar;
 use App\Utils\Payments\Manual;
@@ -9,32 +15,54 @@ use App\Utils\Payments\Stripe;
 
 Route::controller(UserController::class)->group(function () {
     Route::get('/', 'index')->name('home'); // 用户首页
-    Route::get('article/{article}', 'article')->name('article'); // 文章详情
-    Route::post('exchangeSubscribe', 'exchangeSubscribe')->name('changeSub'); // 更换节点订阅地址
-    Route::match(['get', 'post'], 'nodeList', 'nodeList')->name('node'); // 节点列表
+    Route::post('exchange/subscribe', 'exchangeSubscribe')->name('changeSub'); // 更换节点订阅地址
     Route::post('checkIn', 'checkIn')->name('checkIn'); // 签到
-    Route::get('services', 'services')->name('shop'); // 商品列表
-    Route::get('tickets', 'ticketList')->name('ticket'); // 工单
-    Route::post('createTicket', 'createTicket')->name('openTicket'); // 快速添加工单
-    Route::match(['get', 'post'], 'replyTicket', 'replyTicket')->name('replyTicket'); // 回复工单
-    Route::post('closeTicket', 'closeTicket')->name('closeTicket'); // 关闭工单
-    Route::get('invoices', 'invoices')->name('invoice'); // 订单列表
-    Route::post('closePlan', 'closePlan')->name('cancelPlan'); // 激活预支付套餐
-    Route::get('invoice/{sn}', 'invoiceDetail')->name('invoiceInfo'); // 订单明细
-    Route::post('resetUserTraffic', 'resetUserTraffic')->name('resetTraffic'); // 重置用户流量
-    Route::post('buy/{good}/redeem', 'redeemCoupon')->name('redeemCoupon'); // 使用优惠券
-    Route::get('buy/{good}', 'buy')->name('buy'); // 购买商品
-    Route::get('invite', 'invite')->name('invite'); // 邀请码
-    Route::post('makeInvite', 'makeInvite')->name('createInvite'); // 生成邀请码
-    Route::match(['get', 'post'], 'profile', 'profile')->name('profile'); // 修改个人信息
-    Route::post('switchToAdmin', 'switchToAdmin')->name('switch'); // 转换成管理员的身份
-    Route::post('charge', 'charge')->name('recharge'); // 卡券余额充值
-    Route::get('knowledge', 'knowledge')->name('knowledge'); // 帮助中心
-    Route::get('currency/{code}', 'switchCurrency')->name('currency'); // 语言切换
-});
-Route::controller(AffiliateController::class)->group(function () {
-    Route::get('referral', 'referral')->name('commission'); // 推广返利
-    Route::post('extractMoney', 'extractMoney')->name('applyCommission'); // 申请提现
+    Route::get('profile', 'profile')->name('profile.show'); // 查看个人信息
+    Route::post('profile', 'updateProfile')->name('profile.update'); // 修改个人信息
+    Route::post('switch/admin', 'switchToAdmin')->name('switch'); // 转换成管理员的身份
+    Route::get('currency/{code}', 'switchCurrency')->name('currency'); // 货币切换
+});
+
+Route::prefix('shop')->name('shop.')->controller(ShopController::class)->group(function () {
+    Route::get('/', 'index')->name('index'); // 商品页面
+    Route::get('/{good}', 'show')->name('show'); // 商品详细
+    Route::post('/{good}/coupon/redeem', 'checkBonus')->name('coupon.check'); // 兑换优惠券码
+    Route::post('/coupon/redeem', 'redeemCoupon')->name('coupon.redeem'); // 卡券余额充值
+    Route::post('reset-traffic', 'resetTraffic')->name('resetTraffic'); // 重置用户流量
+});
+
+Route::prefix('invite')->name('invite.')->controller(InviteController::class)->group(function () {
+    Route::get('/', 'index')->name('index'); // 邀请码
+    Route::post('/', 'store')->name('store'); // 生成邀请码
+});
+
+Route::prefix('invoice')->name('invoice.')->controller(InvoiceController::class)->group(function () {
+    Route::get('/', 'index')->name('index'); // 订单列表
+    Route::get('/{sn}', 'show')->name('show'); // 订单详情
+    Route::post('activate', 'activate')->name('activate'); // 激活预支付套餐
+});
+
+Route::prefix('node')->name('node.')->controller(NodeController::class)->group(function () {
+    Route::get('/', 'index')->name('index'); // 节点列表
+    Route::post('/{node}', 'show')->name('show'); // 节点详情
+});
+
+Route::prefix('knowledge')->name('knowledge.')->controller(ArticleController::class)->group(function () {
+    Route::get('/', 'index')->name('index'); // 文章帮助中心
+    Route::get('/{article}', 'show')->name('show'); // 文章详情
+});
+
+Route::prefix('ticket')->name('ticket.')->controller(TicketController::class)->group(function () {
+    Route::get('/', 'index')->name('index'); // 工单列表
+    Route::post('/', 'store')->name('store'); // 创建工单
+    Route::get('{ticket}', 'edit')->name('edit'); // 查阅工单
+    Route::put('{ticket}', 'reply')->name('reply'); // 回复工单
+    Route::patch('{ticket}', 'close')->name('close'); // 关闭工单
+});
+
+Route::prefix('referral')->name('referral.')->controller(AffiliateController::class)->group(function () {
+    Route::get('/', 'index')->name('index'); // 推广返利
+    Route::post('/withdraw', 'withdraw')->name('withdraw'); // 申请提现
 });
 
 Route::prefix('payment')->controller(PaymentController::class)->group(function () {
@@ -50,7 +78,7 @@ Route::prefix('pay')->group(function () {
     Route::get('/stripe/{session_id}', [Stripe::class, 'redirectPage'])->name('stripe.checkout'); // Stripe Checkout page
 });
 
-Route::get('avatar', [Avatar::class, 'getAvatar'])->name('getAvatar'); // 获取随机头像
 Route::get('create/string', [Str::class, 'random'])->name('createStr'); // 生成随机密码
 Route::get('create/uuid', [Str::class, 'uuid'])->name('createUUID'); // 生成UUID
-Route::get('getPort', [Helpers::class, 'getPort'])->name('getPort'); // 获取端口
+Route::get('get/avatar', [Avatar::class, 'get'])->name('getAvatar'); // 获取随机头像
+Route::get('get/port', [Helpers::class, 'getPort'])->name('getPort'); // 获取端口