Explorar o código

积分历史记录

bianjunhui hai 3 semanas
pai
achega
2f95968a96

+ 27 - 16
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/CustomerController.php

@@ -24,11 +24,28 @@ class CustomerController extends Controller
     /**
      * Display a listing of customers with reward points
      */
-     public function index()
+    public function index()
     {
-        $customers = RewardPointCustomer::with('customer')
-            ->orderBy('mw_reward_point', 'desc')
-            ->paginate(15);
+        $query = RewardPointCustomer::with('customer');
+
+        // 邮箱筛选
+        if ($email = request('email')) {
+            $query->whereHas('customer', function ($q) use ($email) {
+                $q->where('email', 'like', "%{$email}%");
+            });
+        }
+
+        // 积分最小值筛选
+        if ($minPoints = request('min_points')) {
+            $query->where('mw_reward_point', '>=', $minPoints);
+        }
+
+        // 积分最大值筛选
+        if ($maxPoints = request('max_points')) {
+            $query->where('mw_reward_point', '<=', $maxPoints);
+        }
+
+        $customers = $query->orderBy('mw_reward_point', 'desc')->paginate(15);
 
         $totalPoints = RewardPointCustomer::sum('mw_reward_point');
         $totalCustomers = RewardPointCustomer::count();
@@ -62,7 +79,7 @@ class CustomerController extends Controller
 
     public function adjustPointsForm()
     {
-         
+
         $view = isset($this->_config['view']) ? $this->_config['view'] : 'rewardpoints::admin.customers.adjust-points';
         return view($view);
     }
@@ -87,7 +104,7 @@ class CustomerController extends Controller
 
             // 首先尝试按邮箱查找用户
             $customer = Customer::where('email', $identifier)->first();
-            
+
             // 如果没找到邮箱,尝试按ID查找
             if (!$customer) {
                 $customerId = (int)$identifier;
@@ -105,12 +122,12 @@ class CustomerController extends Controller
             if ($action === 'add') {
                 $result = $this->rewardPointRepository->addPoints(
                     $customer->id,
-                    0, // 0 for admin action
+                    99, // 99 for admin action
                     $points,
                     null,
                     "Admin: " . $reason
                 );
-                
+
                 if ($result) {
                     session()->flash('success', "Successfully added {$points} points to customer {$customer->email}.");
                 } else {
@@ -123,7 +140,7 @@ class CustomerController extends Controller
                     null,
                     "Admin: " . $reason
                 );
-                
+
                 if ($result) {
                     session()->flash('success', "Successfully deducted {$points} points from customer {$customer->email}.");
                 } else {
@@ -163,7 +180,7 @@ class CustomerController extends Controller
                 if ($action === 'add') {
                     $result = $this->rewardPointRepository->addPoints(
                         $customerId,
-                        0, // 0 for admin action
+                        99, // 99 for admin action
                         $points,
                         null,
                         "Admin bulk: " . $reason
@@ -241,9 +258,6 @@ class CustomerController extends Controller
                 'Customer Email',
                 'Customer Name',
                 'Reward Points',
-                'Friend ID',
-                'Subscribed Balance Update',
-                'Subscribed Point Expiration',
                 'Last Checkout'
             ]);
 
@@ -254,9 +268,6 @@ class CustomerController extends Controller
                     $customer->customer->email ?? '',
                     $customer->customer->first_name . ' ' . $customer->customer->last_name,
                     $customer->mw_reward_point,
-                    $customer->mw_friend_id,
-                    $customer->subscribed_balance_update ? 'Yes' : 'No',
-                    $customer->subscribed_point_expiration ? 'Yes' : 'No',
                     $customer->last_checkout
                 ]);
             }

+ 4 - 2
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/ReportController.php

@@ -15,7 +15,7 @@ class ReportController extends Controller
 
     public function __construct()
     {
-        $this->_config = request('_config');
+        $this->_config = request('_config') ?: [];
     }
 
     /**
@@ -28,7 +28,9 @@ class ReportController extends Controller
 
         $data = $this->getReportData($startDate, $endDate);
 
-        return view($this->_config['view'], compact('data', 'startDate', 'endDate'));
+        $view = $this->_config['view'] ?? 'rewardpoints::admin.reports.index';
+
+        return view($view, compact('data', 'startDate', 'endDate'));
     }
 
     /**

+ 202 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/TransactionController.php

@@ -0,0 +1,202 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers\Admin;
+
+use Illuminate\Http\Request;
+use Webkul\Admin\Http\Controllers\Controller;
+use Longyi\RewardPoints\Models\RewardPointHistory;
+use Longyi\RewardPoints\Models\RewardPointCustomer;
+use Carbon\Carbon;
+use Maatwebsite\Excel\Facades\Excel;
+use Longyi\RewardPoints\Exports\TransactionsExport;
+
+class TransactionController extends Controller
+{
+    protected $_config;
+
+    public function __construct()
+    {
+        $this->_config = request('_config') ?: [];
+    }
+
+    public function earnedIndex()
+    {
+        request()->merge(['view_type' => 'earned']);
+        return $this->index();
+    }
+
+    /**
+     * Display a listing of reward point transactions
+     */
+    public function index()
+    {
+        $startDate = request()->get('start_date', Carbon::now()->subDays(30)->format('Y-m-d'));
+        $endDate = request()->get('end_date', Carbon::now()->format('Y-m-d'));
+        $customerEmail = request()->get('customer_email');
+        $transactionType = request()->get('transaction_type');
+        $amountType = request()->get('amount_type'); // all, earned, redeemed
+        $viewType = request()->get('view_type', 'all'); // all or earned
+
+        $query = RewardPointHistory::with('customer');
+
+        // 日期筛选
+        if ($startDate && $endDate) {
+            $startDateTime = Carbon::parse($startDate)->startOfDay();
+            $endDateTime = Carbon::parse($endDate)->endOfDay();
+            $query->whereBetween('transaction_time', [$startDateTime, $endDateTime]);
+        }
+
+        // 客户邮箱筛选
+        if ($customerEmail) {
+            $query->whereHas('customer', function ($q) use ($customerEmail) {
+                $q->where('email', 'like', "%{$customerEmail}%");
+            });
+        }
+
+        // 交易类型筛选
+        if ($transactionType !== null && $transactionType !== '') {
+            $query->where('type_of_transaction', $transactionType);
+        }
+
+        // 根据视图类型筛选(仅获取或全部)
+        if ($viewType === 'earned') {
+            $query->where('amount', '>', 0);
+        } elseif ($amountType === 'earned') {
+            $query->where('amount', '>', 0);
+        } elseif ($amountType === 'redeemed') {
+            $query->where('amount', '<', 0);
+        }
+
+        $transactions = $query->orderBy('transaction_time', 'desc')->paginate(20);
+
+        // 统计数据 - 根据视图类型调整
+        if ($viewType === 'earned') {
+            $totalEarned = (clone $query)->sum('amount');
+            $totalRedeemed = 0;
+            $totalTransactions = (clone $query)->count();
+        } else {
+            $totalEarned = (clone $query)->where('amount', '>', 0)->sum('amount');
+            $totalRedeemed = abs((clone $query)->where('amount', '<', 0)->sum('amount'));
+            $totalTransactions = (clone $query)->count();
+        }
+
+        $view = isset($this->_config['view']) ? $this->_config['view'] : 'rewardpoints::admin.transactions.index';
+
+        return view($view, compact(
+            'transactions',
+            'startDate',
+            'endDate',
+            'customerEmail',
+            'transactionType',
+            'amountType',
+            'viewType',
+            'totalEarned',
+            'totalRedeemed',
+            'totalTransactions'
+        ));
+    }
+// ... existing code ...
+
+
+    /**
+     * Export transactions
+     */
+    public function export(Request $request)
+    {
+        $startDate = $request->get('start_date', Carbon::now()->subDays(30)->format('Y-m-d'));
+        $endDate = $request->get('end_date', Carbon::now()->format('Y-m-d'));
+        $customerEmail = $request->get('customer_email');
+        $transactionType = $request->get('transaction_type');
+        $amountType = $request->get('amount_type');
+
+        $query = RewardPointHistory::with('customer');
+
+        // 应用相同的筛选条件
+        if ($startDate && $endDate) {
+            $startDateTime = Carbon::parse($startDate)->startOfDay();
+            $endDateTime = Carbon::parse($endDate)->endOfDay();
+            $query->whereBetween('transaction_time', [$startDateTime, $endDateTime]);
+        }
+
+        if ($customerEmail) {
+            $query->whereHas('customer', function ($q) use ($customerEmail) {
+                $q->where('email', 'like', "%{$customerEmail}%");
+            });
+        }
+
+        if ($transactionType !== null && $transactionType !== '') {
+            $query->where('type_of_transaction', $transactionType);
+        }
+
+        if ($amountType === 'earned') {
+            $query->where('amount', '>', 0);
+        } elseif ($amountType === 'redeemed') {
+            $query->where('amount', '<', 0);
+        }
+
+        $transactions = $query->orderBy('transaction_time', 'desc')->get();
+
+        $filename = "reward_point_transactions_" . date('Y-m-d_His') . ".csv";
+
+        $headers = [
+            'Content-Type' => 'text/csv',
+            'Content-Disposition' => "attachment; filename={$filename}",
+        ];
+
+        $callback = function() use ($transactions) {
+            $file = fopen('php://output', 'w');
+
+            // CSV 头部
+            fputcsv($file, [
+                'ID',
+                'Customer ID',
+                'Customer Name',
+                'Customer Email',
+                'Transaction Type',
+                'Amount',
+                'Balance',
+                'Transaction Time',
+                'Description',
+                'Status'
+            ]);
+
+            $typeNames = [
+                1 => 'Order',
+                2 => 'Registration',
+                3 => 'Product Review',
+                4 => 'Daily Sign In',
+                5 => 'Referral',
+                6 => 'Birthday',
+                8 => 'Subscribe',
+                0 => 'Admin Adjustment',
+                99 => 'Admin Action'
+            ];
+
+            $statusNames = [
+                0 => 'Pending',
+                1 => 'Completed',
+                2 => 'Cancelled',
+                3 => 'Expired'
+            ];
+
+            foreach ($transactions as $transaction) {
+                fputcsv($file, [
+                    $transaction->history_id,
+                    $transaction->customer_id,
+                    $transaction->customer ? $transaction->customer->first_name . ' ' . $transaction->customer->last_name : 'N/A',
+                    $transaction->customer ? $transaction->customer->email : 'N/A',
+                    $typeNames[$transaction->type_of_transaction] ?? 'Unknown',
+                    $transaction->amount,
+                    $transaction->balance,
+                    $transaction->transaction_time,
+                    $transaction->transaction_detail,
+                    $statusNames[$transaction->status] ?? 'Unknown'
+                ]);
+            }
+
+            fclose($file);
+        };
+
+        return response()->stream($callback, 200, $headers);
+    }
+}

+ 5 - 1
packages/Longyi/RewardPoints/src/Models/RewardPointHistory.php

@@ -3,7 +3,7 @@
 namespace Longyi\RewardPoints\Models;
 
 use Illuminate\Database\Eloquent\Model;
-
+use Webkul\Customer\Models\Customer;
 class RewardPointHistory extends Model
 {
     protected $table = 'mw_reward_point_history';
@@ -42,4 +42,8 @@ class RewardPointHistory extends Model
     const STATUS_COMPLETED = 1;
     const STATUS_CANCELLED = 2;
     const STATUS_EXPIRED = 3;
+    public function customer()
+    {
+        return $this->belongsTo(Customer::class, 'customer_id');
+    }
 }

+ 49 - 7
packages/Longyi/RewardPoints/src/Resources/lang/en/rewardpoints.php

@@ -32,13 +32,18 @@ return [
         'expired' => 'Expired',
         'no-history' => 'No points history yet',
     ],
-    
+
     'admin' => [
         'reward-points' => 'Reward Points',
         'rules' => 'Rules',
         'customers' => 'Customers',
         'reports' => 'Reports',
         'add-rule' => 'Add Rule',
+        'transactions' => 'Transactions',
+        'earned-transactions' => 'Earned Transactions',
+        'view-all-transactions' => 'All Transactions',
+        'view-earned-only' => 'Earned Only',
+        'total' => 'Total',
         'edit-rule' => 'Edit Rule',
         'delete-rule' => 'Delete Rule',
         'rule-name' => 'Rule Name',
@@ -71,7 +76,6 @@ return [
         'end-date' => 'End Date',
         'points-earned' => 'Points Earned',
         'points-redeemed' => 'Points Redeemed',
-        'transactions' => 'Transactions',
         'title' => 'Customer Reward Points',
         'details-title' => ':name\'s Reward Points Details',
         'customer-info' => 'Customer Information',
@@ -87,14 +91,19 @@ return [
         'view' => 'View',
         'no-customers-found' => 'No customers found',
         'no-reward-points-records' => 'No reward points records available',
-        'transactions' => [
+
+        // 交易类型映射(使用字符串而不是数组)
+        'transaction-types' => [
             'order' => 'Order',
             'registration' => 'Registration',
-            'review' => 'Review',
-            'sign-in' => 'Sign In',
+            'review' => 'Product Review',
+            'sign-in' => 'Daily Sign In',
             'referral' => 'Referral',
             'birthday' => 'Birthday',
+            'share' => 'Share',
+            'subscribe' => 'Subscribe',
         ],
+
         'adjust-points-by-identifier' => 'Adjust Points by Email or ID',
         'identifier' => 'Identifier',
         'email-or-id' => 'Email or ID',
@@ -103,12 +112,45 @@ return [
         'enter-reason' => 'Enter reason for adjustment',
         'submit' => 'Submit',
         'action' => 'Action',
+
+        // 总交易数
+        'total-transactions' => 'Total Transactions',
+        'total-points-earned' => 'Total Points Earned',
+        'total-points-redeemed' => 'Total Points Redeemed',
+
+        // 搜索相关
+        'search' => 'Search',
+        'reset' => 'Reset',
+        'customer-email' => 'Customer Email',
+        'amount-type' => 'Amount Type',
+        'earned' => 'Earned',
+        'redeemed' => 'Redeemed',
+        'all' => 'All',
+        'all-types' => 'All Types',
+        'admin-adjustment' => 'Admin Adjustment',
+        'admin-action' => 'Admin Action',
+
+        // 表格表头
+        'id' => 'ID',
+        'customer' => 'Customer',
+        'amount' => 'Amount',
+        'date' => 'Date',
+        'description' => 'Description',
+        'balance' => 'Balance',
+
+        // 提示信息
+        'no-transactions-found' => 'No transactions found',
+        'try-adjusting-filters' => 'Try adjusting your filters',
+
+        // 其他
+        'points' => 'Points',
+        'transactions-count' => 'Transactions',
     ],
-    
+
     'validation' => [
         'points-required' => 'Points are required',
         'points-invalid' => 'Invalid points amount',
         'customer-not-found' => 'Customer not found',
         'rule-not-found' => 'Rule not found',
     ],
-];
+];

+ 74 - 17
packages/Longyi/RewardPoints/src/Resources/views/admin/customers/index.blade.php

@@ -17,25 +17,91 @@
 
         <div class="flex gap-x-2.5 items-center">
             {{-- 调整积分按钮 --}}
-            <a 
-                href="{{ route('admin.reward-points.customers.adjust-form') }}" 
+            <a
+                href="{{ route('admin.reward-points.customers.adjust-form') }}"
                 style="padding: 0.5rem 1rem; background-color: #10b981; color: white; font-weight: 500; border-radius: 0.5rem; text-decoration: none; transition: all 0.15s ease;"
                 onmouseover="this.style.backgroundColor='#059669'"
                 onmouseout="this.style.backgroundColor='#10b981'"
             >
                 + Adjust Points
             </a>
-            
+
             {{-- 导出按钮 --}}
-            <a 
-                href="{{ route('admin.reward-points.customers.export') }}" 
+            <a
+                href="{{ route('admin.reward-points.customers.export') }}"
                 class="secondary-button"
             >
                 Export
             </a>
         </div>
     </div>
+    {{-- 筛选查询条件 --}}
+    <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 p-4 mb-6">
+        <form method="GET" action="{{ route('admin.reward-points.customers.index') }}" class="grid grid-cols-1 md:grid-cols-4 gap-4">
+            {{-- 邮箱查询 --}}
+            <div>
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                    @lang('rewardpoints::rewardpoints.admin.email')
+                </label>
+                <input
+                    type="text"
+                    name="email"
+                    value="{{ request('email') }}"
+                    placeholder="@lang('Search by email')"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 积分最小值 --}}
+            <div>
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                    @lang('Min Points')
+                </label>
+                <input
+                    type="number"
+                    name="min_points"
+                    value="{{ request('min_points') }}"
+                    placeholder="0"
+                    min="0"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 积分最大值 --}}
+            <div>
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                    @lang('Max Points')
+                </label>
+                <input
+                    type="number"
+                    name="max_points"
+                    value="{{ request('max_points') }}"
+                    placeholder="10000"
+                    min="0"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
 
+            {{-- 操作按钮 --}}
+            <div class="flex items-end gap-2">
+                <button
+                    type="submit"
+                    class="primary-button flex-1"
+                >
+                    <span class="icon-search text-lg"></span>
+                    @lang('Search')
+                </button>
+
+                <a
+                    href="{{ route('admin.reward-points.customers.index') }}"
+                    class="secondary-button"
+                >
+                    <span class="icon-reset text-lg"></span>
+                    @lang('Reset')
+                </a>
+            </div>
+        </form>
+    </div>
     {{-- 统计卡片 --}}
     <div class="flex flex-wrap gap-4 mb-6">
         <div class="flex-1 min-w-[200px] bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/20 dark:to-blue-800/20 rounded-xl p-5 border border-blue-200 dark:border-blue-800">
@@ -53,7 +119,7 @@
                 </div>
             </div>
         </div>
-        
+
         <div class="flex-1 min-w-[200px] bg-gradient-to-br from-green-50 to-green-100 dark:from-green-900/20 dark:to-green-800/20 rounded-xl p-5 border border-green-200 dark:border-green-800">
             <div class="flex items-center justify-between">
                 <div>
@@ -69,7 +135,7 @@
                 </div>
             </div>
         </div>
-        
+
         <div class="flex-1 min-w-[200px] bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/20 rounded-xl p-5 border border-purple-200 dark:border-purple-800">
             <div class="flex items-center justify-between">
                 <div>
@@ -130,15 +196,6 @@
                                     <span class="text-xs text-gray-500">pts</span>
                                 </div>
                             </td>
-                            <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
-                                <a 
-                                    href="{{ route('admin.reward-points.customers.show', $customer->customer_id) }}" 
-                                    class="inline-flex items-center gap-1 px-3 py-1.5 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded-lg transition-colors"
-                                >
-                                    <span class="icon-eye text-sm"></span>
-                                    <span class="text-sm">@lang('rewardpoints::rewardpoints.admin.view')</span>
-                                </a>
-                            </td>
                         </tr>
                     @empty
                         <tr>
@@ -163,4 +220,4 @@
             </table>
         </div>
     </div>
-</x-admin::layouts>
+</x-admin::layouts>

+ 198 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/reports/index.blade.php

@@ -0,0 +1,198 @@
+<x-admin::layouts>
+    <x-slot:title>
+        @lang('Reward Points Reports')
+        </x-slot>
+
+        {{-- 页面标题 --}}
+        <div class="flex flex-wrap gap-4 justify-between items-center mb-6">
+            <div class="flex items-center gap-3">
+                <div class="icon-chart-bar text-2xl text-blue-500"></div>
+                <p class="text-2xl text-gray-800 dark:text-white font-bold">
+                    @lang('Reward Points Reports')
+                </p>
+            </div>
+
+            <div class="flex gap-x-2.5 items-center">
+                <a href="{{ route('admin.reward-points.reports.export', ['start_date' => $startDate, 'end_date' => $endDate]) }}"
+                   class="primary-button flex items-center gap-2">
+                    <span class="icon-download text-lg"></span>
+                    @lang('Export Report')
+                </a>
+            </div>
+        </div>
+
+        {{-- 日期筛选 --}}
+        <div class="mb-6 bg-white dark:bg-gray-900 rounded-lg shadow-sm p-4 border border-gray-200 dark:border-gray-800">
+            <form method="GET" action="{{ route('admin.reward-points.reports.index') }}" class="flex flex-wrap gap-4 items-end">
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                        @lang('Start Date')
+                    </label>
+                    <input
+                        type="date"
+                        name="start_date"
+                        value="{{ $startDate }}"
+                        class="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-gray-300"
+                    >
+                </div>
+
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                        @lang('End Date')
+                    </label>
+                    <input
+                        type="date"
+                        name="end_date"
+                        value="{{ $endDate }}"
+                        class="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-gray-300"
+                    >
+                </div>
+
+                <div>
+                    <button type="submit" class="primary-button">
+                        @lang('Filter')
+                    </button>
+                </div>
+            </form>
+        </div>
+
+        <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
+            {{-- 统计卡片 --}}
+            <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm p-6 border border-gray-200 dark:border-gray-800">
+                <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">@lang('Summary Statistics')</h3>
+                <div class="space-y-3">
+                    <div class="flex justify-between items-center">
+                        <span class="text-gray-600 dark:text-gray-400">@lang('Total Points Earned'):</span>
+                        <span class="text-2xl font-bold text-green-600 dark:text-green-500">{{ number_format($data['total_points_earned']) }}</span>
+                    </div>
+                    <div class="flex justify-between items-center">
+                        <span class="text-gray-600 dark:text-gray-400">@lang('Points Redeemed'):</span>
+                        <span class="text-2xl font-bold text-red-600 dark:text-red-500">{{ number_format($data['points_redeemed']) }}</span>
+                    </div>
+                    <div class="flex justify-between items-center">
+                        <span class="text-gray-600 dark:text-gray-400">@lang('Total Transactions'):</span>
+                        <span class="text-2xl font-bold text-blue-600 dark:text-blue-500">{{ number_format($data['total_transactions']) }}</span>
+                    </div>
+                    <div class="flex justify-between items-center">
+                        <span class="text-gray-600 dark:text-gray-400">@lang('Customers with Points'):</span>
+                        <span class="text-2xl font-bold text-purple-600 dark:text-purple-500">{{ number_format($data['total_customers']) }}</span>
+                    </div>
+                </div>
+            </div>
+
+            {{-- 积分类型分布 --}}
+            <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm p-6 border border-gray-200 dark:border-gray-800">
+                <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">@lang('Points by Type')</h3>
+                <div class="space-y-3">
+                    @php
+                        $typeNames = [
+                            1 => 'Order',
+                            2 => 'Registration',
+                            3 => 'Product Review',
+                            4 => 'Daily Sign In',
+                            5 => 'Referral',
+                            6 => 'Birthday',
+                            8 => 'Subscribe',
+                            0 => 'Admin Adjustment'
+                        ];
+                    @endphp
+
+                    @forelse($data['points_by_type'] as $item)
+                        <div class="flex justify-between items-center">
+                            <div>
+                                <span class="text-gray-700 dark:text-gray-300">{{ $typeNames[$item->type_of_transaction] ?? 'Unknown' }}</span>
+                                <span class="text-xs text-gray-500 ml-2">({{ number_format($item->total_transactions) }} @lang('transactions'))</span>
+                            </div>
+                            <span class="font-semibold text-gray-800 dark:text-white">{{ number_format($item->total_points) }}</span>
+                        </div>
+                    @empty
+                        <p class="text-gray-500 dark:text-gray-400 text-center py-4">@lang('No data available')</p>
+                    @endforelse
+                </div>
+            </div>
+        </div>
+
+        {{-- 每日签到统计 --}}
+        <div class="mt-6 bg-white dark:bg-gray-900 rounded-lg shadow-sm p-6 border border-gray-200 dark:border-gray-800">
+            <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">@lang('Daily Sign-In Statistics')</h3>
+            <div class="overflow-x-auto">
+                <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
+                    <thead class="bg-gray-50 dark:bg-gray-800">
+                    <tr>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">@lang('Date')</th>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">@lang('Total Sign-Ins')</th>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">@lang('Points Earned')</th>
+                    </tr>
+                    </thead>
+                    <tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800">
+                    @forelse($data['sign_ins'] as $sign)
+                        <tr>
+                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                                {{ \Carbon\Carbon::parse($sign->date)->format('Y-m-d') }}
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                                {{ number_format($sign->total_sign_ins) }}
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                                {{ number_format($sign->total_points_earned) }}
+                            </td>
+                        </tr>
+                    @empty
+                        <tr>
+                            <td colspan="3" class="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
+                                @lang('No sign-in data available')
+                            </td>
+                        </tr>
+                    @endforelse
+                    </tbody>
+                </table>
+            </div>
+        </div>
+
+        {{-- 积分排行榜 --}}
+        <div class="mt-6 bg-white dark:bg-gray-900 rounded-lg shadow-sm p-6 border border-gray-200 dark:border-gray-800">
+            <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">@lang('Top 10 Customers by Points')</h3>
+            <div class="overflow-x-auto">
+                <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
+                    <thead class="bg-gray-50 dark:bg-gray-800">
+                    <tr>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">@lang('Rank')</th>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">@lang('Customer')</th>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">@lang('Email')</th>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">@lang('Points Balance')</th>
+                    </tr>
+                    </thead>
+                    <tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800">
+                    @forelse($data['top_customers'] as $index => $customer)
+                        <tr>
+                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                                #{{ $index + 1 }}
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                                @if($customer->customer)
+                                    {{ $customer->customer->first_name }} {{ $customer->customer->last_name }}
+                                @else
+                                    N/A
+                                @endif
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                                {{ $customer->customer ? $customer->customer->email : 'N/A' }}
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap">
+                                <span class="text-lg font-bold text-yellow-600 dark:text-yellow-500">
+                                    {{ number_format($customer->mw_reward_point) }}
+                                </span>
+                            </td>
+                        </tr>
+                    @empty
+                        <tr>
+                            <td colspan="4" class="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
+                                @lang('No customer data available')
+                            </td>
+                        </tr>
+                    @endforelse
+                    </tbody>
+                </table>
+            </div>
+        </div>
+</x-admin::layouts>

+ 23 - 17
packages/Longyi/RewardPoints/src/Resources/views/admin/rules/create.blade.php

@@ -34,7 +34,8 @@
                                 </p>
                             @enderror
                         </div>
-                        
+
+
                         <div class="mb-2.5">
                             <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 required">
                                 @lang('Transaction Type')
@@ -52,12 +53,17 @@
                                     </option>
                                 @endforeach
                             </select>
+                            <p class="mt-1 text-xs text-gray-500">
+                                @lang('When Transaction Type is "Order", Reward Points will be multiplied by the actual payment amount (default: 1x)')
+                            </p>
                             @error('type_of_transaction')
-                                <p class="mt-1 text-xs text-red-600">
-                                    {{ $message }}
-                                </p>
+                            <p class="mt-1 text-xs text-red-600">
+                                {{ $message }}
+                            </p>
                             @enderror
                         </div>
+
+
                     </div>
 
                     {{-- Store View 字段 - 多选下拉框,包含 All Stores 选项 --}}
@@ -157,7 +163,7 @@
                             <p class="mt-1 text-xs text-gray-500">
                                 @lang('Set different points for each customer group')
                             </p>
-                            
+
                             <div class="mt-2 space-y-3">
                                 @foreach($customerGroups as $key => $value)
                                     <div class="flex items-center gap-2">
@@ -210,7 +216,7 @@
                             </p>
                         </div>
                     </div>
-    
+
                     <div class="mb-2.5">
                         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                             @lang('Comment')
@@ -223,7 +229,7 @@
                     </div>
 
                     <div class="flex gap-2.5 items-center">
-                        <button 
+                        <button
                             type="submit"
                             class="primary-button"
                         >
@@ -249,12 +255,12 @@
     (function() {
         // Store View 多选逻辑
         const storeViewSelect = document.getElementById('storeViewSelect');
-        
+
         if (storeViewSelect) {
             storeViewSelect.addEventListener('change', function(e) {
                 const selectedOptions = Array.from(this.selectedOptions);
                 const hasAllStores = selectedOptions.some(option => option.value === 'all');
-                
+
                 if (hasAllStores) {
                     // 如果选中了 "All Stores",清空其他选项,只保留 "All Stores"
                     Array.from(this.options).forEach(option => {
@@ -271,21 +277,21 @@
                 }
             });
         }
-        
+
         // 定义更新显示的函数
         function updateSectionsVisibility() {
             const checkbox = document.getElementById('enableDifferentPointsByGroup');
             const uniformSection = document.getElementById('uniformPointsSection');
             const groupSection = document.getElementById('groupSpecificPointsSection');
-            
+
             if (!checkbox || !uniformSection || !groupSection) {
                 return;
             }
-            
+
             if (checkbox.checked) {
                 uniformSection.style.display = 'none';
                 groupSection.style.display = 'block';
-                
+
                 // 当切换到分组模式时,移除统一积分字段的 required 属性
                 const rewardPointsInput = document.getElementById('rewardPoints');
                 if (rewardPointsInput) {
@@ -294,7 +300,7 @@
             } else {
                 uniformSection.style.display = 'block';
                 groupSection.style.display = 'none';
-                
+
                 // 当切换到统一模式时,添加 required 属性
                 const rewardPointsInput = document.getElementById('rewardPoints');
                 if (rewardPointsInput) {
@@ -302,14 +308,14 @@
                 }
             }
         }
-        
+
         // 使用事件委托捕获所有 change 事件
         document.body.addEventListener('change', function(e) {
             if (e.target && e.target.id === 'enableDifferentPointsByGroup') {
                 updateSectionsVisibility();
             }
         });
-        
+
         // DOM 加载完成后初始化显示状态
         if (document.readyState === 'loading') {
             document.addEventListener('DOMContentLoaded', updateSectionsVisibility);
@@ -318,4 +324,4 @@
         }
     })();
     </script>
-</x-admin::layouts>
+</x-admin::layouts>

+ 333 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/transactions/index.blade.php

@@ -0,0 +1,333 @@
+<x-admin::layouts>
+    <x-slot:title>
+        @lang('rewardpoints::rewardpoints.admin.transactions')
+    </x-slot:title>
+
+    {{-- 页面标题和操作按钮 --}}
+    <div class="flex flex-wrap gap-4 justify-between items-center mb-6">
+        <div class="flex items-center gap-3">
+            <div class="icon-list text-2xl text-blue-500"></div>
+            <p class="text-2xl text-gray-800 dark:text-white font-bold">
+                @if($viewType === 'earned')
+                    @lang('rewardpoints::rewardpoints.admin.earned-transactions')
+                @else
+                    @lang('rewardpoints::rewardpoints.admin.transactions')
+                @endif
+            </p>
+            <span class="px-2.5 py-0.5 text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
+                {{ $transactions->total() }}
+                @lang('rewardpoints::rewardpoints.admin.total')
+            </span>
+        </div>
+
+        <div class="flex gap-x-2.5 items-center">
+            {{-- 视图切换按钮 --}}
+            <div class="flex gap-2">
+                <a
+                    href="{{ route('admin.reward-points.transactions.index', array_merge(request()->except('view_type'), ['view_type' => 'all'])) }}"
+                    class="{{ $viewType !== 'earned' ? 'primary-button' : 'secondary-button' }}"
+                >
+                    <span class="icon-list text-lg"></span>
+                    @lang('rewardpoints::rewardpoints.admin.view-all-transactions')
+                </a>
+                <a
+                    href="{{ route('admin.reward-points.transactions.earned', array_merge(request()->except('view_type'))) }}"
+                    class="{{ $viewType === 'earned' ? 'primary-button' : 'secondary-button' }}"
+                >
+                    <span class="icon-arrow-up text-lg"></span>
+                    @lang('rewardpoints::rewardpoints.admin.view-earned-only')
+                </a>
+            </div>
+
+            <a
+                href="{{ route('admin.reward-points.transactions.export', request()->all()) }}"
+                class="secondary-button"
+            >
+                <span class="icon-download text-lg"></span>
+                @lang('Export')
+            </a>
+        </div>
+    </div>
+
+
+    {{-- 筛选器 --}}
+    <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 p-4 mb-6">
+        <form method="GET" action="{{ route('admin.reward-points.transactions.index') }}" class="flex flex-wrap items-end gap-4">
+            {{-- 开始日期 --}}
+            <div class="flex-1 min-w-[180px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('Start Date')
+                </label>
+                <input
+                    type="date"
+                    name="start_date"
+                    value="{{ $startDate }}"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 结束日期 --}}
+            <div class="flex-1 min-w-[180px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('End Date')
+                </label>
+                <input
+                    type="date"
+                    name="end_date"
+                    value="{{ $endDate }}"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 客户邮箱 --}}
+            <div class="flex-1 min-w-[200px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('Customer Email')
+                </label>
+                <input
+                    type="text"
+                    name="customer_email"
+                    value="{{ $customerEmail }}"
+                    placeholder="@lang('Search by email')"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 交易类型 --}}
+            <div class="flex-1 min-w-[160px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('Transaction Type')
+                </label>
+                <select
+                    name="transaction_type"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+                    <option value="">@lang('All Types')</option>
+                    <option value="1" {{ $transactionType == '1' ? 'selected' : '' }}>@lang('Order')</option>
+                    <option value="2" {{ $transactionType == '2' ? 'selected' : '' }}>@lang('Registration')</option>
+                    <option value="3" {{ $transactionType == '3' ? 'selected' : '' }}>@lang('Product Review')</option>
+                    <option value="4" {{ $transactionType == '4' ? 'selected' : '' }}>@lang('Daily Sign In')</option>
+                    <option value="5" {{ $transactionType == '5' ? 'selected' : '' }}>@lang('Referral')</option>
+                    <option value="6" {{ $transactionType == '6' ? 'selected' : '' }}>@lang('Birthday')</option>
+                    <option value="8" {{ $transactionType == '8' ? 'selected' : '' }}>@lang('Subscribe')</option>
+                    <option value="0" {{ $transactionType == '0' ? 'selected' : '' }}>@lang('Admin Adjustment')</option>
+                    <option value="99" {{ $transactionType == '99' ? 'selected' : '' }}>@lang('Admin Action')</option>
+                </select>
+            </div>
+
+            {{-- 金额类型 --}}
+            <div class="flex-1 min-w-[140px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('Amount Type')
+                </label>
+                <select
+                    name="amount_type"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+                    <option value="">@lang('All')</option>
+                    <option value="earned" {{ $amountType == 'earned' ? 'selected' : '' }}>@lang('Earned')</option>
+                    <option value="redeemed" {{ $amountType == 'redeemed' ? 'selected' : '' }}>@lang('Redeemed')</option>
+                </select>
+            </div>
+
+            {{-- 操作按钮 --}}
+            <div class="flex gap-2">
+                <button
+                    type="submit"
+                    class="primary-button"
+                >
+                    <span class="icon-search text-lg"></span>
+                    @lang('Search')
+                </button>
+
+                <a
+                    href="{{ route('admin.reward-points.transactions.index') }}"
+                    class="secondary-button"
+                >
+                    <span class="icon-reset text-lg"></span>
+                    @lang('Reset')
+                </a>
+            </div>
+        </form>
+    </div>
+    {{-- 统计卡片 - 强制一行三列 --}}
+    <div class="grid grid-cols-3 gap-4 mb-6">
+        {{-- 总赚取积分 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+            <div class="p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-sm text-gray-500 dark:text-gray-400">
+                            @lang('Total Points Earned')
+                        </p>
+                        <p class="text-3xl font-bold text-green-600 dark:text-green-400 mt-2">
+                            {{ number_format($totalEarned) }}
+                        </p>
+                    </div>
+                    <div class="w-12 h-12 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center">
+                        <span class="icon-arrow-up text-2xl text-green-600 dark:text-green-400"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        {{-- 总兑换积分 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+            <div class="p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-sm text-gray-500 dark:text-gray-400">
+                            @lang('Total Points Redeemed')
+                        </p>
+                        <p class="text-3xl font-bold text-red-600 dark:text-red-400 mt-2">
+                            {{ number_format($totalRedeemed) }}
+                        </p>
+                    </div>
+                    <div class="w-12 h-12 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center">
+                        <span class="icon-arrow-down text-2xl text-red-600 dark:text-red-400"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        {{-- 总交易数 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+            <div class="p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-sm text-gray-500 dark:text-gray-400">
+                            @lang('Total Transactions')
+                        </p>
+                        <p class="text-3xl font-bold text-blue-600 dark:text-blue-400 mt-2">
+                            {{ number_format($totalTransactions) }}
+                        </p>
+                    </div>
+                    <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center">
+                        <span class="icon-list text-2xl text-blue-600 dark:text-blue-400"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    {{-- 表格 --}}
+    <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+        <div class="overflow-x-auto">
+            <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
+                <thead class="bg-gray-50 dark:bg-gray-800">
+                <tr>
+                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                        @lang('ID')
+                    </th>
+                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                        @lang('Customer')
+                    </th>
+                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                        @lang('Type')
+                    </th>
+                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                        @lang('Amount')
+                    </th>
+                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                        @lang('Balance')
+                    </th>
+                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                        @lang('Date')
+                    </th>
+                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                        @lang('Description')
+                    </th>
+                </tr>
+                </thead>
+                <tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800">
+                @forelse($transactions as $transaction)
+                    <tr class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
+                        <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                            #{{ $transaction->history_id }}
+                        </td>
+                        <td class="px-6 py-4 whitespace-nowrap">
+                            <div class="text-sm text-gray-900 dark:text-gray-300">
+                                @if($transaction->customer)
+                                    {{ $transaction->customer->first_name }} {{ $transaction->customer->last_name }}
+                                @else
+                                    N/A
+                                @endif
+                            </div>
+                            <div class="text-sm text-gray-500 dark:text-gray-400">
+                                {{ $transaction->customer ? $transaction->customer->email : 'N/A' }}
+                            </div>
+                        </td>
+                        <td class="px-6 py-4 whitespace-nowrap">
+                            @php
+                                $typeNames = [
+                                    1 => 'rewardpoints::rewardpoints.admin.transaction-types.order',
+                                    2 => 'rewardpoints::rewardpoints.admin.transaction-types.registration',
+                                    3 => 'rewardpoints::rewardpoints.admin.transaction-types.review',
+                                    4 => 'rewardpoints::rewardpoints.admin.transaction-types.sign-in',
+                                    5 => 'rewardpoints::rewardpoints.admin.transaction-types.referral',
+                                    6 => 'rewardpoints::rewardpoints.admin.transaction-types.birthday',
+                                    7 => 'rewardpoints::rewardpoints.admin.transaction-types.share',
+                                    8 => 'rewardpoints::rewardpoints.admin.transaction-types.subscribe',
+                                    0 => 'rewardpoints::rewardpoints.admin.admin-adjustment',
+                                    99 => 'rewardpoints::rewardpoints.admin.admin-action',
+                                ];
+                            @endphp
+                            <span class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full
+            @if($transaction->amount > 0)
+                bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200
+            @else
+                bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200
+            @endif">
+        @lang($typeNames[$transaction->type_of_transaction] ?? 'Unknown')
+    </span>
+                        </td>
+                        <td class="px-6 py-4 whitespace-nowrap text-sm">
+                                <span class="font-semibold
+                                    @if($transaction->amount > 0)
+                                        text-green-600 dark:text-green-400
+                                    @else
+                                        text-red-600 dark:text-red-400
+                                    @endif">
+                                    {{ $transaction->amount > 0 ? '+' : '' }}{{ number_format($transaction->amount) }}
+                                </span>
+                        </td>
+                        <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                            {{ number_format($transaction->balance) }}
+                        </td>
+                        <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
+                            {{ \Carbon\Carbon::parse($transaction->transaction_time)->format('Y-m-d H:i') }}
+                        </td>
+                        <td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-300 max-w-xs truncate">
+                            {{ $transaction->transaction_detail }}
+                        </td>
+                    </tr>
+                @empty
+                    <tr>
+                        <td colspan="7" class="px-6 py-12 text-center">
+                            <div class="flex flex-col items-center gap-4">
+                                <div class="w-20 h-20 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center">
+                                    <span class="icon-list text-4xl text-gray-400"></span>
+                                </div>
+                                <div>
+                                    <p class="text-lg font-medium text-gray-600 dark:text-gray-400 mb-1">
+                                        @lang('No transactions found')
+                                    </p>
+                                    <p class="text-sm text-gray-500 dark:text-gray-500">
+                                        @lang('Try adjusting your filters')
+                                    </p>
+                                </div>
+                            </div>
+                        </td>
+                    </tr>
+                @endforelse
+                </tbody>
+            </table>
+        </div>
+
+        {{-- 分页 --}}
+        @if($transactions->hasPages())
+            <div class="p-4 border-t border-gray-200 dark:border-gray-800">
+                {{ $transactions->links() }}
+            </div>
+        @endif
+    </div>
+</x-admin::layouts>

+ 35 - 17
packages/Longyi/RewardPoints/src/Routes/routes.php

@@ -19,17 +19,17 @@ Route::group(['middleware' => ['web', 'customer'], 'prefix' => 'customer'], func
         'as' => 'customer.reward-points.sign-status',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getSignStatus'
     ]);
-    
+
     Route::post('apply-reward-points', [
         'as' => 'checkout.apply-reward-points',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@applyPoints'
     ]);
-    
+
     Route::post('remove-reward-points', [
         'as' => 'checkout.remove-reward-points',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@removePoints'
     ]);
-    
+
     Route::get('reward-points-info', [
         'as' => 'checkout.reward-points-info',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsInfo'
@@ -38,7 +38,7 @@ Route::group(['middleware' => ['web', 'customer'], 'prefix' => 'customer'], func
 
 // 后台路由
 Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/reward-points'], function () {
-    
+
     // ========== 规则管理 - 静态路由 ==========
     Route::get('rules', [
         'as' => 'admin.reward-points.rules.index',
@@ -61,43 +61,43 @@ Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/reward-points
         'as' => 'admin.reward-points.customers.export',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@export'
     ]);
-    
+
     // 调整积分表单路由
     Route::get('customers/adjust-points', [
         'as' => 'admin.reward-points.customers.adjust-form',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@adjustPointsForm'
     ]);
-    
+
     // 提交调整积分
     Route::post('customers/adjust-points', [
         'as' => 'admin.reward-points.customers.adjust-points.submit',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@adjustPointsByIdOrEmail'
     ]);
-    
+
     // 批量更新
     Route::post('customers/bulk-update', [
         'as' => 'admin.reward-points.customers.bulk-update',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@bulkUpdate'
     ]);
-    
+
     // 添加积分
     Route::post('customers/add-points', [
         'as' => 'admin.reward-points.customers.add-points',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@addPoints'
     ]);
-    
+
     // 扣除积分
     Route::post('customers/deduct-points', [
         'as' => 'admin.reward-points.customers.deduct-points',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@deductPoints'
     ]);
-    
+
     // ========== 报表管理 - 静态路由 ==========
     Route::get('reports/export', [
         'as' => 'admin.reward-points.reports.export',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@export'
     ]);
-    
+
     // ========== 设置管理 - 静态路由 ==========
     Route::get('settings', [
         'as' => 'admin.reward-points.settings.index',
@@ -108,7 +108,7 @@ Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/reward-points
         'as' => 'admin.reward-points.settings.save',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\SettingController@save'
     ]);
-    
+
     // ========== 规则管理 - 动态路由 ==========
     Route::get('rules/{id}/edit', [
         'as' => 'admin.reward-points.rules.edit',
@@ -129,23 +129,41 @@ Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/reward-points
         'as' => 'admin.reward-points.rules.update-status',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@updateStatus'
     ]);
-    
+
     // ========== 客户管理 - 动态路由 ==========
     // 客户列表(放在动态路由前面也可以,但为了清晰,放在这里)
     Route::get('customers', [
         'as' => 'admin.reward-points.customers.index',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@index'
     ]);
-    
+
     // 客户详情(动态参数路由,必须放在所有静态路由之后)
     Route::get('customers/{customerId}', [
         'as' => 'admin.reward-points.customers.show',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@show'
     ]);
-    
-    // ========== 报表管理 - 动态路由 ==========
+
+    // ========== 报表管理 - 静态路由 ==========
+    Route::get('reports/export', [
+        'as' => 'admin.reward-points.reports.export',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@export'
+    ]);
+// ========== 报表管理 - 动态路由 ==========
     Route::get('reports', [
         'as' => 'admin.reward-points.reports.index',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@index'
     ]);
-});
+// 积分获取记录专用路由
+    Route::get('transactions/earned', [
+        'as' => 'admin.reward-points.transactions.earned',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\TransactionController@earnedIndex'
+    ]);
+    Route::get('transactions', [
+        'as' => 'admin.reward-points.transactions.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\TransactionController@index'
+    ]);
+    Route::get('transactions/export', [
+        'as' => 'admin.reward-points.transactions.export',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\TransactionController@export'
+    ]);
+});