bianjunhui 4 月之前
父節點
當前提交
d8fac3b42f

+ 1 - 1
packages/Longyi/DynamicMenu/src/Database/Migrations/2026_01_01_000001_create_dynamic_menu_items_table.php

@@ -33,6 +33,6 @@ return new class extends Migration
      */
     public function down(): void
     {
-        Schema::dropIfExists('product_options');
+        Schema::dropIfExists('dynamic_menu_items');
     }
 };

+ 84 - 50
packages/Longyi/DynamicMenu/src/Http/Controllers/MenuController.php

@@ -19,27 +19,34 @@ class MenuController extends Controller
         $this->_config = request('_config');
     }
     
+    
     public function index()
     {
-        
-        
-    try {
-        
-        $allItems = MenuItem::with('children')
-            ->orderBy('parent_id')
-            ->orderBy('sort_order')
-            ->get();
-        $menuItems = $allItems->whereNull('parent_id');
-        return view('dynamicmenu::admin.menu.index', compact('menuItems', 'allItems'));
-        
-    } catch (\Exception $e) {
-        \Log::error('MenuController 错误: ' . $e->getMessage());
-        \Log::error($e->getTraceAsString());
-        throw $e;
-    }
-        //\Log::info('===== MenuController::index 结束 =====');
+        try {
+            // 直接从数据库获取最新数据,不使用缓存
+            $allItems = MenuItem::with('children')
+                ->orderBy('parent_id', 'ASC')
+                ->orderBy('sort_order', 'ASC')
+                ->get();
+            
+            // 获取顶级菜单项
+             $menuItems = $allItems->filter(function($item) {
+                return is_null($item->parent_id) || $item->parent_id == 0;
+            })->values();
+            
+            
+            return view('dynamicmenu::admin.menu.index', compact('menuItems', 'allItems'));
+            
+        } catch (\Exception $e) {
+            \Log::error('MenuController 错误:' . $e->getMessage());
+            \Log::error($e->getTraceAsString());
+            
+            session()->flash('error', '加载菜单列表失败:' . $e->getMessage());
+            return redirect()->route('admin.dashboard.index');
+        }
     }
     
+    
     public function create()
     {
         $menuItems = MenuItem::orderBy('parent_id')->orderBy('sort_order')->get();
@@ -72,46 +79,73 @@ class MenuController extends Controller
     
     public function edit($id)
     {
-        $menuItem = MenuItem::with('parent')->findOrFail($id);
-        $menuItems = MenuItem::orderBy('parent_id')->orderBy('sort_order')->get();
-        
-        return view($this->_config['view'], compact('menuItem', 'menuItems'));
+        try {
+            $menuItem = MenuItem::with('parent')->findOrFail($id);
+            $menuItems = MenuItem::orderBy('parent_id')->orderBy('sort_order')->get();
+            
+            return view($this->_config['view'], compact('menuItem', 'menuItems'));
+            
+        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+            session()->flash('error', '菜单项不存在');
+            return redirect()->route('admin.dynamicmenu.index');
+        }
     }
     
     public function update(Request $request, $id)
-{
-    $request->validate([
-        'name' => 'required|string|max:255',
-        'key' => 'required|string|unique:dynamic_menu_items,key,' . $id,  // 排除当前记录
-        'route' => 'required|string',
-        'icon' => 'nullable|string',
-        'parent_id' => 'nullable|exists:dynamic_menu_items,id',
-        'sort_order' => 'integer',
-        'status' => 'boolean'
-    ]);
-    
-    $menuItem = MenuItem::findOrFail($id);
-    $menuItem->update($request->all());
-    
-    session()->flash('success', '菜单项更新成功');
-    
-    return redirect()->route($this->_config['redirect']);
-}
+    {
+        try {
+            $request->validate([
+                'name' => 'required|string|max:255',
+                'key' => 'required|string|unique:dynamic_menu_items,key,' . $id,
+                'route' => 'required|string',
+                'icon' => 'nullable|string',
+                'parent_id' => 'nullable|exists:dynamic_menu_items,id',
+                'sort_order' => 'integer',
+                'status' => 'boolean'
+            ]);
+            
+            $menuItem = MenuItem::findOrFail($id);
+            $menuItem->update($request->all());
+            
+            session()->flash('success', '菜单项更新成功');
+            
+            return redirect()->route($this->_config['redirect']);
+            
+        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+            session()->flash('error', '菜单项不存在');
+            return redirect()->back();
+        } catch (\Exception $e) {
+            \Log::error('更新菜单项失败:' . $e->getMessage());
+            session()->flash('error', '更新失败:' . $e->getMessage());
+            return redirect()->back()->withInput();
+        }
+    }
     
-    public function destroy($id)
+     public function destroy($id)
     {
-        $menuItem = MenuItem::with('children')->findOrFail($id);
-        
-        if ($menuItem->children->count() > 0) {
-            session()->flash('error', '请先删除子菜单项');
+        try {
+            $menuItem = MenuItem::with('children')->findOrFail($id);
+            
+            // 检查是否有子菜单
+            if ($menuItem->children && $menuItem->children->count() > 0) {
+                session()->flash('error', '请先删除子菜单项');
+                return redirect()->back();
+            }
+            
+            $menuItem->delete();
+            
+            session()->flash('success', '菜单项删除成功');
+            
+            return redirect()->route($this->_config['redirect']);
+            
+        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+            session()->flash('error', '菜单项不存在');
+            return redirect()->back();
+        } catch (\Exception $e) {
+            \Log::error('删除菜单项失败:' . $e->getMessage());
+            session()->flash('error', '删除失败:' . $e->getMessage());
             return redirect()->back();
         }
-        
-        $menuItem->delete();
-        
-        session()->flash('success', '菜单项删除成功');
-        
-        return redirect()->route($this->_config['redirect']);
     }
     
     public function permission()

+ 15 - 11
packages/Longyi/DynamicMenu/src/Providers/DynamicMenuServiceProvider.php

@@ -54,23 +54,26 @@ class DynamicMenuServiceProvider extends ServiceProvider
         }
     }
 
+  
     /**
      * 从数据库获取菜单配置
      */
-    protected function getMenuConfigFromDatabase()
+    protected function getMenuConfigFromDatabase($useCache = true)
     {
-        $menuItems = MenuItem::with('children')
-                    ->where('status', 1)
-                    ->orderBy('sort_order')
-                    ->get();
-                
-        return $this->buildMenuConfig($menuItems);
+        if (!$useCache) {
+            // 不使用缓存,直接查询数据库
+            $menuItems = MenuItem::with('children')
+                        ->where('status', 1)
+                        ->orderBy('sort_order')
+                        ->get();
+                    
+            return $this->buildMenuConfig($menuItems);
+        }
+        
         // 使用缓存
-        /*
         return Cache::remember('dynamic_menu_config', 3600, function () {
             try {
                 $menuItems = MenuItem::with('children')
-                    ->whereNull('parent_id')
                     ->where('status', 1)
                     ->orderBy('sort_order')
                     ->get();
@@ -78,13 +81,14 @@ class DynamicMenuServiceProvider extends ServiceProvider
                 return $this->buildMenuConfig($menuItems);
                 
             } catch (\Exception $e) {
-                \Log::warning('无法从数据库获取菜单: ' . $e->getMessage());
+                \Log::warning('无法从数据库获取菜单' . $e->getMessage());
                 return [];
             }
         });
-        */
     }
 
+// ... existing code ...
+
     /**
      * 构建菜单配置数组
      */

+ 2 - 2
packages/Longyi/RewardPoints/composer.json

@@ -12,8 +12,8 @@
         }
     ],
     "require": {
-        "php": "^7.4|^8.0",
-        "bagisto/bagisto": "^2.3.11"
+        "php": "^8.1|^8.2",
+        "illuminate/support": "^10.0|^11.0"
     },
     "autoload": {
         "psr-4": {

+ 117 - 0
packages/Longyi/RewardPoints/src/Console/Commands/InitializeSettings.php

@@ -0,0 +1,117 @@
+<?php
+
+namespace Longyi\RewardPoints\Console\Commands;
+
+use Illuminate\Console\Command;
+use Longyi\RewardPoints\Repositories\RewardPointSettingRepository;
+use Longyi\DynamicMenu\Models\MenuItem;
+
+class InitializeSettings extends Command
+{
+    protected $signature = 'reward-points:init-settings';
+
+    protected $description = 'Initialize reward points settings in database';
+
+    protected $settingRepository;
+
+    public function __construct(RewardPointSettingRepository $settingRepository)
+    {
+        parent::__construct();
+        $this->settingRepository = $settingRepository;
+    }
+
+    public function handle()
+    {
+        $this->info('Initializing reward points settings...');
+        
+        try {
+            $this->settingRepository->initializeDefaultSettings();
+            
+            $this->info('✓ Default settings initialized successfully!');
+            $this->info('You can now configure reward points in admin panel.');
+            
+            // 添加积分模块菜单项到动态菜单
+            $this->addMenuItems();
+            
+            return 0;
+            
+        } catch (\Exception $e) {
+            $this->error('Error initializing settings: ' . $e->getMessage());
+            return 1;
+        }
+    }
+    
+    /**
+     * 添加积分模块的菜单项到动态菜单
+     */
+    protected function addMenuItems()
+    {
+        $this->info('Adding reward points menu items to dynamic menu...');
+        
+        // 检查是否已存在积分模块的菜单项
+        $existingParent = MenuItem::where('key', 'settings.reward-points')->first();
+        
+        if ($existingParent) {
+            $this->warn('Reward points menu items already exist in dynamic menu.');
+            return;
+        }
+        
+        // 创建父级菜单项
+        $parentItem = MenuItem::create([
+            'name' => '积分管理',
+            'key' => 'rewardPoint',
+            'route' => 'admin.reward-points.rules.index',
+            'icon' => 'icon-star',
+            'sort_order' => 10,
+            'status' => 1,
+            'created_by' => 1,
+        ]);
+        
+        // 创建子菜单项
+        $childItems = [
+            [
+                'name' => '积分规则',
+                'key' => 'rewardPoint.rules',
+                'route' => 'admin.reward-points.rules.index',
+                'icon' => 'icon-list',
+                'sort_order' => 1,
+            ],
+            [
+                'name' => '客户积分',
+                'key' => 'rewardPoint.customers',
+                'route' => 'admin.reward-points.customers.index',
+                'icon' => 'icon-users',
+                'sort_order' => 2,
+            ],
+            [
+                'name' => '积分报表',
+                'key' => 'rewardPoint.reports',
+                'route' => 'admin.reward-points.reports.index',
+                'icon' => 'icon-chart',
+                'sort_order' => 3,
+            ],
+            [
+                'name' => '积分设置',
+                'key' => 'rewardPoint.settings',
+                'route' => 'admin.reward-points.settings.index',
+                'icon' => 'icon-setting',
+                'sort_order' => 4,
+            ],
+        ];
+        
+        foreach ($childItems as $item) {
+            MenuItem::create([
+                'name' => $item['name'],
+                'key' => $item['key'],
+                'route' => $item['route'],
+                'icon' => $item['icon'],
+                'sort_order' => $item['sort_order'],
+                'parent_id' => $parentItem->id,
+                'status' => 1,
+                'created_by' => 1,
+            ]);
+        }
+        
+        $this->info('✓ Reward points menu items added to dynamic menu successfully!');
+    }
+}

+ 41 - 15
packages/Longyi/RewardPoints/src/Database/Migrations/2026_01_01_000001_create_points_tables.php

@@ -31,11 +31,13 @@ return new class extends Migration
         if (!Schema::hasTable('mw_reward_point_customer')) {
             Schema::create('mw_reward_point_customer', function (Blueprint $table) {
                 $table->unsignedInteger('customer_id')->primary();
-                $table->unsignedInteger('mw_reward_point');
-                $table->unsignedInteger('mw_friend_id');
-                $table->integer('subscribed_balance_update')->nullable()->default(1);
-                $table->integer('subscribed_point_expiration')->nullable()->default(1);
-                $table->dateTime('last_checkout');
+                $table->unsignedBigInteger('mw_reward_point')->default(0);
+                $table->unsignedInteger('mw_friend_id')->default(0);
+                $table->boolean('subscribed_balance_update')->default(true);
+                $table->boolean('subscribed_point_expiration')->default(true);
+                $table->timestamp('last_checkout')->useCurrent();
+                
+                $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade');
             });
         }
 
@@ -58,23 +60,43 @@ return new class extends Migration
             Schema::create('mw_reward_point_history', function (Blueprint $table) {
                 $table->increments('history_id');
                 $table->unsignedInteger('customer_id');
-                $table->unsignedInteger('type_of_transaction');
-                $table->unsignedInteger('amount');
+                $table->unsignedTinyInteger('type_of_transaction');
+                $table->integer('amount');
                 $table->integer('balance');
                 $table->text('transaction_detail')->nullable();
-                $table->dateTime('transaction_time')->nullable();
-                $table->integer('history_order_id')->nullable()->default(0);
-                $table->integer('expired_day')->nullable()->default(0);
-                $table->dateTime('expired_time')->nullable();
-                $table->integer('point_remaining')->nullable()->default(0);
-                $table->integer('check_time')->nullable()->default(1);
-                $table->integer('status');
+                $table->timestamp('transaction_time')->useCurrent();
+                $table->unsignedInteger('history_order_id')->nullable()->default(0);
+                $table->unsignedSmallInteger('expired_day')->default(0);
+                $table->timestamp('expired_time')->nullable();
+                $table->integer('point_remaining')->default(0);
+                $table->boolean('check_time')->default(true);
+                $table->tinyInteger('status')->default(0);
                 
                 $table->index('customer_id', 'customer_id_index');
                 $table->index('transaction_time', 'transaction_time_index');
                 $table->index(['customer_id', 'status'], 'customer_status_index');
                 $table->index(['customer_id', 'history_order_id'], 'customer_order_index');
                 $table->index('expired_time', 'expired_time_index');
+                
+                $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade');
+            });
+        }
+        if (!Schema::hasTable('mw_reward_points_settings')) {
+            Schema::create('mw_reward_points_settings', function (Blueprint $table) {
+                $table->increments('id');
+                $table->string('code')->unique()->comment('配置代码');
+                $table->string('name')->comment('配置名称');
+                $table->text('value')->nullable()->comment('配置值');
+                $table->string('type')->default('text')->comment('配置类型:text, number, boolean, select');
+                $table->string('group')->default('general')->comment('配置分组');
+                $table->integer('sort_order')->default(0)->comment('排序');
+                $table->text('options')->nullable()->comment('选项(JSON 格式,用于 select 类型)');
+                $table->string('description')->nullable()->comment('描述');
+                $table->boolean('is_enabled')->default(true)->comment('是否启用');
+                $table->timestamps();
+                
+                $table->index('group', 'idx_group');
+                $table->index('is_enabled', 'idx_enabled');
             });
         }
     }
@@ -83,6 +105,10 @@ return new class extends Migration
      */
     public function down(): void
     {
-        Schema::dropIfExists('product_options');
+        Schema::dropIfExists('mw_reward_point_history');
+        Schema::dropIfExists('mw_reward_point_customer_sign');
+        Schema::dropIfExists('mw_reward_point_customer');
+        Schema::dropIfExists('mw_reward_active_rules');
+        Schema::dropIfExists('mw_reward_points_settings');
     }
 };

+ 78 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/SettingController.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers\Admin;
+
+use Illuminate\Http\Request;
+use Webkul\Admin\Http\Controllers\Controller;
+use Longyi\RewardPoints\Repositories\RewardPointSettingRepository;
+
+class SettingController extends Controller
+{
+    protected $settingRepository;
+    protected $_config;
+
+    public function __construct(
+        RewardPointSettingRepository $settingRepository
+    ) {
+        $this->settingRepository = $settingRepository;
+        $this->_config = request('_config');
+    }
+
+    /**
+     * 显示配置页面
+     */
+    public function index()
+    {
+        $groups = $this->settingRepository->getGroups();
+        $currentGroup = request('group', 'general');
+        $settings = $this->settingRepository->getSettingsByGroup($currentGroup);
+        
+        return view($this->_config['view'], compact('groups', 'currentGroup', 'settings'));
+    }
+
+    /**
+     * 保存配置
+     */
+    public function save(Request $request)
+    {
+        $this->validate($request, [
+            'settings' => 'required|array'
+        ]);
+        
+        try {
+            $settings = $request->input('settings');
+            
+            foreach ($settings as $code => $value) {
+                $this->settingRepository->setConfigValue($code, $value);
+            }
+            
+            // 清除缓存
+            cache()->forget('reward_points_settings');
+            
+            session()->flash('success', '配置保存成功');
+            
+            return redirect()->route($this->_config['redirect'], ['group' => $request->input('group', 'general')]);
+            
+        } catch (\Exception $e) {
+            session()->flash('error', '保存配置失败:' . $e->getMessage());
+            return redirect()->back()->withInput();
+        }
+    }
+    
+    /**
+     * 获取分组名称
+     */
+    protected function getGroupName($group)
+    {
+        $names = [
+            'general' => '通用设置',
+            'signin' => '签到设置',
+            'order' => '订单设置',
+            'registration' => '注册设置',
+            'review' => '评价设置',
+            'referral' => '推荐设置',
+            'birthday' => '生日设置',
+        ];
+        return $names[$group] ?? ucfirst($group);
+    }
+}

+ 25 - 24
packages/Longyi/RewardPoints/src/Listeners/OrderEvents.php

@@ -41,41 +41,42 @@ class OrderEvents
         }
     }
 
-    public function handleOrderCancellation($order)
+     public function handleOrderCancellation($order)
     {
         if (!$order->customer_id) {
             return;
         }
 
-        $histories = $this->rewardPointRepository->findWhere([
-            'customer_id' => $order->customer_id,
-            'history_order_id' => $order->id,
-            'type_of_transaction' => RewardActiveRule::TYPE_ORDER,
-            'status' => RewardPointHistory::STATUS_COMPLETED
-        ])->get();
+        \DB::transaction(function () use ($order) {
+            $histories = $this->rewardPointRepository->findWhere([
+                'customer_id' => $order->customer_id,
+                'history_order_id' => $order->id,
+                'type_of_transaction' => RewardActiveRule::TYPE_ORDER,
+                'status' => RewardPointHistory::STATUS_COMPLETED
+            ])->get();
 
-        if ($histories->isEmpty()) {
-            return;
-        }
+            if ($histories->isEmpty()) {
+                return;
+            }
 
-        foreach ($histories as $history) {
-            if ($history->amount > 0 && $history->point_remaining > 0) {
-                $result = $this->rewardPointRepository->deductPoints(
-                    $order->customer_id,
-                    $history->point_remaining,
-                    $order->id,
-                    "Points deducted due to order cancellation #{$order->increment_id}"
-                );
+            foreach ($histories as $history) {
+                if ($history->amount > 0 && $history->point_remaining > 0) {
+                    $result = $this->rewardPointRepository->deductPoints(
+                        $order->customer_id,
+                        $history->point_remaining,
+                        $order->id,
+                        "Points deducted due to order cancellation #{$order->increment_id}"
+                    );
 
-                if ($result) {
-                    $history->status = RewardPointHistory::STATUS_CANCELLED;
-                    $history->point_remaining = 0;
-                    $history->save();
+                    if ($result) {
+                        $history->status = RewardPointHistory::STATUS_CANCELLED;
+                        $history->point_remaining = 0;
+                        $history->save();
+                    }
                 }
             }
-        }
+        });
     }
-
     protected function calculateOrderPoints($order, $rule)
     {
         // Simple calculation based on order grand total

+ 1 - 1
packages/Longyi/RewardPoints/src/Models/RewardPointCustomerSign.php

@@ -28,6 +28,6 @@ class RewardPointCustomerSign extends Model
 
     public function customer()
     {
-        return $this->belongsTo(RewardPointCustomer::class, 'customer_id');
+        return $this->belongsTo(Customer::class, 'customer_id');
     }
 }

+ 84 - 0
packages/Longyi/RewardPoints/src/Models/RewardPointSetting.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace Longyi\RewardPoints\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class RewardPointSetting extends Model
+{
+    protected $table = 'mw_reward_points_settings';
+    
+    protected $fillable = [
+        'code',
+        'name',
+        'value',
+        'type',
+        'group',
+        'sort_order',
+        'options',
+        'description',
+        'is_enabled'
+    ];
+    
+    protected $casts = [
+        'sort_order' => 'integer',
+        'is_enabled' => 'boolean',
+        'options' => 'array'
+    ];
+    
+    /**
+     * 获取配置值(自动转换类型)
+     */
+    public function getTypedValueAttribute()
+    {
+        switch ($this->type) {
+            case 'boolean':
+                return (bool) $this->value;
+            case 'number':
+                return (float) $this->value;
+            case 'select':
+                $options = $this->options ?? [];
+                return $options[$this->value] ?? $this->value;
+            default:
+                return $this->value;
+        }
+    }
+    
+    /**
+     * 按分组获取所有启用的配置
+     */
+    public static function getGroupSettings($group)
+    {
+        return self::where('group', $group)
+            ->where('is_enabled', true)
+            ->orderBy('sort_order')
+            ->get()
+            ->pluck('value', 'code')
+            ->toArray();
+    }
+    
+    /**
+     * 获取单个配置值
+     */
+    public static function getValue($code, $default = null)
+    {
+        $setting = self::where('code', $code)->first();
+        
+        if (!$setting) {
+            return $default;
+        }
+        
+        return $setting->typed_value;
+    }
+    
+    /**
+     * 设置配置值
+     */
+    public static function setValue($code, $value)
+    {
+        return self::updateOrCreate(
+            ['code' => $code],
+            ['value' => $value]
+        );
+    }
+}

+ 91 - 5
packages/Longyi/RewardPoints/src/Providers/RewardPointsServiceProvider.php

@@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider;
 use Illuminate\Routing\Router;
 use Longyi\RewardPoints\Providers\EventServiceProvider;
 use Longyi\RewardPoints\Services\CartRewardPoints;
+use Illuminate\Support\Facades\DB;
 
 class RewardPointsServiceProvider extends ServiceProvider
 {
@@ -24,6 +25,10 @@ class RewardPointsServiceProvider extends ServiceProvider
         $this->publishes([
             __DIR__ . '/../Config/rewardpoints.php' => config_path('rewardpoints.php'),
         ], 'config');
+        // 加载数据库配置覆盖文件配置
+        $this->app->booted(function () {
+            $this->loadDatabaseSettings();
+        });
     }
 
     public function register()
@@ -36,11 +41,6 @@ class RewardPointsServiceProvider extends ServiceProvider
             return new CartRewardPoints();
         });
         
-        $this->app->bind(
-            'Longyi\RewardPoints\Repositories\RewardPointRepository',
-            'Longyi\RewardPoints\Repositories\RewardPointRepository'
-        );
-        
         $this->app->register(EventServiceProvider::class);
         
         $this->app->alias('cartrewardpoints', CartRewardPoints::class);
@@ -48,7 +48,93 @@ class RewardPointsServiceProvider extends ServiceProvider
         if ($this->app->runningInConsole()) {
             $this->commands([
                 \Longyi\RewardPoints\Console\Commands\CheckExpiredPoints::class,
+                \Longyi\RewardPoints\Console\Commands\InitializeSettings::class,
             ]);
         }
     }
+     /**
+     * 从数据库加载配置
+     */
+    protected function loadDatabaseSettings()
+    {
+        try {
+            $settingRepository = app(\Longyi\RewardPoints\Repositories\RewardPointSettingRepository::class);
+            
+            // 如果数据库中没有配置,初始化默认值
+            if (DB::table('mw_reward_points_settings')->count() === 0) {
+                $settingRepository->initializeDefaultSettings();
+            }
+            
+            // 获取所有配置
+            $settings = DB::table('mw_reward_points_settings')
+                ->where('is_enabled', true)
+                ->get();
+            
+            foreach ($settings as $setting) {
+                $this->overrideConfig($setting->code, $setting->value, $setting->type);
+            }
+            
+        } catch (\Exception $e) {
+            \Log::error('Load reward points settings from database failed: ' . $e->getMessage());
+        }
+    }
+    
+    /**
+     * 覆盖配置文件
+     */
+    protected function overrideConfig($code, $value, $type)
+    {
+        $configKey = $this->codeToConfigKey($code);
+        
+        if ($configKey && config()->has($configKey)) {
+            $typedValue = $this->castValue($value, $type);
+            config()->set($configKey, $typedValue);
+        }
+    }
+    
+    /**
+     * 将代码转换为配置键
+     */
+    protected function codeToConfigKey($code)
+    {
+        $mapping = [
+            'reward_enabled' => 'rewardpoints.general.enabled',
+            'point_value' => 'rewardpoints.general.point_value',
+            'max_discount_percentage' => 'rewardpoints.general.max_discount_percentage',
+            'min_points_to_redeem' => 'rewardpoints.general.min_points_to_redeem',
+            'signin_base_points' => 'rewardpoints.sign_in.base_points',
+            'signin_week_bonus' => 'rewardpoints.sign_in.week_bonus_points',
+            'signin_month_bonus' => 'rewardpoints.sign_in.month_bonus_points',
+            'order_points_per_currency' => 'rewardpoints.order.points_per_currency',
+            'order_min_amount' => 'rewardpoints.order.min_order_amount',
+            'registration_points' => 'rewardpoints.registration.points_per_registration',
+            'review_points' => 'rewardpoints.review.points_per_review',
+            'review_require_approval' => 'rewardpoints.review.require_approval',
+            'referral_points' => 'rewardpoints.referral.points_per_referral',
+            'referral_require_order' => 'rewardpoints.referral.require_first_order',
+            'birthday_points' => 'rewardpoints.birthday.points_per_birthday',
+        ];
+        
+        return $mapping[$code] ?? null;
+    }
+    
+    /**
+     * 类型转换
+     */
+    protected function castValue($value, $type)
+    {
+        if ($value === null) {
+            return null;
+        }
+        
+        switch ($type) {
+            case 'boolean':
+                return (bool) $value;
+            case 'number':
+                return (float) $value;
+            default:
+                return $value;
+        }
+    }
+    
 }

+ 75 - 72
packages/Longyi/RewardPoints/src/Repositories/RewardPointRepository.php

@@ -28,83 +28,86 @@ class RewardPointRepository extends Repository
 
     public function addPoints($customerId, $type, $amount, $orderId = null, $detail = null)
     {
-        $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()
-            ]
-        );
-
-        $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([
-            '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
-        ]);
-
-        // Update customer points
-        $customerPoints->mw_reward_point = $newBalance;
-        $customerPoints->save();
-
-        return $history;
+        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([
+                '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
+            ]);
+
+            // Update customer points
+            $customerPoints->mw_reward_point = $newBalance;
+            $customerPoints->save();
+
+            return $history;
+        });
     }
-
     public function deductPoints($customerId, $amount, $orderId = null, $detail = null)
     {
-        $customerPoints = RewardPointCustomer::where('customer_id', $customerId)->first();
+        return \DB::transaction(function () use ($customerId, $amount, $orderId, $detail) {
+            $customerPoints = RewardPointCustomer::lockForUpdate()->where('customer_id', $customerId)->first();
 
-        if (!$customerPoints || $customerPoints->mw_reward_point < $amount) {
-            return false;
-        }
+            if (!$customerPoints || $customerPoints->mw_reward_point < $amount) {
+                return false;
+            }
 
-        $currentBalance = $customerPoints->mw_reward_point;
-        $newBalance = $currentBalance - $amount;
-
-        // Create history for deduction (using negative amount)
-        $history = $this->create([
-            'customer_id' => $customerId,
-            'type_of_transaction' => 0, // 0 for deduction
-            '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();
-
-        return $history;
+            $currentBalance = $customerPoints->mw_reward_point;
+            $newBalance = $currentBalance - $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
+            ]);
+
+            // Update customer points
+            $customerPoints->mw_reward_point = $newBalance;
+            $customerPoints->save();
+
+            return $history;
+        });
     }
 
     public function getHistory($customerId, $limit = 20)

+ 282 - 0
packages/Longyi/RewardPoints/src/Repositories/RewardPointSettingRepository.php

@@ -0,0 +1,282 @@
+<?php
+
+namespace Longyi\RewardPoints\Repositories;
+
+use Webkul\Core\Eloquent\Repository;
+use Longyi\RewardPoints\Models\RewardPointSetting;
+
+class RewardPointSettingRepository extends Repository
+{
+    public function model()
+    {
+        return RewardPointSetting::class;
+    }
+    
+    /**
+     * 获取所有配置分组
+     */
+    public function getGroups()
+    {
+        return $this->model
+            ->select('group')
+            ->distinct()
+            ->pluck('group')
+            ->toArray();
+    }
+    
+    /**
+     * 按分组获取配置
+     */
+    public function getSettingsByGroup($group)
+    {
+        return $this->model
+            ->where('group', $group)
+            ->where('is_enabled', true)
+            ->orderBy('sort_order')
+            ->get();
+    }
+    
+    /**
+     * 获取配置值
+     */
+    public function getConfigValue($code, $default = null)
+    {
+        $setting = $this->findOneByField('code', $code);
+        
+        if (!$setting) {
+            return $default;
+        }
+        
+        return $this->castValue($setting->value, $setting->type);
+    }
+    
+    /**
+     * 批量获取配置值
+     */
+    public function getConfigValues($codes)
+    {
+        $settings = $this->model
+            ->whereIn('code', $codes)
+            ->where('is_enabled', true)
+            ->get();
+        
+        $result = [];
+        foreach ($settings as $setting) {
+            $result[$setting->code] = $this->castValue($setting->value, $setting->type);
+        }
+        
+        return $result;
+    }
+    
+    /**
+     * 设置配置值
+     */
+    public function setConfigValue($code, $value)
+    {
+        return $this->updateOrCreate(
+            ['code' => $code],
+            ['value' => $value]
+        );
+    }
+    
+    /**
+     * 批量设置配置值
+     */
+    public function setConfigValues($values)
+    {
+        foreach ($values as $code => $value) {
+            $this->setConfigValue($code, $value);
+        }
+    }
+    
+    /**
+     * 类型转换
+     */
+    protected function castValue($value, $type)
+    {
+        if ($value === null) {
+            return null;
+        }
+        
+        switch ($type) {
+            case 'boolean':
+                return (bool) $value;
+            case 'number':
+                return (float) $value;
+            default:
+                return $value;
+        }
+    }
+    
+    /**
+     * 初始化默认配置
+     */
+    public function initializeDefaultSettings()
+    {
+        $defaultSettings = $this->getDefaultSettings();
+        
+        foreach ($defaultSettings as $setting) {
+            $this->model->firstOrCreate(
+                ['code' => $setting['code']],
+                $setting
+            );
+        }
+    }
+    
+    /**
+     * 获取默认配置列表
+     */
+    protected function getDefaultSettings()
+    {
+        return [
+            // 通用设置
+            [
+                'code' => 'reward_enabled',
+                'name' => '启用积分系统',
+                'value' => '1',
+                'type' => 'boolean',
+                'group' => 'general',
+                'sort_order' => 1,
+                'description' => '是否启用积分奖励系统'
+            ],
+            [
+                'code' => 'point_value',
+                'name' => '积分价值',
+                'value' => '0.01',
+                'type' => 'number',
+                'group' => 'general',
+                'sort_order' => 2,
+                'description' => '每个积分的价值(元)'
+            ],
+            [
+                'code' => 'max_discount_percentage',
+                'name' => '最大抵扣比例',
+                'value' => '100',
+                'type' => 'number',
+                'group' => 'general',
+                'sort_order' => 3,
+                'description' => '订单最大可抵扣百分比(%)'
+            ],
+            [
+                'code' => 'min_points_to_redeem',
+                'name' => '最低使用积分',
+                'value' => '100',
+                'type' => 'number',
+                'group' => 'general',
+                'sort_order' => 4,
+                'description' => '最低可使用的积分数量'
+            ],
+            
+            // 签到设置
+            [
+                'code' => 'signin_base_points',
+                'name' => '基础签到积分',
+                'value' => '10',
+                'type' => 'number',
+                'group' => 'signin',
+                'sort_order' => 1,
+                'description' => '每日签到的基础积分'
+            ],
+            [
+                'code' => 'signin_week_bonus',
+                'name' => '周奖励积分',
+                'value' => '20',
+                'type' => 'number',
+                'group' => 'signin',
+                'sort_order' => 2,
+                'description' => '连续签到 7 天的额外奖励积分'
+            ],
+            [
+                'code' => 'signin_month_bonus',
+                'name' => '月奖励积分',
+                'value' => '100',
+                'type' => 'number',
+                'group' => 'signin',
+                'sort_order' => 3,
+                'description' => '连续签到 30 天的额外奖励积分'
+            ],
+            
+            // 订单设置
+            [
+                'code' => 'order_points_per_currency',
+                'name' => '每元获得积分',
+                'value' => '10',
+                'type' => 'number',
+                'group' => 'order',
+                'sort_order' => 1,
+                'description' => '每消费 1 元获得的积分数量'
+            ],
+            [
+                'code' => 'order_min_amount',
+                'name' => '最低订单金额',
+                'value' => '0',
+                'type' => 'number',
+                'group' => 'order',
+                'sort_order' => 2,
+                'description' => '获得积分的最低订单金额要求'
+            ],
+            
+            // 注册设置
+            [
+                'code' => 'registration_points',
+                'name' => '注册赠送积分',
+                'value' => '100',
+                'type' => 'number',
+                'group' => 'registration',
+                'sort_order' => 1,
+                'description' => '新客户注册赠送的积分'
+            ],
+            
+            // 评价设置
+            [
+                'code' => 'review_points',
+                'name' => '评价赠送积分',
+                'value' => '50',
+                'type' => 'number',
+                'group' => 'review',
+                'sort_order' => 1,
+                'description' => '发表商品评价获得的积分'
+            ],
+            [
+                'code' => 'review_require_approval',
+                'name' => '评价需要审核',
+                'value' => '1',
+                'type' => 'boolean',
+                'group' => 'review',
+                'sort_order' => 2,
+                'description' => '是否需要审核通过后才给积分'
+            ],
+            
+            // 推荐设置
+            [
+                'code' => 'referral_points',
+                'name' => '推荐好友积分',
+                'value' => '200',
+                'type' => 'number',
+                'group' => 'referral',
+                'sort_order' => 1,
+                'description' => '成功推荐好友获得的积分'
+            ],
+            [
+                'code' => 'referral_require_order',
+                'name' => '需要首单',
+                'value' => '1',
+                'type' => 'boolean',
+                'group' => 'referral',
+                'sort_order' => 2,
+                'description' => '是否需要好友完成首单才给积分'
+            ],
+            
+            // 生日设置
+            [
+                'code' => 'birthday_points',
+                'name' => '生日赠送积分',
+                'value' => '500',
+                'type' => 'number',
+                'group' => 'birthday',
+                'sort_order' => 1,
+                'description' => '客户生日赠送的积分'
+            ],
+        ];
+    }
+}

+ 109 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/settings/index.blade.php

@@ -0,0 +1,109 @@
+<x-admin::layouts>
+    <x-slot:title>
+        @lang('积分系统配置')
+    </x-slot>
+
+    <div class="content">
+        <div class="flex justify-between items-center gap-2.5 mb-6">
+            <p class="text-xl text-gray-800 dark:text-white font-bold">
+                @lang('积分系统配置')
+            </p>
+        </div>
+
+        @if(session('success'))
+            <div class="alert alert-success mb-4">
+                {{ session('success') }}
+            </div>
+        @endif
+
+        @if(session('error'))
+            <div class="alert alert-danger mb-4">
+                {{ session('error') }}
+            </div>
+        @endif
+
+        {{-- 分组标签页 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow mb-6">
+            <div class="flex border-b border-gray-200 dark:border-gray-800 overflow-x-auto">
+                @foreach($groups as $group)
+                    <a href="{{ route('admin.reward-points.settings.index', ['group' => $group]) }}"
+                       class="px-6 py-3 text-sm font-medium whitespace-nowrap {{ $currentGroup === $group ? 'border-b-2 border-blue-600 text-blue-600' : 'text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200' }}">
+                        @lang($this->getGroupName($group))
+                    </a>
+                @endforeach
+            </div>
+        </div>
+
+        {{-- 配置表单 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow">
+            <form method="POST" action="{{ route('admin.reward-points.settings.save') }}">
+                @csrf
+                <input type="hidden" name="group" value="{{ $currentGroup }}">
+
+                <div class="p-6">
+                    @if($settings->isEmpty())
+                        <p class="text-gray-500">暂无配置项</p>
+                    @else
+                        @foreach($settings as $setting)
+                            <div class="mb-6">
+                                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
+                                    {{ $setting->name }}
+                                    @if($setting->description)
+                                        <span class="text-xs text-gray-500 block mt-1">{{ $setting->description }}</span>
+                                    @endif
+                                </label>
+
+                                @switch($setting->type)
+                                    @case('boolean')
+                                        <select name="settings[{{ $setting->code }}]" 
+                                                class="control w-full">
+                                            <option value="1" {{ $setting->value == '1' ? 'selected' : '' }}>启用</option>
+                                            <option value="0" {{ $setting->value == '0' ? 'selected' : '' }}>禁用</option>
+                                        </select>
+                                        @break
+
+                                    @case('number')
+                                        <input type="number" 
+                                               step="any"
+                                               name="settings[{{ $setting->code }}]" 
+                                               value="{{ old('settings.' . $setting->code, $setting->value) }}"
+                                               class="control w-full"
+                                               @if($setting->code === 'point_value') step="0.01" @endif
+                                               @if(in_array($setting->code, ['max_discount_percentage'])) min="0" max="100" @endif>
+                                        @break
+
+                                    @case('select')
+                                        <select name="settings[{{ $setting->code }}]" 
+                                                class="control w-full">
+                                            @foreach(($setting->options ?? []) as $key => $label)
+                                                <option value="{{ $key }}" {{ $setting->value == $key ? 'selected' : '' }}>
+                                                    {{ $label }}
+                                                </option>
+                                            @endforeach
+                                        </select>
+                                        @break
+
+                                    @default
+                                        <input type="text" 
+                                               name="settings[{{ $setting->code }}]" 
+                                               value="{{ old('settings.' . $setting->code, $setting->value) }}"
+                                               class="control w-full">
+                                @endswitch
+
+                                @error('settings.' . $setting->code)
+                                    <span class="text-red-500 text-xs mt-1">{{ $message }}</span>
+                                @enderror
+                            </div>
+                        @endforeach
+                    @endif
+                </div>
+
+                <div class="border-t border-gray-200 dark:border-gray-800 px-6 py-4 flex justify-end">
+                    <button type="submit" class="btn-primary">
+                        @lang('保存配置')
+                    </button>
+                </div>
+            </form>
+        </div>
+    </div>
+</x-admin::layouts>

+ 9 - 0
packages/Longyi/RewardPoints/src/Routes/routes.php

@@ -109,4 +109,13 @@ Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/reward-points
         'as' => 'admin.reward-points.reports.export',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@export'
     ]);
+    Route::get('settings', [
+        'as' => 'admin.reward-points.settings.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\SettingController@index'
+    ]);
+
+    Route::post('settings', [
+        'as' => 'admin.reward-points.settings.save',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\SettingController@save'
+    ]);
 });