bianjunhui 4 недель назад
Родитель
Сommit
205c0ea356

+ 2 - 1
packages/Longyi/RewardPoints/src/Database/Migrations/2026_01_01_000001_create_points_tables.php

@@ -31,7 +31,8 @@ return new class extends Migration
 
 
         if (!Schema::hasTable('mw_reward_point_customer')) {
         if (!Schema::hasTable('mw_reward_point_customer')) {
             Schema::create('mw_reward_point_customer', function (Blueprint $table) {
             Schema::create('mw_reward_point_customer', function (Blueprint $table) {
-                $table->unsignedInteger('customer_id')->primary();
+                $table->increments('id');
+                $table->unsignedInteger('customer_id');
                 $table->unsignedBigInteger('mw_reward_point')->default(0);
                 $table->unsignedBigInteger('mw_reward_point')->default(0);
                 $table->unsignedInteger('mw_friend_id')->default(0);
                 $table->unsignedInteger('mw_friend_id')->default(0);
                 $table->boolean('subscribed_balance_update')->default(true);
                 $table->boolean('subscribed_balance_update')->default(true);

+ 371 - 228
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/RuleController.php

@@ -2,259 +2,176 @@
 
 
 namespace Longyi\RewardPoints\Http\Controllers\Admin;
 namespace Longyi\RewardPoints\Http\Controllers\Admin;
 
 
+use Exception;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Webkul\Admin\Http\Controllers\Controller;
+use Illuminate\Validation\Rule;
+use Illuminate\View\View;
 use Longyi\RewardPoints\Models\RewardActiveRule;
 use Longyi\RewardPoints\Models\RewardActiveRule;
 use Longyi\RewardPoints\Repositories\RewardPointRepository;
 use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Webkul\Admin\Http\Controllers\Controller;
+use Webkul\Core\Models\Channel;
+use Webkul\Customer\Models\CustomerGroup;
 
 
 class RuleController extends Controller
 class RuleController extends Controller
 {
 {
-    protected $rewardPointRepository;
-    protected $_config;
+    /**
+     * Transaction type mapping with complete configuration
+     */
+    private const TRANSACTION_TYPES = [
+        1 => ['name' => 'Order', 'icon' => 'icon-shopping-cart', 'color' => 'blue'],
+        2 => ['name' => 'Registration', 'icon' => 'icon-user', 'color' => 'green'],
+        3 => ['name' => 'Product Review', 'icon' => 'icon-star', 'color' => 'yellow'],
+        4 => ['name' => 'Daily Sign In', 'icon' => 'icon-calendar', 'color' => 'purple'],
+        5 => ['name' => 'Referral', 'icon' => 'icon-share', 'color' => 'indigo'],
+        6 => ['name' => 'Birthday', 'icon' => 'icon-gift', 'color' => 'pink'],
+        7 => ['name' => 'Share', 'icon' => 'icon-share-alt', 'color' => 'orange'],
+        8 => ['name' => 'Subscribe', 'icon' => 'icon-envelope', 'color' => 'orange'],
+    ];
+
+    /**
+     * Valid transaction type IDs
+     */
+    private const VALID_TRANSACTION_TYPES = [1, 2, 3, 4, 5, 6, 7, 8];
+
+    /**
+     * Color classes mapping for transaction types
+     */
+    private const COLOR_CLASSES = [
+        'blue' => 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300',
+        'green' => 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300',
+        'yellow' => 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300',
+        'purple' => 'bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300',
+        'indigo' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300',
+        'pink' => 'bg-pink-100 dark:bg-pink-900 text-pink-700 dark:text-pink-300',
+        'orange' => 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-300',
+        'gray' => 'bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400',
+    ];
+
+    protected RewardPointRepository $rewardPointRepository;
+    
+    protected array $_config;
 
 
     public function __construct(RewardPointRepository $rewardPointRepository)
     public function __construct(RewardPointRepository $rewardPointRepository)
     {
     {
         $this->rewardPointRepository = $rewardPointRepository;
         $this->rewardPointRepository = $rewardPointRepository;
-        $this->_config = request('_config') ?: [];
+        $this->_config = request('_config', []);
     }
     }
 
 
     /**
     /**
      * Display a listing of reward rules
      * Display a listing of reward rules
      */
      */
-    public function index()
+    public function index(): View
     {
     {
-        $rules = RewardActiveRule::orderBy('rule_id', 'desc')->paginate(15);
-        $customerGroupsList = $this->getCustomerGroups(); // 获取群组列表
+        $rules = RewardActiveRule::query()
+            ->orderBy('rule_id', 'desc')
+            ->paginate(15);
         
         
-        $view = isset($this->_config['view']) ? $this->_config['view'] : 'rewardpoints::admin.rules.index';
+        $customerGroupsList = $this->getCustomerGroups();
+        $transactionTypes = self::TRANSACTION_TYPES;
+        $colorClasses = self::COLOR_CLASSES;
+
+        $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.index';
         
         
-        return view($view, compact('rules', 'customerGroupsList'));
+        return view($view, compact('rules', 'customerGroupsList', 'transactionTypes', 'colorClasses'));
     }
     }
 
 
     /**
     /**
-     * Helper method to get transaction type name
+     * Show the form for creating a new reward rule
      */
      */
-    protected function getTransactionTypeName($typeId)
+    public function create(): View
     {
     {
-        $types = [
-            1 => 'Order',
-            2 => 'Registration',
-            3 => 'Product Review',
-            4 => 'Daily Sign In',
-            5 => 'Referral',
-            6 => 'Birthday',
-            7 => 'Share'
-        ];
-
-        return $types[$typeId] ?? 'Unknown';
+        $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.create';
+        
+        return view($view, [
+            'transactionTypes' => self::TRANSACTION_TYPES,
+            'storeViews' => $this->getStoreViews(),
+            'customerGroups' => $this->getCustomerGroups(),
+            'colorClasses' => self::COLOR_CLASSES,
+        ]);
     }
     }
 
 
+
     /**
     /**
-     * Show the form for creating a new reward rule
+     * Show the form for editing the specified reward rule
      */
      */
-    public function create()
+    public function edit(int $id): View
     {
     {
-        $transactionTypes = [
-            1 => 'Order',
-            2 => 'Registration',
-            3 => 'Product Review',
-            4 => 'Daily Sign In',
-            5 => 'Referral',
-            6 => 'Birthday',
-            7 => 'Share'  // 添加分享类型的交易
-        ];
+        $rule = RewardActiveRule::findOrFail($id);
 
 
-        $storeViews = $this->getStoreViews();
-        $customerGroups = $this->getCustomerGroups();
+        // 处理 Store Views 数据
+        $selectedStoreViews = $this->parseStoreViews($rule);
+        
+        // 如果规则适用于所有店铺(store_view = '0'),则转换为 'all' 选项
+        $selectedStoreViewsForSelect = [];
+        if ($rule->store_view === '0' || empty($selectedStoreViews)) {
+            $selectedStoreViewsForSelect = ['all'];
+        } else {
+            $selectedStoreViewsForSelect = $selectedStoreViews;
+        }
+        
+        // 处理客户群组数据
+        $selectedCustomerGroupsArray = $this->parseCustomerGroups($rule);
+        $selectedCustomerGroupsPoints = $rule->enable_different_points_by_group 
+            ? (json_decode($rule->customer_group_ids, true) ?: [])
+            : [];
 
 
-        // 确保 _config 存在且包含必要的键
-        $view = isset($this->_config['view']) ? $this->_config['view'] : 'rewardpoints::admin.rules.create';
+        $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.edit';
         
         
-        return view($view, compact('transactionTypes', 'storeViews', 'customerGroups'));
+        return view($view, [
+            'rule' => $rule,
+            'transactionTypes' => self::TRANSACTION_TYPES,
+            'storeViews' => $this->getStoreViews(),
+            'customerGroups' => $this->getCustomerGroups(),
+            'selectedStoreViewsForSelect' => $selectedStoreViewsForSelect,
+            'selectedCustomerGroupsArray' => $selectedCustomerGroupsArray,
+            'selectedCustomerGroupsPoints' => $selectedCustomerGroupsPoints,
+            'colorClasses' => self::COLOR_CLASSES,
+        ]);
     }
     }
 
 
     /**
     /**
      * Store a newly created reward rule
      * Store a newly created reward rule
      */
      */
-    public function store(Request $request)
+    public function store(Request $request): RedirectResponse
     {
     {
-        $this->validate($request, [
-            'rule_name' => 'required|string|max:255',
-            'type_of_transaction' => 'required|integer|in:1,2,3,4,5,6,7',
-            'status' => 'required|boolean',
-            'enable_different_points_by_group' => 'nullable|boolean',
-            //'default_expired' => 'nullable|boolean',
-            'expired_day' => 'nullable|integer|min:0',
-            //'coupon_code' => 'nullable|string|max:255',
-        ]);
+        $validated = $this->validateStoreRequest($request);
 
 
         try {
         try {
-            $data = $request->only([
-                'rule_name',
-                'type_of_transaction',
-                'store_view',
-                //'default_expired',
-                'expired_day',
-                //'date_event',
-                'comment',
-                //'coupon_code',
-                'status',
-                'enable_different_points_by_group'
-            ]);
-
-            // 处理 store views
-            if (isset($data['store_view']) && is_array($data['store_view'])) {
-                $data['store_view'] = implode(',', $data['store_view']);
-            } else {
-                $data['store_view'] = '0';
-            }
-
-            // 根据是否启用不同群组不同积分来处理客户群组和积分
-            if ($request->get('enable_different_points_by_group')) {
-                // 当启用不同群组不同积分时,将群组积分数据存储为JSON
-                $groupPoints = $request->get('group_points', []);
-                $data['customer_group_ids'] = json_encode($groupPoints);
-                // 统一积分为0,因为实际积分存储在customer_group_ids中
-                $data['reward_point'] = 0;
-            } else {
-                // 验证统一积分值
-                $this->validate($request, [
-                    'reward_point' => 'required|integer|min:0',
-                ]);
-                $data['reward_point'] = $request->get('reward_point');
-                
-                // 处理客户群组IDs
-                if (isset($request['customer_group_ids']) && is_array($request['customer_group_ids'])) {
-                    $data['customer_group_ids'] = implode(',', $request['customer_group_ids']);
-                } else {
-                    $data['customer_group_ids'] = '';
-                }
-            }
-
-            $rule = RewardActiveRule::create($data);
+            $data = $this->prepareDataForSave($request, $validated);
+            RewardActiveRule::create($data);
 
 
             session()->flash('success', 'Reward rule created successfully.');
             session()->flash('success', 'Reward rule created successfully.');
             
             
-            $redirectRoute = isset($this->_config['redirect']) ? $this->_config['redirect'] : 'admin.reward-points.rules.index';
-            return redirect()->route($redirectRoute);
-
-        } catch (\Exception $e) {
+            return $this->redirectToIndex();
+        } catch (Exception $e) {
             session()->flash('error', 'Error creating reward rule: ' . $e->getMessage());
             session()->flash('error', 'Error creating reward rule: ' . $e->getMessage());
             return redirect()->back()->withInput();
             return redirect()->back()->withInput();
         }
         }
     }
     }
 
 
-    /**
-     * Show the form for editing the specified reward rule
-     */
-    public function edit($id)
-    {
-        $rule = RewardActiveRule::findOrFail($id);
-
-        $transactionTypes = [
-            1 => 'Order',
-            2 => 'Registration',
-            3 => 'Product Review',
-            4 => 'Daily Sign In',
-            5 => 'Referral',
-            6 => 'Birthday',
-            7 => 'Share'  // 添加分享类型的交易
-        ];
-
-        $storeViews = $this->getStoreViews();
-        $customerGroups = $this->getCustomerGroups();
-
-        // 解析存储的值
-        $selectedStoreViews = $rule->store_view ? explode(',', $rule->store_view) : [];
-        
-        // 如果启用了不同群组不同积分,则解析customer_group_ids为数组
-        if ($rule->enable_different_points_by_group) {
-            $selectedCustomerGroups = json_decode($rule->customer_group_ids, true) ?: [];
-        } else {
-            $selectedCustomerGroups = $rule->customer_group_ids ? explode(',', $rule->customer_group_ids) : [];
-        }
-
-        // 确保 _config 存在且包含必要的键
-        $view = isset($this->_config['view']) ? $this->_config['view'] : 'rewardpoints::admin.rules.edit';
-        
-        return view($view, compact(
-            'rule',
-            'transactionTypes',
-            'storeViews',
-            'customerGroups',
-            'selectedStoreViews',
-            'selectedCustomerGroups'
-        ));
-    }
-
+   
     /**
     /**
      * Update the specified reward rule
      * Update the specified reward rule
      */
      */
-    public function update(Request $request, $id)
+    public function update(Request $request, int $id): RedirectResponse
     {
     {
-        $this->validate($request, [
-            'rule_name' => 'required|string|max:255',
-            'type_of_transaction' => 'required|integer|in:1,2,3,4,5,6,7',
-            'status' => 'required|boolean',
-            'enable_different_points_by_group' => 'nullable|boolean',
-            'default_expired' => 'nullable|boolean',
-            'expired_day' => 'nullable|integer|min:0',
-            'coupon_code' => 'nullable|string|max:255',
-        ]);
+        $validated = $this->validateUpdateRequest($request);
+        $rule = RewardActiveRule::findOrFail($id);
 
 
         try {
         try {
-            $rule = RewardActiveRule::findOrFail($id);
+            $data = $this->prepareDataForSave($request, $validated);
+            
+            // 确保 enable_different_points_by_group 字段被正确设置
+            $data['enable_different_points_by_group'] = $request->boolean('enable_different_points_by_group');
             
             
-            $data = $request->only([
-                'rule_name',
-                'type_of_transaction',
-                'store_view',
-                'default_expired',
-                'expired_day',
-                'date_event',
-                'comment',
-                'coupon_code',
-                'status',
-                'enable_different_points_by_group'
-            ]);
-
-            // 处理 store views
-            if (isset($data['store_view']) && is_array($data['store_view'])) {
-                $data['store_view'] = implode(',', $data['store_view']);
-            } else {
-                $data['store_view'] = '0';
-            }
-
-            // 根据是否启用不同群组不同积分来处理客户群组和积分
-            if ($request->get('enable_different_points_by_group')) {
-                // 当启用不同群组不同积分时,将群组积分数据存储为JSON
-                $groupPoints = $request->get('group_points', []);
-                $data['customer_group_ids'] = json_encode($groupPoints);
-                // 统一积分为0,因为实际积分存储在customer_group_ids中
-                $data['reward_point'] = 0;
-            } else {
-                // 验证统一积分值
-                $this->validate($request, [
-                    'reward_point' => 'required|integer|min:0',
-                ]);
-                $data['reward_point'] = $request->get('reward_point');
-                
-                // 处理客户群组IDs
-                if (isset($request['customer_group_ids']) && is_array($request['customer_group_ids'])) {
-                    $data['customer_group_ids'] = implode(',', $request['customer_group_ids']);
-                } else {
-                    $data['customer_group_ids'] = '';
-                }
-            }
-
             $rule->update($data);
             $rule->update($data);
 
 
             session()->flash('success', 'Reward rule updated successfully.');
             session()->flash('success', 'Reward rule updated successfully.');
             
             
-            $redirectRoute = isset($this->_config['redirect']) ? $this->_config['redirect'] : 'admin.reward-points.rules.index';
-            return redirect()->route($redirectRoute);
-
-        } catch (\Exception $e) {
+            return $this->redirectToIndex();
+        } catch (Exception $e) {
             session()->flash('error', 'Error updating reward rule: ' . $e->getMessage());
             session()->flash('error', 'Error updating reward rule: ' . $e->getMessage());
             return redirect()->back()->withInput();
             return redirect()->back()->withInput();
         }
         }
@@ -263,45 +180,36 @@ class RuleController extends Controller
     /**
     /**
      * Remove the specified reward rule
      * Remove the specified reward rule
      */
      */
-    public function destroy($id)
+    public function destroy(int $id): JsonResponse|RedirectResponse
     {
     {
         try {
         try {
             $rule = RewardActiveRule::findOrFail($id);
             $rule = RewardActiveRule::findOrFail($id);
             $rule->delete();
             $rule->delete();
 
 
-            session()->flash('success', 'Reward rule deleted successfully.');
-            
-            // 检查请求是否是 AJAX 请求
+            $message = 'Reward rule deleted successfully.';
+            session()->flash('success', $message);
+
             if (request()->ajax()) {
             if (request()->ajax()) {
-                return response()->json([
-                    'status' => true,
-                    'message' => 'Reward rule deleted successfully.'
-                ]);
-            } else {
-                // 非 AJAX 请求则进行重定向
-                $redirectRoute = isset($this->_config['redirect']) ? $this->_config['redirect'] : 'admin.reward-points.rules.index';
-                return redirect()->route($redirectRoute);
+                return response()->json(['status' => true, 'message' => $message]);
             }
             }
 
 
-        } catch (\Exception $e) {
-            session()->flash('error', 'Error deleting reward rule: ' . $e->getMessage());
-            
+            return $this->redirectToIndex();
+        } catch (Exception $e) {
+            $message = 'Error deleting reward rule: ' . $e->getMessage();
+            session()->flash('error', $message);
+
             if (request()->ajax()) {
             if (request()->ajax()) {
-                return response()->json([
-                    'status' => false,
-                    'message' => 'Error deleting reward rule.'
-                ]);
-            } else {
-                $redirectRoute = isset($this->_config['redirect']) ? $this->_config['redirect'] : 'admin.reward-points.rules.index';
-                return redirect()->route($redirectRoute)->with('error', 'Error deleting reward rule: ' . $e->getMessage());
+                return response()->json(['status' => false, 'message' => 'Error deleting reward rule.'], 500);
             }
             }
+
+            return $this->redirectToIndex()->with('error', $message);
         }
         }
     }
     }
 
 
     /**
     /**
      * Update rule status
      * Update rule status
      */
      */
-    public function updateStatus($id, Request $request)
+    public function updateStatus(int $id, Request $request): JsonResponse
     {
     {
         try {
         try {
             $rule = RewardActiveRule::findOrFail($id);
             $rule = RewardActiveRule::findOrFail($id);
@@ -310,25 +218,219 @@ class RuleController extends Controller
 
 
             return response()->json([
             return response()->json([
                 'status' => true,
                 'status' => true,
-                'message' => 'Rule status updated successfully.'
+                'message' => 'Rule status updated successfully.',
             ]);
             ]);
-
-        } catch (\Exception $e) {
+        } catch (Exception $e) {
             return response()->json([
             return response()->json([
                 'status' => false,
                 'status' => false,
-                'message' => 'Error updating rule status.'
+                'message' => 'Error updating rule status.',
+            ], 500);
+        }
+    }
+
+    /**
+     * Validate store request
+     */
+    private function validateStoreRequest(Request $request): array
+    {
+        $rules = [
+            'rule_name' => 'required|string|max:255',
+            'type_of_transaction' => ['required', 'integer', Rule::in(self::VALID_TRANSACTION_TYPES)],
+            'status' => 'required|boolean',
+            'enable_different_points_by_group' => 'nullable|boolean',
+            'expired_day' => 'nullable|integer|min:0',
+            'comment' => 'nullable|string|max:500',
+            'store_view' => 'nullable|array',
+            'store_view.*' => 'nullable|string',
+        ];
+
+        return $request->validate($rules);
+    }
+
+    /**
+     * Validate update request
+     */
+    private function validateUpdateRequest(Request $request): array
+    {
+        $rules = [
+            'rule_name' => 'required|string|max:255',
+            'type_of_transaction' => ['required', 'integer', Rule::in(self::VALID_TRANSACTION_TYPES)],
+            'status' => 'required|boolean',
+            'enable_different_points_by_group' => 'nullable|boolean',
+            'default_expired' => 'nullable|boolean',
+            'expired_day' => 'nullable|integer|min:0',
+            'coupon_code' => 'nullable|string|max:255',
+            'comment' => 'nullable|string|max:500',
+            'store_view' => 'nullable|array',
+            'store_view.*' => 'nullable|string',
+        ];
+
+        return $request->validate($rules);
+    }
+
+    /**
+     * Prepare data for saving
+     */
+    private function prepareDataForSave(Request $request, array $validated): array
+    {
+        $data = array_intersect_key($validated, array_flip([
+            'rule_name',
+            'type_of_transaction',
+            'default_expired',
+            'expired_day',
+            'date_event',
+            'comment',
+            'coupon_code',
+            'status',
+            'enable_different_points_by_group',
+        ]));
+
+        // Handle store views - 修复:确保正确处理 store_view
+        $storeViews = $request->get('store_view', []);
+        $data['store_view'] = $this->formatStoreViews($storeViews);
+
+        // Handle points configuration
+        $this->handlePointsConfiguration($request, $data);
+
+        return $data;
+    }
+
+    /**
+     * Format store views for database storage
+     */
+    private function formatStoreViews($storeViews): string
+{
+    // 如果 storeViews 为空或为 '0',返回 '0' 表示所有店铺
+    if (empty($storeViews) || $storeViews === '0') {
+        return '0';
+    }
+    
+    // 如果是单个值,直接返回
+    if (is_scalar($storeViews) && !empty($storeViews)) {
+        return (string)$storeViews;
+    }
+    
+    return '0';
+}
+
+    /**
+     * Handle points configuration based on group settings
+     */
+    private function handlePointsConfiguration(Request $request, array &$data): void
+    {
+        if ($request->boolean('enable_different_points_by_group')) {
+            $groupPoints = $request->get('group_points', []);
+            
+            // 确保 $groupPoints 是数组
+            if (!is_array($groupPoints)) {
+                $groupPoints = [];
+            }
+            
+            // 过滤并处理群组积分数据,确保键是字符串或整数
+            $filteredPoints = [];
+            foreach ($groupPoints as $groupId => $points) {
+                // 确保键是标量类型且不为空
+                if (is_scalar($groupId) && !empty($groupId)) {
+                    // 确保积分值是数字
+                    $pointsValue = is_numeric($points) ? (int)$points : 0;
+                    if ($pointsValue >= 0) {
+                        $filteredPoints[(string)$groupId] = $pointsValue;
+                    }
+                }
+            }
+            
+            $data['customer_group_ids'] = !empty($filteredPoints) ? json_encode($filteredPoints) : '{}';
+            $data['reward_point'] = 0;
+        } else {
+            $request->validate([
+                'reward_point' => 'required|integer|min:0',
             ]);
             ]);
+            $data['reward_point'] = (int)$request->get('reward_point');
+            $data['customer_group_ids'] = $this->formatCustomerGroups($request->get('customer_group_ids', []));
         }
         }
     }
     }
 
 
+    /**
+     * Format customer groups for database storage
+     */
+    private function formatCustomerGroups($customerGroups): string
+    {
+        if (is_array($customerGroups) && !empty($customerGroups)) {
+            // 过滤掉空值并确保所有值都是标量
+            $filtered = [];
+            foreach ($customerGroups as $groupId) {
+                if (is_scalar($groupId) && !empty($groupId)) {
+                    $filtered[] = (string)$groupId;
+                }
+            }
+            return !empty($filtered) ? implode(',', array_unique($filtered)) : '';
+        }
+        
+        return '';
+    }
+
+    /**
+     * Parse store views from rule
+     */
+    private function parseStoreViews(RewardActiveRule $rule): array
+    {
+        if ($rule->store_view && $rule->store_view !== '0') {
+            $views = explode(',', $rule->store_view);
+            // 过滤空值
+            return array_filter($views, function($view) {
+                return !empty($view);
+            });
+        }
+        
+        return [];
+    }
+
+    /**
+     * Parse customer groups from rule
+     */
+    private function parseCustomerGroups(RewardActiveRule $rule): array
+    {
+        if ($rule->enable_different_points_by_group) {
+            $decoded = json_decode($rule->customer_group_ids, true);
+            if (is_array($decoded)) {
+                // 确保解码后的数据格式正确
+                $result = [];
+                foreach ($decoded as $groupId => $points) {
+                    if (is_scalar($groupId) && !empty($groupId)) {
+                        $result[(string)$groupId] = is_numeric($points) ? (int)$points : 0;
+                    }
+                }
+                return $result;
+            }
+            return [];
+        }
+        
+        if ($rule->customer_group_ids && $rule->customer_group_ids !== '') {
+            $groups = explode(',', $rule->customer_group_ids);
+            // 过滤空值
+            return array_filter($groups, function($group) {
+                return !empty($group);
+            });
+        }
+        
+        return [];
+    }
+
     /**
     /**
      * Get all store views
      * Get all store views
      */
      */
-    protected function getStoreViews()
+    protected function getStoreViews(): array
     {
     {
         try {
         try {
-            return \Webkul\Core\Models\Channel::pluck('name', 'code')->toArray();
-        } catch (\Exception $e) {
+            $channels = Channel::all();
+            $storeViews = [];
+            
+            foreach ($channels as $channel) {
+                $storeViews[$channel->code] = $channel->name;
+            }
+            
+            return $storeViews;
+        } catch (Exception $e) {
             return ['0' => 'All Stores'];
             return ['0' => 'All Stores'];
         }
         }
     }
     }
@@ -336,12 +438,53 @@ class RuleController extends Controller
     /**
     /**
      * Get all customer groups
      * Get all customer groups
      */
      */
-    protected function getCustomerGroups()
+    protected function getCustomerGroups(): array
     {
     {
         try {
         try {
-            return \Webkul\Customer\Models\CustomerGroup::pluck('name', 'id')->toArray();
-        } catch (\Exception $e) {
+            $groups = CustomerGroup::orderBy('id')->get();
+            $customerGroups = [];
+            
+            foreach ($groups as $group) {
+                $customerGroups[$group->id] = $group->name;
+            }
+            
+            return $customerGroups;
+        } catch (Exception $e) {
             return ['0' => 'All Groups'];
             return ['0' => 'All Groups'];
         }
         }
     }
     }
+
+    /**
+     * Get transaction type configuration
+     */
+    public function getTransactionTypeConfig(): array
+    {
+        return self::TRANSACTION_TYPES;
+    }
+
+    /**
+     * Get transaction type name
+     */
+    protected function getTransactionTypeName(int $typeId): string
+    {
+        return self::TRANSACTION_TYPES[$typeId]['name'] ?? 'Unknown';
+    }
+
+    /**
+     * Get transaction type color classes
+     */
+    protected function getTransactionTypeColorClass(int $typeId): string
+    {
+        $color = self::TRANSACTION_TYPES[$typeId]['color'] ?? 'gray';
+        return self::COLOR_CLASSES[$color] ?? self::COLOR_CLASSES['gray'];
+    }
+
+    /**
+     * Redirect to index route
+     */
+    private function redirectToIndex(): RedirectResponse
+    {
+        $route = $this->_config['redirect'] ?? 'admin.reward-points.rules.index';
+        return redirect()->route($route);
+    }
 }
 }

+ 81 - 22
packages/Longyi/RewardPoints/src/Listeners/CustomerEvents.php

@@ -4,7 +4,8 @@ namespace Longyi\RewardPoints\Listeners;
 
 
 use Longyi\RewardPoints\Repositories\RewardPointRepository;
 use Longyi\RewardPoints\Repositories\RewardPointRepository;
 use Longyi\RewardPoints\Models\RewardActiveRule;
 use Longyi\RewardPoints\Models\RewardActiveRule;
-use Longyi\RewardPoints\Models\RewardPointCustomer;
+use Webkul\Customer\Models\Customer;
+use Illuminate\Support\Facades\Log;
 
 
 class CustomerEvents
 class CustomerEvents
 {
 {
@@ -17,43 +18,101 @@ class CustomerEvents
 
 
     public function handleCustomerRegistration($event)
     public function handleCustomerRegistration($event)
     {
     {
-        // 检查是否是 Customer 模型实例
+        // 检查事件参数类型
         if (is_object($event) && method_exists($event, 'getAttribute')) {
         if (is_object($event) && method_exists($event, 'getAttribute')) {
             $customer = $event;
             $customer = $event;
+        } elseif (is_array($event) && isset($event['customer'])) {
+            $customer = $event['customer'];
+        } elseif (is_array($event) && isset($event['id'])) {
+            $customer = Customer::find($event['id']);
+        } elseif (is_int($event) || is_string($event)) {
+            $customer = Customer::find($event);
         } else {
         } else {
-            // 如果传递的是 customer ID,则需要查询数据库
-            $customerId = $event;
-            $customer = \Webkul\Customer\Models\Customer::find($customerId);
+            $customer = $event;
         }
         }
 
 
         if (!$customer) {
         if (!$customer) {
+            Log::warning('Customer not found in registration event');
             return;
             return;
         }
         }
 
 
-        // 尝试通过规则获取积分
-        $rule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_REGISTRATION ?? 2) // 默认值为2
+        // 查询注册规则
+        $registrationRule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_REGISTRATION)
             ->where('status', 1)
             ->where('status', 1)
             ->first();
             ->first();
 
 
-        $pointsToAdd = 0;
-        $pointsSource = 'rule'; // 默认从规则获取
+        // 查询订阅规则
+        $subscribeRule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_SUBSCRIBE)
+            ->where('status', 1)
+            ->first();
 
 
-        if ($rule && $rule->reward_point > 0) {
-            $pointsToAdd = $rule->reward_point;
-        } else {
-            // 如果没有规则或者规则无效,尝试从配置获取
-            $pointsToAdd = config('rewardpoints.registration.points_per_registration', 100); // 默认100分
-            $pointsSource = 'config';
+        // 准备批量添加的积分列表
+        $pointsList = [];
+
+        // 添加注册积分
+        $registrationPoints = $this->getPointsFromRuleOrConfig(
+            $registrationRule,
+            'rewardpoints.registration.points_per_registration',
+            100
+        );
+        
+        if ($registrationPoints > 0) {
+            $pointsList[] = [
+                'amount' => $registrationPoints,
+                'type' => RewardActiveRule::TYPE_REGISTRATION,
+                'detail' => 'Registration bonus',
+                'rule' => $registrationRule
+            ];
+        }
+
+        // 检查客户是否订阅了新闻通讯
+        $isSubscribedToNewsletter = false;
+        if (property_exists($customer, 'subscribed_to_news_letter')) {
+            $isSubscribedToNewsletter = $customer->subscribed_to_news_letter;
+        } elseif (method_exists($customer, 'getAttribute')) {
+            $isSubscribedToNewsletter = $customer->getAttribute('subscribed_to_news_letter');
         }
         }
 
 
-        if ($pointsToAdd > 0) {
-            $this->rewardPointRepository->addPoints(
-                $customer->id,
-                RewardActiveRule::TYPE_REGISTRATION ?? 2, // 使用常量,如果没有则使用默认值
-                $pointsToAdd,
-                null,
-                "Registration bonus (via {$pointsSource})"
+        // 如果用户订阅了新闻通讯,添加订阅积分
+        if ($isSubscribedToNewsletter) {
+            $subscribePoints = $this->getPointsFromRuleOrConfig(
+                $subscribeRule,
+                'rewardpoints.newsletter_subscribe.points_per_subscription',
+                200
             );
             );
+            
+            if ($subscribePoints > 0) {
+                $pointsList[] = [
+                    'amount' => $subscribePoints,
+                    'type' => RewardActiveRule::TYPE_SUBSCRIBE,
+                    'detail' => 'Newsletter subscription bonus',
+                    'rule' => $subscribeRule
+                ];
+            }
+        }
+
+        // 如果有积分需要添加,使用批量添加方法
+        if (!empty($pointsList)) {
+            $histories = $this->rewardPointRepository->addPointsBatch($customer->id, $pointsList);
+            
+            Log::info('Points added for customer registration', [
+                'customer_id' => $customer->id,
+                'points_count' => count($pointsList),
+                'points_breakdown' => array_column($pointsList, 'amount'),
+                'total_points' => array_sum(array_column($pointsList, 'amount'))
+            ]);
+        }
+    }
+
+    /**
+     * 从规则或配置获取积分值
+     */
+    private function getPointsFromRuleOrConfig($rule, $configKey, $defaultValue)
+    {
+        if ($rule && $rule->reward_point > 0) {
+            return (int) $rule->reward_point;
         }
         }
+        
+        return (int) config($configKey, $defaultValue);
     }
     }
 }
 }

+ 19 - 4
packages/Longyi/RewardPoints/src/Models/RewardActiveRule.php

@@ -13,8 +13,8 @@ class RewardActiveRule extends Model
     const TYPE_REVIEW = 4;         // 评价
     const TYPE_REVIEW = 4;         // 评价
     const TYPE_REFERRAL = 5;       // 推荐
     const TYPE_REFERRAL = 5;       // 推荐
     const TYPE_BIRTHDAY = 6;       // 生日
     const TYPE_BIRTHDAY = 6;       // 生日
-    const TYPE_CUSTOM = 7;         // 自定义
-    
+    const TYPE_SHARE  = 7;         // 分享
+    const TYPE_SUBSCRIBE  = 8;         // 关注
     protected $table = 'mw_reward_active_rules';
     protected $table = 'mw_reward_active_rules';
 
 
     protected $primaryKey = 'rule_id';
     protected $primaryKey = 'rule_id';
@@ -37,9 +37,9 @@ class RewardActiveRule extends Model
     ];
     ];
 
 
     protected $casts = [
     protected $casts = [
-        'default_expired' => 'boolean',
+        'type_of_transaction' => 'integer',
         'status' => 'boolean',
         'status' => 'boolean',
-        'enable_different_points_by_group' => 'boolean',
+        'expired_day' => 'integer',
     ];
     ];
 
 
     /**
     /**
@@ -77,4 +77,19 @@ class RewardActiveRule extends Model
         // 如果不启用不同群组不同积分,则返回统一的积分值
         // 如果不启用不同群组不同积分,则返回统一的积分值
         return (int)$this->reward_point;
         return (int)$this->reward_point;
     }
     }
+     public function getTransactionTypeTextAttribute()
+    {
+        $types = [
+            self::TYPE_ORDER => 'Order',
+            self::TYPE_REGISTRATION => 'Registration',
+            self::TYPE_REVIEW => 'Product Review',
+            self::TYPE_SIGN_IN => 'Daily Sign In',
+            self::TYPE_REFERRAL => 'Referral',
+            self::TYPE_BIRTHDAY => 'Birthday',
+            self::TYPE_SHARE => 'Share',
+            self::TYPE_SUBSCRIBE => 'Subscription'
+        ];
+
+        return $types[$this->type_of_transaction] ?? 'Unknown';
+    }
 }
 }

+ 252 - 79
packages/Longyi/RewardPoints/src/Repositories/RewardPointRepository.php

@@ -7,6 +7,8 @@ use Longyi\RewardPoints\Models\RewardPointHistory;
 use Longyi\RewardPoints\Models\RewardPointCustomer;
 use Longyi\RewardPoints\Models\RewardPointCustomer;
 use Longyi\RewardPoints\Models\RewardActiveRule;
 use Longyi\RewardPoints\Models\RewardActiveRule;
 use Carbon\Carbon;
 use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 
 
 class RewardPointRepository extends Repository
 class RewardPointRepository extends Repository
 {
 {
@@ -26,88 +28,242 @@ class RewardPointRepository extends Repository
         return $customerPoints->mw_reward_point;
         return $customerPoints->mw_reward_point;
     }
     }
 
 
-    public function addPoints($customerId, $type, $amount, $orderId = null, $detail = null)
+    /**
+     * 添加积分(支持传入规则信息)
+     */
+    public function addPoints($customerId, $type, $amount, $orderId = null, $detail = null, $rule = null)
     {
     {
-        return \DB::transaction(function () use ($customerId, $type, $amount, $orderId, $detail) {
-            $customerPoints = RewardPointCustomer::lockForUpdate()->firstOrCreate(
-                ['customer_id' => $customerId],
-                [
-                    'mw_reward_point' => 0,
-                    'mw_friend_id' => 0,
-                    'subscribed_balance_update' => 1,
-                    'subscribed_point_expiration' => 1,
-                    'last_checkout' => Carbon::now()
-                ]
-            );
-
-            $currentBalance = $customerPoints->mw_reward_point;
-            $newBalance = $currentBalance + $amount;
-
-            // Get rule for expiration
-            $rule = RewardActiveRule::where('type_of_transaction', $type)
-                ->where('status', 1)
-                ->first();
-
-            $expiredDay = $rule ? $rule->expired_day : 0;
-            $expiredTime = $expiredDay > 0 ? Carbon::now()->addDays($expiredDay) : null;
-
-            // Create history
-            $history = $this->create([
+        try {
+            return DB::transaction(function () use ($customerId, $type, $amount, $orderId, $detail, $rule) {
+                // 1. 获取或创建用户积分记录
+                $customerPoints = RewardPointCustomer::firstOrCreate(
+                    ['customer_id' => $customerId],
+                    [
+                        'mw_reward_point' => 0,
+                        'mw_friend_id' => 0,
+                        'subscribed_balance_update' => 1,
+                        'subscribed_point_expiration' => 1,
+                        'last_checkout' => Carbon::now()
+                    ]
+                );
+                
+                // 2. 使用 increment 方法增加积分
+                $customerPoints->increment('mw_reward_point', $amount);
+                
+                // 3. 更新最后检查时间
+                $customerPoints->last_checkout = Carbon::now();
+                $customerPoints->save();
+                
+                // 4. 重新查询获取最新的余额
+                $customerPoints = RewardPointCustomer::where('customer_id', $customerId)->first();
+                $newBalance = $customerPoints ? (int) $customerPoints->mw_reward_point : $amount;
+
+                Log::info('Points added', [
+                    'customer_id' => $customerId,
+                    'type' => $type,
+                    'amount' => $amount,
+                    'new_balance' => $newBalance
+                ]);
+
+                // 5. 获取过期信息(如果没有传入规则,则查询)
+                $expiredDay = 0;
+                $expiredTime = null;
+                
+                if ($rule) {
+                    $expiredDay = $rule->expired_day ?? 0;
+                    $expiredTime = $expiredDay > 0 ? Carbon::now()->addDays($expiredDay) : null;
+                } else {
+                    // 兼容旧代码,如果没有传入规则则查询
+                    $rule = RewardActiveRule::where('type_of_transaction', $type)
+                        ->where('status', 1)
+                        ->first();
+                    $expiredDay = $rule ? $rule->expired_day : 0;
+                    $expiredTime = $expiredDay > 0 ? Carbon::now()->addDays($expiredDay) : null;
+                }
+
+                // 6. 创建历史记录
+                $history = $this->create([
+                    'customer_id' => $customerId,
+                    'type_of_transaction' => $type,
+                    'amount' => $amount,
+                    'balance' => $newBalance,
+                    'transaction_detail' => $detail,
+                    'transaction_time' => Carbon::now(),
+                    'history_order_id' => $orderId ?? 0,
+                    'expired_day' => $expiredDay,
+                    'expired_time' => $expiredTime,
+                    'point_remaining' => $amount,
+                    'check_time' => 1,
+                    'status' => RewardPointHistory::STATUS_COMPLETED
+                ]);
+
+                return $history;
+            });
+        } catch (\Exception $e) {
+            Log::error('Error adding points', [
                 'customer_id' => $customerId,
                 'customer_id' => $customerId,
-                'type_of_transaction' => $type,
-                'amount' => $amount,
-                'balance' => $newBalance,
-                'transaction_detail' => $detail,
-                'transaction_time' => Carbon::now(),
-                'history_order_id' => $orderId ?? 0,
-                'expired_day' => $expiredDay,
-                'expired_time' => $expiredTime,
-                'point_remaining' => $amount,
-                'check_time' => 1,
-                'status' => RewardPointHistory::STATUS_COMPLETED
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
             ]);
             ]);
+            throw $e;
+        }
+    }
 
 
-            // Update customer points
-            $customerPoints->mw_reward_point = $newBalance;
-            $customerPoints->save();
-
-            return $history;
-        });
+    /**
+     * 批量添加积分(用于同时多个事件)
+     * 确保每条历史记录都有正确的余额值
+     */
+    public function addPointsBatch($customerId, array $pointsList)
+    {
+        try {
+            return DB::transaction(function () use ($customerId, $pointsList) {
+                // 1. 获取或创建用户积分记录(不使用 lockForUpdate 避免问题)
+                $customerPoints = RewardPointCustomer::firstOrCreate(
+                    ['customer_id' => $customerId],
+                    [
+                        'mw_reward_point' => 0,
+                        'mw_friend_id' => 0,
+                        'subscribed_balance_update' => 1,
+                        'subscribed_point_expiration' => 1,
+                        'last_checkout' => Carbon::now()
+                    ]
+                );
+                
+                // 确保 $customerPoints 存在
+                if (!$customerPoints) {
+                    throw new \Exception('Failed to create or retrieve customer points record');
+                }
+                
+                // 2. 获取当前余额
+                $currentBalance = (int) $customerPoints->mw_reward_point;
+                $histories = [];
+                $totalAmount = 0;                                
+                
+                // 3. 逐条创建历史记录(每条记录使用累加后的余额)
+                foreach ($pointsList as $index => $pointData) {
+                    $amount = (int) $pointData['amount'];
+                    $type = $pointData['type'];
+                    $detail = $pointData['detail'] ?? null;
+                    $orderId = $pointData['order_id'] ?? null;
+                    $rule = $pointData['rule'] ?? null;
+                    
+                    // 累加总积分
+                    $totalAmount += $amount;
+                    
+                    // 计算当前这条记录后的余额(累加后的余额)
+                    $newBalance = $currentBalance + $totalAmount;
+                    
+                    // 获取过期信息
+                    $expiredDay = 0;
+                    $expiredTime = null;
+                    
+                    if ($rule) {
+                        $expiredDay = $rule->expired_day ?? 0;
+                        $expiredTime = $expiredDay > 0 ? Carbon::now()->addDays($expiredDay) : null;
+                    }
+                    
+                    // 创建历史记录(每条记录独立)
+                    $history = $this->create([
+                        'customer_id' => $customerId,
+                        'type_of_transaction' => $type,
+                        'amount' => $amount,
+                        'balance' => $newBalance,
+                        'transaction_detail' => $detail,
+                        'transaction_time' => Carbon::now(),
+                        'history_order_id' => $orderId ?? 0,
+                        'expired_day' => $expiredDay,
+                        'expired_time' => $expiredTime,
+                        'point_remaining' => $amount,
+                        'check_time' => 1,
+                        'status' => RewardPointHistory::STATUS_COMPLETED
+                    ]);
+                    
+                    $histories[] = $history;
+        
+                }
+                
+                 // 4. 最后一次性更新用户总积分
+                $updated = DB::table('mw_reward_point_customer')
+                        ->where('customer_id', $customerId)
+                        ->update([
+                            'mw_reward_point' => DB::raw('mw_reward_point + ' . (float)$totalAmount),
+                            'last_checkout' => Carbon::now()
+                        ]);
+                                              
+                return $histories;
+            });
+        } catch (\Exception $e) {
+            Log::error('Error adding batch points', [
+                'customer_id' => $customerId,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            throw $e;
+        }
     }
     }
+
     public function deductPoints($customerId, $amount, $orderId = null, $detail = null)
     public function deductPoints($customerId, $amount, $orderId = null, $detail = null)
     {
     {
-        return \DB::transaction(function () use ($customerId, $amount, $orderId, $detail) {
-            $customerPoints = RewardPointCustomer::lockForUpdate()->where('customer_id', $customerId)->first();
+        try {
+            return DB::transaction(function () use ($customerId, $amount, $orderId, $detail) {
+                $customerPoints = RewardPointCustomer::where('customer_id', $customerId)
+                    ->lockForUpdate()
+                    ->first();
 
 
-            if (!$customerPoints || $customerPoints->mw_reward_point < $amount) {
-                return false;
-            }
+                if (!$customerPoints) {
+                    Log::warning('Customer points record not found', ['customer_id' => $customerId]);
+                    return false;
+                }
+                
+                if ($customerPoints->mw_reward_point < $amount) {
+                    Log::warning('Insufficient points', [
+                        'customer_id' => $customerId,
+                        'current' => $customerPoints->mw_reward_point,
+                        'required' => $amount
+                    ]);
+                    return false;
+                }
 
 
-            $currentBalance = $customerPoints->mw_reward_point;
-            $newBalance = $currentBalance - $amount;
+                $currentBalance = (int) $customerPoints->mw_reward_point;
+                $newBalance = $currentBalance - (int) $amount;
 
 
-            // Create history for deduction (using negative amount)
-            $history = $this->create([
-                'customer_id' => $customerId,
-                'type_of_transaction' => 0,
-                'amount' => -$amount,
-                'balance' => $newBalance,
-                'transaction_detail' => $detail,
-                'transaction_time' => Carbon::now(),
-                'history_order_id' => $orderId ?? 0,
-                'expired_day' => 0,
-                'expired_time' => null,
-                'point_remaining' => 0,
-                'check_time' => 1,
-                'status' => RewardPointHistory::STATUS_COMPLETED
-            ]);
+                // 更新积分
+                $customerPoints->mw_reward_point = $newBalance;
+                $customerPoints->last_checkout = Carbon::now();
+                $customerPoints->save();
+
+                // 创建历史记录
+                $history = $this->create([
+                    'customer_id' => $customerId,
+                    'type_of_transaction' => 0,
+                    'amount' => -$amount,
+                    'balance' => $newBalance,
+                    'transaction_detail' => $detail,
+                    'transaction_time' => Carbon::now(),
+                    'history_order_id' => $orderId ?? 0,
+                    'expired_day' => 0,
+                    'expired_time' => null,
+                    'point_remaining' => 0,
+                    'check_time' => 1,
+                    'status' => RewardPointHistory::STATUS_COMPLETED
+                ]);
 
 
-            // Update customer points
-            $customerPoints->mw_reward_point = $newBalance;
-            $customerPoints->save();
+                Log::info('Points deducted successfully', [
+                    'customer_id' => $customerId,
+                    'amount' => $amount,
+                    'new_balance' => $newBalance
+                ]);
 
 
-            return $history;
-        });
+                return $history;
+            });
+        } catch (\Exception $e) {
+            Log::error('Error deducting points', [
+                'customer_id' => $customerId,
+                'amount' => $amount,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
     }
     }
 
 
     public function getHistory($customerId, $limit = 20)
     public function getHistory($customerId, $limit = 20)
@@ -125,16 +281,33 @@ class RewardPointRepository extends Repository
             ->get();
             ->get();
 
 
         foreach ($expiredHistories as $history) {
         foreach ($expiredHistories as $history) {
-            $customerPoints = RewardPointCustomer::where('customer_id', $history->customer_id)->first();
+            try {
+                DB::transaction(function () use ($history) {
+                    $customerPoints = RewardPointCustomer::where('customer_id', $history->customer_id)
+                        ->lockForUpdate()
+                        ->first();
 
 
-            if ($customerPoints) {
-                $customerPoints->mw_reward_point -= $history->point_remaining;
-                $customerPoints->save();
-            }
+                    if ($customerPoints && $customerPoints->mw_reward_point >= $history->point_remaining) {
+                        $customerPoints->mw_reward_point -= $history->point_remaining;
+                        $customerPoints->save();
 
 
-            $history->status = RewardPointHistory::STATUS_EXPIRED;
-            $history->point_remaining = 0;
-            $history->save();
+                        $history->status = RewardPointHistory::STATUS_EXPIRED;
+                        $history->point_remaining = 0;
+                        $history->save();
+                        
+                        Log::info('Expired points processed', [
+                            'history_id' => $history->history_id,
+                            'customer_id' => $history->customer_id,
+                            'points' => $history->point_remaining
+                        ]);
+                    }
+                });
+            } catch (\Exception $e) {
+                Log::error('Error processing expired points', [
+                    'history_id' => $history->history_id,
+                    'error' => $e->getMessage()
+                ]);
+            }
         }
         }
     }
     }
-}
+}

+ 48 - 31
packages/Longyi/RewardPoints/src/Resources/views/admin/rules/create.blade.php

@@ -46,9 +46,9 @@
                                 required
                                 required
                             >
                             >
                                 <option value="">@lang('Select Type')</option>
                                 <option value="">@lang('Select Type')</option>
-                                @foreach($transactionTypes as $key => $value)
+                                @foreach($transactionTypes as $key => $type)
                                     <option value="{{ $key }}" {{ old('type_of_transaction') == $key ? 'selected' : '' }}>
                                     <option value="{{ $key }}" {{ old('type_of_transaction') == $key ? 'selected' : '' }}>
-                                        @lang($value)
+                                        @lang($type['name'])
                                     </option>
                                     </option>
                                 @endforeach
                                 @endforeach
                             </select>
                             </select>
@@ -58,18 +58,22 @@
                                 </p>
                                 </p>
                             @enderror
                             @enderror
                         </div>
                         </div>
-                         
                     </div>
                     </div>
 
 
+                    {{-- Store View 字段 - 多选下拉框,包含 All Stores 选项 --}}
                     <div class="mb-2.5">
                     <div class="mb-2.5">
                         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                             @lang('Store View')
                             @lang('Store View')
                         </label>
                         </label>
                         <select
                         <select
                             name="store_view[]"
                             name="store_view[]"
+                            id="storeViewSelect"
                             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"
                             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"
                             multiple
                             multiple
                         >
                         >
+                            <option value="all" {{ in_array('all', old('store_view', [])) ? 'selected' : '' }}>
+                                @lang('All Stores')
+                            </option>
                             @foreach($storeViews as $key => $value)
                             @foreach($storeViews as $key => $value)
                                 <option value="{{ $key }}" {{ in_array($key, old('store_view', [])) ? 'selected' : '' }}>
                                 <option value="{{ $key }}" {{ in_array($key, old('store_view', [])) ? 'selected' : '' }}>
                                     {{ $value }}
                                     {{ $value }}
@@ -77,7 +81,7 @@
                             @endforeach
                             @endforeach
                         </select>
                         </select>
                         <p class="mt-1 text-xs text-gray-500">
                         <p class="mt-1 text-xs text-gray-500">
-                            @lang('Leave empty for all stores')
+                            @lang('Select "All Stores" or choose specific stores')
                         </p>
                         </p>
                     </div>
                     </div>
 
 
@@ -116,6 +120,9 @@
                                     </option>
                                     </option>
                                 @endforeach
                                 @endforeach
                             </select>
                             </select>
+                            <p class="mt-1 text-xs text-gray-500">
+                                @lang('Leave empty for all customer groups')
+                            </p>
                         </div>
                         </div>
 
 
                         <div class="grid grid-cols-2 gap-2.5">
                         <div class="grid grid-cols-2 gap-2.5">
@@ -187,20 +194,6 @@
                     </div>
                     </div>
 
 
                     <div class="grid grid-cols-2 gap-2.5">
                     <div class="grid grid-cols-2 gap-2.5">
-                        <!--
-                        <div class="mb-2.5">
-                            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
-                                @lang('Default Expired')
-                            </label>
-                            <select
-                                name="default_expired"
-                                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="1" {{ old('default_expired', 0) == 1 ? 'selected' : '' }}>@lang('Yes')</option>
-                                <option value="0" {{ old('default_expired', 0) == 0 ? 'selected' : '' }}>@lang('No')</option>
-                            </select>
-                        </div>
-                        -->
                         <div class="mb-2.5">
                         <div class="mb-2.5">
                             <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                             <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                                 @lang('Expired Day')
                                 @lang('Expired Day')
@@ -218,19 +211,6 @@
                         </div>
                         </div>
                     </div>
                     </div>
     
     
-                    <!--
-                    <div class="mb-2.5">
-                        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
-                            @lang('Coupon Code')
-                        </label>
-                        <input
-                            type="text"
-                            name="coupon_code"
-                            value="{{ old('coupon_code') }}"
-                            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="mb-2.5">
                     <div class="mb-2.5">
                         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                         <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                             @lang('Comment')
                             @lang('Comment')
@@ -267,6 +247,31 @@
 
 
    <script>
    <script>
     (function() {
     (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 => {
+                        if (option.value !== 'all') {
+                            option.selected = false;
+                        }
+                    });
+                } else {
+                    // 如果没有选中 "All Stores",确保 "All Stores" 不被选中
+                    const allStoresOption = Array.from(this.options).find(option => option.value === 'all');
+                    if (allStoresOption && allStoresOption.selected) {
+                        allStoresOption.selected = false;
+                    }
+                }
+            });
+        }
+        
         // 定义更新显示的函数
         // 定义更新显示的函数
         function updateSectionsVisibility() {
         function updateSectionsVisibility() {
             const checkbox = document.getElementById('enableDifferentPointsByGroup');
             const checkbox = document.getElementById('enableDifferentPointsByGroup');
@@ -280,9 +285,21 @@
             if (checkbox.checked) {
             if (checkbox.checked) {
                 uniformSection.style.display = 'none';
                 uniformSection.style.display = 'none';
                 groupSection.style.display = 'block';
                 groupSection.style.display = 'block';
+                
+                // 当切换到分组模式时,移除统一积分字段的 required 属性
+                const rewardPointsInput = document.getElementById('rewardPoints');
+                if (rewardPointsInput) {
+                    rewardPointsInput.removeAttribute('required');
+                }
             } else {
             } else {
                 uniformSection.style.display = 'block';
                 uniformSection.style.display = 'block';
                 groupSection.style.display = 'none';
                 groupSection.style.display = 'none';
+                
+                // 当切换到统一模式时,添加 required 属性
+                const rewardPointsInput = document.getElementById('rewardPoints');
+                if (rewardPointsInput) {
+                    rewardPointsInput.setAttribute('required', 'required');
+                }
             }
             }
         }
         }
         
         

+ 217 - 212
packages/Longyi/RewardPoints/src/Resources/views/admin/rules/edit.blade.php

@@ -11,144 +11,122 @@
 
 
     <div class="flex gap-2.5 mt-3.5">
     <div class="flex gap-2.5 mt-3.5">
         <div class="flex flex-col gap-2 flex-1 max-xl:flex-auto">
         <div class="flex flex-col gap-2 flex-1 max-xl:flex-auto">
-            <div class="p-6 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800">
+            <div class="p-4 bg-white dark:bg-gray-900 rounded box-shadow">
                 <form method="POST" action="{{ route('admin.reward-points.rules.update', $rule->rule_id) }}" id="ruleForm">
                 <form method="POST" action="{{ route('admin.reward-points.rules.update', $rule->rule_id) }}" id="ruleForm">
                     @csrf
                     @csrf
                     @method('PUT')
                     @method('PUT')
 
 
-                    {{-- 基本信息 --}}
-                    <div class="mb-6 pb-6 border-b border-gray-200 dark:border-gray-800">
-                        <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
-                            @lang('Basic Information')
-                        </h3>
+                    <div class="grid grid-cols-2 gap-2.5">
+                        <div class="mb-2.5">
+                            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 required">
+                                @lang('Rule Name')
+                            </label>
+                            <input
+                                type="text"
+                                name="rule_name"
+                                value="{{ old('rule_name', $rule->rule_name) }}"
+                                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"
+                                required
+                                placeholder="@lang('E.g.: Sign-In Points')"
+                            >
+                            @error('rule_name')
+                                <p class="mt-1 text-xs text-red-600">
+                                    {{ $message }}
+                                </p>
+                            @enderror
+                        </div>
                         
                         
-                        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
-                            <div class="mb-2.5">
-                                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 required">
-                                    @lang('Rule Name')
-                                </label>
-                                <input
-                                    type="text"
-                                    name="rule_name"
-                                    value="{{ old('rule_name', $rule->rule_name) }}"
-                                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
-                                    required
-                                    placeholder="@lang('E.g.: Sign-In Points')"
-                                >
-                                @error('rule_name')
-                                    <p class="mt-1 text-xs text-red-600">
-                                        {{ $message }}
-                                    </p>
-                                @enderror
-                            </div>
                         <div class="mb-2.5">
                         <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')
-                                </label>
-                                <select
-                                    name="type_of_transaction"
-                                    id="transactionType"
-                                    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"
-                                    required
-                                >
-                                    <option value="">@lang('Select Type')</option>
-                                    @foreach($transactionTypes as $key => $value)
-                                        <option value="{{ $key }}" {{ old('type_of_transaction', $rule->type_of_transaction) == $key ? 'selected' : '' }}>
-                                            @lang($value)
-                                        </option>
-                                    @endforeach
-                                </select>
-                                @error('type_of_transaction')
-                                    <p class="mt-1 text-xs text-red-600">
-                                        {{ $message }}
-                                    </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('Status')
-                                </label>
-                                <select
-                                    name="status"
-                                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
-                                >
-                                    <option value="1" {{ old('status', $rule->status) == 1 ? 'selected' : '' }}>
-                                        @lang('Active')
-                                    </option>
-                                    <option value="0" {{ old('status', $rule->status) == 0 ? 'selected' : '' }}>
-                                        @lang('Inactive')
+                            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 required">
+                                @lang('Transaction Type')
+                            </label>
+                            <select
+                                name="type_of_transaction"
+                                id="transactionType"
+                                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"
+                                required
+                            >
+                                <option value="">@lang('Select Type')</option>
+                                @foreach($transactionTypes as $key => $type)
+                                    <option value="{{ $key }}" {{ old('type_of_transaction', $rule->type_of_transaction) == $key ? 'selected' : '' }}>
+                                        @lang($type['name'])
                                     </option>
                                     </option>
-                                </select>
-                            </div>
+                                @endforeach
+                            </select>
+                            @error('type_of_transaction')
+                                <p class="mt-1 text-xs text-red-600">
+                                    {{ $message }}
+                                </p>
+                            @enderror
                         </div>
                         </div>
+                    </div>
+
+                    {{-- Store View 字段 - 多选下拉框,包含 All Stores 选项 --}}
+                    <div class="mb-2.5">
+                        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                            @lang('Store View')
+                        </label>
+                        <select
+                            name="store_view[]"
+                            id="storeViewSelect"
+                            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"
+                            multiple
+                        >
+                            <option value="all" {{ in_array('all', old('store_view', $selectedStoreViewsForSelect)) ? 'selected' : '' }}>
+                                @lang('All Stores')
+                            </option>
+                            @foreach($storeViews as $key => $value)
+                                <option value="{{ $key }}" {{ in_array($key, old('store_view', $selectedStoreViewsForSelect)) ? 'selected' : '' }}>
+                                    {{ $value }}
+                                </option>
+                            @endforeach
+                        </select>
+                        <p class="mt-1 text-xs text-gray-500">
+                            @lang('Select "All Stores" or choose specific stores')
+                        </p>
+                    </div>
+
+                    <!-- 启用不同群组不同积分的开关 -->
+                    <div class="mb-2.5">
+                        <label class="flex items-center gap-2">
+                            <input
+                                type="checkbox"
+                                name="enable_different_points_by_group"
+                                id="enableDifferentPointsByGroup"
+                                value="1"
+                                class="rounded-md border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
+                                {{ old('enable_different_points_by_group', $rule->enable_different_points_by_group) ? 'checked' : '' }}
+                            >
+                            <span class="text-sm font-medium text-gray-700 dark:text-gray-300">
+                                @lang('Enable different points for different customer groups')
+                            </span>
+                        </label>
+                    </div>
 
 
+                    <!-- 统一积分设置部分 -->
+                    <div id="uniformPointsSection" style="{{ old('enable_different_points_by_group', $rule->enable_different_points_by_group) ? 'display: none;' : '' }}">
                         <div class="mb-2.5">
                         <div class="mb-2.5">
                             <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                             <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
-                                @lang('Store View')
+                                @lang('Customer Groups')
                             </label>
                             </label>
                             <select
                             <select
-                                name="store_view[]"
-                                class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
+                                name="customer_group_ids[]"
+                                id="customerGroups"
+                                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"
                                 multiple
                                 multiple
                             >
                             >
-                                @foreach($storeViews as $key => $value)
-                                    <option value="{{ $key }}" {{ in_array($key, old('store_view', $selectedStoreViews)) ? 'selected' : '' }}>
+                                @foreach($customerGroups as $key => $value)
+                                    <option value="{{ $key }}" {{ in_array($key, old('customer_group_ids', $selectedCustomerGroupsArray)) ? 'selected' : '' }}>
                                         {{ $value }}
                                         {{ $value }}
                                     </option>
                                     </option>
                                 @endforeach
                                 @endforeach
                             </select>
                             </select>
                             <p class="mt-1 text-xs text-gray-500">
                             <p class="mt-1 text-xs text-gray-500">
-                                @lang('Leave empty for all stores')
+                                @lang('Leave empty for all customer groups')
                             </p>
                             </p>
                         </div>
                         </div>
-                    </div>
-
-                    {{-- 积分设置 --}}
-                    <div class="mb-6 pb-6 border-b border-gray-200 dark:border-gray-800">
-                        <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
-                            @lang('Points Configuration')
-                        </h3>
-
-                        {{-- 启用不同群组不同积分的开关 --}}
-                        <div class="mb-4">
-                            <label class="flex items-center gap-2 cursor-pointer">
-                                <input
-                                    type="checkbox"
-                                    name="enable_different_points_by_group"
-                                    id="enableDifferentPointsByGroup"
-                                    value="1"
-                                    class="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
-                                    {{ old('enable_different_points_by_group', $rule->enable_different_points_by_group) ? 'checked' : '' }}
-                                >
-                                <span class="text-sm font-medium text-gray-700 dark:text-gray-300">
-                                    @lang('Enable different points for different customer groups')
-                                </span>
-                            </label>
-                        </div>
-
-                        {{-- 统一积分设置部分 --}}
-                        <div id="uniformPointsSection" style="{{ old('enable_different_points_by_group', $rule->enable_different_points_by_group) ? 'display: none;' : '' }}">
-                            <div class="mb-4">
-                                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
-                                    @lang('Customer Groups')
-                                </label>
-                                <select
-                                    name="customer_group_ids[]"
-                                    id="customerGroups"
-                                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
-                                    multiple
-                                >
-                                    @foreach($customerGroups as $key => $value)
-                                        <option value="{{ $key }}" {{ in_array($key, old('customer_group_ids', $selectedCustomerGroups)) ? 'selected' : '' }}>
-                                            {{ $value }}
-                                        </option>
-                                    @endforeach
-                                </select>
-                                <p class="mt-1 text-xs text-gray-500">
-                                    @lang('Select customer groups that can earn these points. Leave empty for all groups.')
-                                </p>
-                            </div>
 
 
+                        <div class="grid grid-cols-2 gap-2.5">
                             <div class="mb-2.5">
                             <div class="mb-2.5">
                                 <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 required">
                                 <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 required">
                                     @lang('Reward Points')
                                     @lang('Reward Points')
@@ -158,14 +136,10 @@
                                     name="reward_point"
                                     name="reward_point"
                                     id="rewardPoints"
                                     id="rewardPoints"
                                     value="{{ old('reward_point', $rule->reward_point) }}"
                                     value="{{ old('reward_point', $rule->reward_point) }}"
-                                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
-                                    required
+                                    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"
+                                    {{ !old('enable_different_points_by_group', $rule->enable_different_points_by_group) ? 'required' : '' }}
                                     min="0"
                                     min="0"
-                                    step="1"
                                 >
                                 >
-                                <p class="mt-1 text-xs text-gray-500">
-                                    @lang('Number of points to award for this transaction')
-                                </p>
                                 @error('reward_point')
                                 @error('reward_point')
                                     <p class="mt-1 text-xs text-red-600">
                                     <p class="mt-1 text-xs text-red-600">
                                         {{ $message }}
                                         {{ $message }}
@@ -173,116 +147,136 @@
                                 @enderror
                                 @enderror
                             </div>
                             </div>
                         </div>
                         </div>
+                    </div>
 
 
-                        {{-- 客户群组特定积分设置部分 --}}
-                        <div id="groupSpecificPointsSection" style="{{ old('enable_different_points_by_group', $rule->enable_different_points_by_group) ? 'display: block;' : 'display: none;' }}">
-                            <div class="mb-2.5">
-                                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
-                                    @lang('Customer Group Specific Points')
-                                </label>
-                                <p class="mt-1 text-xs text-gray-500 mb-3">
-                                    @lang('Set different points for each customer group')
-                                </p>
-                                
-                                <div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 space-y-3">
+                    <!-- 客户群组特定积分设置部分 -->
+                    <div id="groupSpecificPointsSection" style="{{ !old('enable_different_points_by_group', $rule->enable_different_points_by_group) ? 'display: none;' : '' }}">
+                        <div class="mb-2.5">
+                            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                                @lang('Customer Group Specific Points')
+                            </label>
+                            <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)
                                     @php
                                     @php
-                                        $groupPointsData = [];
-                                        if ($rule->enable_different_points_by_group) {
-                                            $groupPointsData = json_decode($rule->customer_group_ids, true) ?: [];
-                                        }
+                                        $pointsValue = isset($selectedCustomerGroupsPoints[$key]) ? $selectedCustomerGroupsPoints[$key] : 0;
+                                        $oldValue = old("group_points.{$key}", $pointsValue);
                                     @endphp
                                     @endphp
-                                    
-                                    @foreach($customerGroups as $key => $value)
-                                        <div class="flex flex-wrap items-center gap-3">
-                                            <label class="w-32 text-sm font-medium text-gray-700 dark:text-gray-300">
-                                                {{ $value }}:
-                                            </label>
-                                            <input
-                                                type="number"
-                                                name="group_points[{{ $key }}]"
-                                                value="{{ old("group_points.{$key}", $groupPointsData[$key] ?? 0) }}"
-                                                class="flex w-40 min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
-                                                min="0"
-                                                step="1"
-                                                placeholder="0"
-                                            >
-                                            <span class="text-xs text-gray-500">@lang('points')</span>
-                                        </div>
-                                    @endforeach
-                                </div>
+                                    <div class="flex items-center gap-2">
+                                        <label class="w-32 text-sm text-gray-600 dark:text-gray-300">
+                                            {{ $value }}:
+                                        </label>
+                                        <input
+                                            type="number"
+                                            name="group_points[{{ $key }}]"
+                                            value="{{ $oldValue }}"
+                                            class="flex w-32 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"
+                                            min="0"
+                                            placeholder="0"
+                                        >
+                                    </div>
+                                @endforeach
                             </div>
                             </div>
                         </div>
                         </div>
                     </div>
                     </div>
 
 
-                    {{-- 过期设置 --}}
-                    <div class="mb-6 pb-6 border-b border-gray-200 dark:border-gray-800">
-                        <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
-                            @lang('Expiration Settings')
-                        </h3>
-                        
-                        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
-                        
-                            <div class="mb-2.5">
-                                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
-                                    @lang('Expired Day')
-                                </label>
-                                <input
-                                    type="number"
-                                    name="expired_day"
-                                    value="{{ old('expired_day', $rule->expired_day) }}"
-                                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
-                                    min="0"
-                                    step="1"
-                                >
-                                <p class="mt-1 text-xs text-gray-500">
-                                    @lang('Number of days after which points expire. 0 means never expires (if Default Expired is set to No).')
-                                </p>
-                            </div>
+                    <div class="grid grid-cols-2 gap-2.5">
+                        <div class="mb-2.5">
+                            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
+                                @lang('Status')
+                            </label>
+                            <select
+                                name="status"
+                                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="1" {{ old('status', $rule->status) == 1 ? 'selected' : '' }}>@lang('Active')</option>
+                                <option value="0" {{ old('status', $rule->status) == 0 ? 'selected' : '' }}>@lang('Inactive')</option>
+                            </select>
                         </div>
                         </div>
                     </div>
                     </div>
 
 
-                    {{-- 其他设置 --}}
-                    <div class="mb-6 pb-6 border-b border-gray-200 dark:border-gray-800">
-                        <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
-                            @lang('Additional Settings')
-                        </h3>
-                        
-                       
-
+                    <div class="grid grid-cols-2 gap-2.5">
                         <div class="mb-2.5">
                         <div class="mb-2.5">
                             <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
                             <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
-                                @lang('Comment')
+                                @lang('Expired Day')
                             </label>
                             </label>
-                            <textarea
-                                name="comment"
-                                rows="3"
-                                class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-lg text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800"
-                                placeholder="@lang('Optional: Add internal notes or comments about this rule')"
-                            >{{ old('comment', $rule->comment) }}</textarea>
+                            <input
+                                type="number"
+                                name="expired_day"
+                                value="{{ old('expired_day', $rule->expired_day) }}"
+                                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"
+                                min="0"
+                            >
+                            <p class="mt-1 text-xs text-gray-500">
+                                @lang('0 means never expires')
+                            </p>
                         </div>
                         </div>
                     </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')
+                        </label>
+                        <textarea
+                            name="comment"
+                            rows="3"
+                            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"
+                        >{{ old('comment', $rule->comment) }}</textarea>
+                    </div>
 
 
-                    {{-- 操作按钮 --}}
-                    <div class="flex gap-2.5 items-center justify-end">
-                        <a href="{{ route('admin.reward-points.rules.index') }}" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
-                            @lang('Cancel')
-                        </a>
-                        <button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
-                            <span class="icon-save text-sm mr-1"></span>
+                    <div class="flex gap-2.5 items-center">
+                        <button 
+                            type="submit"
+                            class="primary-button"
+                        >
                             @lang('Update Rule')
                             @lang('Update Rule')
                         </button>
                         </button>
+                        <a href="{{ route('admin.reward-points.rules.index') }}" class="secondary-button">
+                            @lang('Back')
+                        </a>
                     </div>
                     </div>
                 </form>
                 </form>
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
 
 
-    <script>
-    /**
-     * 动态切换积分规则设置区域
-     * 根据"启用不同群组不同积分"开关的状态,显示/隐藏对应的设置表单
-     */
+    <style>
+    /* 确保隐藏样式生效 */
+    .hidden {
+        display: none !important;
+    }
+    </style>
+
+   <script>
     (function() {
     (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 => {
+                        if (option.value !== 'all') {
+                            option.selected = false;
+                        }
+                    });
+                } else {
+                    // 如果没有选中 "All Stores",确保 "All Stores" 不被选中
+                    const allStoresOption = Array.from(this.options).find(option => option.value === 'all');
+                    if (allStoresOption && allStoresOption.selected) {
+                        allStoresOption.selected = false;
+                    }
+                }
+            });
+        }
+        
         // 定义更新显示的函数
         // 定义更新显示的函数
         function updateSectionsVisibility() {
         function updateSectionsVisibility() {
             const checkbox = document.getElementById('enableDifferentPointsByGroup');
             const checkbox = document.getElementById('enableDifferentPointsByGroup');
@@ -296,18 +290,29 @@
             if (checkbox.checked) {
             if (checkbox.checked) {
                 uniformSection.style.display = 'none';
                 uniformSection.style.display = 'none';
                 groupSection.style.display = 'block';
                 groupSection.style.display = 'block';
+                
+                // 当切换到分组模式时,移除统一积分字段的 required 属性
+                const rewardPointsInput = document.getElementById('rewardPoints');
+                if (rewardPointsInput) {
+                    rewardPointsInput.removeAttribute('required');
+                }
             } else {
             } else {
                 uniformSection.style.display = 'block';
                 uniformSection.style.display = 'block';
                 groupSection.style.display = 'none';
                 groupSection.style.display = 'none';
+                
+                // 当切换到统一模式时,添加 required 属性
+                const rewardPointsInput = document.getElementById('rewardPoints');
+                if (rewardPointsInput) {
+                    rewardPointsInput.setAttribute('required', 'required');
+                }
             }
             }
         }
         }
         
         
-        // 使用事件委托捕获所有 change 事件
-        document.body.addEventListener('change', function(e) {
-            if (e.target && e.target.id === 'enableDifferentPointsByGroup') {
-                updateSectionsVisibility();
-            }
-        });
+        // 监听复选框变化
+        const checkbox = document.getElementById('enableDifferentPointsByGroup');
+        if (checkbox) {
+            checkbox.addEventListener('change', updateSectionsVisibility);
+        }
         
         
         // DOM 加载完成后初始化显示状态
         // DOM 加载完成后初始化显示状态
         if (document.readyState === 'loading') {
         if (document.readyState === 'loading') {

+ 34 - 74
packages/Longyi/RewardPoints/src/Resources/views/admin/rules/index.blade.php

@@ -42,13 +42,9 @@
                     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"
                     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"
                 >
                 >
                     <option value="">@lang('All Types')</option>
                     <option value="">@lang('All Types')</option>
-                    <option value="1">@lang('Order')</option>
-                    <option value="2">@lang('Registration')</option>
-                    <option value="3">@lang('Product Review')</option>
-                    <option value="4">@lang('Daily Sign In')</option>
-                    <option value="5">@lang('Referral')</option>
-                    <option value="6">@lang('Birthday')</option>
-                    <option value="7">@lang('Share')</option>
+                    @foreach($transactionTypes as $id => $type)
+                        <option value="{{ $id }}">@lang($type['name'])</option>
+                    @endforeach
                 </select>
                 </select>
                 
                 
                 <select 
                 <select 
@@ -106,6 +102,25 @@
                 </thead>
                 </thead>
                 <tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800">
                 <tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800">
                     @forelse($rules as $rule)
                     @forelse($rules as $rule)
+                        @php
+                            $typeConfig = $transactionTypes[$rule->type_of_transaction] ?? [
+                                'name' => 'Unknown',
+                                'icon' => 'icon-question',
+                                'color' => 'gray'
+                            ];
+                            
+                            $colorClasses = [
+                                'blue' => 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300',
+                                'green' => 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300',
+                                'yellow' => 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300',
+                                'purple' => 'bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300',
+                                'indigo' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300',
+                                'pink' => 'bg-pink-100 dark:bg-pink-900 text-pink-700 dark:text-pink-300',
+                                'orange' => 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-300',
+                                'gray' => 'bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400',
+                            ];
+                        @endphp
+                        
                         <tr class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
                         <tr class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
                             {{-- ID --}}
                             {{-- ID --}}
                             <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
                             <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-300">
@@ -124,59 +139,12 @@
                                 @endif
                                 @endif
                             </td>
                             </td>
 
 
-                            {{-- 交易类型 --}}
+                            {{-- 交易类型 - 使用配置数组 --}}
                             <td class="px-6 py-4 whitespace-nowrap">
                             <td class="px-6 py-4 whitespace-nowrap">
-                                <div class="flex items-center gap-2">
-                                    @switch($rule->type_of_transaction)
-                                        @case(1)
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300">
-                                                <span class="icon-shopping-cart text-xs"></span>
-                                                @lang('Order')
-                                            </span>
-                                            @break
-                                        @case(2)
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300">
-                                                <span class="icon-user text-xs"></span>
-                                                @lang('Registration')
-                                            </span>
-                                            @break
-                                        @case(3)
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300">
-                                                <span class="icon-star text-xs"></span>
-                                                @lang('Product Review')
-                                            </span>
-                                            @break
-                                        @case(4)
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300">
-                                                <span class="icon-calendar text-xs"></span>
-                                                @lang('Daily Sign In')
-                                            </span>
-                                            @break
-                                        @case(5)
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300">
-                                                <span class="icon-share text-xs"></span>
-                                                @lang('Referral')
-                                            </span>
-                                            @break
-                                        @case(6)
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-pink-100 dark:bg-pink-900 text-pink-700 dark:text-pink-300">
-                                                <span class="icon-gift text-xs"></span>
-                                                @lang('Birthday')
-                                            </span>
-                                            @break
-                                        @case(7)
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-300">
-                                                <span class="icon-share-alt text-xs"></span>
-                                                @lang('Share')
-                                            </span>
-                                            @break
-                                        @default
-                                            <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400">
-                                                <span class="icon-question text-xs"></span>
-                                                @lang('Unknown')
-                                            </span>
-                                    @endswitch
-                                </div>
+                                <span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium {{ $colorClasses[$typeConfig['color']] }}">
+                                    <span class="{{ $typeConfig['icon'] }} text-xs"></span>
+                                    @lang($typeConfig['name'])
+                                </span>
                             </td>
                             </td>
 
 
                             {{-- 群组设置类型 --}}
                             {{-- 群组设置类型 --}}
@@ -197,7 +165,6 @@
                             {{-- 积分配置 --}}
                             {{-- 积分配置 --}}
                             <td class="px-6 py-4">
                             <td class="px-6 py-4">
                                 @if($rule->enable_different_points_by_group)
                                 @if($rule->enable_different_points_by_group)
-                                    {{-- 积分不同模式:显示群组和对应积分 --}}
                                     @php
                                     @php
                                         $groupPoints = json_decode($rule->customer_group_ids, true) ?: [];
                                         $groupPoints = json_decode($rule->customer_group_ids, true) ?: [];
                                     @endphp
                                     @endphp
@@ -223,14 +190,12 @@
                                         </span>
                                         </span>
                                     @endif
                                     @endif
                                 @else
                                 @else
-                                    {{-- 积分相同模式:显示统一积分 --}}
                                     <div class="flex items-center gap-1">
                                     <div class="flex items-center gap-1">
                                         <span class="text-2xl font-bold text-yellow-600 dark:text-yellow-500">
                                         <span class="text-2xl font-bold text-yellow-600 dark:text-yellow-500">
                                             {{ $rule->reward_point }}
                                             {{ $rule->reward_point }}
                                         </span>
                                         </span>
                                         <span class="text-xs text-gray-500">@lang('points')</span>
                                         <span class="text-xs text-gray-500">@lang('points')</span>
                                         
                                         
-                                        {{-- 显示适用的客户群组 --}}
                                         @php
                                         @php
                                             $customerGroups = $rule->customer_group_ids ? explode(',', $rule->customer_group_ids) : [];
                                             $customerGroups = $rule->customer_group_ids ? explode(',', $rule->customer_group_ids) : [];
                                         @endphp
                                         @endphp
@@ -430,6 +395,9 @@
         const statusFilter = document.getElementById('statusFilter');
         const statusFilter = document.getElementById('statusFilter');
         const groupPointsFilter = document.getElementById('groupPointsFilter');
         const groupPointsFilter = document.getElementById('groupPointsFilter');
         
         
+        // 从服务器端获取交易类型映射(通过数据属性或全局变量)
+        const transactionTypeNames = @json($transactionTypes);
+        
         function filterRules() {
         function filterRules() {
             const searchTerm = searchInput.value.toLowerCase();
             const searchTerm = searchInput.value.toLowerCase();
             const transactionTypeValue = transactionTypeFilter.value;
             const transactionTypeValue = transactionTypeFilter.value;
@@ -439,10 +407,11 @@
             const rows = document.querySelectorAll('tbody tr');
             const rows = document.querySelectorAll('tbody tr');
             
             
             rows.forEach(row => {
             rows.forEach(row => {
-                if (row.querySelector('td[colspan]')) return; // 跳过空状态行
+                if (row.querySelector('td[colspan]')) return;
                 
                 
                 const ruleName = row.querySelector('td:nth-child(2) .font-medium')?.innerText.toLowerCase() || '';
                 const ruleName = row.querySelector('td:nth-child(2) .font-medium')?.innerText.toLowerCase() || '';
-                const transactionTypeText = row.querySelector('td:nth-child(3) .rounded-full')?.innerText || '';
+                const transactionTypeSpan = row.querySelector('td:nth-child(3) .rounded-full');
+                const transactionTypeText = transactionTypeSpan?.innerText.trim() || '';
                 const statusBadge = row.querySelector('td:nth-child(6) .rounded-full')?.innerText.includes('Active') || false;
                 const statusBadge = row.querySelector('td:nth-child(6) .rounded-full')?.innerText.includes('Active') || false;
                 const groupPointsType = row.querySelector('td:nth-child(4) .rounded-full')?.innerText.includes('积分不同') || false;
                 const groupPointsType = row.querySelector('td:nth-child(4) .rounded-full')?.innerText.includes('积分不同') || false;
                 
                 
@@ -455,16 +424,7 @@
                 
                 
                 // 交易类型过滤
                 // 交易类型过滤
                 if (transactionTypeValue !== '') {
                 if (transactionTypeValue !== '') {
-                    const typeMap = {
-                        '1': 'Order',
-                        '2': 'Registration',
-                        '3': 'Product Review',
-                        '4': 'Daily Sign In',
-                        '5': 'Referral',
-                        '6': 'Birthday',
-                        '7': 'Share'
-                    };
-                    const expectedType = typeMap[transactionTypeValue];
+                    const expectedType = transactionTypeNames[transactionTypeValue]?.name || '';
                     if (!transactionTypeText.includes(expectedType)) {
                     if (!transactionTypeText.includes(expectedType)) {
                         show = false;
                         show = false;
                     }
                     }

+ 7 - 0
packages/Webkul/Admin/src/Http/Controllers/Customers/CustomerController.php

@@ -110,11 +110,18 @@ class CustomerController extends Controller
         $customer = $this->customerRepository->create($data);
         $customer = $this->customerRepository->create($data);
 
 
         if (core()->getConfigData('emails.general.notifications.emails.general.notifications.customer_account_credentials')) {
         if (core()->getConfigData('emails.general.notifications.emails.general.notifications.customer_account_credentials')) {
+            // 临时禁用邮件发送,用于测试
+            Log::info('Customer registered but email sending disabled for testing', [
+                'customer_id' => $customer->id,
+                'email' => $customer->email
+            ]);
+            /*
             try {
             try {
                 Mail::queue(new NewCustomerNotification($customer, $password));
                 Mail::queue(new NewCustomerNotification($customer, $password));
             } catch (\Exception $e) {
             } catch (\Exception $e) {
                 report($e);
                 report($e);
             }
             }
+            */
         }
         }
 
 
         Event::dispatch('customer.create.after', $customer);
         Event::dispatch('customer.create.after', $customer);

+ 6 - 2
packages/Webkul/Admin/src/Listeners/Customer.php

@@ -19,8 +19,12 @@ class Customer extends Base
             if (! core()->getConfigData('emails.general.notifications.emails.general.notifications.customer_registration_confirmation_mail_to_admin')) {
             if (! core()->getConfigData('emails.general.notifications.emails.general.notifications.customer_registration_confirmation_mail_to_admin')) {
                 return;
                 return;
             }
             }
-
-            Mail::queue(new RegistrationNotification($customer));
+             // 临时禁用邮件发送,用于测试
+            Log::info('Customer registered but email sending disabled for testing', [
+                'customer_id' => $customer->id,
+                'email' => $customer->email
+            ]);
+            //Mail::queue(new RegistrationNotification($customer));
         } catch (\Exception $e) {
         } catch (\Exception $e) {
             report($e);
             report($e);
         }
         }