Parcourir la source

Major update: 强化卡券玩法 & 相关代码优化

详情 https://proxypanel.gitbook.io/wiki/coupon
兔姬桑 il y a 3 ans
Parent
commit
7010822acc

+ 42 - 6
app/Http/Controllers/Admin/CouponController.php

@@ -5,6 +5,8 @@ namespace App\Http\Controllers\Admin;
 use App\Http\Controllers\Controller;
 use App\Http\Controllers\Controller;
 use App\Http\Requests\Admin\CouponRequest;
 use App\Http\Requests\Admin\CouponRequest;
 use App\Models\Coupon;
 use App\Models\Coupon;
+use App\Models\Level;
+use App\Models\UserGroup;
 use Exception;
 use Exception;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
@@ -35,6 +37,16 @@ class CouponController extends Controller
         return view('admin.coupon.index', ['couponList' => $query->latest()->paginate(15)->appends($request->except('page'))]);
         return view('admin.coupon.index', ['couponList' => $query->latest()->paginate(15)->appends($request->except('page'))]);
     }
     }
 
 
+    // 优惠券列表
+    public function show(Coupon $coupon)
+    {
+        return view('admin.coupon.show', [
+            'coupon'     => $coupon,
+            'userGroups' => UserGroup::all()->pluck('name', 'id')->toArray(),
+            'levels'     => Level::all()->pluck('name', 'level')->toArray(),
+        ]);
+    }
+
     // 添加优惠券
     // 添加优惠券
     public function store(CouponRequest $request)
     public function store(CouponRequest $request)
     {
     {
@@ -49,7 +61,28 @@ class CouponController extends Controller
             $logo = 'upload/'.$fileName;
             $logo = 'upload/'.$fileName;
         }
         }
         $num = (int) $request->input('num');
         $num = (int) $request->input('num');
-        $data = $request->only(['name', 'type', 'usable_times', 'value', 'rule', 'start_time', 'end_time']);
+        $data = $request->only(['name', 'type', 'priority', 'usable_times', 'value', 'start_time', 'end_time']);
+        $data['limit'] = [
+            'minimum'  => $request->input('minimum'),
+            'used'     => $request->input('used'),
+            'users'    => [
+                'white'  => $request->has('users_whitelist') ? array_map('intval', explode(', ', $request->input('users_whitelist'))) : null,
+                'black'  => $request->has('users_blacklist') ? array_map('intval', explode(', ', $request->input('users_blacklist'))) : null,
+                'newbie' => [
+                    'coupon' => $request->input('coupon'),
+                    'order'  => $request->input('order'),
+                    'days'   => $request->has('days') ? (int) $request->input(['days']) : null,
+                ],
+                'levels' => $request->has('levels') ? array_map('intval', $request->input('levels')) : null,
+                'groups' => $request->has('groups') ? array_map('intval', $request->input('groups')) : null,
+            ],
+            'services' => [
+                'white' => $request->has('services_whitelist') ? array_map('intval', explode(', ', $request->input('services_whitelist'))) : null,
+                'black' => $request->has('services_blacklist') ? array_map('intval', explode(', ', $request->input('services_blacklist'))) : null,
+            ],
+        ];
+        array_clean($data['limit']);
+
         $data['logo'] = $logo;
         $data['logo'] = $logo;
         $data['status'] = 0;
         $data['status'] = 0;
         try {
         try {
@@ -69,7 +102,10 @@ class CouponController extends Controller
     // 添加优惠券页面
     // 添加优惠券页面
     public function create()
     public function create()
     {
     {
-        return view('admin.coupon.create');
+        return view('admin.coupon.create', [
+            'userGroups' => UserGroup::all()->pluck('name', 'id')->toArray(),
+            'levels'     => Level::all()->pluck('name', 'level')->toArray(),
+        ]);
     }
     }
 
 
     // 删除优惠券
     // 删除优惠券
@@ -108,10 +144,10 @@ class CouponController extends Controller
             $spreadsheet->setActiveSheetIndex(0);
             $spreadsheet->setActiveSheetIndex(0);
             $sheet = $spreadsheet->getActiveSheet();
             $sheet = $spreadsheet->getActiveSheet();
             $sheet->setTitle('抵用券');
             $sheet->setTitle('抵用券');
-            $sheet->fromArray(['名称', '使用次数', '有效期', '券码', '金额(元)', '使用限制(元)']);
+            $sheet->fromArray(['名称', '使用次数', '有效期', '券码', '金额(元)', '权重', '使用限制']);
             foreach ($voucherList as $k => $vo) {
             foreach ($voucherList as $k => $vo) {
                 $dateRange = $vo->start_time.' ~ '.$vo->end_time;
                 $dateRange = $vo->start_time.' ~ '.$vo->end_time;
-                $sheet->fromArray([$vo->name, $vo->usable_times ?? '无限制', $dateRange, $vo->sn, $vo->value, $vo->rule], null, 'A'.($k + 2));
+                $sheet->fromArray([$vo->name, $vo->usable_times ?? '无限制', $dateRange, $vo->sn, $vo->value, $vo->priority, json_encode($vo->limit)], null, 'A'.($k + 2));
             }
             }
 
 
             // 折扣券
             // 折扣券
@@ -119,10 +155,10 @@ class CouponController extends Controller
             $spreadsheet->setActiveSheetIndex(1);
             $spreadsheet->setActiveSheetIndex(1);
             $sheet = $spreadsheet->getActiveSheet();
             $sheet = $spreadsheet->getActiveSheet();
             $sheet->setTitle('折扣券');
             $sheet->setTitle('折扣券');
-            $sheet->fromArray(['名称', '使用次数', '有效期', '券码', '折扣(折)', '使用限制(元)']);
+            $sheet->fromArray(['名称', '使用次数', '有效期', '券码', '折扣(折)', '权重', '使用限制']);
             foreach ($discountCouponList as $k => $vo) {
             foreach ($discountCouponList as $k => $vo) {
                 $dateRange = $vo->start_time.' ~ '.$vo->end_time;
                 $dateRange = $vo->start_time.' ~ '.$vo->end_time;
-                $sheet->fromArray([$vo->name, $vo->usable_times ?? '无限制', $dateRange, $vo->sn, $vo->value, $vo->rule], null, 'A'.($k + 2));
+                $sheet->fromArray([$vo->name, $vo->usable_times ?? '无限制', $dateRange, $vo->sn, $vo->value, $vo->priority, json_encode($vo->limit)], null, 'A'.($k + 2));
             }
             }
 
 
             // 充值券
             // 充值券

+ 4 - 48
app/Http/Controllers/PaymentController.php

@@ -18,6 +18,7 @@ use App\Models\Coupon;
 use App\Models\Goods;
 use App\Models\Goods;
 use App\Models\Order;
 use App\Models\Order;
 use App\Models\Payment;
 use App\Models\Payment;
+use App\Services\CouponService;
 use Auth;
 use Auth;
 use Exception;
 use Exception;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
@@ -127,13 +128,12 @@ class PaymentController extends Controller
 
 
             // 使用优惠券
             // 使用优惠券
             if ($coupon_sn) {
             if ($coupon_sn) {
-                $ret = $this->couponCheck($coupon_sn, $goods->price);
+                $ret = (new CouponService($coupon_sn))->search($goods); // 检查券合规性
 
 
-                if ($ret !== true) {
+                if (! $ret instanceof Coupon) {
                     return $ret;
                     return $ret;
                 }
                 }
-
-                $coupon = Coupon::whereStatus(0)->whereIn('type', [1, 2])->whereSn($coupon_sn)->firstOrFail();
+                $coupon = $ret;
 
 
                 // 计算实际应支付总价
                 // 计算实际应支付总价
                 $amount = $coupon->type === 2 ? $goods->price * $coupon->value / 100 : $goods->price - $coupon->value;
                 $amount = $coupon->type === 2 ? $goods->price * $coupon->value / 100 : $goods->price - $coupon->value;
@@ -198,50 +198,6 @@ class PaymentController extends Controller
         return Response::json(['status' => 'fail', 'message' => '订单创建失败']);
         return Response::json(['status' => 'fail', 'message' => '订单创建失败']);
     }
     }
 
 
-    public function couponCheck($coupon_sn, $price) // 检查券合规性
-    {
-        if (empty($coupon_sn)) {
-            return Response::json([
-                'status' => 'fail', 'title' => trans('common.failed'), 'message' => trans('validation.required', ['attribute' => trans('user.coupon.attribute')]),
-            ]);
-        }
-
-        $coupon = Coupon::whereSn($coupon_sn)->whereIn('type', [1, 2])->first();
-        if (! $coupon) {
-            return Response::json(['status' => 'fail', 'title' => trans('common.failed'), 'message' => trans('user.coupon.error.unknown')]);
-        }
-
-        if ($coupon->status === 1) {
-            return Response::json(['status' => 'fail', 'title' => trans('common.sorry'), 'message' => trans('user.coupon.error.used')]);
-        }
-        if ($coupon->getRawOriginal('end_time') < time()) {
-            $coupon->status = 2;
-            $coupon->save();
-
-            return Response::json(['status' => 'fail', 'title' => trans('common.sorry'), 'message' => trans('user.coupon.error.expired')]);
-        }
-
-        if ($coupon->status === 2) {
-            if ($coupon->usable_times === 0) {
-                return Response::json(['status' => 'fail', 'title' => trans('common.sorry'), 'message' => trans('user.coupon.error.run_out')]);
-            }
-
-            return Response::json(['status' => 'fail', 'title' => trans('common.sorry'), 'message' => trans('user.coupon.error.expired')]);
-        }
-
-        if ($coupon->start_time > date('Y-m-d H:i:s')) {
-            return Response::json(['status'  => 'fail', 'title' => trans('user.coupon.error.inactive'),
-                'message' => trans('user.coupon.error.wait', ['time' => $coupon->start_time]),
-            ]);
-        }
-
-        if ($price < $coupon->rule) {
-            return Response::json(['status' => 'fail', 'title' => trans('user.coupon.error.limit'), 'message' => trans('user.coupon.error.higher', ['amount' => $coupon->rule])]);
-        }
-
-        return true;
-    }
-
     public function close(Order $order): JsonResponse
     public function close(Order $order): JsonResponse
     {
     {
         if (! $order->close()) {
         if (! $order->close()) {

+ 23 - 46
app/Http/Controllers/UserController.php

@@ -14,6 +14,7 @@ use App\Models\Ticket;
 use App\Models\User;
 use App\Models\User;
 use App\Notifications\TicketCreated;
 use App\Notifications\TicketCreated;
 use App\Notifications\TicketReplied;
 use App\Notifications\TicketReplied;
+use App\Services\CouponService;
 use Cache;
 use Cache;
 use DB;
 use DB;
 use Exception;
 use Exception;
@@ -205,14 +206,11 @@ class UserController extends Controller
 
 
         if ($user && $nodes = $user->userGroup) {
         if ($user && $nodes = $user->userGroup) {
             $nodes = $nodes->nodes();
             $nodes = $nodes->nodes();
-            foreach ($goodsList as $goods) {
-                $goods->node_count = $nodes->where('level', '<=', $goods->level)->count();
-            }
         } else {
         } else {
             $nodes = Node::all();
             $nodes = Node::all();
-            foreach ($goodsList as $goods) {
-                $goods->node_count = $nodes->where('level', '<=', $goods->level)->count();
-            }
+        }
+        foreach ($goodsList as $goods) {
+            $goods->node_count = $nodes->where('level', '<=', $goods->level)->count();
         }
         }
 
 
         return view('user.services', [
         return view('user.services', [
@@ -385,14 +383,12 @@ class UserController extends Controller
         if ($user->invite_num <= 0) {
         if ($user->invite_num <= 0) {
             return Response::json(['status' => 'fail', 'message' => trans('user.invite.generate_failed')]);
             return Response::json(['status' => 'fail', 'message' => trans('user.invite.generate_failed')]);
         }
         }
-
-        $obj = new Invite();
-        $obj->inviter_id = $user->id;
-        $obj->code = strtoupper(mb_substr(md5(microtime().Str::random()), 8, 12));
-        $obj->dateline = date('Y-m-d H:i:s', strtotime(sysConfig('user_invite_days').' days'));
-        $obj->save();
-        if ($obj) {
-            $user->update(['invite_num' => $user->invite_num - 1]);
+        $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.generate_item', ['attribute' => trans('common.success')])]);
             return Response::json(['status' => 'success', 'message' => trans('common.generate_item', ['attribute' => trans('common.success')])]);
         }
         }
@@ -401,21 +397,24 @@ class UserController extends Controller
     }
     }
 
 
     // 使用优惠券
     // 使用优惠券
-    public function redeemCoupon(Request $request): JsonResponse
+    public function redeemCoupon(Request $request, Goods $good): JsonResponse
     {
     {
         $coupon_sn = $request->input('coupon_sn');
         $coupon_sn = $request->input('coupon_sn');
 
 
-        $ret = (new PaymentController())->couponCheck($coupon_sn, $request->input('price'));
+        if (empty($coupon_sn)) {
+            return Response::json(['status' => 'fail', 'title' => trans('common.failed'), 'message' => trans('user.coupon.error.unknown')]);
+        }
+
+        $ret = (new CouponService($coupon_sn))->search($good); // 检查券合规性
 
 
-        if ($ret !== true) {
+        if (! $ret instanceof Coupon) {
             return $ret;
             return $ret;
         }
         }
-        $coupon = Coupon::whereSn($coupon_sn)->whereIn('type', [1, 2])->firstOrFail();
 
 
         $data = [
         $data = [
-            'name'  => $coupon->name,
-            'type'  => $coupon->type,
-            'value' => $coupon->value,
+            'name'  => $ret->name,
+            'type'  => $ret->type,
+            'value' => $ret->value,
         ];
         ];
 
 
         return Response::json(['status' => 'success', 'data' => $data, 'message' => trans('common.applied', ['attribute' => trans('user.coupon.attribute')])]);
         return Response::json(['status' => 'success', 'data' => $data, 'message' => trans('common.applied', ['attribute' => trans('user.coupon.attribute')])]);
@@ -534,32 +533,10 @@ class UserController extends Controller
             return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
             return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
         }
         }
 
 
-        $coupon = Coupon::whereSn($request->input('coupon_sn'))->firstOrFail();
-
-        try {
-            DB::beginTransaction();
-            // 写入日志
-            $user = auth()->user();
-            Helpers::addUserCreditLog($user->id, null, $user->credit, $user->credit + $coupon->value, $coupon->value,
-                trans('user.recharge').' - ['.trans('user.coupon.recharge').':'.$request->input('coupon_sn').']');
-
-            // 余额充值
-            $user->updateCredit($coupon->value);
-
-            // 更改卡券状态
-            $coupon->update(['status' => 1]);
-
-            // 写入卡券日志
-            Helpers::addCouponLog(trans('user.recharge_credit'), $coupon->id);
-
-            DB::commit();
-
+        if ((new CouponService($request->input('coupon_sn')))->charge()) {
             return Response::json(['status' => 'success', 'message' => trans('user.recharge').trans('common.success')]);
             return Response::json(['status' => 'success', 'message' => trans('user.recharge').trans('common.success')]);
-        } catch (Exception $e) {
-            Log::error(trans('user.recharge').trans('common.failed').$e->getMessage());
-            DB::rollBack();
-
-            return Response::json(['status' => 'fail', 'message' => trans('user.recharge').trans('common.failed')]);
         }
         }
+
+        return Response::json(['status' => 'fail', 'message' => trans('user.recharge').trans('common.failed')]);
     }
     }
 }
 }

+ 14 - 3
app/Http/Requests/Admin/CouponRequest.php

@@ -10,12 +10,23 @@ class CouponRequest extends FormRequest
     {
     {
         return [
         return [
             'name'               => 'required|string',
             'name'               => 'required|string',
-            'sn'                 => 'unique:coupon',
+            'sn'                 => 'exclude_unless:type,3|unique:coupon',
             'logo'               => 'nullable|image',
             'logo'               => 'nullable|image',
             'type'               => 'required|numeric|between:1,3',
             'type'               => 'required|numeric|between:1,3',
-            'usable_times'       => 'nullable|numeric',
+            'priority'           => 'nullable|numeric|min:0|max:255',
+            'usable_times'       => 'nullable|numeric|min:1',
             'value'              => 'required|numeric|min:0',
             'value'              => 'required|numeric|min:0',
-            'rule'               => 'nullable|numeric',
+            'minimum'            => 'nullable|numeric',
+            'used'               => 'nullable|numeric',
+            'levels'             => 'nullable|array',
+            'groups'             => 'nullable|array',
+            'users_whitelist'    => 'nullable|string',
+            'users_blacklist'    => 'nullable|string',
+            'services_blacklist' => 'nullable|string',
+            'services_whitelist' => 'nullable|string',
+            'coupon'             => 'nullable',
+            'order'              => 'nullable',
+            'days'               => 'nullable|numeric',
             'num'                => 'required|numeric|min:1',
             'num'                => 'required|numeric|min:1',
             'start_time'         => 'required|date|before_or_equal:end_time',
             'start_time'         => 'required|date|before_or_equal:end_time',
             'end_time'           => 'required|date|after_or_equal:start_time',
             'end_time'           => 'required|date|after_or_equal:start_time',

+ 15 - 1
app/Models/Coupon.php

@@ -13,7 +13,7 @@ class Coupon extends Model
     use SoftDeletes;
     use SoftDeletes;
 
 
     protected $table = 'coupon';
     protected $table = 'coupon';
-    protected $casts = ['start_time' => 'date:Y-m-d', 'end_time' => 'date:Y-m-d'];
+    protected $casts = ['limit' => 'array', 'start_time' => 'date:Y-m-d', 'end_time' => 'date:Y-m-d'];
     protected $dates = ['deleted_at'];
     protected $dates = ['deleted_at'];
     protected $guarded = [];
     protected $guarded = [];
 
 
@@ -32,4 +32,18 @@ class Coupon extends Model
     {
     {
         return $this->attributes['end_time'] = strtotime($value);
         return $this->attributes['end_time'] = strtotime($value);
     }
     }
+
+    public function used()
+    {
+        $this->attributes['status'] = 1;
+
+        return $this->save();
+    }
+
+    public function expired()
+    {
+        $this->attributes['status'] = 2;
+
+        return $this->save();
+    }
 }
 }

+ 142 - 0
app/Services/CouponService.php

@@ -0,0 +1,142 @@
+<?php
+
+namespace App\Services;
+
+use App\Components\Helpers;
+use App\Models\Coupon;
+use App\Models\Goods;
+use Auth;
+use Exception;
+use Illuminate\Support\Facades\Log;
+use Response;
+
+class CouponService
+{
+    public $code;
+    public $user;
+
+    public function __construct(string $code)
+    {
+        $this->code = $code;
+        $this->user = Auth::getUser();
+    }
+
+    public function search(Goods $goods) // 寻找合适的券
+    {
+        $coupons = Coupon::whereSn($this->code)->whereIn('type', [1, 2])->orderByDesc('priority')->get();
+        if ($coupons->isNotEmpty()) {
+            foreach ($coupons as $coupon) {
+                $ret = $this->check($goods, $coupon);
+                if ($ret === true) { // passed
+                    return $coupon;
+                }
+            }
+
+            return $ret ?? $this->failedReturn(trans('common.failed'), trans('user.coupon.error.unknown'));
+        }
+
+        return $this->failedReturn(trans('common.failed'), trans('user.coupon.error.unknown'));
+    }
+
+    private function check(Goods $goods, Coupon $coupon) // 检查券合规性
+    {
+        if ($coupon->status === 1) {
+            return $this->failedReturn(trans('common.sorry'), trans('user.coupon.error.used'));
+        }
+
+        if (time() > $coupon->getRawOriginal('end_time')) {
+            $coupon->expired();
+
+            return $this->failedReturn(trans('common.sorry'), trans('user.coupon.error.expired'));
+        }
+
+        if ($coupon->usable_times === 0) {
+            return $this->failedReturn(trans('common.sorry'), trans('user.coupon.error.run_out'));
+        }
+
+        if ($coupon->status === 2) {
+            return $this->failedReturn(trans('common.sorry'), trans('user.coupon.error.expired'));
+        }
+
+        if (time() < $coupon->getRawOriginal('start_time')) {
+            return $this->failedReturn(trans('user.coupon.error.inactive'), trans('user.coupon.error.wait', ['time' => $coupon->start_time]));
+        }
+
+        if (isset($coupon->limit['minimum']) && $goods->price < $coupon->limit['minimum']) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.minimum', ['amount' => $coupon->limit['minimum']]));
+        }
+
+        if (isset($coupon->limit['users']['black']) && in_array($this->user->id, $coupon->limit['users']['black'], true)) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.users'));
+        }
+
+        if (isset($coupon->limit['services']['black']) && in_array($goods->id, $coupon->limit['services']['black'], true)) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.services'));
+        }
+
+        if (isset($coupon->limit['users']['white']) && ! in_array($this->user->id, $coupon->limit['users']['white'], true)) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.users'));
+        }
+
+        if (isset($coupon->limit['services']['white']) && ! in_array($goods->id, $coupon->limit['services']['white'], true)) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.services'));
+        }
+
+        if (isset($coupon->limit['users']['levels']) && ! in_array($this->user->level, $coupon->limit['users']['levels'], true)) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.users'));
+        }
+
+        if (isset($coupon->limit['users']['groups']) && ! in_array($this->user->user_group_id, $coupon->limit['users']['groups'], true)) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.users'));
+        }
+
+        if (isset($coupon->limit['users']['newbie'])) { // 新用户可用
+            if (isset($coupon->limit['users']['newbie']['coupon']) && $this->user->orders()->whereNotNull('coupon_id')->exists()) { // 第一次使用优惠券
+                return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.users'));
+            }
+
+            if (isset($coupon->limit['users']['newbie']['order']) && $this->user->orders()->exists()) { // 第一个套餐订单
+
+                return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.users'));
+            }
+
+            if (isset($coupon->limit['users']['newbie']['days']) && (time() > strtotime($this->user->created_at.' +'.$coupon->limit['users']['newbie']['days'].' days') ||
+                    $this->user->orders()->whereCouponId($coupon->id)->exists())) { // 创号N天内, 且第一次用优惠券
+
+                return $this->failedReturn(trans('user.coupon.error.unmet'), trans('user.coupon.error.users'));
+            }
+        }
+
+        if (isset($coupon->limit['used']) && $this->user->orders()->whereCouponId($coupon->id)->count() >= $coupon->limit['used']) {
+            return $this->failedReturn(trans('user.coupon.error.unmet'),
+                trans_choice('user.coupon.error.overused', $coupon->limit['used'], ['times' => $coupon->limit['used']]));
+        }
+
+        return true;
+    }
+
+    private function failedReturn(string $title, string $message)
+    {
+        return Response::json(['status' => 'fail', 'title' => $title, 'message' => $message]);
+    }
+
+    public function charge(): bool
+    {
+        $coupon = Coupon::whereSn($this->code)->whereType(3)->first();
+        if ($coupon && $coupon->status === 0) {
+            try {
+                Helpers::addUserCreditLog($this->user->id, null, $this->user->credit, $this->user->credit + $coupon->value, $coupon->value,
+                    trans('user.recharge').' - ['.trans('user.coupon.recharge').':'.$coupon->sn.']'); // 写入用户余额变动日志
+                $this->user->updateCredit($coupon->value); // 余额充值
+                $coupon->used(); // 更改卡券状态
+                Helpers::addCouponLog(trans('user.recharge_credit'), $coupon->id); // 写入卡券使用日志
+
+                return true;
+            } catch (Exception $exception) {
+                Log::emergency('[重置券处理出现错误] '.$exception->getMessage());
+            }
+        }
+
+        return false;
+    }
+}

+ 17 - 0
app/helpers.php

@@ -109,3 +109,20 @@ if (! function_exists('string_decrypt')) {
         return openssl_decrypt(base64url_decode($data), 'aes-128-ctr', hash('sha256', config('app.key')), OPENSSL_RAW_DATA, substr(sha1(config('app.key')), 0, 16));
         return openssl_decrypt(base64url_decode($data), 'aes-128-ctr', hash('sha256', config('app.key')), OPENSSL_RAW_DATA, substr(sha1(config('app.key')), 0, 16));
     }
     }
 }
 }
+
+// Array values and indexes clean
+if (! function_exists('array_clean')) {
+    function array_clean(array &$array): array
+    {
+        foreach ($array as $key => &$value) {
+            if (is_array($value)) {
+                $value = array_clean($value);
+            }
+            if (empty($value)) {
+                unset($array[$key]);
+            }
+        }
+
+        return $array;
+    }
+}

+ 59 - 0
database/migrations/2022_08_25_204229_improve_coupon.php

@@ -0,0 +1,59 @@
+<?php
+
+use App\Components\MigrationToolBox;
+use App\Models\Coupon;
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class ImproveCoupon extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('coupon', function (Blueprint $table) {
+            if ((new MigrationToolBox())->versionCheck()) {
+                $table->json('limit')->nullable()->comment('使用限制')->after('rule');
+            } else {
+                $table->text('limit')->nullable()->comment('使用限制')->after('rule');
+            }
+            $table->unsignedTinyInteger('priority')->default(0)->comment('使用权重, 高者优先')->after('limit');
+            $table->dropUnique(['sn']);
+        });
+
+        foreach (Coupon::whereNotNull('rule')->get() as $coupon) {
+            $coupon->update(['limit' => ['minimum' => $coupon->rule]]);
+        }
+
+        Schema::table('coupon', function (Blueprint $table) {
+            $table->dropColumn('rule');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('coupon', function (Blueprint $table) {
+            $table->unsignedInteger('rule')->nullable()->comment('使用限制(元)')->after('limit');
+        });
+
+        foreach (Coupon::whereNotNull('limit')->get() as $coupon) {
+            if (isset($coupon->limit['minimum'])) {
+                $coupon->update(['rule' => $coupon->limit['minimum']]);
+            }
+        }
+
+        Schema::table('coupon', function (Blueprint $table) {
+            $table->dropColumn('limit', 'priority');
+            $table->unique('sn');
+        });
+    }
+}

+ 5 - 2
resources/lang/en/user.php

@@ -96,8 +96,11 @@ return [
             'run_out'  => 'Run Out of Usage',
             'run_out'  => 'Run Out of Usage',
             'inactive' => 'Coupon Inactive yet',
             'inactive' => 'Coupon Inactive yet',
             'wait'     => 'The Event will begin until :time, Please wait',
             'wait'     => 'The Event will begin until :time, Please wait',
-            'limit'    => 'Order\'s price is reach the minimum requirement of this coupon',
-            'higher'   => 'The minimum requirement of this coupon is ¥:amount',
+            'unmet'    => 'Conditions of use are not met',
+            'minimum'  => 'The minimum requirement of this coupon is ¥:amount',
+            'overused' => 'You can only use this coupon once|You can only use this coupon :times times',
+            'users'    => 'This account is not eligible for promotions',
+            'services' => 'This item is not qualify for this discount, please check the promotion terms',
         ],
         ],
     ],
     ],
     'error_response'      => 'Something went wrong, please try again later.',
     'error_response'      => 'Something went wrong, please try again later.',

+ 7 - 4
resources/lang/zh_CN/user.php

@@ -96,8 +96,11 @@ return [
             'run_out'  => '优惠券耗尽',
             'run_out'  => '优惠券耗尽',
             'inactive' => '优惠券尚未生效',
             'inactive' => '优惠券尚未生效',
             'wait'     => '活动将于:time生效,请耐心等待!',
             'wait'     => '活动将于:time生效,请耐心等待!',
-            'limit'    => '使用条件未满足',
-            'higher'   => '本券最低使用金额为 ¥:amount',
+            'unmet'    => '使用条件未满足',
+            'minimum'  => '本券最低使用金额为 ¥:amount',
+            'overused' => '本券只能使用 :times 次',
+            'users'    => '帐户不符合促销条件',
+            'services' => '商品不符合折扣条件,请查看促销条款',
         ],
         ],
     ],
     ],
     'error_response'      => '出现了错误,请稍后再试。',
     'error_response'      => '出现了错误,请稍后再试。',
@@ -119,7 +122,7 @@ return [
     ],
     ],
     'referral'            => [
     'referral'            => [
         'link'       => '推广链接',
         'link'       => '推广链接',
-        'total'      => '合计返利 :amount( :total 次),满 :money 可以申请提现。',
+        'total'      => '合计返利 ¥ :amount( :total 次),满 ¥ :money 可以申请提现。',
         'amount'     => '消费金额',
         'amount'     => '消费金额',
         'commission' => '返利金额',
         'commission' => '返利金额',
         'logs'       => '佣金记录',
         'logs'       => '佣金记录',
@@ -128,7 +131,7 @@ return [
         'msg'        => [
         'msg'        => [
             'account'     => '账号已过期,请先购买服务吧',
             'account'     => '账号已过期,请先购买服务吧',
             'applied'     => '已存在申请,请等待之前的申请处理完',
             'applied'     => '已存在申请,请等待之前的申请处理完',
-            'unfulfilled' => '满:amount才可以提现,继续努力吧',
+            'unfulfilled' => '满¥ :amount才可以提现,继续努力吧',
             'wait'        => '请等待管理员审核',
             'wait'        => '请等待管理员审核',
             'error'       => '返利单建立失败,请稍后尝试或通知管理员',
             'error'       => '返利单建立失败,请稍后尝试或通知管理员',
         ],
         ],

+ 152 - 37
resources/views/admin/coupon/create.blade.php

@@ -1,7 +1,9 @@
 @extends('admin.layouts')
 @extends('admin.layouts')
 @section('css')
 @section('css')
     <link href="/assets/global/vendor/dropify/dropify.min.css" rel="stylesheet">
     <link href="/assets/global/vendor/dropify/dropify.min.css" rel="stylesheet">
+    <link href="/assets/global/vendor/bootstrap-select/bootstrap-select.min.css" rel="stylesheet">
     <link href="/assets/global/vendor/bootstrap-datepicker/bootstrap-datepicker.min.css" rel="stylesheet">
     <link href="/assets/global/vendor/bootstrap-datepicker/bootstrap-datepicker.min.css" rel="stylesheet">
+    <link href="/assets/global/vendor/bootstrap-tokenfield/bootstrap-tokenfield.min.css" rel="stylesheet">
 @endsection
 @endsection
 @section('content')
 @section('content')
     <div class="page-content container">
     <div class="page-content container">
@@ -58,33 +60,143 @@
                             <span class="text-help"> 抵用:抵扣商品金额,折扣:商品百分比打折,充值:充值用户账号余额 </span>
                             <span class="text-help"> 抵用:抵扣商品金额,折扣:商品百分比打折,充值:充值用户账号余额 </span>
                         </div>
                         </div>
                     </div>
                     </div>
-                    <div class="form-group row usage">
-                        <label class="col-md-2 col-form-label" for="usable_times">使用次数</label>
-                        <div class="col-md-4 input-group">
-                            <input type="number" class="form-control" name="usable_times" id="usable_times" value="{{old('usable_times')}}"/>
-                            <span class="input-group-text">次</span>
-                        </div>
-                    </div>
                     <div class="form-group row">
                     <div class="form-group row">
                         <label class="col-md-2 col-form-label" for="value">优惠额度</label>
                         <label class="col-md-2 col-form-label" for="value">优惠额度</label>
                         <div class="col-md-10">
                         <div class="col-md-10">
                             <div class="input-group">
                             <div class="input-group">
-                                <input type="number" class="form-control col-md-3" name="value" id="value" value="{{old('value')}}" required/>
+                                <input type="number" class="form-control col-md-3" min="1" max="99" name="value" id="value" value="{{old('value')}}" required/>
                                 <span class="input-group-text" id="amount">元</span>
                                 <span class="input-group-text" id="amount">元</span>
                                 <span class="input-group-text discount" style="display: none;">%</span>
                                 <span class="input-group-text discount" style="display: none;">%</span>
                             </div>
                             </div>
-                            <span class="text-help discount" style="display: none;"> 范围为 1~99折,即 1% ~ 99% </span>
+                            <span class="text-help discount" style="display: none;"> 范围为 1% ~ 99% </span>
                         </div>
                         </div>
                     </div>
                     </div>
-                    <div class="form-group row usage">
-                        <label class="col-md-2 col-form-label" for="rule">满减条件</label>
-                        <div class="col-md-10">
-                            <div class="input-group">
-                                <input type="number" class="form-control col-md-3" name="rule" id="rule" value="{{old('rule')}}" step="0.01" />
-                                <span class="input-group-text">元</span>
+                    <div class="usage">
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="priority"> 权 重 </label>
+                            <div class="col-md-10">
+                                <div class="input-group">
+                                    <input type="number" class="form-control col-md-2" min="0" max="255" name="priority" id="priority" value="{{old('priority')}}"/>
+                                </div>
+                                <span class="text-help"> 同【使用券码】下,符合条件的高权重码将会被优先使用。最高为 255 </span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="usable_times">使用次数</label>
+                            <div class="col-md-4 input-group">
+                                <input type="number" class="form-control" min="1" name="usable_times" id="usable_times" value="{{old('usable_times', 1)}}"/>
+                                <span class="input-group-text">次</span>
+                            </div>
+                        </div>
+                        <hr>
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="minimum">满减条件</label>
+                            <div class="col-md-10">
+                                <div class="input-group">
+                                    <input type="number" class="form-control col-md-3" name="minimum" id="minimum" value="{{old('minimum')}}" step="0.01"/>
+                                    <span class="input-group-text">元</span>
+                                </div>
+                                <span class="text-help"> 当支付金额超过N值时,才能使用本优惠劵;不设置/0,即为无限制 </span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="used">个人限用</label>
+                            <div class="col-md-10">
+                                <div class="input-group">
+                                    <input type="number" class="form-control col-md-3" name="used" id="used" value="{{old('used')}}" step="1"/>
+                                    <span class="input-group-text">次</span>
+                                </div>
+                                <span class="text-help"> 符合条件的用户可以使用本券N次;不设置/0,即为无限制 </span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label for="levels" class="col-md-2 col-form-label">等级限定</label>
+                            <div class="col-md-10">
+                                <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control show-tick" id="levels" name="levels[]"
+                                        multiple>
+                                    @foreach($levels as $key => $level)
+                                        <option value="{{$key}}">{{$level}}</option>
+                                    @endforeach
+                                </select>
+                                <span class="text-help"> 用户等级在选定等级内,方可使用本券</span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label for="groups" class="col-md-2 col-form-label">分组限定</label>
+                            <div class="col-md-10">
+                                <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control show-tick" id="groups" name="groups[]"
+                                        multiple>
+                                    @foreach($userGroups as $key => $group)
+                                        <option value="{{$key}}">{{$group}}</option>
+                                    @endforeach
+                                </select>
+                                <span class="text-help"> 选定的用户分组,方可使用本券</span>
                             </div>
                             </div>
-                            <span class="text-help"> 当支付金额超过N值时,才能使用本优惠劵;不设置/0,即为无限制 </span>
                         </div>
                         </div>
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="users_whitelist">专属用户</label>
+                            <div class="col-md-10">
+                                <input type="text" class="form-control col-md-6" data-plugin="tokenfield" id="users_whitelist" name="users_whitelist"
+                                       value="{{old('users_whitelist')}}" placeholder="输入用户ID, 再回车"/>
+                                <span class="text-help"> 涉及用户均可使用本券,留空为不使用此条件</span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="users_blacklist">禁用用户</label>
+                            <div class="col-md-10">
+                                <input type="text" class="form-control col-md-6" data-plugin="tokenfield" id="users_blacklist" name="users_blacklist"
+                                       value="{{old('users_blacklist')}}" placeholder="输入用户ID, 再回车"/>
+                                <span class="text-help"> 涉及用户均不可使用本券,空为不使用此条件</span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="services_whitelist">许可商品</label>
+                            <div class="col-md-10">
+                                <input type="text" class="form-control col-md-4" data-plugin="tokenfield" id="services_whitelist" name="services_whitelist"
+                                       value="{{old('services_whitelist')}}" placeholder="输入商品ID, 再回车"/>
+                                <span class="text-help"> 涉及商品方可使用本券,留空为不使用此条件</span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="services_blacklist">禁用商品</label>
+                            <div class="col-md-10">
+                                <input type="text" class="form-control col-md-4" data-plugin="tokenfield" id="services_blacklist" name="services_blacklist"
+                                       value="{{old('services_blacklist')}}" placeholder="输入商品ID, 再回车"/>
+                                <span class="text-help"> 涉及商品不可使用本券,留空为不使用此条件</span>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label for="newbie" class="col-md-2 col-form-label">新人专属</label>
+                            <div class="col-md-10">
+                                <ul class="list-unstyled">
+                                    <li class="list-group-item p-0">
+                                        <div class="checkbox-custom checkbox-primary">
+                                            <input type="checkbox" id="coupon" name="coupon" {{ old('coupon') ? 'checked' : '' }}/>
+                                            <label for="coupon">首次用任意券</label>
+                                        </div>
+                                    </li>
+                                    <li class="list-group-item p-0">
+                                        <div class="checkbox-custom checkbox-primary">
+                                            <input type="checkbox" id="order" name="order" {{ old('order') ? 'checked' : '' }}/>
+                                            <label for="order">首单</label>
+                                        </div>
+                                    </li>
+                                    <li class="list-group-item pb-0 pl-0">
+                                        <div class="input-group">
+                                            <div class="input-group-prepend">
+                                                <label class="input-group-text" for="days">创号</label>
+                                            </div>
+                                            <input type="number" class="form-control col-md-3" name="days" id="days" value="{{old('days')}}"/>
+                                            <div class="input-group-append">
+                                                <span class="input-group-text">天</span>
+                                            </div>
+                                        </div>
+                                    </li>
+                                </ul>
+                                <span class="text-help"> 本项各条件为 <strong>并且</strong> 关系,请自行搭配使用 </span>
+                            </div>
+                        </div>
+                        <hr>
                     </div>
                     </div>
                     <div class="form-group row">
                     <div class="form-group row">
                         <label class="col-md-2 col-form-label" for="num">数量</label>
                         <label class="col-md-2 col-form-label" for="num">数量</label>
@@ -119,38 +231,41 @@
     </div>
     </div>
 @endsection
 @endsection
 @section('javascript')
 @section('javascript')
-    <script src="/assets/global/vendor/bootstrap-datepicker/bootstrap-datepicker.min.js"></script>
     <script src="/assets/global/vendor/dropify/dropify.min.js"></script>
     <script src="/assets/global/vendor/dropify/dropify.min.js"></script>
-    <script src="/assets/global/js/Plugin/bootstrap-datepicker.js"></script>
+    <script src="/assets/global/vendor/bootstrap-select/bootstrap-select.min.js"></script>
+    <script src="/assets/global/vendor/bootstrap-datepicker/bootstrap-datepicker.min.js"></script>
+    <script src="/assets/global/vendor/bootstrap-tokenfield/bootstrap-tokenfield.min.js"></script>
     <script src="/assets/global/js/Plugin/dropify.js"></script>
     <script src="/assets/global/js/Plugin/dropify.js"></script>
+    <script src="/assets/global/js/Plugin/bootstrap-select.js"></script>
+    <script src="/assets/global/js/Plugin/bootstrap-datepicker.js"></script>
+    <script src="/assets/global/js/Plugin/bootstrap-tokenfield.js"></script>
     <script>
     <script>
-        @if(old('type'))
+        @if(old())
         $(document).ready(function() {
         $(document).ready(function() {
-            $("input[name='type'][value='{{old('type')}}']").click();
+          $("input[name='type'][value='{{old('type')}}']").click();
+          $('#levels').selectpicker('val', @json(old('levels')));
+          $('#groups').selectpicker('val', @json(old('groups')));
         });
         });
         @endif
         @endif
 
 
         $('.input-daterange>input').datepicker({
         $('.input-daterange>input').datepicker({
-            format: 'yyyy-mm-dd',
+          format: 'yyyy-mm-dd',
         });
         });
 
 
         $('input[name=\'type\']').change(function() {
         $('input[name=\'type\']').change(function() {
-            if ($(this).val() === '2') {
-                $('#rule').attr('required', true);
-                $('.discount').show();
-                $('.usage').show();
-                $('.amount').hide();
-            } else if ($(this).val() === '3') {
-                $('#rule').attr('required', false);
-                $('.discount').hide();
-                $('.usage').hide();
-                $('.amount').show();
-            } else {
-                $('#rule').attr('required', true);
-                $('.discount').hide();
-                $('.usage').show();
-                $('.amount').show();
-            }
+          if ($(this).val() === '2') {
+            $('.discount').show();
+            $('.usage').show();
+            $('#amount').hide();
+          } else if ($(this).val() === '3') {
+            $('.discount').hide();
+            $('.usage').hide();
+            $('#amount').show();
+          } else {
+            $('.discount').hide();
+            $('.usage').show();
+            $('#amount').show();
+          }
         });
         });
     </script>
     </script>
 @endsection
 @endsection

+ 66 - 69
resources/views/admin/coupon/index.blade.php

@@ -52,6 +52,7 @@
                         <th> 券码</th>
                         <th> 券码</th>
                         <th> 图片</th>
                         <th> 图片</th>
                         <th> 类型</th>
                         <th> 类型</th>
+                        <th> 权重</th>
                         <th> 使用次数</th>
                         <th> 使用次数</th>
                         <th> 优惠</th>
                         <th> 优惠</th>
                         <th> 有效期</th>
                         <th> 有效期</th>
@@ -67,36 +68,32 @@
                             <td> {{$coupon->sn}} </td>
                             <td> {{$coupon->sn}} </td>
                             <td> @if($coupon->logo) <img src="{{asset($coupon->logo)}}" class="h-50" alt="优惠码logo"/> @endif </td>
                             <td> @if($coupon->logo) <img src="{{asset($coupon->logo)}}" class="h-50" alt="优惠码logo"/> @endif </td>
                             <td>
                             <td>
-                                @if($coupon->type === 1)
-                                    抵用券
-                                @elseif($coupon->type === 2)
-                                    折扣券
-                                @else
-                                    充值券
-                                @endif
+                                {{ ['未知卡券','抵用券','折扣券','充值券'][$coupon->type] }}
                             </td>
                             </td>
+                            <td> {{$coupon->priority}} </td>
                             <td> {{$coupon->type === 3 ? '一次性' : ($coupon->usable_times ?? '无限制')}} </td>
                             <td> {{$coupon->type === 3 ? '一次性' : ($coupon->usable_times ?? '无限制')}} </td>
                             <td>
                             <td>
-                                {{$coupon->value}}@if($coupon->type === 2)%@else元@endif
+                                {{($coupon->type === 2 ?'减 ':'抵 ').$coupon->value.($coupon->type === 2 ?' %':' 元')}}
                             </td>
                             </td>
                             <td> {{$coupon->start_time}} ~ {{$coupon->end_time}} </td>
                             <td> {{$coupon->start_time}} ~ {{$coupon->end_time}} </td>
                             <td>
                             <td>
-                                @if($coupon->status === 1)
-                                    <span class="badge badge-lg badge-default"> 已使用 </span>
-                                @elseif ($coupon->status === 2)
-                                    <span class="badge badge-lg badge-default"> 已失效 </span>
-                                @else
-                                    <span class="badge badge-lg badge-success"> 生效中 </span>
-                                @endif
+                                <span class="badge badge-lg @if($coupon->status) badge-default @else badge-success @endif">  {{['生效中','已使用','已失效'][$coupon->status]}} </span>
                             </td>
                             </td>
                             <td>
                             <td>
-                                @if($coupon->status !== 1)
-                                    @can('admin.coupon.destroy')
-                                        <button class="btn btn-danger" onclick="delCoupon('{{$coupon->id}}','{{$coupon->name}}')">
-                                            <i class="icon wb-close"></i>
-                                        </button>
+                                <div class="btn-group">
+                                    @can('admin.coupon.show')
+                                        <a class="btn btn-info" href="{{route('admin.coupon.show', $coupon)}}" target="_blank">
+                                            <i class="icon wb-eye"></i>
+                                        </a>
                                     @endcan
                                     @endcan
-                                @endif
+                                    @if($coupon->status !== 1)
+                                        @can('admin.coupon.destroy')
+                                            <button class="btn btn-danger" onclick="delCoupon('{{$coupon->id}}','{{$coupon->name}}')">
+                                                <i class="icon wb-close"></i>
+                                            </button>
+                                        @endcan
+                                    @endif
+                                </div>
                             </td>
                             </td>
                         </tr>
                         </tr>
                     @endforeach
                     @endforeach
@@ -122,59 +119,59 @@
     <script src="/assets/global/vendor/bootstrap-table/bootstrap-table.min.js"></script>
     <script src="/assets/global/vendor/bootstrap-table/bootstrap-table.min.js"></script>
     <script src="/assets/global/vendor/bootstrap-table/extensions/mobile/bootstrap-table-mobile.min.js"></script>
     <script src="/assets/global/vendor/bootstrap-table/extensions/mobile/bootstrap-table-mobile.min.js"></script>
     <script>
     <script>
-        $(document).ready(function() {
-            $('#type').val({{Request::query('type')}});
-            $('#status').val({{Request::query('status')}});
+      $(document).ready(function() {
+        $('#type').val({{Request::query('type')}});
+        $('#status').val({{Request::query('status')}});
 
 
-            $('select').on('change', function() { this.form.submit(); });
-        });
+        $('select').on('change', function() { this.form.submit(); });
+      });
 
 
-        @can('admin.coupon.export')
-        // 批量导出卡券
-        function exportCoupon() {
-            swal.fire({
-                title: '卡券导出',
-                text: '确定导出所有卡券吗?',
-                icon: 'question',
-                showCancelButton: true,
-                cancelButtonText: '{{trans('common.close')}}',
-                confirmButtonText: '{{trans('common.confirm')}}',
-            }).then((result) => {
-                if (result.value) {
-                    window.location.href = '{{route('admin.coupon.export')}}';
-                }
-            });
-        }
-        @endcan
+      @can('admin.coupon.export')
+      // 批量导出卡券
+      function exportCoupon() {
+        swal.fire({
+          title: '卡券导出',
+          text: '确定导出所有卡券吗?',
+          icon: 'question',
+          showCancelButton: true,
+          cancelButtonText: '{{trans('common.close')}}',
+          confirmButtonText: '{{trans('common.confirm')}}',
+        }).then((result) => {
+          if (result.value) {
+            window.location.href = '{{route('admin.coupon.export')}}';
+          }
+        });
+      }
+      @endcan
 
 
-        @can('admin.coupon.destroy')
-        // 删除卡券
-        function delCoupon(id, name) {
-            swal.fire({
-                title: '确定删除卡券 【' + name + '】 吗?',
-                icon: 'question',
-                allowEnterKey: false,
-                showCancelButton: true,
-                cancelButtonText: '{{trans('common.close')}}',
-                confirmButtonText: '{{trans('common.confirm')}}',
-            }).then((result) => {
-                if (result.value) {
-                    $.ajax({
-                        method: 'DELETE',
-                        url: '{{route('admin.coupon.destroy', '')}}/' + id,
-                        data: {_token: '{{csrf_token()}}'},
-                        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());
-                            }
-                        },
-                    });
+      @can('admin.coupon.destroy')
+      // 删除卡券
+      function delCoupon(id, name) {
+        swal.fire({
+          title: '确定删除卡券 【' + name + '】 吗?',
+          icon: 'question',
+          allowEnterKey: false,
+          showCancelButton: true,
+          cancelButtonText: '{{trans('common.close')}}',
+          confirmButtonText: '{{trans('common.confirm')}}',
+        }).then((result) => {
+          if (result.value) {
+            $.ajax({
+              method: 'DELETE',
+              url: '{{route('admin.coupon.destroy', '')}}/' + id,
+              data: {_token: '{{csrf_token()}}'},
+              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());
                 }
                 }
+              },
             });
             });
-        }
+          }
+        });
+      }
         @endcan
         @endcan
     </script>
     </script>
 @endsection
 @endsection

+ 234 - 0
resources/views/admin/coupon/show.blade.php

@@ -0,0 +1,234 @@
+@extends('admin.layouts')
+@section('css')
+    <link href="/assets/global/vendor/bootstrap-select/bootstrap-select.min.css" rel="stylesheet">
+    <link href="/assets/global/vendor/bootstrap-tokenfield/bootstrap-tokenfield.min.css" rel="stylesheet">
+    <style>
+        .text-fit {
+            width: fit-content;
+            width: -moz-fit-content;
+        }
+    </style>
+@endsection
+@section('content')
+    <div class="page-content container">
+        <div class="panel">
+            <div class="panel-heading">
+                <h1 class="panel-title">卡券信息</h1>
+                <div class="panel-actions">
+                    <a href="{{route('admin.coupon.index')}}" class="btn btn-danger">返 回</a>
+                </div>
+            </div>
+            <div class="panel-body">
+                <div class="form-group row">
+                    <label class="col-md-2 col-form-label" for="name">卡券名称</label>
+                    <div class="col-md-10">
+                        <input class="form-control text-fit" id="name" value="{{$coupon->name}}" disabled/>
+                    </div>
+                </div>
+                <div class="form-group row">
+                    <label class="col-md-2 col-form-label" for="sn">使用券码</label>
+                    <div class="col-md-10">
+                        <input type="text" class="form-control text-fit" id="sn" value="{{$coupon->sn}}" disabled/>
+                    </div>
+                </div>
+                @if($coupon->logo)
+                    <div class="form-group row">
+                        <span class="col-md-2 col-form-label">卡券图片</span>
+                        <div class="col-md-10">
+                            <img src="{{asset($coupon->logo)}}" class="h-100" alt="优惠码logo"/>
+                        </div>
+                    </div>
+                @endif
+                <div class="form-group row">
+                    <span class="col-md-2 col-form-label">类型</span>
+                    <div class="col-md-10 align-items-center">
+                        <div class="radio-custom radio-primary radio-inline">
+                            <input type="radio" id="voucher" checked/>
+                            <label for="voucher">
+                                {{  ['未知卡券','抵用券','折扣券','充值券'][$coupon->type] }}
+                            </label>
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group row">
+                    <label class="col-md-2 col-form-label" for="value">优惠额度</label>
+                    <div class="col-md-10">
+                        <p class="form-control text-fit">
+                            @switch ($coupon->type)
+                                @case(1)
+                                    抵用 <code>{{$coupon->value}}</code> 元
+                                    @break
+                                @case(2)
+                                    减 <code>{{$coupon->value}}</code> %
+                                    @break
+                                @case(3)
+                                    充值 <code>{{$coupon->value}}</code> 元
+                                    @break
+                                @default
+                                    未知卡券
+                            @endswitch
+                        </p>
+                    </div>
+                </div>
+                @isset($coupon->priority)
+                    <div class="form-group row">
+                        <span class="col-md-2 col-form-label"> 权 重 </span>
+                        <div class="col-md-10">
+                            <span class="form-control text-fit"> {{$coupon->priority}} </span>
+                        </div>
+                    </div>
+                @endisset
+                @isset($coupon->usable_times)
+                    <div class="form-group row">
+                        <span class="col-md-2 col-form-label">剩余使用次数</span>
+                        <div class="col-md-10">
+                            <span class="form-control text-fit"><code>{{$coupon->usable_times}}</code> 次</span>
+                        </div>
+                    </div>
+                @endisset
+                @if(!empty($coupon->limit))
+                    <hr>
+                    @isset($coupon->limit['minimum'])
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="minimum">满减条件</label>
+                            <div class="col-md-10">
+                                <p class="form-control text-fit">当支付金额超过<strong> {{$coupon->limit['minimum']}}元</strong> 时,才能使用本优惠劵</p>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['used'])
+                        <div class="form-group row">
+                            <span class="col-md-2 col-form-label">个人限用</span>
+                            <div class="col-md-10">
+                                <p class="form-control text-fit">符合条件的用户可以使用本券 <strong>{{$coupon->limit['used']}}次</strong></p>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['users']['levels'])
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="levels">等级限定</label>
+                            <div class="col-md-10">
+                                <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control show-tick" id="levels" multiple disabled>
+                                    @foreach($levels as $key => $level)
+                                        <option value="{{$key}}">{{$level}}</option>
+                                    @endforeach
+                                </select>
+                                <span class="text-help"> 以上用户等级,方可使用本券</span>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['users']['groups'])
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="groups">分组限定</label>
+                            <div class="col-md-10">
+                                <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control show-tick" id="groups" multiple disabled>
+                                    @foreach($userGroups as $key => $group)
+                                        <option value="{{$key}}">{{$group}}</option>
+                                    @endforeach
+                                </select>
+                                <span class="text-help"> 以上用户分组,方可使用本券</span>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['users']['white'])
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="users_whitelist">专属用户</label>
+                            <div class="col-md-6">
+                                <input class="form-control" data-plugin="tokenfield" id="users_whitelist" value="{{ implode(',', $coupon->limit['users']['white']) }}"
+                                       disabled/>
+                                <span class="text-help"> 以上用户均可使用本券</span>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['users']['black'])
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="users_blacklist">禁用用户</label>
+                            <div class="col-md-6">
+                                <input class="form-control" data-plugin="tokenfield" id="users_blacklist" value="{{ implode(',', $coupon->limit['users']['black']) }}"
+                                       disabled/>
+                                <span class="text-help"> 以上用户均不可使用本券</span>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['services']['white'])
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="services_whitelist">许可商品</label>
+                            <div class="col-md-6">
+                                <input class="form-control" data-plugin="tokenfield" id="services_whitelist" value="{{ implode(',', $coupon->limit['services']['white']) }}"
+                                       disabled/>
+                                <span class="text-help"> 以上商品方可使用本券</span>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['services']['black'])
+                        <div class="form-group row">
+                            <label class="col-md-2 col-form-label" for="services_blacklist">禁用商品</label>
+                            <div class="col-md-6">
+                                <input class="form-control" data-plugin="tokenfield" id="services_blacklist" value="{{ implode(',', $coupon->limit['services']['black']) }}"
+                                       disabled/>
+                                <span class="text-help"> 以上商品不可使用本券</span>
+                            </div>
+                        </div>
+                    @endisset
+                    @isset($coupon->limit['users']['newbie'])
+                        <div class="form-group row">
+                            <label for="newbie" class="col-md-2 col-form-label">新人专属</label>
+                            <div class="col-md-10">
+                                <ul class="list-unstyled">
+                                    <li class="list-group-item p-0">
+                                        <div class="checkbox-custom checkbox-primary">
+                                            <input type="checkbox" id="coupon" {{ isset($coupon->limit['users']['newbie']['coupon']) ? 'checked' : '' }} disabled/>
+                                            <label for="coupon">首次用任意券</label>
+                                        </div>
+                                    </li>
+                                    <li class="list-group-item p-0">
+                                        <div class="checkbox-custom checkbox-primary">
+                                            <input type="checkbox" id="order" {{ isset($coupon->limit['users']['newbie']['order']) ? 'checked' : '' }} disabled/>
+                                            <label for="order">首单</label>
+                                        </div>
+                                    </li>
+                                    @isset($coupon->limit['users']['newbie']['days'])
+                                        <li class="list-group-item p-0">
+                                            <span class="form-control text-fit">且 创号 <code>{{$coupon->limit['users']['newbie']['days']}}</code> 天</span>
+                                        </li>
+                                    @endisset
+                                </ul>
+                            </div>
+                        </div>
+                    @endisset
+                    <hr>
+                @endif
+                <div class="form-group row">
+                    <label class="col-md-2 col-form-label">有效期</label>
+                    <div class="col-md-6 input-group">
+                        <div class="input-group-prepend">
+                            <span class="input-group-text"><i class="icon wb-calendar" aria-hidden="true"></i></span>
+                        </div>
+                        <span class="form-control"> {{$coupon->start_time}} </span>
+                        <div class="input-group-prepend">
+                            <span class="input-group-text">至</span>
+                        </div>
+                        <span class="form-control"> {{$coupon->end_time}} </span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection
+@section('javascript')
+    <script src="/assets/global/vendor/bootstrap-select/bootstrap-select.min.js"></script>
+    <script src="/assets/global/vendor/bootstrap-tokenfield/bootstrap-tokenfield.min.js"></script>
+    <script src="/assets/global/js/Plugin/bootstrap-select.js"></script>
+    <script src="/assets/global/js/Plugin/bootstrap-tokenfield.js"></script>
+    <script>
+      $(document).ready(function() {
+          @isset($coupon->limit['users']['levels'])
+          $('#levels').selectpicker('val', @json($coupon->limit['users']['levels']));
+          @endisset
+
+          @isset($coupon->limit['users']['groups'])
+          $('#groups').selectpicker('val', @json($coupon->limit['users']['groups']));
+          @endisset
+      });
+    </script>
+@endsection

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

@@ -77,9 +77,9 @@
             const goods_price = '{{$goods->price}}';
             const goods_price = '{{$goods->price}}';
             $.ajax({
             $.ajax({
                 method: 'POST',
                 method: 'POST',
-                url: '{{route('redeemCoupon')}}',
+                url: '{{route('redeemCoupon', $goods)}}',
                 dataType: 'json',
                 dataType: 'json',
-                data: {_token: '{{csrf_token()}}', coupon_sn: coupon_sn, price: '{{$goods->price}}'},
+                data: {_token: '{{csrf_token()}}', coupon_sn: coupon_sn},
                 success: function(ret) {
                 success: function(ret) {
                     $('.input-group-prepend').remove();
                     $('.input-group-prepend').remove();
                     if (ret.status === 'success') {
                     if (ret.status === 'success') {

+ 1 - 1
routes/admin.php

@@ -59,7 +59,7 @@ Route::prefix('admin')->name('admin.')->group(function () {
         });
         });
 
 
         Route::resource('goods', 'ShopController')->except('show'); // 商品管理
         Route::resource('goods', 'ShopController')->except('show'); // 商品管理
-        Route::resource('coupon', 'CouponController')->except('show', 'edit', 'update'); // 优惠券
+        Route::resource('coupon', 'CouponController')->except('edit', 'update'); // 优惠券
         Route::get('coupon/export', 'CouponController@exportCoupon')->name('coupon.export'); // 导出优惠券
         Route::get('coupon/export', 'CouponController@exportCoupon')->name('coupon.export'); // 导出优惠券
 
 
         Route::prefix('aff')->name('aff.')->group(function () {
         Route::prefix('aff')->name('aff.')->group(function () {

+ 1 - 1
routes/user.php

@@ -14,8 +14,8 @@ Route::get('invoices', 'UserController@invoices')->name('invoice'); // 订单列
 Route::post('closePlan', 'UserController@closePlan')->name('cancelPlan'); // 激活预支付套餐
 Route::post('closePlan', 'UserController@closePlan')->name('cancelPlan'); // 激活预支付套餐
 Route::get('invoice/{sn}', 'UserController@invoiceDetail')->name('invoiceInfo'); // 订单明细
 Route::get('invoice/{sn}', 'UserController@invoiceDetail')->name('invoiceInfo'); // 订单明细
 Route::post('resetUserTraffic', 'UserController@resetUserTraffic')->name('resetTraffic'); // 重置用户流量
 Route::post('resetUserTraffic', 'UserController@resetUserTraffic')->name('resetTraffic'); // 重置用户流量
+Route::post('buy/{good}/redeem', 'UserController@redeemCoupon')->name('redeemCoupon'); // 使用优惠券
 Route::get('buy/{good}', 'UserController@buy')->name('buy'); // 购买商品
 Route::get('buy/{good}', 'UserController@buy')->name('buy'); // 购买商品
-Route::post('redeemCoupon', 'UserController@redeemCoupon')->name('redeemCoupon'); // 使用优惠券
 Route::get('invite', 'UserController@invite')->name('invite'); // 邀请码
 Route::get('invite', 'UserController@invite')->name('invite'); // 邀请码
 Route::post('makeInvite', 'UserController@makeInvite')->name('createInvite'); // 生成邀请码
 Route::post('makeInvite', 'UserController@makeInvite')->name('createInvite'); // 生成邀请码
 Route::match(['get', 'post'], 'profile', 'UserController@profile')->name('profile'); // 修改个人信息
 Route::match(['get', 'post'], 'profile', 'UserController@profile')->name('profile'); // 修改个人信息