Просмотр исходного кода

Merge branch 'dev-rewardPoints' into dev

bianjunhui 1 неделя назад
Родитель
Сommit
9cdffc87c6
42 измененных файлов с 4379 добавлено и 321 удалено
  1. 2 0
      packages/Longyi/RewardPoints/README.md
  2. 30 0
      packages/Longyi/RewardPoints/src/Console/Commands/CheckExpiredGrowthValues.php
  3. 223 0
      packages/Longyi/RewardPoints/src/Console/Commands/InitializeGrowthSettings.php
  4. 89 0
      packages/Longyi/RewardPoints/src/Console/Commands/TestRegistrationPoints.php
  5. 7 7
      packages/Longyi/RewardPoints/src/Database/Migrations/2026_01_01_000001_create_points_tables.php
  6. 91 0
      packages/Longyi/RewardPoints/src/Database/Migrations/2026_01_01_000002_create_points_tables.php
  7. 97 0
      packages/Longyi/RewardPoints/src/Database/Seeders/GrowthValueSeeder.php
  8. 80 0
      packages/Longyi/RewardPoints/src/Helpers/ApiResponse.php
  9. 427 0
      packages/Longyi/RewardPoints/src/Http/Controllers/Admin/GrowthValueController.php
  10. 30 30
      packages/Longyi/RewardPoints/src/Http/Controllers/Admin/RuleController.php
  11. 20 15
      packages/Longyi/RewardPoints/src/Http/Controllers/Admin/SettingController.php
  12. 5 6
      packages/Longyi/RewardPoints/src/Http/Controllers/Admin/TransactionController.php
  13. 175 0
      packages/Longyi/RewardPoints/src/Http/Controllers/GrowthValueController.php
  14. 226 60
      packages/Longyi/RewardPoints/src/Http/Controllers/RewardPointsController.php
  15. 95 0
      packages/Longyi/RewardPoints/src/Http/Middleware/ApiCustomerAuthenticate.php
  16. 96 8
      packages/Longyi/RewardPoints/src/Listeners/CustomerEvents.php
  17. 82 20
      packages/Longyi/RewardPoints/src/Listeners/OrderEvents.php
  18. 43 0
      packages/Longyi/RewardPoints/src/Models/GrowthValueCustomer.php
  19. 65 0
      packages/Longyi/RewardPoints/src/Models/GrowthValueHistory.php
  20. 4 3
      packages/Longyi/RewardPoints/src/Models/RewardActiveRule.php
  21. 1 3
      packages/Longyi/RewardPoints/src/Models/RewardPointCustomerSign.php
  22. 7 0
      packages/Longyi/RewardPoints/src/Providers/EventServiceProvider.php
  23. 19 17
      packages/Longyi/RewardPoints/src/Providers/RewardPointsServiceProvider.php
  24. 111 38
      packages/Longyi/RewardPoints/src/Repositories/RewardPointRepository.php
  25. 99 16
      packages/Longyi/RewardPoints/src/Repositories/RewardPointSettingRepository.php
  26. 39 15
      packages/Longyi/RewardPoints/src/Resources/lang/en/rewardpoints.php
  27. 180 0
      packages/Longyi/RewardPoints/src/Resources/lang/zh_CN/rewardpoints.php
  28. 86 0
      packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/adjust.blade.php
  29. 251 0
      packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/history.blade.php
  30. 396 0
      packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/index.blade.php
  31. 109 0
      packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/settings.blade.php
  32. 316 0
      packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/show.blade.php
  33. 8 8
      packages/Longyi/RewardPoints/src/Resources/views/admin/settings/index.blade.php
  34. 16 15
      packages/Longyi/RewardPoints/src/Resources/views/admin/transactions/index.blade.php
  35. 104 9
      packages/Longyi/RewardPoints/src/Routes/routes.php
  36. 442 0
      packages/Longyi/RewardPoints/src/Services/GrowthValueService.php
  37. 57 0
      packages/Longyi/RewardPoints/src/Services/LevelCalculationTrait.php
  38. 6 1
      packages/Webkul/Admin/src/DataGrids/Customers/GroupDataGrid.php
  39. 32 3
      packages/Webkul/Admin/src/Http/Controllers/Customers/CustomerGroupController.php
  40. 204 45
      packages/Webkul/Admin/src/Resources/views/customers/groups/index.blade.php
  41. 4 2
      packages/Webkul/BagistoApi/src/State/LoginProcessor.php
  42. 5 0
      packages/Webkul/Customer/src/Models/CustomerGroup.php

+ 2 - 0
packages/Longyi/RewardPoints/README.md

@@ -25,6 +25,8 @@ php artisan migrate
 
 ### 3. 初始化默认设置
 php artisan reward-points:init-settings
+php artisan reward-points:init-growth-settings
+
 ### 4. 清理缓存
 php artisan cache:clear      # 清除应用缓存
 php artisan config:clear     # 清除配置缓存

+ 30 - 0
packages/Longyi/RewardPoints/src/Console/Commands/CheckExpiredGrowthValues.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace Longyi\RewardPoints\Console\Commands;
+
+use Illuminate\Console\Command;
+use Longyi\RewardPoints\Services\GrowthValueService;
+
+class CheckExpiredGrowthValues extends Command
+{
+    protected $signature = 'growth-value:check-expired';
+
+    protected $description = '检查并处理过期的成长值';
+
+    protected $growthValueService;
+
+    public function __construct(GrowthValueService $growthValueService)
+    {
+        parent::__construct();
+        $this->growthValueService = $growthValueService;
+    }
+
+    public function handle()
+    {
+        $this->info('开始检查过期的成长值...');
+
+        $this->growthValueService->checkExpiredGrowthValues();
+
+        $this->info('过期成长值检查完成!');
+    }
+}

+ 223 - 0
packages/Longyi/RewardPoints/src/Console/Commands/InitializeGrowthSettings.php

@@ -0,0 +1,223 @@
+<?php
+
+namespace Longyi\RewardPoints\Console\Commands;
+
+use Illuminate\Console\Command;
+use Longyi\RewardPoints\Repositories\RewardPointSettingRepository;
+use Longyi\DynamicMenu\Models\MenuItem;
+use Webkul\Customer\Models\CustomerGroup;
+
+class InitializeGrowthSettings extends Command
+{
+    protected $signature = 'reward-points:init-growth-settings';
+
+    protected $description = 'Initialize reward points growth value settings and menu items';
+
+    protected $settingRepository;
+
+    public function __construct(RewardPointSettingRepository $settingRepository)
+    {
+        parent::__construct();
+        $this->settingRepository = $settingRepository;
+    }
+
+    public function handle()
+    {
+        $this->info('Initializing reward points growth value settings...');
+
+        try {
+            $this->initializeGrowthLevelsInCustomerGroups();
+
+            $this->initializeGrowthSettings();
+
+            $this->addMenuItems();
+
+            $this->info('✓ Growth value settings initialized successfully!');
+            $this->info('✓ Menu items added to admin panel!');
+            $this->info('You can now configure growth value in: Admin > Settings > Growth Value');
+
+            return 0;
+
+        } catch (\Exception $e) {
+            $this->error('Error initializing settings: ' . $e->getMessage());
+            $this->error($e->getTraceAsString());
+            return 1;
+        }
+    }
+
+    /**
+     * 在 ly_customer_groups 表中初始化成长值等级数据
+     */
+    protected function initializeGrowthLevelsInCustomerGroups()
+    {
+        $this->info('Initializing growth value levels in customer groups...');
+
+        $generalGroup = CustomerGroup::where('code', 'general')->first();
+        $wholesaleGroup = CustomerGroup::where('code', 'wholesale')->first();
+
+        if ($generalGroup) {
+            $generalGroup->update([
+                'min_growth_value' => 0,
+                'require_first_order' => false,
+                'growth_level_name' => 'General-V0',
+                'growth_discount_rate' => 0,
+                'growth_benefits' => ['基础会员服务'],
+                'growth_level_icon' => null,
+            ]);
+            $this->info('✓ General group updated with V0 level');
+        }
+
+        if ($generalGroup) {
+            CustomerGroup::updateOrCreate(
+                ['code' => 'general_v1'],
+                [
+                    'name' => '普通会员-V1',
+                    'min_growth_value' => 0,
+                    'require_first_order' => true,
+                    'growth_level_name' => 'V1',
+                    'growth_discount_rate' => 2,
+                    'growth_benefits' => ['完成首单升级', '享受2%额外折扣', '优先客服支持'],
+                    'growth_level_icon' => null,
+                    'is_user_defined' => 1,
+                ]
+            );
+            $this->info('✓ Created V1 level (General V1)');
+        }
+
+        if ($generalGroup) {
+            CustomerGroup::updateOrCreate(
+                ['code' => 'general_v2'],
+                [
+                    'name' => '普通会员-V2',
+                    'min_growth_value' => 1000,
+                    'require_first_order' => false,
+                    'growth_level_name' => 'V2',
+                    'growth_discount_rate' => 5,
+                    'growth_benefits' => ['享受5%额外折扣', '专属优惠券', '生日礼品', '优先发货'],
+                    'growth_level_icon' => null,
+                    'is_user_defined' => 1,
+                ]
+            );
+            $this->info('✓ Created V2 level (General V2)');
+        }
+
+        if ($wholesaleGroup) {
+            $wholesaleGroup->update([
+                'min_growth_value' => 5000,
+                'require_first_order' => false,
+                'growth_level_name' => 'V3',
+                'growth_discount_rate' => 10,
+                'growth_benefits' => ['享受10%额外折扣', '专属客服经理', '新品优先体验', '免费配送', '专属活动邀请'],
+                'growth_level_icon' => null,
+            ]);
+            $this->info('✓ Wholesale group updated with V3 level');
+        }
+
+        $this->info('✓ Growth value levels initialized in ly_customer_groups table');
+    }
+
+    /**
+     * 初始化成长值配置项
+     */
+    protected function initializeGrowthSettings()
+    {
+        $this->info('Initializing growth value settings...');
+
+        $settings = [
+            [
+                'code' => 'growth_value_ratio',
+                'name' => '成长值计算比例',
+                'value' => '1',
+                'type' => 'number',
+                'group' => 'growth_value',
+                'description' => '每消费1元获得的成长值数量',
+                'is_enabled' => true,
+            ],
+            [
+                'code' => 'growth_value_validity_days',
+                'name' => '成长值有效期(天)',
+                'value' => '365',
+                'type' => 'number',
+                'group' => 'growth_value',
+                'description' => '成长值的有效期,0表示永久有效',
+                'is_enabled' => true,
+            ],
+        ];
+
+        foreach ($settings as $settingData) {
+            \Longyi\RewardPoints\Models\RewardPointSetting::updateOrCreate(
+                ['code' => $settingData['code']],
+                $settingData
+            );
+        }
+
+        $this->info('✓ Growth value settings initialized');
+    }
+
+    /**
+     * 添加成长值模块的菜单项到动态菜单
+     */
+    protected function addMenuItems()
+    {
+        $this->info('Adding growth value menu items to dynamic menu...');
+
+        // 检查是否已存在成长值模块的菜单项
+        $existingParent = MenuItem::where('key', 'settings.growth-value')->first();
+
+        if ($existingParent) {
+            $this->warn('Growth value menu items already exist in dynamic menu.');
+            return;
+        }
+        // 创建父级菜单项
+        $parentItem = MenuItem::create([
+            'name' => '用户管理',
+            'key' => 'customers',
+            'route' => 'admin.customers.customers.index',
+            'icon' => 'icon-customer',
+            'sort_order' => 10,
+            'status' => 1,
+            'created_by' => 1,
+        ]);
+
+        // 创建子菜单项
+        $childItems = [
+            [
+                'name' => '会员成长值',
+                'key' => 'customers.growth-value',
+                'route' => 'admin.growth-value.index',
+                'icon' => 'icon-users',
+                'sort_order' => 5,
+            ],
+            [
+                'name' => '会员成长值记录',
+                'key' => 'customers.growth-history',
+                'route' => 'admin.growth-value.history',
+                'icon' => 'icon-list',
+                'sort_order' => 6,
+            ],
+            [
+                'name' => '成长值设置',
+                'key' => 'customers.growth-settings',
+                'route' => 'admin.growth-value.settings',
+                'icon' => 'icon-setting',
+                'sort_order' => 7,
+            ],
+        ];
+
+        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('✓ Growth value menu items added to dynamic menu successfully!');
+    }
+}
+

+ 89 - 0
packages/Longyi/RewardPoints/src/Console/Commands/TestRegistrationPoints.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace Longyi\RewardPoints\Console\Commands;
+
+use Illuminate\Console\Command;
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+use Webkul\Customer\Models\Customer;
+
+class TestRegistrationPoints extends Command
+{
+    protected $signature = 'reward-points:test-registration {email?}';
+
+    protected $description = 'Test registration points for a customer';
+
+    protected $rewardPointRepository;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        parent::__construct();
+        $this->rewardPointRepository = $rewardPointRepository;
+    }
+
+    public function handle()
+    {
+        $email = $this->argument('email');
+
+        if (!$email) {
+            // 获取最后一个注册的客户
+            $customer = Customer::orderBy('id', 'desc')->first();
+        } else {
+            $customer = Customer::where('email', $email)->first();
+        }
+
+        if (!$customer) {
+            $this->error('Customer not found!');
+            return 1;
+        }
+
+        $this->info("Testing registration points for customer: {$customer->name} ({$customer->email})");
+        $this->info("Customer ID: {$customer->id}");
+
+        // 检查是否有注册规则
+        $registrationRule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_REGISTRATION)
+            ->where('status', 1)
+            ->first();
+
+        if ($registrationRule) {
+            $this->info("Found registration rule:");
+            $this->table(
+                ['ID', 'Name', 'Points', 'Status'],
+                [[
+                    $registrationRule->id,
+                    $registrationRule->name,
+                    $registrationRule->reward_point,
+                    $registrationRule->status ? 'Active' : 'Inactive'
+                ]]
+            );
+        } else {
+            $this->warn("No active registration rule found. Using config value.");
+            $pointsFromConfig = config('rewardpoints.registration.points_per_registration', 100);
+            $this->info("Points from config: {$pointsFromConfig}");
+        }
+
+        // 手动触发注册积分
+        $this->info("\nManually adding registration points...");
+
+        try {
+            $event = new \stdClass();
+            $event->id = $customer->id;
+
+            $listener = new \Longyi\RewardPoints\Listeners\CustomerEvents($this->rewardPointRepository);
+            $listener->handleCustomerRegistration($customer);
+
+            $this->info("✓ Points added successfully!");
+
+            // 显示当前积分
+            $currentPoints = $this->rewardPointRepository->getCustomerPoints($customer->id);
+            $this->info("Current points balance: {$currentPoints}");
+
+        } catch (\Exception $e) {
+            $this->error("Error: " . $e->getMessage());
+            $this->error($e->getTraceAsString());
+            return 1;
+        }
+
+        return 0;
+    }
+}

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

@@ -38,7 +38,7 @@ return new class extends Migration
                 $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');
             });
         }
@@ -51,9 +51,9 @@ return new class extends Migration
                 $table->unsignedInteger('count_date');
                 $table->unsignedInteger('point');
                 $table->string('code', 255)->default('');
-                $table->dateTime('created')->nullable();
-                $table->dateTime('updated')->nullable();
-                
+                $table->dateTime('created_at')->nullable();
+                $table->dateTime('updated_at')->nullable();
+
                 $table->index('customer_id', 'customer_id_index');
             });
         }
@@ -73,13 +73,13 @@ return new class extends Migration
                 $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');
             });
         }
@@ -96,7 +96,7 @@ return new class extends Migration
                 $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');
             });

+ 91 - 0
packages/Longyi/RewardPoints/src/Database/Migrations/2026_01_01_000002_create_points_tables.php

@@ -0,0 +1,91 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        // 为 ly_customer_groups 表添加成长值相关字段
+        if (!Schema::hasColumn('customer_groups', 'min_growth_value')) {
+            Schema::table('customer_groups', function (Blueprint $table) {
+                $table->integer('min_growth_value')->default(0)->after('is_user_defined')->comment('最低成长值要求');
+                $table->boolean('require_first_order')->default(false)->after('min_growth_value')->comment('是否需要完成首单');
+                $table->string('growth_level_name', 100)->nullable()->after('require_first_order')->comment('成长值等级显示名称,如V1、V2');
+                $table->integer('growth_discount_rate')->default(0)->after('growth_level_name')->comment('成长值等级额外折扣率(百分比)');
+                $table->text('growth_benefits')->nullable()->after('growth_discount_rate')->comment('成长值等级权益说明(JSON格式)');
+                $table->string('growth_level_icon', 255)->nullable()->after('growth_benefits')->comment('成长值等级图标URL');
+            });
+
+            echo "✓ Added growth value columns to ly_customer_groups table\n";
+        } else {
+            echo "✓ Growth value columns already exist in ly_customer_groups table\n";
+        }
+
+        if (!Schema::hasTable('mw_growth_value_customer')) {
+            Schema::create('mw_growth_value_customer', function (Blueprint $table) {
+                $table->increments('id');
+                $table->unsignedInteger('customer_id');
+                $table->unsignedBigInteger('growth_value')->default(0)->comment('当前成长值');
+                $table->string('growth_level', 20)->default('V0')->comment('成长值等级:V0, V1, V2, V3...');
+                $table->timestamp('first_order_completed_at')->nullable()->comment('首单完成时间');
+                $table->timestamp('level_updated_at')->nullable()->comment('等级更新时间');
+                $table->timestamp('growth_value_expires_at')->nullable()->comment('成长值过期时间');
+                $table->timestamps();
+
+                $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade');
+                $table->unique('customer_id', 'uk_customer_id');
+                $table->index('growth_level', 'idx_growth_level');
+            });
+        }
+
+        if (!Schema::hasTable('mw_growth_value_history')) {
+            Schema::create('mw_growth_value_history', function (Blueprint $table) {
+                $table->increments('history_id');
+                $table->unsignedInteger('customer_id');
+                $table->unsignedInteger('order_id')->nullable()->comment('关联订单ID');
+                $table->decimal('order_amount', 12, 4)->default(0)->comment('订单金额');
+                $table->integer('growth_value_earned')->default(0)->comment('获得的成长值');
+                $table->integer('total_growth_value')->default(0)->comment('累计成长值');
+                $table->string('level_before', 20)->nullable()->comment('升级前等级');
+                $table->string('level_after', 20)->nullable()->comment('升级后等级');
+                $table->string('event_type', 50)->default('order')->comment('事件类型:order, admin_adjust');
+                $table->text('description')->nullable()->comment('描述');
+                $table->timestamp('expires_at')->nullable()->comment('过期时间');
+                $table->timestamp('created_at')->nullable();
+
+                $table->index('customer_id', 'idx_customer_id');
+                $table->index('order_id', 'idx_order_id');
+                $table->index('created_at', 'idx_created_at');
+
+                $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade');
+            });
+        }
+
+    }
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        if (Schema::hasColumn('customer_groups', 'min_growth_value')) {
+            Schema::table('customer_groups', function (Blueprint $table) {
+                $table->dropColumn([
+                    'min_growth_value',
+                    'require_first_order',
+                    'growth_level_name',
+                    'growth_discount_rate',
+                    'growth_benefits',
+                    'growth_level_icon',
+                ]);
+            });
+        }
+        Schema::dropIfExists('mw_growth_value_history');
+        Schema::dropIfExists('mw_growth_value_customer');
+    }
+};

+ 97 - 0
packages/Longyi/RewardPoints/src/Database/Seeders/GrowthValueSeeder.php

@@ -0,0 +1,97 @@
+<?php
+namespace Longyi\RewardPoints\Database\Seeders;
+use Illuminate\Database\Seeder;
+use Longyi\RewardPoints\Models\GrowthValueLevel;
+use Longyi\RewardPoints\Models\RewardPointSetting;
+
+class GrowthValueSeeder extends Seeder
+{
+    public function run()
+    {
+        $levels = [
+            [
+                'level_code' => 'V0',
+                'level_name' => '普通会员',
+                'min_growth_value' => 0,
+                'max_growth_value' => null,
+                'require_first_order' => false,
+                'discount_rate' => 0,
+                'benefits' => ['基础会员服务'],
+                'icon' => null,
+                'sort_order' => 0,
+                'is_active' => true,
+            ],
+            [
+                'level_code' => 'V1',
+                'level_name' => '铜牌会员',
+                'min_growth_value' => 0,
+                'max_growth_value' => 999,
+                'require_first_order' => true,
+                'discount_rate' => 2,
+                'benefits' => ['完成首单升级', '享受2%额外折扣', '优先客服支持'],
+                'icon' => null,
+                'sort_order' => 1,
+                'is_active' => true,
+            ],
+            [
+                'level_code' => 'V2',
+                'level_name' => '银牌会员',
+                'min_growth_value' => 1000,
+                'max_growth_value' => 4999,
+                'require_first_order' => false,
+                'discount_rate' => 5,
+                'benefits' => ['享受5%额外折扣', '专属优惠券', '生日礼品', '优先发货'],
+                'icon' => null,
+                'sort_order' => 2,
+                'is_active' => true,
+            ],
+            [
+                'level_code' => 'V3',
+                'level_name' => '金牌会员',
+                'min_growth_value' => 5000,
+                'max_growth_value' => null,
+                'require_first_order' => false,
+                'discount_rate' => 10,
+                'benefits' => ['享受10%额外折扣', '专属客服经理', '新品优先体验', '免费配送', '专属活动邀请'],
+                'icon' => null,
+                'sort_order' => 3,
+                'is_active' => true,
+            ],
+        ];
+
+        foreach ($levels as $levelData) {
+            GrowthValueLevel::updateOrCreate(
+                ['level_code' => $levelData['level_code']],
+                $levelData
+            );
+        }
+
+        $settings = [
+            [
+                'code' => 'growth_value_ratio',
+                'name' => '成长值计算比例',
+                'value' => '1',
+                'type' => 'number',
+                'group' => 'growth_value',
+                'description' => '每消费1元获得的成长值数量',
+                'is_enabled' => true,
+            ],
+            [
+                'code' => 'growth_value_validity_days',
+                'name' => '成长值有效期(天)',
+                'value' => '365',
+                'type' => 'number',
+                'group' => 'growth_value',
+                'description' => '成长值的有效期,0表示永久有效',
+                'is_enabled' => true,
+            ],
+        ];
+
+        foreach ($settings as $settingData) {
+            RewardPointSetting::updateOrCreate(
+                ['code' => $settingData['code']],
+                $settingData
+            );
+        }
+    }
+}

+ 80 - 0
packages/Longyi/RewardPoints/src/Helpers/ApiResponse.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace Longyi\RewardPoints\Helpers;
+use Illuminate\Pagination\LengthAwarePaginator;
+
+class ApiResponse
+{
+    /**
+     * 成功响应
+     */
+    public static function success($data = [], string $message = 'Success', int $code = 200)
+    {
+        return response()->json([
+            'success' => true,
+            'message' => $message,
+            'data' => $data
+        ], $code);
+    }
+    /**
+     * 带分页的成功响应
+     */
+    public static function paginated($data, LengthAwarePaginator $paginator, string $message = 'Success', int $code = 200)
+    {
+        return response()->json([
+            'success' => true,
+            'message' => $message,
+            'data' => $data,
+            'pagination' => [
+                'current_page' => $paginator->currentPage(),
+                'per_page' => $paginator->perPage(),
+                'total' => $paginator->total(),
+                'last_page' => $paginator->lastPage(),
+                'has_more' => $paginator->hasMorePages()
+            ]
+        ], $code);
+    }
+    /**
+     * 错误响应
+     */
+    public static function error(string $message, $data = [], int $code = 400)
+    {
+        return response()->json([
+            'success' => false,
+            'message' => $message,
+            'data' => $data
+        ], $code);
+    }
+
+    /**
+     * 未授权响应
+     */
+    public static function unauthorized(string $message = 'Please login first')
+    {
+        return self::error($message, [], 401);
+    }
+
+    /**
+     * 禁止访问响应
+     */
+    public static function forbidden(string $message = 'Access denied')
+    {
+        return self::error($message, [], 403);
+    }
+
+    /**
+     * 未找到响应
+     */
+    public static function notFound(string $message = 'Resource not found')
+    {
+        return self::error($message, [], 404);
+    }
+
+    /**
+     * 验证错误响应
+     */
+    public static function validationError(string $message, $errors = [])
+    {
+        return self::error($message, $errors, 422);
+    }
+}

+ 427 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/GrowthValueController.php

@@ -0,0 +1,427 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers\Admin;
+
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Carbon\Carbon;
+use Longyi\RewardPoints\Repositories\RewardPointSettingRepository;
+use Longyi\RewardPoints\Services\GrowthValueService;
+use Longyi\RewardPoints\Models\GrowthValueCustomer;
+use Longyi\RewardPoints\Models\GrowthValueHistory;
+use Webkul\Admin\Http\Controllers\Controller;
+use Webkul\Customer\Models\Customer;
+
+class GrowthValueController extends Controller
+{
+    protected GrowthValueService $growthValueService;
+    protected RewardPointSettingRepository $settingRepository;
+
+    // 允许的排序字段
+    private const ALLOWED_SORT_FIELDS = ['growth_value', 'created_at', 'updated_at'];
+
+    // 允许的排序方向
+    private const ALLOWED_SORT_DIRECTIONS = ['asc', 'desc'];
+
+    public function __construct(
+        GrowthValueService $growthValueService,
+        RewardPointSettingRepository $settingRepository
+    ) {
+        $this->growthValueService = $growthValueService;
+        $this->settingRepository = $settingRepository;
+    }
+
+    /**
+     * 显示所有会员的成长值列表
+     */
+    public function index(Request $request)
+    {
+        $query = GrowthValueCustomer::with('customer');
+
+        // 应用筛选条件
+        $query = $this->applyFilters($query, $request);
+
+        // 应用排序
+        $sortField = $request->get('sort', 'growth_value');
+        $sortDirection = $request->get('direction', 'desc');
+
+        if (in_array($sortField, self::ALLOWED_SORT_FIELDS) &&
+            in_array($sortDirection, self::ALLOWED_SORT_DIRECTIONS)) {
+            $query->orderBy($sortField, $sortDirection);
+        } else {
+            $query->orderBy('growth_value', 'desc');
+        }
+
+        $customers = $query->paginate(15)->withQueryString();
+
+        // 统计数据(使用缓存优化)
+        $stats = $this->getStatistics();
+
+        $view = $this->_config['view'] ?? 'rewardpoints::admin.growth-value.index';
+
+        return view($view, array_merge([
+            'customers' => $customers,
+        ], $stats));
+    }
+    public function history(Request $request)
+    {
+        $startDate = $request->get('start_date', Carbon::now()->subDays(30)->format('Y-m-d'));
+        $endDate = $request->get('end_date', Carbon::now()->format('Y-m-d'));
+        $customerEmail = $request->get('customer_email');
+        $eventType = $request->get('event_type');
+
+        $query = GrowthValueHistory::with('customer');
+
+        // 日期筛选
+        if ($startDate && $endDate) {
+            $startDateTime = Carbon::parse($startDate)->startOfDay();
+            $endDateTime = Carbon::parse($endDate)->endOfDay();
+            $query->whereBetween('created_at', [$startDateTime, $endDateTime]);
+        }
+
+        // 客户邮箱筛选
+        if ($customerEmail) {
+            $query->whereHas('customer', function ($q) use ($customerEmail) {
+                $q->where('email', 'like', "%{$customerEmail}%");
+            });
+        }
+
+        // 事件类型筛选
+        if ($eventType !== null && $eventType !== '') {
+            $query->where('event_type', $eventType);
+        }
+
+        $histories = $query->orderBy('created_at', 'desc')->paginate(20);
+
+        // 统计数据
+        $totalEarned = (clone $query)->where('growth_value_earned', '>', 0)->sum('growth_value_earned');
+        $totalDeducted = abs((clone $query)->where('growth_value_earned', '<', 0)->sum('growth_value_earned'));
+        $totalRecords = (clone $query)->count();
+
+        return view('rewardpoints::admin.growth-value.history', compact(
+            'histories',
+            'startDate',
+            'endDate',
+            'customerEmail',
+            'eventType',
+            'totalEarned',
+            'totalDeducted',
+            'totalRecords'
+        ));
+    }
+    /**
+     * 应用筛选条件
+     */
+    private function applyFilters($query, Request $request)
+    {
+        // 搜索客户
+        if ($search = $request->get('search')) {
+            $query->whereHas('customer', function ($q) use ($search) {
+                $q->where('email', 'like', "%{$search}%")
+                    ->orWhere('name', 'like', "%{$search}%");
+            });
+        }
+
+        // 等级筛选
+        if ($request->filled('growth_level')) {
+            $query->where('growth_level', $request->get('growth_level'));
+        }
+
+        // 成长值范围筛选
+        if ($request->filled('min_growth_value')) {
+            $query->where('growth_value', '>=', (int) $request->get('min_growth_value'));
+        }
+
+        if ($request->filled('max_growth_value')) {
+            $query->where('growth_value', '<=', (int) $request->get('max_growth_value'));
+        }
+
+        // 首单完成筛选
+        if ($request->filled('has_first_order')) {
+            if ($request->get('has_first_order') === 'yes') {
+                $query->whereNotNull('first_order_completed_at');
+            } else {
+                $query->whereNull('first_order_completed_at');
+            }
+        }
+
+        return $query;
+    }
+
+    /**
+     * 获取统计数据(带缓存)
+     */
+    private function getStatistics(): array
+    {
+        $cacheKey = 'growth_value_statistics';
+
+        return cache()->remember($cacheKey, 300, function () {
+            $totalCustomers = GrowthValueCustomer::count();
+            $totalGrowthValue = GrowthValueCustomer::sum('growth_value') ?? 0;
+
+            return [
+                'totalCustomers' => $totalCustomers,
+                'totalGrowthValue' => $totalGrowthValue,
+                'avgGrowthValue' => $totalCustomers > 0
+                    ? round($totalGrowthValue / $totalCustomers, 2)
+                    : 0,
+                'firstOrderCount' => GrowthValueCustomer::whereNotNull('first_order_completed_at')->count(),
+            ];
+        });
+    }
+
+    /**
+     * 配置页面
+     */
+    public function settings(Request $request)
+    {
+        $groups = $this->settingRepository->getGroups();
+        $currentGroup = $request->get('group', 'growth_value');
+        $settings = $this->settingRepository->getSettingsByGroup($currentGroup);
+
+        $groupData = $this->getGroupData($groups);
+
+        return view('rewardpoints::admin.growth-value.settings', compact(
+            'groups',
+            'currentGroup',
+            'settings',
+            'groupData'
+        ));
+    }
+
+    /**
+     * 保存配置
+     */
+    public function saveSettings(Request $request): JsonResponse
+    {
+        $validator = Validator::make($request->all(), [
+            'settings' => 'required|array',
+            'settings.*' => 'nullable|string|max:5000',
+        ]);
+
+        if ($validator->fails()) {
+            return response()->json([
+                'success' => false,
+                'message' => '验证失败',
+                'errors' => $validator->errors(),
+            ], 422);
+        }
+
+        try {
+            DB::transaction(function () use ($request) {
+                foreach ($request->input('settings') as $code => $value) {
+                    $this->settingRepository->setConfigValue($code, $value);
+                }
+            });
+
+            // 清除相关缓存
+            $this->clearSettingsCache();
+
+            session()->flash('success', '配置保存成功');
+
+            $redirectRoute = $this->_config['redirect'] ?? 'admin.growth-value.settings';
+            return response()->json([
+                'success' => true,
+                'redirect' => route($redirectRoute, ['group' => $request->input('group', 'general')]),
+            ]);
+
+        } catch (\Exception $e) {
+            Log::error('保存配置失败', [
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            return response()->json([
+                'success' => false,
+                'message' => '保存配置失败:' . $e->getMessage(),
+            ], 500);
+        }
+    }
+
+    /**
+     * 清除配置缓存
+     */
+    private function clearSettingsCache(): void
+    {
+        cache()->forget('reward_points_settings');
+        cache()->forget('growth_value_statistics');
+    }
+
+    /**
+     * 获取分组数据
+     */
+    protected function getGroupData(array $groups): array
+    {
+        $groupOrder = ['general' => 1];
+        $groupNames = [
+            'general' => '成长值设置',
+        ];
+
+        $groupData = [];
+        foreach ($groups as $group) {
+            $groupData[$group] = [
+                'name' => $groupNames[$group] ?? ucfirst($group),
+                'order' => $groupOrder[$group] ?? 99,
+            ];
+        }
+
+        uasort($groupData, fn($a, $b) => $a['order'] <=> $b['order']);
+
+        return $groupData;
+    }
+
+    /**
+     * 显示会员成长值详情
+     */
+    public function show($customerId, Request $request)
+    {
+        $customer = Customer::findOrFail($customerId);
+        $growthValueInfo = $this->growthValueService->getCustomerGrowthValue($customerId);
+        $history = GrowthValueHistory::where('customer_id', $customerId)
+            ->orderBy('created_at', 'desc')
+            ->take(10)  // 只取最近10条用于弹窗显示
+            ->get();
+
+        // 如果是 AJAX 请求,返回 JSON
+        if ($request->ajax() || $request->wantsJson()) {
+            return response()->json([
+                'success' => true,
+                'customer' => [
+                    'id' => $customer->id,
+                    'name' => $customer->name,
+                    'email' => $customer->email,
+                ],
+                'growth_value' => $growthValueInfo['growth_value'],
+                'growth_level' => $growthValueInfo['growth_level_name'],
+                'first_order_completed_at' => $growthValueInfo['first_order_completed'] ? $growthValueInfo['first_order_completed'] : null,
+                'expires_at' => $growthValueInfo['expires_at'] ? \Carbon\Carbon::parse($growthValueInfo['expires_at'])->format('Y-m-d H:i:s') : null,
+                'history' => $history->map(function($item) {
+                    return [
+                        'growth_value_earned' => $item->growth_value_earned,
+                        'description' => $item->description,
+                        'created_at' => $item->created_at->format('Y-m-d H:i:s'),
+                    ];
+                }),
+            ]);
+        }
+
+        // 普通请求返回视图
+        return view('rewardpoints::admin.growth-value.show', compact(
+            'growthValueInfo',
+            'history',
+            'customer'
+        ));
+    }
+
+    /**
+     * 从列表页调整成长值
+     */
+    public function adjustFromList(Request $request): JsonResponse
+    {
+        $validator = Validator::make($request->all(), [
+            'customer_identifier' => 'required',
+            'amount' => 'required|integer|min:1',
+            'action' => 'required|in:add,deduct',
+            'description' => 'nullable|string|max:500',
+        ]);
+
+        if ($validator->fails()) {
+            return response()->json([
+                'success' => false,
+                'message' => '验证失败',
+                'errors' => $validator->errors(),
+            ], 422);
+        }
+
+        try {
+            $customer = $this->findCustomerByIdentifier($request->get('customer_identifier'));
+
+            if (!$customer) {
+                return response()->json([
+                    'success' => false,
+                    'message' => '未找到该客户,请检查邮箱或ID是否正确',
+                ], 404);
+            }
+
+            $amount = (int) $request->get('amount');
+            if ($request->get('action') === 'deduct') {
+                $amount = -$amount;
+            }
+
+            $description = $request->get('description', '管理员调整');
+            $result = $this->growthValueService->adjustGrowthValue($customer->id, $amount, $description);
+
+            $message = $amount > 0
+                ? "成功增加 {$amount} 成长值"
+                : "成功扣减 " . abs($amount) . " 成长值";
+
+            return response()->json([
+                'success' => true,
+                'message' => $message,
+                'data' => [
+                    'new_value' => $result['history']->total_growth_value,
+                    'change' => $result['history']->growth_value_earned,
+                    'old_level' => $result['old_level'],
+                    'new_level' => $result['new_level'],
+                    'level_changed' => $result['level_changed'],
+                ],
+            ]);
+
+        } catch (\Exception $e) {
+            Log::error('成长值调整失败', [
+                'identifier' => $request->get('customer_identifier'),
+                'amount' => $request->get('amount'),
+                'error' => $e->getMessage(),
+            ]);
+
+            return response()->json([
+                'success' => false,
+                'message' => '调整失败:' . $e->getMessage(),
+            ], 500);
+        }
+    }
+
+    /**
+     * 根据标识符查找客户
+     */
+    private function findCustomerByIdentifier($identifier)
+    {
+        if (filter_var($identifier, FILTER_VALIDATE_EMAIL)) {
+            return Customer::where('email', $identifier)->first();
+        }
+
+        if (is_numeric($identifier)) {
+            return Customer::find((int) $identifier);
+        }
+
+        return null;
+    }
+
+    /**
+     * 批量处理过期成长值(可定时任务调用)
+     */
+    public function processExpiredGrowthValues(): JsonResponse
+    {
+        try {
+            $result = $this->growthValueService->checkExpiredGrowthValues();
+
+            return response()->json([
+                'success' => true,
+                'message' => "已处理 {$result['processed']} 条记录,其中 {$result['affected']} 条被清零",
+                'data' => $result,
+            ]);
+        } catch (\Exception $e) {
+            Log::error('批量处理过期成长值失败', [
+                'error' => $e->getMessage(),
+            ]);
+
+            return response()->json([
+                'success' => false,
+                'message' => '处理失败:' . $e->getMessage(),
+            ], 500);
+        }
+    }
+}

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

@@ -20,10 +20,10 @@ class RuleController extends Controller
      * Transaction type mapping with complete configuration
      */
     private const TRANSACTION_TYPES = [
-        1 => ['name' => 'Order', 'icon' => 'icon-shopping-cart', 'color' => 'blue'],
+        3 => ['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'],
+        4 => ['name' => 'Product Review', 'icon' => 'icon-star', 'color' => 'yellow'],
+        9 => ['name' => 'Login', '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'],
@@ -50,7 +50,7 @@ class RuleController extends Controller
     ];
 
     protected RewardPointRepository $rewardPointRepository;
-    
+
     protected array $_config;
 
     public function __construct(RewardPointRepository $rewardPointRepository)
@@ -67,13 +67,13 @@ class RuleController extends Controller
         $rules = RewardActiveRule::query()
             ->orderBy('rule_id', 'desc')
             ->paginate(15);
-        
+
         $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', 'transactionTypes', 'colorClasses'));
     }
 
@@ -83,7 +83,7 @@ class RuleController extends Controller
     public function create(): View
     {
         $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.create';
-        
+
         return view($view, [
             'transactionTypes' => self::TRANSACTION_TYPES,
             'storeViews' => $this->getStoreViews(),
@@ -102,7 +102,7 @@ class RuleController extends Controller
 
         // 处理 Store Views 数据
         $selectedStoreViews = $this->parseStoreViews($rule);
-        
+
         // 如果规则适用于所有店铺(store_view = '0'),则转换为 'all' 选项
         $selectedStoreViewsForSelect = [];
         if ($rule->store_view === '0' || empty($selectedStoreViews)) {
@@ -110,15 +110,15 @@ class RuleController extends Controller
         } else {
             $selectedStoreViewsForSelect = $selectedStoreViews;
         }
-        
+
         // 处理客户群组数据
         $selectedCustomerGroupsArray = $this->parseCustomerGroups($rule);
-        $selectedCustomerGroupsPoints = $rule->enable_different_points_by_group 
+        $selectedCustomerGroupsPoints = $rule->enable_different_points_by_group
             ? (json_decode($rule->customer_group_ids, true) ?: [])
             : [];
 
         $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.edit';
-        
+
         return view($view, [
             'rule' => $rule,
             'transactionTypes' => self::TRANSACTION_TYPES,
@@ -143,7 +143,7 @@ class RuleController extends Controller
             RewardActiveRule::create($data);
 
             session()->flash('success', 'Reward rule created successfully.');
-            
+
             return $this->redirectToIndex();
         } catch (Exception $e) {
             session()->flash('error', 'Error creating reward rule: ' . $e->getMessage());
@@ -151,7 +151,7 @@ class RuleController extends Controller
         }
     }
 
-   
+
     /**
      * Update the specified reward rule
      */
@@ -162,14 +162,14 @@ class RuleController extends Controller
 
         try {
             $data = $this->prepareDataForSave($request, $validated);
-            
+
             // 确保 enable_different_points_by_group 字段被正确设置
             $data['enable_different_points_by_group'] = $request->boolean('enable_different_points_by_group');
-            
+
             $rule->update($data);
 
             session()->flash('success', 'Reward rule updated successfully.');
-            
+
             return $this->redirectToIndex();
         } catch (Exception $e) {
             session()->flash('error', 'Error updating reward rule: ' . $e->getMessage());
@@ -304,12 +304,12 @@ class RuleController extends Controller
     if (empty($storeViews) || $storeViews === '0') {
         return '0';
     }
-    
+
     // 如果是单个值,直接返回
     if (is_scalar($storeViews) && !empty($storeViews)) {
         return (string)$storeViews;
     }
-    
+
     return '0';
 }
 
@@ -320,12 +320,12 @@ class RuleController extends Controller
     {
         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) {
@@ -338,7 +338,7 @@ class RuleController extends Controller
                     }
                 }
             }
-            
+
             $data['customer_group_ids'] = !empty($filteredPoints) ? json_encode($filteredPoints) : '{}';
             $data['reward_point'] = 0;
         } else {
@@ -365,7 +365,7 @@ class RuleController extends Controller
             }
             return !empty($filtered) ? implode(',', array_unique($filtered)) : '';
         }
-        
+
         return '';
     }
 
@@ -381,7 +381,7 @@ class RuleController extends Controller
                 return !empty($view);
             });
         }
-        
+
         return [];
     }
 
@@ -404,7 +404,7 @@ class RuleController extends Controller
             }
             return [];
         }
-        
+
         if ($rule->customer_group_ids && $rule->customer_group_ids !== '') {
             $groups = explode(',', $rule->customer_group_ids);
             // 过滤空值
@@ -412,7 +412,7 @@ class RuleController extends Controller
                 return !empty($group);
             });
         }
-        
+
         return [];
     }
 
@@ -424,11 +424,11 @@ class RuleController extends Controller
         try {
             $channels = Channel::all();
             $storeViews = [];
-            
+
             foreach ($channels as $channel) {
                 $storeViews[$channel->code] = $channel->name;
             }
-            
+
             return $storeViews;
         } catch (Exception $e) {
             return ['0' => 'All Stores'];
@@ -443,11 +443,11 @@ class RuleController extends Controller
         try {
             $groups = CustomerGroup::orderBy('id')->get();
             $customerGroups = [];
-            
+
             foreach ($groups as $group) {
                 $customerGroups[$group->id] = $group->name;
             }
-            
+
             return $customerGroups;
         } catch (Exception $e) {
             return ['0' => 'All Groups'];
@@ -487,4 +487,4 @@ class RuleController extends Controller
         $route = $this->_config['redirect'] ?? 'admin.reward-points.rules.index';
         return redirect()->route($route);
     }
-}
+}

+ 20 - 15
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/SettingController.php

@@ -26,16 +26,17 @@ class SettingController extends Controller
         $groups = $this->settingRepository->getGroups();
         $currentGroup = request('group', 'general');
         $settings = $this->settingRepository->getSettingsByGroup($currentGroup);
-        
+
         // 获取分组数据(包含名称和排序)
         $groupData = $this->getGroupData($groups);
-        
+
         // 确保 _config 存在且包含必要的键
         $view = isset($this->_config['view']) ? $this->_config['view'] : 'rewardpoints::admin.settings.index';
-        
+
         return view($view, compact('groups', 'currentGroup', 'settings', 'groupData'));
     }
 
+
     /**
      * 保存配置
      */
@@ -44,28 +45,28 @@ class SettingController extends Controller
         $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', '配置保存成功');
-            
+
             $redirectRoute = isset($this->_config['redirect']) ? $this->_config['redirect'] : 'admin.reward-points.settings.index';
             return redirect()->route($redirectRoute, ['group' => $request->input('group', 'general')]);
-            
+
         } catch (\Exception $e) {
             session()->flash('error', '保存配置失败:' . $e->getMessage());
             return redirect()->back()->withInput();
         }
     }
-    
+
     /**
      * 获取分组数据(包含名称和排序)
      */
@@ -75,23 +76,26 @@ class SettingController extends Controller
         $groupOrder = [
             'general' => 1,      // 通用设置
         ];
-        
+
         $groupData = [];
         foreach ($groups as $group) {
+            if($group=='growth_value'){
+                continue;
+            }
             $groupData[$group] = [
                 'name' => $this->getGroupName($group),
                 'order' => $groupOrder[$group] ?? 99, // 如果未定义顺序,默认排在最后
             ];
         }
-        
+
         // 按照排序顺序排列
         uasort($groupData, function($a, $b) {
             return $a['order'] <=> $b['order'];
         });
-        
+
         return $groupData;
     }
-    
+
     /**
      * 获取分组名称
      */
@@ -99,7 +103,8 @@ class SettingController extends Controller
     {
         $names = [
             'general' => '通用设置',
+            'sign_in' => '签到',
         ];
         return $names[$group] ?? ucfirst($group);
     }
-}
+}

+ 5 - 6
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/TransactionController.php

@@ -95,8 +95,6 @@ class TransactionController extends Controller
             'totalTransactions'
         ));
     }
-// ... existing code ...
-
 
     /**
      * Export transactions
@@ -161,14 +159,15 @@ class TransactionController extends Controller
             ]);
 
             $typeNames = [
-                1 => 'Order',
+                3 => 'Order',
                 2 => 'Registration',
-                3 => 'Product Review',
-                4 => 'Daily Sign In',
+                4 => 'Product Review',
+                1 => 'Daily Sign In',
                 5 => 'Referral',
                 6 => 'Birthday',
+                7 => 'Share',
                 8 => 'Subscribe',
-                0 => 'Admin Adjustment',
+                9 => 'login',
                 99 => 'Admin Action'
             ];
 

+ 175 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/GrowthValueController.php

@@ -0,0 +1,175 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers;
+
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller;
+use Longyi\RewardPoints\Models\GrowthValueHistory;
+use Longyi\RewardPoints\Services\GrowthValueService;
+use Longyi\RewardPoints\Helpers\ApiResponse;
+use Webkul\Customer\Models\CustomerGroup;
+
+class GrowthValueController extends Controller
+{
+    protected $growthValueService;
+
+    public function __construct(GrowthValueService $growthValueService)
+    {
+        $this->growthValueService = $growthValueService;
+    }
+
+    /**
+     * Get customer growth value information
+     *
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function index()
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return ApiResponse::unauthorized();
+        }
+
+        // 获取当前用户的成长值信息
+        $growthValueInfo = $this->growthValueService->getCustomerGrowthValue($customer->id);
+        $currentLevelName = $growthValueInfo['growth_level_name'];
+
+        // 获取所有配置了成长值的客户组,按最低成长值排序
+        $allLevels = CustomerGroup::whereNotNull('growth_level_name')
+            ->orderBy('min_growth_value', 'asc')
+            ->get()
+            ->map(function ($group) use ($currentLevelName) {
+                return [
+                    'id' => $group->id,
+                    'name' => $group->name,
+                    'code' => $group->code,
+                    'growth_level_name' => $group->growth_level_name,
+                    'min_growth_value' => $group->min_growth_value,
+                    'benefits' => is_string($group->growth_benefits)
+                        ? json_decode($group->growth_benefits, true)
+                        : ($group->growth_benefits ?? []),
+                    'icon' => $group->growth_level_icon ?? null,
+                    'require_first_order' => (bool) $group->require_first_order,
+                    'is_current' => $group->growth_level_name === $currentLevelName,
+                ];
+            });
+
+        return ApiResponse::success([
+            'growth_value' => $growthValueInfo['growth_value'],
+            'current_level' => [
+                'name' => $growthValueInfo['growth_level_name'],
+                'benefits' => is_string($growthValueInfo['growth_benefits'])
+                    ? json_decode($growthValueInfo['growth_benefits'], true)
+                    : $growthValueInfo['growth_benefits'],
+                'icon' => $growthValueInfo['growth_level_icon'] ?? null,
+            ],
+            'next_level' => $growthValueInfo['next_level'],
+            'growth_to_next_level' => $growthValueInfo['points_to_next_level'],
+            'all_levels' => $allLevels,
+        ]);
+    }
+
+
+    /**
+     * Get customer growth value history
+     *
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function history(Request $request)
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return ApiResponse::unauthorized();
+        }
+
+        // Get pagination parameters
+        $page = $request->input('page', 1);
+        $limit = $request->input('per_page', 20);
+
+        // Validate limit
+        $limit = min(max($limit, 1), 100);
+
+        // Get paginated history
+        $history = GrowthValueHistory::where('customer_id', $customer->id)
+            ->orderBy('created_at', 'desc')
+            ->paginate($limit, ['*'], 'page', $page);
+
+        // Transform history data
+        $transformedHistory = collect($history->items())->map(function ($item) {
+            return [
+                'id' => $item->history_id,
+                'event_type' => $item->event_type,
+                'event_type_text' => $this->getEventTypeText($item->event_type),
+                'growth_value_earned' => $item->growth_value_earned,
+                'total_growth_value' => $item->total_growth_value,
+                'order_id' => $item->order_id,
+                'order_amount' => $item->order_amount,
+                'description' => $item->description,
+                'expires_at' => $item->expires_at ? Carbon::parse($item->expires_at)->format('Y-m-d H:i:s') : null,
+                'created_at' => Carbon::parse($item->created_at)->format('Y-m-d H:i:s'),
+            ];
+        });
+
+        return ApiResponse::paginated(
+            [
+                'current_growth_value' => $this->growthValueService->getCustomerGrowthValue($customer->id)['growth_value'],
+                'history' => $transformedHistory
+            ],
+            $history
+        );
+    }
+    public function levels()
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return ApiResponse::unauthorized();
+        }
+
+        // 获取当前用户的成长值信息
+        $growthValueInfo = $this->growthValueService->getCustomerGrowthValue($customer->id);
+        $currentLevelName = $growthValueInfo['growth_level_name'];
+
+        // 获取所有配置了成长值的客户组,按最低成长值排序
+        $levels = CustomerGroup::whereNotNull('growth_level_name')
+            ->orderBy('min_growth_value', 'asc')
+            ->get()
+            ->map(function ($group) use ($currentLevelName) {
+                return [
+                    'id' => $group->id,
+                    'name' => $group->name,
+                    'code' => $group->code,
+                    'growth_level_name' => $group->growth_level_name,
+                    'min_growth_value' => $group->min_growth_value,
+                    'benefits' => is_string($group->growth_benefits)
+                        ? json_decode($group->growth_benefits, true)
+                        : ($group->growth_benefits ?? []),
+                    'icon' => $group->growth_level_icon ?? null,
+                    'require_first_order' => (bool) $group->require_first_order,
+                    'is_current' => $group->growth_level_name === $currentLevelName,
+                ];
+            });
+
+        return ApiResponse::success([
+            'levels' => $levels,
+            'current_level' => $currentLevelName,
+        ]);
+    }
+    /**
+     * Get growth event type text
+     */
+    protected function getEventTypeText($type)
+    {
+        $types = [
+            GrowthValueHistory::EVENT_TYPE_ORDER => 'Order Purchase',
+            GrowthValueHistory::EVENT_TYPE_ADMIN_ADJUST => 'Admin Adjustment',
+            GrowthValueHistory::EVENT_TYPE_EXPIRATION => 'Expired',
+        ];
+
+        return $types[$type] ?? 'Unknown';
+    }
+}

+ 226 - 60
packages/Longyi/RewardPoints/src/Http/Controllers/RewardPointsController.php

@@ -2,22 +2,28 @@
 
 namespace Longyi\RewardPoints\Http\Controllers;
 
+use Illuminate\Support\Facades\Log;
+use Longyi\RewardPoints\Models\RewardActiveRule;
 use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Repositories\RewardPointSettingRepository;
 use Longyi\RewardPoints\Models\RewardPointCustomerSign;
-use Longyi\RewardPoints\Models\RewardActiveRule;
+use Longyi\RewardPoints\Helpers\ApiResponse;
 use Carbon\Carbon;
 use Illuminate\Http\Request;
 use Illuminate\Routing\Controller;
-use Webkul\User\Repositories\RoleRepository;
-use Illuminate\Support\Facades\DB;
+use Longyi\RewardPoints\Models\RewardPointHistory;
 class RewardPointsController extends Controller
 {
     protected $rewardPointRepository;
+    protected $settingRepository;
     protected $_config;
 
-    public function __construct(RewardPointRepository $rewardPointRepository)
-    {
+    public function __construct(
+        RewardPointRepository $rewardPointRepository,
+        RewardPointSettingRepository $settingRepository
+    ) {
         $this->rewardPointRepository = $rewardPointRepository;
+        $this->settingRepository = $settingRepository;
         $this->_config = request('_config');
     }
 
@@ -40,7 +46,13 @@ class RewardPointsController extends Controller
         $customer = auth()->guard('customer')->user();
 
         if (!$customer) {
-            return response()->json(['error' => 'Please login first'], 401);
+            return ApiResponse::unauthorized();
+        }
+
+        // Check if sign in feature is enabled
+        $signinEnabled = $this->settingRepository->getConfigValue('signin_enabled', true);
+        if (!$signinEnabled) {
+            return ApiResponse::forbidden('Sign in feature is disabled');
         }
 
         $today = Carbon::now()->format('Y-m-d');
@@ -50,7 +62,7 @@ class RewardPointsController extends Controller
             ->first();
 
         if ($existingSign) {
-            return response()->json(['error' => 'Already signed in today'], 400);
+            return ApiResponse::error('Already signed in today');
         }
 
         $lastSign = RewardPointCustomerSign::where('customer_id', $customer->id)
@@ -61,7 +73,7 @@ class RewardPointsController extends Controller
         if ($lastSign) {
             $lastSignDate = Carbon::parse($lastSign->sign_date);
             $yesterday = Carbon::yesterday();
-            
+
             if ($lastSignDate->toDateString() === $yesterday->toDateString()) {
                 $countDate = $lastSign->count_date + 1;
             } elseif ($lastSignDate->toDateString() !== $today) {
@@ -69,54 +81,68 @@ class RewardPointsController extends Controller
             }
         }
 
-        $rule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_SIGN_IN)
-            ->where('status', 1)
-            ->first();
-
-        $basePoints = config('rewardpoints.sign_in.base_points', 10);
-        $points = $rule ? $rule->reward_point : $basePoints;
-
-        if ($countDate % 30 == 0) {
-            $bonusPoints = config('rewardpoints.sign_in.month_bonus_points', 100);
-            $points += $bonusPoints;
-        } elseif ($countDate % 7 == 0) {
-            $bonusPoints = config('rewardpoints.sign_in.week_bonus_points', 20);
-            $points += $bonusPoints;
-        }
+        // Get sign in points from ly_mw_reward_points_settings table
+        $points = $this->getSignInPointsByDay($countDate);
 
+        // Create sign in record (Laravel auto-maintains created_at and updated_at)
         $sign = RewardPointCustomerSign::create([
             'customer_id' => $customer->id,
             'sign_date' => $today,
             'count_date' => $countDate,
             'point' => $points,
-            'code' => uniqid('SIGN_'),
-            'created' => Carbon::now(),
-            'updated' => Carbon::now()
+            'code' => uniqid('SIGN_')
         ]);
 
+        // Add points to customer account
         $this->rewardPointRepository->addPoints(
             $customer->id,
-            RewardActiveRule::TYPE_SIGN_IN,
+       RewardActiveRule::TYPE_SIGN_IN,
             $points,
             null,
-            "Daily sign-in bonus (Day {$countDate})"
+            "Daily sign-in reward (Day {$countDate})"
         );
 
-        return response()->json([
-            'success' => true,
+        $totalPoints = $this->rewardPointRepository->getCustomerPoints($customer->id);
+
+        return ApiResponse::success([
             'points' => $points,
             'streak' => $countDate,
-            'total_points' => $this->rewardPointRepository->getCustomerPoints($customer->id)
-        ]);
+            'total_points' => $totalPoints
+        ], "Sign in successful! Earned {$points} points");
+    }
+
+    /**
+     * Get sign in points by day from ly_mw_reward_points_settings table
+     */
+    protected function getSignInPointsByDay($day)
+    {
+        // Map sign in days to configuration codes
+        $dayPointsMap = [
+            1 => 'signin_day1_points',
+            2 => 'signin_day2_points',
+            3 => 'signin_day3_points',
+            4 => 'signin_day4_points',
+            5 => 'signin_day5_points',
+            6 => 'signin_day6_points',
+            7 => 'signin_day7_points',
+        ];
+
+        // If <= 7 days, use corresponding configuration
+        if ($day <= 7 && isset($dayPointsMap[$day])) {
+            return (int) $this->settingRepository->getConfigValue($dayPointsMap[$day], 10);
+        }
+
+        // 8 days and beyond, use signin_day8_plus_points configuration
+        return (int) $this->settingRepository->getConfigValue('signin_day8_plus_points', 70);
     }
+
     public function getSignStatus()
     {
         $customer = auth()->guard('customer')->user();
-
         if (!$customer) {
-            return response()->json(['error' => 'Please login first'], 401);
+            return ApiResponse::unauthorized();
         }
-
+        Log::info('Customer login event triggered');
         $today = Carbon::now()->format('Y-m-d');
         $signedToday = RewardPointCustomerSign::where('customer_id', $customer->id)
             ->where('sign_date', $today)
@@ -125,47 +151,153 @@ class RewardPointsController extends Controller
         $lastSign = RewardPointCustomerSign::where('customer_id', $customer->id)
             ->orderBy('sign_date', 'desc')
             ->first();
+        $currentGroup = 'sign_in';
+        $settings = $this->settingRepository->getSettingsByGroup($currentGroup);
+        $dayNamesMap = [
+            'signin_day1_points' => 'Day 1 Sign-in Points',
+            'signin_day2_points' => 'Day 2 Sign-in Points',
+            'signin_day3_points' => 'Day 3 Sign-in Points',
+            'signin_day4_points' => 'Day 4 Sign-in Points',
+            'signin_day5_points' => 'Day 5 Sign-in Points',
+            'signin_day6_points' => 'Day 6 Sign-in Points',
+            'signin_day7_points' => 'Day 7 Sign-in Points',
+            'signin_day8_plus_points' => 'Day 7+ Sign-in Points',
+        ];
 
-        return response()->json([
+        $new = [];
+        foreach ($settings as $setting) {
+            $code = $setting['code'];
+            if (array_key_exists($code, $dayNamesMap)) {
+                $new[] = [
+                    'day' => $dayNamesMap[$code],
+                    'points' => $setting['value']
+                ];
+            }
+        }
+
+
+        return ApiResponse::success([
             'signed_today' => $signedToday,
             'current_streak' => $lastSign ? $lastSign->count_date : 0,
+            'signed_detail' => $new,
             'total_points' => $this->rewardPointRepository->getCustomerPoints($customer->id)
         ]);
     }
 
+    /**
+     * Get customer points history/records
+     *
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function getPointsHistory(Request $request)
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return ApiResponse::unauthorized();
+        }
+
+        // Get pagination parameters
+        $page = $request->input('page', 1);
+        $limit = $request->input('per_page', 20);
+
+        // Validate limit
+        $limit = min(max($limit, 1), 100); // Limit between 1 and 100
+
+        // Get paginated history
+        $history = $this->rewardPointRepository->getHistory($customer->id, $limit,$page);
+
+        // Transform history data
+        $transformedHistory = collect($history->items())->map(function ($item) {
+            return [
+                'id' => $item->history_id,
+                'type' => $item->type_of_transaction,
+                'type_text' => $this->getTransactionTypeText($item->type_of_transaction),
+                'amount' => $item->amount,
+                'balance' => $item->balance,
+                'detail' => $item->transaction_detail,
+                'order_id' => $item->history_order_id > 0 ? $item->history_order_id : null,
+                'expired_day' => $item->expired_day,
+                'expired_time' => $item->expired_time ? Carbon::parse($item->expired_time)->format('Y-m-d H:i:s') : null,
+                'remaining_points' => $item->point_remaining,
+                'status' => $item->status,
+                'status_text' => $this->getStatusText($item->status),
+                'created_at' => Carbon::parse($item->transaction_time)->format('Y-m-d H:i:s'),
+            ];
+        });
+        return ApiResponse::paginated(
+            [
+                'current_points' => $this->rewardPointRepository->getCustomerPoints($customer->id),
+                'history' => $transformedHistory
+            ],
+            $history
+        );
+    }
+
+    /**
+     * Get customer points summary
+     *
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function getPointsSummary()
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return ApiResponse::unauthorized();
+        }
+
+        $totalPoints = $this->rewardPointRepository->getCustomerPoints($customer->id);
+        // Get points expired in the last month
+        $oneMonthAgo = Carbon::now()->subMonth();
+        $recentlyExpired = RewardPointHistory::where('customer_id', $customer->id)
+                ->where('status', RewardPointHistory::STATUS_EXPIRED)
+                ->whereNotNull('expired_time')
+                ->where('expired_time', '>=', $oneMonthAgo)
+                ->sum('amount') * -1;
+
+        // Get expiring points in the next month (still valid but will expire soon)
+        $nextMonth = Carbon::now()->addMonth();
+        $expiringSoon = RewardPointHistory::where('customer_id', $customer->id)
+            ->where('status', RewardPointHistory::STATUS_COMPLETED)
+            ->whereNotNull('expired_time')
+            ->where('expired_time', '>', Carbon::now())
+            ->where('expired_time', '<=', $nextMonth)
+            ->sum('point_remaining');
+
+        return ApiResponse::success([
+            'recently_expired' => $recentlyExpired,
+            'expiring_soon' => $expiringSoon,
+            'current_points' => $totalPoints
+           ]);
+    }
+
     /**
      * Apply reward points to cart
      */
     public function applyPoints(Request $request)
     {
         $points = $request->input('points', 0);
-        
+
         $cartRewardPoints = app('cartrewardpoints');
         $validation = $cartRewardPoints->validatePoints($points);
-        
+
         if ($validation !== true) {
-            return response()->json([
-                'success' => false,
-                'message' => $validation
-            ], 400);
+            return ApiResponse::error($validation);
         }
-        
+
         $result = $cartRewardPoints->applyPoints($points);
-        
+
         if ($result) {
             $discountDetails = $cartRewardPoints->getDiscountDetails();
-            
-            return response()->json([
-                'success' => true,
-                'message' => 'Reward points applied successfully',
+
+            return ApiResponse::success([
                 'discount' => $discountDetails
-            ]);
+            ], 'Reward points applied successfully');
         }
-        
-        return response()->json([
-            'success' => false,
-            'message' => 'Failed to apply reward points'
-        ], 400);
+
+        return ApiResponse::error('Failed to apply reward points');
     }
 
     /**
@@ -175,11 +307,8 @@ class RewardPointsController extends Controller
     {
         $cartRewardPoints = app('cartrewardpoints');
         $cartRewardPoints->removePoints();
-        
-        return response()->json([
-            'success' => true,
-            'message' => 'Reward points removed successfully'
-        ]);
+
+        return ApiResponse::success([], 'Reward points removed successfully');
     }
 
     /**
@@ -188,8 +317,8 @@ class RewardPointsController extends Controller
     public function getPointsInfo()
     {
         $cartRewardPoints = app('cartrewardpoints');
-        
-        return response()->json([
+
+        return ApiResponse::success([
             'available_points' => $cartRewardPoints->getAvailablePoints(),
             'points_used' => $cartRewardPoints->getPointsUsed(),
             'discount_amount' => $cartRewardPoints->getDiscountAmount(),
@@ -197,4 +326,41 @@ class RewardPointsController extends Controller
             'max_points_allowed' => $cartRewardPoints->getMaxPointsByCartTotal()
         ]);
     }
+
+    /**
+     * Get transaction type text
+     */
+    protected function getTransactionTypeText($type)
+    {
+        $types = [
+            1 => 'Daily Sign In',
+            2 => 'Registration',
+            3 => 'Order Purchase',
+            4 => 'Product Review',
+            5 => 'Referral',
+            6 => 'Birthday',
+            7 => 'Share',
+            8 => 'Subscription',
+            9 => 'Login',
+            99 => 'admin',
+        ];
+
+        return $types[$type] ?? 'Unknown';
+    }
+
+    /**
+     * Get status text
+     */
+    protected function getStatusText($status)
+    {
+        $statuses = [
+            1 => 'Completed',
+            0 => 'Pending',
+            3 => 'Expired',
+            2 => 'Cancelled'
+        ];
+
+        return $statuses[$status] ?? 'Unknown';
+    }
 }
+

+ 95 - 0
packages/Longyi/RewardPoints/src/Http/Middleware/ApiCustomerAuthenticate.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Laravel\Sanctum\PersonalAccessToken;
+
+class ApiCustomerAuthenticate
+{
+    /**
+     * Handle an incoming request.
+     *
+     * 支持三种认证方式:
+     * 1. Session Cookie(传统 Web 登录)
+     * 2. Sanctum Bearer Token(BagistoApi 登录)
+     * 3. Storefront Key + Customer Session
+     *
+     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
+     */
+    public function handle(Request $request, Closure $next): Response
+    {
+        // 方式 1: Session Cookie 认证(传统 Web 登录)
+        if (auth()->guard('customer')->check()) {
+            return $next($request);
+        }
+
+        // 方式 2: Sanctum Bearer Token 认证(BagistoApi 登录)
+        if ($request->bearerToken()) {
+            $customer = $this->authenticateViaSanctum($request);
+
+            if ($customer) {
+                // 将 Sanctum 认证的用户设置到 customer guard
+                auth()->guard('customer')->setUser($customer);
+                return $next($request);
+            }
+        }
+
+        // 都未认证,返回 JSON 错误
+        return response()->json([
+            'error' => 'Please login first',
+            'message' => 'Authentication required. Please login via /customer/login (Session) or /api/shop/customer/login (Token)'
+        ], 401);
+    }
+
+    /**
+     * 通过 Sanctum Token 验证客户
+     *
+     * @return \Webkul\Customer\Models\Customer|null
+     */
+    protected function authenticateViaSanctum(Request $request)
+    {
+        $token = $request->bearerToken();
+
+        if (!$token) {
+            return null;
+        }
+
+        try {
+            // 使用 Sanctum 的 PersonalAccessToken 模型验证
+            $accessToken = PersonalAccessToken::findToken($token);
+
+            if (!$accessToken) {
+                return null;
+            }
+
+            // 检查 Token 是否过期
+            if ($accessToken->expires_at && $accessToken->expires_at->isPast()) {
+                return null;
+            }
+
+            // 获取关联的用户模型
+            $tokenable = $accessToken->tokenable;
+
+            // 确保是 Customer 模型
+            if (!$tokenable || !($tokenable instanceof \Webkul\Customer\Models\Customer)) {
+                return null;
+            }
+
+            // 更新 Token 最后使用时间
+            $accessToken->touch();
+
+            return $tokenable;
+
+        } catch (\Exception $e) {
+            // 记录错误日志(可选)
+            \Log::debug('Sanctum authentication failed', [
+                'error' => $e->getMessage()
+            ]);
+
+            return null;
+        }
+    }
+}

+ 96 - 8
packages/Longyi/RewardPoints/src/Listeners/CustomerEvents.php

@@ -2,11 +2,12 @@
 
 namespace Longyi\RewardPoints\Listeners;
 
+use Longyi\RewardPoints\Models\RewardPointHistory;
 use Longyi\RewardPoints\Repositories\RewardPointRepository;
 use Longyi\RewardPoints\Models\RewardActiveRule;
 use Webkul\Customer\Models\Customer;
 use Illuminate\Support\Facades\Log;
-
+use Illuminate\Support\Facades\Cache;
 class CustomerEvents
 {
     protected $rewardPointRepository;
@@ -18,6 +19,7 @@ class CustomerEvents
 
     public function handleCustomerRegistration($event)
     {
+
         // 检查事件参数类型
         if (is_object($event) && method_exists($event, 'getAttribute')) {
             $customer = $event;
@@ -55,7 +57,7 @@ class CustomerEvents
             'rewardpoints.registration.points_per_registration',
             100
         );
-        
+
         if ($registrationPoints > 0) {
             $pointsList[] = [
                 'amount' => $registrationPoints,
@@ -80,7 +82,7 @@ class CustomerEvents
                 'rewardpoints.newsletter_subscribe.points_per_subscription',
                 200
             );
-            
+
             if ($subscribePoints > 0) {
                 $pointsList[] = [
                     'amount' => $subscribePoints,
@@ -94,14 +96,100 @@ class CustomerEvents
         // 如果有积分需要添加,使用批量添加方法
         if (!empty($pointsList)) {
             $histories = $this->rewardPointRepository->addPointsBatch($customer->id, $pointsList);
-            
-            Log::info('Points added for customer registration', [
+
+            /*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'))
-            ]);
+            ]);*/
+        }
+    }
+
+    /**
+     * 处理客户登录事件,赠送登录积分
+     */
+    public function handleCustomerLogin($event)
+    {
+        // 检查事件参数类型
+        if (is_object($event) && method_exists($event, 'getAttribute')) {
+            $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 {
+            $customer = $event;
+        }
+
+        if (!$customer) {
+            Log::warning('Customer not found in login event');
+            return;
+        }
+
+        Log::info('Customer identified', [
+            'customer_id' => $customer->id,
+            'customer_email' => $customer->email ?? 'N/A'
+        ]);
+
+        // 检查今天是否已经获得过登录积分(使用 Redis/Cache 标记)
+        $today = \Carbon\Carbon::now()->format('Y-m-d');
+        $cacheKey = "reward_points:login_received:{$customer->id}:{$today}";
+
+        // 尝试从缓存获取标记
+        $alreadyReceivedToday = Cache::has($cacheKey);
+        // 尝试从数据库获取登陆状态
+        if(empty($alreadyReceivedToday)){
+            $alreadyReceivedToday = RewardPointHistory::where('customer_id', $customer->id)
+                ->where('type_of_transaction', RewardActiveRule::TYPE_LOGIN)
+                ->whereDate('transaction_time', $today)
+                ->where('status', RewardPointHistory::STATUS_COMPLETED)
+                ->exists();
+        }
+
+        if ($alreadyReceivedToday) {
+            /*Log::info('Customer already received login points today (from cache), skipping', [
+                'customer_id' => $customer->id,
+                'date' => $today,
+                'cache_store' => config('cache.default')
+            ]);*/
+            return;
+        }
+
+
+        // 查询登录规则
+        $loginRule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_LOGIN)
+            ->where('status', 1)
+            ->first();
+
+        if (!$loginRule) {
+            Log::info('No active login rule found, skipping login points');
+            return;
+        }
+
+        // 获取登录积分
+        $loginPoints = (int) $loginRule->reward_point;
+        if ($loginPoints <= 0) {
+            Log::info('Login points is 0 or negative, skipping login points');
+            return;
         }
+
+        // 添加登录积分
+        $history = $this->rewardPointRepository->addPoints(
+            $customer->id,
+            RewardActiveRule::TYPE_LOGIN,
+            $loginPoints,
+            null,
+            'Login bonus'
+        );
+
+        /*Log::info('Login points added successfully', [
+            'customer_id' => $customer->id,
+            'points' => $loginPoints,
+            'history_id' => $history->history_id ?? null
+        ]);*/
     }
 
     /**
@@ -112,7 +200,7 @@ class CustomerEvents
         if ($rule && $rule->reward_point > 0) {
             return (int) $rule->reward_point;
         }
-        
+
         return (int) config($configKey, $defaultValue);
     }
-}
+}

+ 82 - 20
packages/Longyi/RewardPoints/src/Listeners/OrderEvents.php

@@ -4,14 +4,19 @@ namespace Longyi\RewardPoints\Listeners;
 
 use Longyi\RewardPoints\Repositories\RewardPointRepository;
 use Longyi\RewardPoints\Models\RewardActiveRule;
+use Longyi\RewardPoints\Models\RewardPointHistory;
+use Longyi\RewardPoints\Services\GrowthValueService;
 
 class OrderEvents
 {
     protected $rewardPointRepository;
 
-    public function __construct(RewardPointRepository $rewardPointRepository)
+    protected $growthValueService;
+
+    public function __construct(RewardPointRepository $rewardPointRepository, GrowthValueService $growthValueService)
     {
         $this->rewardPointRepository = $rewardPointRepository;
+        $this->growthValueService = $growthValueService;
     }
 
     public function handleOrderPlacement($order)
@@ -31,28 +36,39 @@ class OrderEvents
         $points = $this->calculateOrderPoints($order, $rule);
 
         if ($points > 0) {
+            // 支付订单后,积分状态为 PENDING(待确认),不计入用户总积分
             $this->rewardPointRepository->addPoints(
                 $order->customer_id,
                 RewardActiveRule::TYPE_ORDER,
                 $points,
                 $order->id,
-                "Points earned from order #{$order->increment_id}"
+                "Points earned from order #{$order->increment_id} (Pending)",
+                $rule
             );
+
+            Log::info('Order points added as PENDING (not counted in balance yet)', [
+                'order_id' => $order->id,
+                'customer_id' => $order->customer_id,
+                'points' => $points
+            ]);
         }
     }
 
-     public function handleOrderCancellation($order)
+    public function handleOrderCancellation($order)
     {
         if (!$order->customer_id) {
             return;
         }
 
         \DB::transaction(function () use ($order) {
+            // 查找该订单相关的所有积分记录(包括 PENDING 和 COMPLETED)
             $histories = $this->rewardPointRepository->findWhere([
                 'customer_id' => $order->customer_id,
                 'history_order_id' => $order->id,
-                'type_of_transaction' => RewardActiveRule::TYPE_ORDER,
-                'status' => RewardPointHistory::STATUS_COMPLETED
+                'type_of_transaction' => RewardActiveRule::TYPE_ORDER
+            ])->whereIn('status', [
+                RewardPointHistory::STATUS_PENDING,
+                RewardPointHistory::STATUS_COMPLETED
             ])->get();
 
             if ($histories->isEmpty()) {
@@ -61,27 +77,73 @@ class OrderEvents
 
             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();
+                    // 只有 COMPLETED 状态的积分需要从用户余额中扣减
+                    if ($history->status === RewardPointHistory::STATUS_COMPLETED) {
+                        $result = $this->rewardPointRepository->deductPoints(
+                            $order->customer_id,
+                            $history->point_remaining,
+                            $order->id,
+                            "Points deducted due to order cancellation #{$order->increment_id}"
+                        );
+
+                        if ($result) {
+                            Log::info('Completed points deducted on cancellation', [
+                                'history_id' => $history->history_id,
+                                'order_id' => $order->id,
+                                'points_deducted' => $history->point_remaining
+                            ]);
+                        }
+                    } else {
+                        // PENDING 状态的积分只需更新状态,不需要扣减(因为从未计入余额)
+                        Log::info('Pending points cancelled (no deduction needed)', [
+                            'history_id' => $history->history_id,
+                            'order_id' => $order->id,
+                            'points' => $history->point_remaining
+                        ]);
                     }
+
+                    // 更新历史记录状态
+                    $history->status = RewardPointHistory::STATUS_CANCELLED;
+                    $history->point_remaining = 0;
+                    $history->save();
                 }
             }
         });
     }
+
+    public function handleOrderCompletion($order)
+    {
+        if (!$order->customer_id) {
+            return;
+        }
+
+        if ($order->status !== \Webkul\Sales\Models\Order::STATUS_COMPLETED) {
+            return;
+        }
+
+        // 将该订单的 PENDING 积分确认为 COMPLETED,并计入用户总积分
+        $confirmedPoints = $this->rewardPointRepository->confirmPendingPoints(
+            $order->customer_id,
+            $order->id
+        );
+
+       /* if ($confirmedPoints > 0) {
+            Log::info('Order completed, pending points confirmed', [
+                'order_id' => $order->id,
+                'customer_id' => $order->customer_id,
+                'confirmed_points' => $confirmedPoints
+            ]);
+        }*/
+
+        // 处理成长值
+        $this->growthValueService->handleOrderCompleted($order);
+    }
+
     protected function calculateOrderPoints($order, $rule)
     {
-        // Simple calculation based on order grand total
-        // You can implement more complex logic based on your requirements
-        $pointsPerCurrency = 10; // 10 points per currency unit
-        return floor($order->grand_total * $pointsPerCurrency);
+        $pointsPerCurrency = 10;
+        return floor($order->base_grand_total * $pointsPerCurrency);
     }
+
 }
+

+ 43 - 0
packages/Longyi/RewardPoints/src/Models/GrowthValueCustomer.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Longyi\RewardPoints\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Webkul\Customer\Models\Customer;
+
+class GrowthValueCustomer extends Model
+{
+    protected $table = 'mw_growth_value_customer';
+
+    protected $fillable = [
+        'customer_id',
+        'growth_value',
+        'growth_level',
+        'first_order_completed_at',
+        'level_updated_at',
+        'growth_value_expires_at',
+    ];
+
+    protected $casts = [
+        'customer_id' => 'integer',
+        'growth_value' => 'integer',
+        'first_order_completed_at' => 'datetime',
+        'level_updated_at' => 'datetime',
+        'growth_value_expires_at' => 'datetime',
+    ];
+
+    public function customer()
+    {
+        return $this->belongsTo(Customer::class, 'customer_id');
+    }
+
+    public function history()
+    {
+        return $this->hasMany(GrowthValueHistory::class, 'customer_id', 'customer_id');
+    }
+
+    public function levelConfig()
+    {
+        return $this->hasOne(GrowthValueLevel::class, 'level_code', 'growth_level');
+    }
+}

+ 65 - 0
packages/Longyi/RewardPoints/src/Models/GrowthValueHistory.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace Longyi\RewardPoints\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Webkul\Customer\Models\Customer;
+
+class GrowthValueHistory extends Model
+{
+    protected $table = 'mw_growth_value_history';
+    protected $primaryKey = 'history_id';
+
+    const EVENT_TYPE_ORDER = 'order';
+    const EVENT_TYPE_ADMIN_ADJUST = 'admin_adjust';
+    const EVENT_TYPE_EXPIRATION = 'expiration';
+
+    protected $fillable = [
+        'customer_id',
+        'order_id',
+        'order_amount',
+        'growth_value_earned',
+        'total_growth_value',
+        'level_before',
+        'level_after',
+        'event_type',
+        'description',
+        'expires_at',
+        'created_at',
+    ];
+
+    protected $casts = [
+        'customer_id' => 'integer',
+        'order_id' => 'integer',
+        'order_amount' => 'decimal:4',
+        'growth_value_earned' => 'integer',
+        'total_growth_value' => 'integer',
+        'expires_at' => 'datetime',
+        'created_at' => 'datetime',
+    ];
+
+    public $timestamps = false;
+
+    public function customer()
+    {
+        return $this->belongsTo(Customer::class, 'customer_id');
+    }
+
+    public function order()
+    {
+        return $this->belongsTo(\Webkul\Sales\Models\Order::class, 'order_id');
+    }
+    /**
+     * 获取事件类型标签
+     */
+    public function getEventTypeLabelAttribute()
+    {
+        $labels = [
+            'order' => '订单获得',
+            'admin_adjust' => '管理员调整',
+            'expired' => '过期扣除',
+        ];
+
+        return $labels[$this->event_type] ?? $this->event_type;
+    }
+}

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

@@ -8,13 +8,14 @@ class RewardActiveRule extends Model
 {
     // 定义交易类型常量
     const TYPE_REGISTRATION = 2;   // 注册
-    const TYPE_SIGN_IN = 1;        // 登录
+    const TYPE_SIGN_IN = 1;        // 签到
     const TYPE_ORDER = 3;          // 订单
     const TYPE_REVIEW = 4;         // 评价
     const TYPE_REFERRAL = 5;       // 推荐
     const TYPE_BIRTHDAY = 6;       // 生日
     const TYPE_SHARE  = 7;         // 分享
     const TYPE_SUBSCRIBE  = 8;         // 关注
+    const TYPE_LOGIN = 9;        // 登录
     protected $table = 'mw_reward_active_rules';
 
     protected $primaryKey = 'rule_id';
@@ -83,7 +84,7 @@ class RewardActiveRule extends Model
             self::TYPE_ORDER => 'Order',
             self::TYPE_REGISTRATION => 'Registration',
             self::TYPE_REVIEW => 'Product Review',
-            self::TYPE_SIGN_IN => 'Daily Sign In',
+            self::TYPE_LOGIN => 'Login',
             self::TYPE_REFERRAL => 'Referral',
             self::TYPE_BIRTHDAY => 'Birthday',
             self::TYPE_SHARE => 'Share',
@@ -92,4 +93,4 @@ class RewardActiveRule extends Model
 
         return $types[$this->type_of_transaction] ?? 'Unknown';
     }
-}
+}

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

@@ -14,9 +14,7 @@ class RewardPointCustomerSign extends Model
         'sign_date',
         'count_date',
         'point',
-        'code',
-        'created',
-        'updated'
+        'code'
     ];
 
     protected $casts = [

+ 7 - 0
packages/Longyi/RewardPoints/src/Providers/EventServiceProvider.php

@@ -10,6 +10,9 @@ class EventServiceProvider extends ServiceProvider
         'customer.registration.after' => [
             'Longyi\RewardPoints\Listeners\CustomerEvents@handleCustomerRegistration'
         ],
+        'customer.after.login' => [
+            'Longyi\RewardPoints\Listeners\CustomerEvents@handleCustomerLogin'
+        ],
         'checkout.order.save.after' => [
             'Longyi\RewardPoints\Listeners\OrderEvents@handleOrderPlacement'
         ],
@@ -19,5 +22,9 @@ class EventServiceProvider extends ServiceProvider
         'customer.review.create.after' => [
             'Longyi\RewardPoints\Listeners\ReviewEvents@handleReviewCreation'
         ],
+        'sales.order.complete.after' => [
+            'Longyi\RewardPoints\Listeners\OrderEvents@handleOrderCompletion'
+        ],
     ];
 }
+

+ 19 - 17
packages/Longyi/RewardPoints/src/Providers/RewardPointsServiceProvider.php

@@ -16,7 +16,7 @@ class RewardPointsServiceProvider extends ServiceProvider
         $this->loadRoutesFrom(__DIR__ . '/../Routes/routes.php');
         $this->loadViewsFrom(__DIR__ . '/../Resources/views', 'rewardpoints');
         $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', 'rewardpoints');
-        
+
         $this->publishes([
             __DIR__ . '/../../publishable/assets' => public_path('vendor/longyi/rewardpoints'),
         ], 'public');
@@ -39,19 +39,21 @@ class RewardPointsServiceProvider extends ServiceProvider
         $this->mergeConfigFrom(
             __DIR__ . '/../Config/rewardpoints.php', 'rewardpoints'
         );
-        
+
         $this->app->singleton('cartrewardpoints', function ($app) {
             return new CartRewardPoints();
         });
-        
+
         $this->app->register(EventServiceProvider::class);
-        
+
         $this->app->alias('cartrewardpoints', CartRewardPoints::class);
-        
+
         if ($this->app->runningInConsole()) {
             $this->commands([
                 \Longyi\RewardPoints\Console\Commands\CheckExpiredPoints::class,
                 \Longyi\RewardPoints\Console\Commands\InitializeSettings::class,
+                \Longyi\RewardPoints\Console\Commands\InitializeGrowthSettings::class,
+                \Longyi\RewardPoints\Console\Commands\CheckExpiredGrowthValues::class
             ]);
         }
     }
@@ -62,39 +64,39 @@ class RewardPointsServiceProvider extends ServiceProvider
     {
         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);
         }
     }
-    
+
     /**
      * 将代码转换为配置键
      */
@@ -117,10 +119,10 @@ class RewardPointsServiceProvider extends ServiceProvider
             'referral_require_order' => 'rewardpoints.referral.require_first_order',
             'birthday_points' => 'rewardpoints.birthday.points_per_birthday',
         ];
-        
+
         return $mapping[$code] ?? null;
     }
-    
+
     /**
      * 类型转换
      */
@@ -129,7 +131,7 @@ class RewardPointsServiceProvider extends ServiceProvider
         if ($value === null) {
             return null;
         }
-        
+
         switch ($type) {
             case 'boolean':
                 return (bool) $value;
@@ -139,5 +141,5 @@ class RewardPointsServiceProvider extends ServiceProvider
                 return $value;
         }
     }
-    
-}
+
+}

+ 111 - 38
packages/Longyi/RewardPoints/src/Repositories/RewardPointRepository.php

@@ -46,38 +46,40 @@ class RewardPointRepository extends Repository
                         'last_checkout' => Carbon::now()
                     ]
                 );
-                
-                // 2. 使用 increment 方法增加积分
-                $customerPoints->increment('mw_reward_point', $amount);
-                
+
+                // 2. 确定积分状态
+                // 订单类型的积分初始状态为 PENDING,其他类型直接 COMPLETED
+                $status = ($type === RewardActiveRule::TYPE_ORDER)
+                    ? RewardPointHistory::STATUS_PENDING
+                    : RewardPointHistory::STATUS_COMPLETED;
+
+                // 3. 只有 COMPLETED 状态的积分才计入用户总积分
+                if ($status === RewardPointHistory::STATUS_COMPLETED) {
+                    $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', [
+               /* 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;
+                    $expiredDay = 365;
                     $expiredTime = $expiredDay > 0 ? Carbon::now()->addDays($expiredDay) : null;
                 }
 
@@ -94,7 +96,7 @@ class RewardPointRepository extends Repository
                     'expired_time' => $expiredTime,
                     'point_remaining' => $amount,
                     'check_time' => 1,
-                    'status' => RewardPointHistory::STATUS_COMPLETED
+                    'status' => $status
                 ]);
 
                 return $history;
@@ -128,17 +130,17 @@ class RewardPointRepository extends Repository
                         '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;                                
-                
+                $totalAmount = 0;
+
                 // 3. 逐条创建历史记录(每条记录使用累加后的余额)
                 foreach ($pointsList as $index => $pointData) {
                     $amount = (int) $pointData['amount'];
@@ -146,22 +148,22 @@ class RewardPointRepository extends Repository
                     $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,
@@ -177,11 +179,11 @@ class RewardPointRepository extends Repository
                         'check_time' => 1,
                         'status' => RewardPointHistory::STATUS_COMPLETED
                     ]);
-                    
+
                     $histories[] = $history;
-        
+
                 }
-                
+
                  // 4. 最后一次性更新用户总积分
                 $updated = DB::table('mw_reward_point_customer')
                         ->where('customer_id', $customerId)
@@ -189,7 +191,7 @@ class RewardPointRepository extends Repository
                             'mw_reward_point' => DB::raw('mw_reward_point + ' . (float)$totalAmount),
                             'last_checkout' => Carbon::now()
                         ]);
-                                              
+
                 return $histories;
             });
         } catch (\Exception $e) {
@@ -214,7 +216,7 @@ class RewardPointRepository extends Repository
                     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,
@@ -265,16 +267,22 @@ class RewardPointRepository extends Repository
             return false;
         }
     }
-
-    public function getHistory($customerId, $limit = 20)
+    public function getHistory($customerId, $limit = 20, $page = null)
     {
-        return $this->where('customer_id', $customerId)
-            ->orderBy('transaction_time', 'desc')
-            ->paginate($limit);
+        $query = $this->where('customer_id', $customerId)
+            ->orderBy('transaction_time', 'desc');
+
+        if ($page) {
+            return $query->paginate($limit, ['*'], 'page', $page);
+        }
+
+        return $query->paginate($limit);
     }
 
     public function checkExpiredPoints()
     {
+        // 只检查已过期的、状态为 COMPLETED 的、还有剩余积分的记录
+        // PENDING 状态的积分不会过期(因为订单还未完成)
         $expiredHistories = $this->where('expired_time', '<', Carbon::now())
             ->where('status', RewardPointHistory::STATUS_COMPLETED)
             ->where('point_remaining', '>', 0)
@@ -294,11 +302,11 @@ class RewardPointRepository extends Repository
                         $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
+                            'points_expired' => $history->getOriginal('point_remaining')
                         ]);
                     }
                 });
@@ -310,4 +318,69 @@ class RewardPointRepository extends Repository
             }
         }
     }
-}
+
+    /**
+     * 确认待处理的积分(将 PENDING 状态改为 COMPLETED 并计入用户余额)
+     */
+    public function confirmPendingPoints($customerId, $orderId)
+    {
+        try {
+            return DB::transaction(function () use ($customerId, $orderId) {
+                // 查找所有 PENDING 状态的订单积分记录
+                $pendingHistories = $this->where('customer_id', $customerId)
+                    ->where('history_order_id', $orderId)
+                    ->where('type_of_transaction', RewardActiveRule::TYPE_ORDER)
+                    ->where('status', RewardPointHistory::STATUS_PENDING)
+                    ->get();
+
+                if ($pendingHistories->isEmpty()) {
+                    Log::info('No pending points to confirm', [
+                        'customer_id' => $customerId,
+                        'order_id' => $orderId
+                    ]);
+                    return 0;
+                }
+
+                $totalConfirmedPoints = 0;
+
+                foreach ($pendingHistories as $history) {
+                    // 增加用户总积分
+                    $customerPoints = RewardPointCustomer::where('customer_id', $customerId)
+                        ->lockForUpdate()
+                        ->first();
+
+                    if ($customerPoints) {
+                        $customerPoints->increment('mw_reward_point', $history->point_remaining);
+                        $customerPoints->save();
+
+                        // 更新历史记录状态
+                        $history->status = RewardPointHistory::STATUS_COMPLETED;
+                        $history->balance = (int) $customerPoints->mw_reward_point;
+                        $history->save();
+
+                        $totalConfirmedPoints += $history->point_remaining;
+
+                        Log::info('Pending points confirmed', [
+                            'history_id' => $history->history_id,
+                            'customer_id' => $customerId,
+                            'order_id' => $orderId,
+                            'points' => $history->point_remaining,
+                            'new_balance' => $customerPoints->mw_reward_point
+                        ]);
+                    }
+                }
+
+                return $totalConfirmedPoints;
+            });
+        } catch (\Exception $e) {
+            Log::error('Error confirming pending points', [
+                'customer_id' => $customerId,
+                'order_id' => $orderId,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            throw $e;
+        }
+    }
+
+}

+ 99 - 16
packages/Longyi/RewardPoints/src/Repositories/RewardPointSettingRepository.php

@@ -11,7 +11,7 @@ class RewardPointSettingRepository extends Repository
     {
         return RewardPointSetting::class;
     }
-    
+
     /**
      * 获取所有配置分组
      */
@@ -23,7 +23,7 @@ class RewardPointSettingRepository extends Repository
             ->pluck('group')
             ->toArray();
     }
-    
+
     /**
      * 按分组获取配置
      */
@@ -35,21 +35,21 @@ class RewardPointSettingRepository extends Repository
             ->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);
     }
-    
+
     /**
      * 批量获取配置值
      */
@@ -59,15 +59,15 @@ class RewardPointSettingRepository extends Repository
             ->whereIn('code', $codes)
             ->where('is_enabled', true)
             ->get();
-        
+
         $result = [];
         foreach ($settings as $setting) {
             $result[$setting->code] = $this->castValue($setting->value, $setting->type);
         }
-        
+
         return $result;
     }
-    
+
     /**
      * 设置配置值
      */
@@ -78,7 +78,7 @@ class RewardPointSettingRepository extends Repository
             ['value' => $value]
         );
     }
-    
+
     /**
      * 批量设置配置值
      */
@@ -88,7 +88,7 @@ class RewardPointSettingRepository extends Repository
             $this->setConfigValue($code, $value);
         }
     }
-    
+
     /**
      * 类型转换
      */
@@ -97,7 +97,7 @@ class RewardPointSettingRepository extends Repository
         if ($value === null) {
             return null;
         }
-        
+
         switch ($type) {
             case 'boolean':
                 return (bool) $value;
@@ -107,14 +107,14 @@ class RewardPointSettingRepository extends Repository
                 return $value;
         }
     }
-    
+
     /**
      * 初始化默认配置
      */
     public function initializeDefaultSettings()
     {
         $defaultSettings = $this->getDefaultSettings();
-        
+
         foreach ($defaultSettings as $setting) {
             $this->model->firstOrCreate(
                 ['code' => $setting['code']],
@@ -122,7 +122,7 @@ class RewardPointSettingRepository extends Repository
             );
         }
     }
-    
+
     /**
      * 获取默认配置列表
      */
@@ -166,6 +166,89 @@ class RewardPointSettingRepository extends Repository
                 'sort_order' => 4,
                 'description' => '最低可使用的积分数量'
             ],
+
+            // 签到设置
+            [
+                'code' => 'signin_day1_points',
+                'name' => '第一天签到积分',
+                'value' => '10',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 1,
+                'description' => '连续签到第1天获得的积分'
+            ],
+            [
+                'code' => 'signin_day2_points',
+                'name' => '第二天签到积分',
+                'value' => '10',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 2,
+                'description' => '连续签到第2天获得的积分'
+            ],
+            [
+                'code' => 'signin_day3_points',
+                'name' => '第三天签到积分',
+                'value' => '15',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 3,
+                'description' => '连续签到第3天获得的积分'
+            ],
+            [
+                'code' => 'signin_day4_points',
+                'name' => '第四天签到积分',
+                'value' => '25',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 4,
+                'description' => '连续签到第4天获得的积分'
+            ],
+            [
+                'code' => 'signin_day5_points',
+                'name' => '第五天签到积分',
+                'value' => '50',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 5,
+                'description' => '连续签到第5天获得的积分'
+            ],
+            [
+                'code' => 'signin_day6_points',
+                'name' => '第六天签到积分',
+                'value' => '70',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 6,
+                'description' => '连续签到第6天获得的积分'
+            ],
+            [
+                'code' => 'signin_day7_points',
+                'name' => '第七天签到积分',
+                'value' => '70',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 7,
+                'description' => '连续签到第7天获得的积分'
+            ],
+            [
+                'code' => 'signin_day8_plus_points',
+                'name' => '第八天及以后签到积分',
+                'value' => '70',
+                'type' => 'number',
+                'group' => 'sign_in',
+                'sort_order' => 8,
+                'description' => '连续签到第8天及以后每天获得的积分'
+            ],
+            [
+                'code' => 'signin_enabled',
+                'name' => '启用签到功能',
+                'value' => '1',
+                'type' => 'boolean',
+                'group' => 'sign_in',
+                'sort_order' => 9,
+                'description' => '是否启用每日签到功能'
+            ],
         ];
     }
-}
+}

+ 39 - 15
packages/Longyi/RewardPoints/src/Resources/lang/en/rewardpoints.php

@@ -6,10 +6,10 @@ return [
         'available-points' => 'Available Points',
         'daily-sign-in' => 'Daily Sign In',
         'sign-in-now' => 'Sign In Now',
-        'signed-today' => 'Signed In Today',
+        'signed-today' => 'Signed Today',
         'sign-in-success' => 'Sign in successful! You earned',
-        'sign-in-failed' => 'Failed to sign in',
-        'sign-in-error' => 'An error occurred during sign in',
+        'sign-in-failed' => 'Sign in failed',
+        'sign-in-error' => 'Error occurred during sign in',
         'current-streak' => 'Current Streak',
         'days' => 'days',
         'points-history' => 'Points History',
@@ -30,11 +30,11 @@ return [
         'completed' => 'Completed',
         'cancelled' => 'Cancelled',
         'expired' => 'Expired',
-        'no-history' => 'No points history yet',
+        'no-history' => 'No points history records',
     ],
 
     'admin' => [
-        'reward-points' => 'Reward Points',
+        'reward-points' => 'Reward Points Management',
         'rules' => 'Rules',
         'customers' => 'Customers',
         'reports' => 'Reports',
@@ -76,8 +76,8 @@ return [
         'end-date' => 'End Date',
         'points-earned' => 'Points Earned',
         'points-redeemed' => 'Points Redeemed',
-        'title' => 'Customer Reward Points',
-        'details-title' => ':name\'s Reward Points Details',
+        'title' => 'Customer Points',
+        'details-title' => ':name\'s Points Details',
         'customer-info' => 'Customer Information',
         'points-history' => 'Points History',
         'adjust-points' => 'Adjust Points',
@@ -90,9 +90,8 @@ return [
         'name' => 'Name',
         'view' => 'View',
         'no-customers-found' => 'No customers found',
-        'no-reward-points-records' => 'No reward points records available',
+        'no-reward-points-records' => 'No reward points records',
 
-        // 交易类型映射(使用字符串而不是数组)
         'transaction-types' => [
             'order' => 'Order',
             'registration' => 'Registration',
@@ -109,16 +108,14 @@ return [
         'email-or-id' => 'Email or ID',
         'enter-email-or-id' => 'Enter customer email or ID',
         'enter-points' => 'Enter points amount',
-        'enter-reason' => 'Enter reason for adjustment',
+        'enter-reason' => 'Enter adjustment reason',
         'submit' => 'Submit',
         'action' => 'Action',
 
-        // 总交易数
         'total-transactions' => 'Total Transactions',
         'total-points-earned' => 'Total Points Earned',
         'total-points-redeemed' => 'Total Points Redeemed',
 
-        // 搜索相关
         'search' => 'Search',
         'reset' => 'Reset',
         'customer-email' => 'Customer Email',
@@ -130,7 +127,6 @@ return [
         'admin-adjustment' => 'Admin Adjustment',
         'admin-action' => 'Admin Action',
 
-        // 表格表头
         'id' => 'ID',
         'customer' => 'Customer',
         'amount' => 'Amount',
@@ -138,13 +134,41 @@ return [
         'description' => 'Description',
         'balance' => 'Balance',
 
-        // 提示信息
         'no-transactions-found' => 'No transactions found',
         'try-adjusting-filters' => 'Try adjusting your filters',
 
-        // 其他
         'points' => 'Points',
         'transactions-count' => 'Transactions',
+
+        'menu' => [
+            'growth-value' => 'Growth Value Management',
+            'growth-value-customers' => 'Member Growth Values',
+            'growth-value-levels' => 'Level Configuration',
+        ],
+
+        'growth-value' => [
+            'customer-detail' => 'Customer Growth Value Details',
+            'customer-info' => 'Customer Information',
+            'current-growth-value' => 'Current Growth Value',
+            'current-level' => 'Current Level',
+            'first-order-completed' => 'First Order Completed',
+            'expires-at' => 'Expires At',
+            'never-expires' => 'Never Expires',
+            'next-level-info' => 'Next Level Information',
+            'next-level-name' => 'Next Level',
+            'points-to-next-level' => 'Points to Next Level',
+            'history' => 'Growth Value History',
+            'no-history' => 'No growth value history records',
+            'event-type' => 'Event Type',
+            'growth-value-change' => 'Growth Value Change',
+            'total-growth-value' => 'Total Growth Value',
+            'level-change' => 'Level Change',
+            'order-id' => 'Order ID',
+            'created-at' => 'Created At',
+            'order-purchase' => 'Order Purchase',
+            'admin-adjustment' => 'Admin Adjustment',
+            'expired' => 'Expired',
+        ],
     ],
 
     'validation' => [

+ 180 - 0
packages/Longyi/RewardPoints/src/Resources/lang/zh_CN/rewardpoints.php

@@ -0,0 +1,180 @@
+<?php
+
+return [
+    'customer' => [
+        'my-reward-points' => '我的积分',
+        'available-points' => '可用积分',
+        'daily-sign-in' => '每日签到',
+        'sign-in-now' => '立即签到',
+        'signed-today' => '今日已签到',
+        'sign-in-success' => '签到成功!您获得了',
+        'sign-in-failed' => '签到失败',
+        'sign-in-error' => '签到时发生错误',
+        'current-streak' => '当前连续签到',
+        'days' => '天',
+        'points-history' => '积分历史',
+        'date' => '日期',
+        'type' => '类型',
+        'description' => '描述',
+        'points' => '积分',
+        'balance' => '余额',
+        'status' => '状态',
+        'order' => '订单',
+        'registration' => '注册',
+        'review' => '评价',
+        'sign-in' => '签到',
+        'referral' => '推荐',
+        'birthday' => '生日',
+        'other' => '其他',
+        'pending' => '待处理',
+        'completed' => '已完成',
+        'cancelled' => '已取消',
+        'expired' => '已过期',
+        'no-history' => '暂无积分历史记录',
+    ],
+
+    'admin' => [
+        'reward-points' => '积分管理',
+        'rules' => '规则',
+        'customers' => '客户',
+        'reports' => '报表',
+        'add-rule' => '添加规则',
+        'transactions' => '交易记录',
+        'earned-transactions' => '获得积分记录',
+        'view-all-transactions' => '全部交易',
+        'view-earned-only' => '仅获得积分',
+        'total' => '总计',
+        'edit-rule' => '编辑规则',
+        'delete-rule' => '删除规则',
+        'rule-name' => '规则名称',
+        'transaction-type' => '交易类型',
+        'status' => '状态',
+        'active' => '启用',
+        'inactive' => '禁用',
+        'actions' => '操作',
+        'edit' => '编辑',
+        'delete' => '删除',
+        'create-rule' => '创建规则',
+        'update-rule' => '更新规则',
+        'back' => '返回',
+        'save' => '保存',
+        'cancel' => '取消',
+        'customer-list' => '客户列表',
+        'customer-name' => '客户姓名',
+        'email' => '邮箱',
+        'points-balance' => '积分余额',
+        'add-points' => '增加积分',
+        'deduct-points' => '扣除积分',
+        'reason' => '原因',
+        'total-points' => '总积分',
+        'total-customers' => '总客户数',
+        'average-points' => '平均积分',
+        'export' => '导出',
+        'bulk-update' => '批量更新',
+        'date-range' => '日期范围',
+        'start-date' => '开始日期',
+        'end-date' => '结束日期',
+        'points-earned' => '获得积分',
+        'points-redeemed' => '使用积分',
+        'title' => '客户积分',
+        'details-title' => ':name 的积分详情',
+        'customer-info' => '客户信息',
+        'points-history' => '积分历史',
+        'adjust-points' => '调整积分',
+        'type' => '类型',
+        'amount' => '数量',
+        'balance' => '余额',
+        'date' => '日期',
+        'description' => '描述',
+        'customer-id' => '客户ID',
+        'name' => '姓名',
+        'view' => '查看',
+        'no-customers-found' => '未找到客户',
+        'no-reward-points-records' => '暂无积分记录',
+
+        'transaction-types' => [
+            'order' => '订单',
+            'registration' => '注册',
+            'review' => '商品评价',
+            'sign-in' => '每日签到',
+            'referral' => '推荐好友',
+            'birthday' => '生日',
+            'share' => '分享',
+            'subscribe' => '订阅',
+        ],
+
+        'adjust-points-by-identifier' => '通过邮箱或ID调整积分',
+        'identifier' => '标识符',
+        'email-or-id' => '邮箱或ID',
+        'enter-email-or-id' => '输入客户邮箱或ID',
+        'enter-points' => '输入积分数量',
+        'enter-reason' => '输入调整原因',
+        'submit' => '提交',
+        'action' => '操作',
+
+        'total-transactions' => '总交易数',
+        'total-points-earned' => '总获得积分',
+        'total-points-redeemed' => '总使用积分',
+
+        'search' => '搜索',
+        'reset' => '重置',
+        'customer-email' => '客户邮箱',
+        'amount-type' => '积分类型',
+        'earned' => '获得',
+        'redeemed' => '使用',
+        'all' => '全部',
+        'all-types' => '全部类型',
+        'admin-adjustment' => '管理员调整',
+        'admin-action' => '管理员操作',
+
+        'id' => 'ID',
+        'customer' => '客户',
+        'amount' => '数量',
+        'date' => '日期',
+        'description' => '描述',
+        'balance' => '余额',
+
+        'no-transactions-found' => '未找到交易记录',
+        'try-adjusting-filters' => '请尝试调整筛选条件',
+
+        'points' => '积分',
+        'transactions-count' => '交易记录',
+
+        'menu' => [
+            'growth-value' => '成长值管理',
+            'growth-value-customers' => '会员成长值',
+            'growth-value-levels' => '等级配置',
+        ],
+
+        'growth-value' => [
+            'customer-detail' => '客户成长值详情',
+            'customer-info' => '客户信息',
+            'current-growth-value' => '当前成长值',
+            'current-level' => '当前等级',
+            'first-order-completed' => '首单完成',
+            'expires-at' => '过期时间',
+            'never-expires' => '永不过期',
+            'next-level-info' => '下一等级信息',
+            'next-level-name' => '下一等级',
+            'points-to-next-level' => '距离升级还需',
+            'history' => '成长值历史记录',
+            'no-history' => '暂无成长值历史记录',
+            'event-type' => '事件类型',
+            'growth-value-change' => '成长值变动',
+            'total-growth-value' => '成长值总额',
+            'level-change' => '等级变化',
+            'order-id' => '订单ID',
+            'created-at' => '创建时间',
+            'order-purchase' => '订单购买',
+            'admin-adjustment' => '管理员调整',
+            'expired' => '已过期',
+        ],
+    ],
+
+    'validation' => [
+        'points-required' => '积分不能为空',
+        'points-invalid' => '积分数量无效',
+        'customer-not-found' => '客户不存在',
+        'rule-not-found' => '规则不存在',
+    ],
+];

+ 86 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/adjust.blade.php

@@ -0,0 +1,86 @@
+<x-admin::layouts>
+    <x-slot:title>
+        调整成长值 - {{ $customer->name ?? '' }}
+        </x-slot>
+
+        <div class="flex gap-4 justify-between items-center max-sm:flex-wrap">
+            <div class="flex items-center gap-4">
+                <a href="{{ route('admin.growth-value.show', $customer->id) }}" class="icon-arrow-left text-2xl"></a>
+                <p class="text-xl text-gray-800 dark:text-white font-bold">
+                    调整成长值 - {{ $customer->name ?? '' }}
+                </p>
+            </div>
+        </div>
+
+        <div class="mt-4 bg-white dark:bg-cherry-900 rounded-lg p-6 shadow">
+            <form method="POST" action="{{ route('admin.growth-value.adjust', $customer->id) }}">
+                @csrf
+
+                <!-- 当前信息 -->
+                <div class="grid grid-cols-2 gap-4 mb-6">
+                    <div>
+                        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
+                            当前成长值
+                        </label>
+                        <div class="text-2xl font-bold text-blue-600">
+                            {{ number_format($growthValueInfo['growth_value']) }}
+                        </div>
+                    </div>
+
+                    <div>
+                        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
+                            当前等级
+                        </label>
+                        <div class="text-2xl font-bold text-purple-600">
+                            {{ $growthValueInfo['growth_level_name'] ?? 'V0' }}
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 调整数量 -->
+                <div class="mb-4">
+                    <label for="amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
+                        调整数量(正数增加,负数减少)
+                    </label>
+                    <input
+                        type="number"
+                        name="amount"
+                        id="amount"
+                        class="border rounded-md px-3 py-2 w-full dark:bg-cherry-800 dark:border-cherry-700"
+                        placeholder="例如:100 或 -50"
+                        required
+                    />
+                    @error('amount')
+                    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
+                    @enderror
+                </div>
+
+                <!-- 调整原因 -->
+                <div class="mb-4">
+                    <label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
+                        调整原因
+                    </label>
+                    <textarea
+                        name="description"
+                        id="description"
+                        rows="3"
+                        class="border rounded-md px-3 py-2 w-full dark:bg-cherry-800 dark:border-cherry-700"
+                        placeholder="请输入调整原因..."
+                    ></textarea>
+                    @error('description')
+                    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
+                    @enderror
+                </div>
+
+                <!-- 提交按钮 -->
+                <div class="flex gap-2">
+                    <button type="submit" class="primary-button">
+                        提交调整
+                    </button>
+                    <a href="{{ route('admin.growth-value.show', $customer->id) }}" class="secondary-button">
+                        取消
+                    </a>
+                </div>
+            </form>
+        </div>
+</x-admin::layouts>

+ 251 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/history.blade.php

@@ -0,0 +1,251 @@
+<x-admin::layouts>
+    <x-slot:title>
+        @lang('rewardpoints::app.admin.growth-value.history')
+    </x-slot:title>
+
+    {{-- 页面标题 --}}
+    <div class="flex flex-wrap gap-4 justify-between items-center mb-6">
+        <div class="flex items-center gap-3">
+            <div class="icon-list text-2xl text-blue-500"></div>
+            <p class="text-2xl text-gray-800 dark:text-white font-bold">
+                @lang('rewardpoints::app.admin.growth-value.history')
+            </p>
+            <span class="px-2.5 py-0.5 text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
+                {{ $histories->total() }}
+                @lang('rewardpoints::app.admin.total')
+            </span>
+        </div>
+    </div>
+
+    {{-- 筛选器 --}}
+    <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 p-4 mb-6">
+        <form method="GET" action="{{ route('admin.growth-value.history') }}" class="flex flex-wrap items-end gap-4">
+            {{-- 开始日期 --}}
+            <div class="flex-1 min-w-[180px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('Start Date')
+                </label>
+                <input
+                    type="date"
+                    name="start_date"
+                    value="{{ $startDate }}"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 结束日期 --}}
+            <div class="flex-1 min-w-[180px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('End Date')
+                </label>
+                <input
+                    type="date"
+                    name="end_date"
+                    value="{{ $endDate }}"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 客户邮箱 --}}
+            <div class="flex-1 min-w-[200px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('Customer Email')
+                </label>
+                <input
+                    type="text"
+                    name="customer_email"
+                    value="{{ $customerEmail }}"
+                    placeholder="@lang('Search by email')"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+            </div>
+
+            {{-- 事件类型 --}}
+            <div class="flex-1 min-w-[160px]">
+                <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
+                    @lang('Event Type')
+                </label>
+                <select
+                    name="event_type"
+                    class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
+                >
+                    <option value="">@lang('All Types')</option>
+                    <option value="order" {{ $eventType == 'order' ? 'selected' : '' }}>@lang('rewardpoints::app.admin.growth-value.order-purchase')</option>
+                    <option value="admin_adjust" {{ $eventType == 'admin_adjust' ? 'selected' : '' }}>@lang('rewardpoints::app.admin.growth-value.admin-adjustment')</option>
+                    <option value="expiration" {{ $eventType == 'expiration' ? 'selected' : '' }}>@lang('rewardpoints::app.admin.growth-value.expired')</option>
+                </select>
+            </div>
+
+            {{-- 操作按钮 --}}
+            <div class="flex gap-2">
+                <button type="submit" class="primary-button">
+                    <span class="icon-search text-lg"></span>
+                    @lang('Search')
+                </button>
+
+                <a href="{{ route('admin.growth-value.history') }}" class="secondary-button">
+                    <span class="icon-reset text-lg"></span>
+                    @lang('Reset')
+                </a>
+            </div>
+        </form>
+    </div>
+
+    {{-- 统计卡片 --}}
+    <div class="grid grid-cols-3 gap-4 mb-6">
+        {{-- 总获得成长值 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+            <div class="p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-sm text-gray-500 dark:text-gray-400">
+                            @lang('Total Growth Value Earned')
+                        </p>
+                        <p class="text-3xl font-bold text-green-600 dark:text-green-400 mt-2">
+                            {{ number_format($totalEarned) }}
+                        </p>
+                    </div>
+                    <div class="w-12 h-12 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center">
+                        <span class="icon-arrow-up text-2xl text-green-600 dark:text-green-400"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        {{-- 总扣除成长值 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+            <div class="p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-sm text-gray-500 dark:text-gray-400">
+                            @lang('Total Growth Value Deducted')
+                        </p>
+                        <p class="text-3xl font-bold text-red-600 dark:text-red-400 mt-2">
+                            {{ number_format($totalDeducted) }}
+                        </p>
+                    </div>
+                    <div class="w-12 h-12 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center">
+                        <span class="icon-arrow-down text-2xl text-red-600 dark:text-red-400"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        {{-- 总记录数 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+            <div class="p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-sm text-gray-500 dark:text-gray-400">
+                            @lang('Total Records')
+                        </p>
+                        <p class="text-3xl font-bold text-blue-600 dark:text-blue-400 mt-2">
+                            {{ number_format($totalRecords) }}
+                        </p>
+                    </div>
+                    <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center">
+                        <span class="icon-list text-2xl text-blue-600 dark:text-blue-400"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    {{-- 数据表格 --}}
+    <div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+        <div class="overflow-x-auto">
+            <table class="w-full text-sm text-left">
+                <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-800 dark:text-gray-400">
+                <tr>
+                    <th class="px-6 py-3">@lang('ID')</th>
+                    <th class="px-6 py-3">@lang('Customer')</th>
+                    <th class="px-6 py-3">@lang('Event Type')</th>
+                    <th class="px-6 py-3">@lang('Growth Value Change')</th>
+                    <th class="px-6 py-3">@lang('Total Growth Value')</th>
+                    <th class="px-6 py-3">@lang('Level Change')</th>
+                    <th class="px-6 py-3">@lang('Order ID')</th>
+                    <th class="px-6 py-3">@lang('Description')</th>
+                    <th class="px-6 py-3">@lang('Created At')</th>
+                </tr>
+                </thead>
+                <tbody>
+                @forelse($histories as $history)
+                    <tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800">
+                        <td class="px-6 py-4">{{ $history->history_id }}</td>
+                        <td class="px-6 py-4">
+                            @if($history->customer)
+                                <div>
+                                    <p class="font-medium text-gray-900 dark:text-white">{{ $history->customer->name }}</p>
+                                    <p class="text-xs text-gray-500 dark:text-gray-400">{{ $history->customer->email }}</p>
+                                </div>
+                            @else
+                                <span class="text-gray-400">N/A</span>
+                            @endif
+                        </td>
+                        <td class="px-6 py-4">
+                            @if($history->event_type === 'order')
+                                <span class="px-2 py-1 bg-green-100 text-green-800 rounded text-xs">
+                                    Order Purchase
+                                </span>
+                                                    @elseif($history->event_type === 'admin_adjust')
+                                                        <span class="px-2 py-1 bg-yellow-100 text-yellow-800 rounded text-xs">
+                                    Admin Adjustment
+                                </span>
+                                                    @elseif($history->event_type === 'expiration')
+                                                        <span class="px-2 py-1 bg-red-100 text-red-800 rounded text-xs">
+                                    Expired
+                                </span>
+                                                    @else
+                                                        <span class="px-2 py-1 bg-gray-100 text-gray-800 rounded text-xs">
+                                    {{ $history->event_type }}
+                                </span>
+                            @endif
+                        </td>
+                        <td class="px-6 py-4">
+                                <span style="color: {{ $history->growth_value_earned > 0 ? '#22c55e' : '#ef4444' }}; font-weight: 600;">
+                                    {{ $history->growth_value_earned > 0 ? '+' : '' }}{{ number_format($history->growth_value_earned) }}
+                                </span>
+                        </td>
+                        <td class="px-6 py-4 font-semibold">{{ number_format($history->total_growth_value) }}</td>
+                        <td class="px-6 py-4">
+                            @if($history->level_before && $history->level_after)
+                                <span class="text-gray-600">{{ $history->level_before }}</span>
+                                <span class="mx-1">→</span>
+                                <span class="text-blue-600 font-medium">{{ $history->level_after }}</span>
+                            @else
+                                <span class="text-gray-400">-</span>
+                            @endif
+                        </td>
+                        <td class="px-6 py-4">
+                            @if($history->order_id)
+                                <a href="{{ route('admin.sales.orders.view', $history->order_id) }}" target="_blank" class="text-blue-600 hover:underline">
+                                    #{{ $history->order_id }}
+                                </a>
+                            @else
+                                <span class="text-gray-400">-</span>
+                            @endif
+                        </td>
+                        <td class="px-6 py-4 text-gray-600 dark:text-gray-400">{{ $history->description }}</td>
+                        <td class="px-6 py-4 text-gray-600 dark:text-gray-400 whitespace-nowrap">
+                            {{ \Carbon\Carbon::parse($history->created_at)->format('Y-m-d H:i:s') }}
+                        </td>
+                    </tr>
+                @empty
+                    <tr>
+                        <td colspan="9" class="px-6 py-8 text-center text-gray-500">
+                            @lang('No growth value history records found')
+                        </td>
+                    </tr>
+                @endforelse
+                </tbody>
+            </table>
+        </div>
+
+        {{-- 分页 --}}
+        @if($histories->hasPages())
+            <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-800">
+                {{ $histories->links() }}
+            </div>
+        @endif
+    </div>
+</x-admin::layouts>

+ 396 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/index.blade.php

@@ -0,0 +1,396 @@
+<x-admin::layouts>
+    <x-slot:title>
+        会员成长值管理
+        </x-slot>
+
+        {{-- 页面标题和操作按钮 --}}
+        <div class="flex flex-wrap gap-4 justify-between items-center mb-6">
+            <div class="flex items-center gap-4">
+                <div class="w-12 h-12 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
+                    <svg class="w-7 h-7 text-white" fill="currentColor" viewBox="0 0 20 20">
+                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
+                    </svg>
+                </div>
+                <div>
+                    <h1 class="text-2xl font-bold text-gray-800 dark:text-white">
+                        会员成长值
+                    </h1>
+                    <p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
+                        管理会员成长值和等级体系
+                    </p>
+                </div>
+            </div>
+
+            <div class="flex items-center gap-3">
+            <span class="px-3 py-1.5 text-sm bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded-full font-medium">
+                {{ $customers->total() }} 位会员
+            </span>
+
+                {{-- 调整成长值按钮 --}}
+                <button
+                    type="button"
+                    onclick="openAdjustModal()"
+                    style="padding: 10px 20px; background: linear-gradient(135deg, #8b5cf6, #7c3aed); color: white; border-radius: 8px; border: none; cursor: pointer; display: flex; align-items: center; gap: 8px; font-weight: 500; box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1);"
+                    onmouseover="this.style.background='linear-gradient(135deg, #7c3aed, #6d28d9)'"
+                    onmouseout="this.style.background='linear-gradient(135deg, #8b5cf6, #7c3aed)'"
+                >
+                    <svg style="width: 20px; height: 20px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
+                    </svg>
+                    调整成长值
+                </button>
+            </div>
+        </div>
+
+        {{-- 统计卡片 --}}
+        <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
+            <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-xs text-gray-500 dark:text-gray-400 mb-1">总会员数</p>
+                        <p class="text-2xl font-bold text-purple-600">{{ number_format($totalCustomers) }}</p>
+                    </div>
+                    <div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
+                        <svg class="w-5 h-5 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
+                            <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
+                        </svg>
+                    </div>
+                </div>
+            </div>
+
+            <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-xs text-gray-500 dark:text-gray-400 mb-1">总成长值</p>
+                        <p class="text-2xl font-bold text-blue-600">{{ number_format($totalGrowthValue) }}</p>
+                    </div>
+                    <div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
+                        <svg class="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
+                            <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
+                        </svg>
+                    </div>
+                </div>
+            </div>
+
+            <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-xs text-gray-500 dark:text-gray-400 mb-1">平均成长值</p>
+                        <p class="text-2xl font-bold text-green-600">{{ number_format($avgGrowthValue) }}</p>
+                    </div>
+                    <div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
+                        <svg class="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 20 20">
+                            <path fill-rule="evenodd" d="M12 7a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0V8.414l-4.293 4.293a1 1 0 01-1.414 0L8 10.414l-4.293 4.293a1 1 0 01-1.414-1.414l5-5a1 1 0 011.414 0L11 10.586 14.586 7H12z"/>
+                        </svg>
+                    </div>
+                </div>
+            </div>
+
+            <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 p-5">
+                <div class="flex items-center justify-between">
+                    <div>
+                        <p class="text-xs text-gray-500 dark:text-gray-400 mb-1">已完成首单</p>
+                        <p class="text-2xl font-bold text-orange-600">{{ number_format($firstOrderCount) }}</p>
+                    </div>
+                    <div class="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
+                        <svg class="w-5 h-5 text-orange-600" fill="currentColor" viewBox="0 0 20 20">
+                            <path d="M3 1a1 1 0 000 2h1.22l.305 1.222a.997.997 0 00.01.042l1.358 5.43-.893.892C3.74 11.846 4.632 14 6.414 14H15a1 1 0 000-2H6.414l1-1H14a1 1 0 00.894-.553l3-6A1 1 0 0017 3H6.28l-.31-1.243A1 1 0 005 1H3zM16 16.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM6.5 18a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"/>
+                        </svg>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        {{-- 搜索和筛选 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 p-6 mb-6">
+            <form method="GET" action="{{ route('admin.growth-value.index') }}" class="grid grid-cols-1 md:grid-cols-4 gap-4">
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">客户邮箱/姓名</label>
+                    <input type="text" name="search" value="{{ request('search') }}" placeholder="搜索客户..." class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
+                </div>
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">成长值等级</label>
+                    <select name="growth_level" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
+                        <option value="">全部</option>
+                        <option value="V0" {{ request('growth_level') == 'V0' ? 'selected' : '' }}>V0</option>
+                        <option value="V1" {{ request('growth_level') == 'V1' ? 'selected' : '' }}>V1</option>
+                        <option value="V2" {{ request('growth_level') == 'V2' ? 'selected' : '' }}>V2</option>
+                        <option value="V3" {{ request('growth_level') == 'V3' ? 'selected' : '' }}>V3</option>
+                    </select>
+                </div>
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">最小成长值</label>
+                    <input type="number" name="min_growth_value" value="{{ request('min_growth_value') }}" placeholder="0" min="0" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
+                </div>
+                <div class="flex items-end gap-2">
+                    <button type="submit" class="flex-1 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700">搜索</button>
+                    <a href="{{ route('admin.growth-value.index') }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200">重置</a>
+                </div>
+            </form>
+        </div>
+
+        {{-- 数据表格 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+            <div class="overflow-x-auto">
+                <table class="min-w-full divide-y divide-gray-200">
+                    <thead class="bg-gray-50">
+                    <tr>
+                        <th class="px-6 py-4 text-left text-xs font-medium text-gray-500">客户信息</th>
+                        <th class="px-6 py-4 text-right text-xs font-medium text-gray-500">当前成长值</th>
+                        <th class="px-6 py-4 text-center text-xs font-medium text-gray-500">当前等级</th>
+                        <th class="px-6 py-4 text-center text-xs font-medium text-gray-500">首单状态</th>
+                        <th class="px-6 py-4 text-center text-xs font-medium text-gray-500">等级更新时间</th>
+                        <th class="px-6 py-4 text-right text-xs font-medium text-gray-500">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody class="divide-y divide-gray-200">
+                    @forelse($customers as $customer)
+                        <tr class="hover:bg-gray-50">
+                            <td class="px-6 py-4">
+                                <div class="flex items-center gap-3">
+                                    <div class="w-10 h-10 bg-gradient-to-br from-purple-400 to-purple-600 rounded-full flex items-center justify-center text-white text-sm font-medium">
+                                        {{ substr($customer->customer->name ?? $customer->customer->email ?? 'U', 0, 1) }}
+                                    </div>
+                                    <div>
+                                        <p class="text-sm font-medium text-gray-900">{{ $customer->customer->name ?? 'N/A' }}</p>
+                                        <p class="text-xs text-gray-500">{{ $customer->customer->email ?? 'N/A' }}</p>
+                                    </div>
+                                </div>
+                            </td>
+                            <td class="px-6 py-4 text-right">
+                                <span class="text-xl font-bold text-blue-600">{{ number_format($customer->growth_value) }}</span>
+                                <span class="text-xs text-gray-500">分</span>
+                            </td>
+                            <td class="px-6 py-4 text-center">
+                                <span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700">{{ $customer->growth_level }}</span>
+                            </td>
+                            <td class="px-6 py-4 text-center">
+                                @if($customer->first_order_completed_at)
+                                    <span class="text-green-600">✓ 已完成</span>
+                                @else
+                                    <span class="text-gray-500">- 未完成</span>
+                                @endif
+                            </td>
+                            <td class="px-6 py-4 text-center text-sm text-gray-500">{{ $customer->level_updated_at ? \Carbon\Carbon::parse($customer->level_updated_at)->format('Y-m-d') : '-' }}</td>
+                            <td class="px-6 py-4 text-right">
+                                <div class="flex items-center justify-end gap-2">
+                                    <button onclick="viewDetails({{ $customer->customer_id }})" class="text-blue-600 hover:text-blue-800" title="查看详情">
+                                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
+                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
+                                        </svg>
+                                    </button>
+                                    <button onclick="quickAdjust({{ $customer->customer_id }}, '{{ addslashes($customer->customer->name ?? $customer->customer->email ?? '') }}')" class="text-green-600 hover:text-green-800" title="快速调整">
+                                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
+                                        </svg>
+                                    </button>
+                                </div>
+                            </td>
+                        </tr>
+                    @empty
+                        <tr>
+                            <td colspan="6" class="px-6 py-12 text-center text-gray-500">暂无会员成长值数据</td>
+                        </tr>
+                    @endforelse
+                    </tbody>
+                </table>
+            </div>
+            @if($customers->hasPages())
+                <div class="px-6 py-4 border-t">{{ $customers->links() }}</div>
+            @endif
+        </div>
+        {{-- 在页面底部添加详情模态框 --}}
+        <div id="detailModal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:100000; align-items:center; justify-content:center;">
+            <div style="background:white; border-radius:12px; width:500px; max-width:90%; max-height:80%; overflow:auto; margin:20px;">
+                <div style="padding:20px; border-bottom:1px solid #e5e7eb; display:flex; justify-content:space-between; align-items:center;">
+                    <h3 style="font-size:18px; font-weight:bold;">客户成长值详情</h3>
+                    <button onclick="closeDetailModal()" style="background:none; border:none; font-size:24px; cursor:pointer;">&times;</button>
+                </div>
+                <div id="detailContent" style="padding:20px;">
+                    <!-- 动态内容会加载到这里 -->
+                    <div style="text-align:center; padding:20px;">加载中...</div>
+                </div>
+            </div>
+        </div>
+        {{-- 使用原生 alert 确认框代替复杂模态框 --}}
+        <script>
+            function openAdjustModal() {
+                const modalHtml = `
+                <div id="simpleModal" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:99999;display:flex;align-items:center;justify-content:center;">
+                    <div style="background:white;border-radius:12px;width:450px;max-width:90%;padding:20px;box-shadow:0 20px 25px -5px rgba(0,0,0,0.1);">
+                        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:10px;border-bottom:1px solid #e5e7eb;">
+                            <h3 style="font-size:18px;font-weight:bold;color:#1f2937;">调整成长值</h3>
+                            <button onclick="closeSimpleModal()" style="background:none;border:none;font-size:24px;cursor:pointer;color:#9ca3af;">&times;</button>
+                        </div>
+                        <form id="simpleAdjustForm">
+                            <input type="hidden" name="_token" value="{{ csrf_token() }}">
+                            <div style="margin-bottom:15px;">
+                                <label style="display:block;font-size:14px;font-weight:500;margin-bottom:5px;color:#374151;">客户邮箱或ID *</label>
+                                <input type="text" name="customer_identifier" id="simpleCustomerId" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;" required placeholder="输入客户邮箱或ID">
+                                <p style="font-size:12px;color:#6b7280;margin-top:5px;">可以直接输入客户邮箱地址或客户ID</p>
+                            </div>
+                            <div style="margin-bottom:15px;">
+                                <label style="display:block;font-size:14px;font-weight:500;margin-bottom:5px;color:#374151;">操作类型 *</label>
+                                <div style="display:flex;gap:10px;">
+                                    <label style="flex:1;padding:8px;text-align:center;border:2px solid #d1d5db;border-radius:8px;cursor:pointer;">
+                                        <input type="radio" name="action" value="add" checked style="margin-right:5px;"> 增加
+                                    </label>
+                                    <label style="flex:1;padding:8px;text-align:center;border:2px solid #d1d5db;border-radius:8px;cursor:pointer;">
+                                        <input type="radio" name="action" value="deduct" style="margin-right:5px;"> 减少
+                                    </label>
+                                </div>
+                            </div>
+                            <div style="margin-bottom:15px;">
+                                <label style="display:block;font-size:14px;font-weight:500;margin-bottom:5px;color:#374151;">数量 *</label>
+                                <input type="number" name="amount" min="1" required style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;" placeholder="输入成长值数量">
+                            </div>
+                            <div style="margin-bottom:20px;">
+                                <label style="display:block;font-size:14px;font-weight:500;margin-bottom:5px;color:#374151;">调整原因</label>
+                                <input type="text" name="description" value="管理员手动调整" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;" placeholder="调整原因">
+                            </div>
+                            <div style="display:flex;gap:10px;">
+                                <button type="button" onclick="closeSimpleModal()" style="flex:1;padding:10px;background:#f3f4f6;border:none;border-radius:8px;cursor:pointer;">取消</button>
+                                <button type="submit" style="flex:1;padding:10px;background:#7c3aed;color:white;border:none;border-radius:8px;cursor:pointer;">确认调整</button>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            `;
+
+                document.body.insertAdjacentHTML('beforeend', modalHtml);
+
+                document.getElementById('simpleAdjustForm').addEventListener('submit', function(e) {
+                    e.preventDefault();
+                    const formData = new FormData(this);
+
+                    fetch('{{ route("admin.growth-value.adjust-from-list") }}', {
+                        method: 'POST',
+                        body: formData,
+                        headers: { 'X-Requested-With': 'XMLHttpRequest' }
+                    })
+                        .then(response => response.json())
+                        .then(data => {
+                            if (data.success) {
+                                alert(data.message || '调整成功!');
+                                closeSimpleModal();
+                                location.reload();
+                            } else {
+                                alert(data.message || '调整失败');
+                            }
+                        })
+                        .catch(error => {
+                            alert('网络错误,请重试');
+                        });
+                });
+            }
+
+            function closeSimpleModal() {
+                const modal = document.getElementById('simpleModal');
+                if (modal) modal.remove();
+            }
+
+            function quickAdjust(customerId, customerName) {
+                const amount = prompt(`为 ${customerName} 调整成长值\n输入正数表示增加,负数表示减少`, "100");
+                if (amount === null) return;
+
+                const finalAmount = parseInt(amount);
+                if (isNaN(finalAmount) || finalAmount === 0) {
+                    alert('请输入有效的数字');
+                    return;
+                }
+
+                const formData = new FormData();
+                formData.append('_token', '{{ csrf_token() }}');
+                formData.append('customer_identifier', customerId);
+                formData.append('amount', finalAmount);
+                formData.append('description', '管理员快速调整');
+
+                fetch('{{ route("admin.growth-value.adjust-from-list") }}', {
+                    method: 'POST',
+                    body: formData,
+                    headers: { 'X-Requested-With': 'XMLHttpRequest' }
+                })
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.success) {
+                            alert(data.message || '调整成功!');
+                            location.reload();
+                        } else {
+                            alert(data.message || '调整失败');
+                        }
+                    })
+                    .catch(error => {
+                        alert('网络错误,请重试');
+                    });
+            }
+
+            function viewDetails(customerId) {
+                // 显示模态框
+                document.getElementById('detailModal').style.display = 'flex';
+                document.getElementById('detailContent').innerHTML = '<div style="text-align:center; padding:20px;">加载中...</div>';
+
+                // 获取详情数据
+                fetch(`{{ url('admin/reward-points/growth-value') }}/${customerId}`, {
+                    headers: {
+                        'X-Requested-With': 'XMLHttpRequest',
+                        'Accept': 'application/json'
+                    }
+                })
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.success) {
+                            let historyHtml = '';
+                            if (data.history && data.history.length > 0) {
+                                historyHtml = '<div style="margin-top:15px;"><h4 style="font-weight:bold;margin-bottom:10px;">最近变动记录</h4>';
+                                data.history.slice(0, 5).forEach(log => {
+                                    historyHtml += `
+                        <div style="padding:8px; border-bottom:1px solid #e5e7eb; display:flex; justify-content:space-between;">
+                            <span style="color:${log.growth_value_earned > 0 ? '#16a34a' : '#dc2626'}">
+                                ${log.growth_value_earned > 0 ? '+' : ''}${log.growth_value_earned}
+                            </span>
+                            <span style="color:#6b7280; font-size:12px;">${log.description || '-'}</span>
+                            <span style="color:#6b7280; font-size:12px;">${log.created_at}</span>
+                        </div>
+                    `;
+                                });
+                                historyHtml += '</div>';
+                            } else {
+                                historyHtml = '<p style="margin-top:15px;color:#9ca3af;">暂无变动记录</p>';
+                            }
+
+                            document.getElementById('detailContent').innerHTML = `
+                <div style="display:grid; gap:12px;">
+                    <div><strong>客户名称:</strong> ${data.customer.name || '-'}</div>
+                    <div><strong>电子邮箱:</strong> ${data.customer.email || '-'}</div>
+                    <div><strong>当前成长值:</strong> <span style="color:#22c55e; font-size:20px; font-weight:bold;">${data.growth_value}</span> 分</div>
+                    <div><strong>当前等级:</strong> <span style="display:inline-block; padding:2px 8px; background:#9333ea; color:white; border-radius:12px; font-size:12px;">${data.growth_level || 'V0'}</span></div>
+                    <div><strong>首单状态:</strong> ${data.first_order_completed_at ? '✓ 已完成' : '未完成'}</div>
+                    ${data.expires_at ? `<div><strong>过期时间:</strong> ${data.expires_at}</div>` : ''}
+                    ${historyHtml}
+                </div>
+            `;
+                        } else {
+                            document.getElementById('detailContent').innerHTML = `<div style="color:#dc2626; text-align:center;">获取详情失败:${data.message || '未知错误'}</div>`;
+                        }
+                    })
+                    .catch(error => {
+                        console.error('Error:', error);
+                        document.getElementById('detailContent').innerHTML = '<div style="color:#dc2626; text-align:center;">网络错误,请重试</div>';
+                    });
+            }
+
+            function closeDetailModal() {
+                document.getElementById('detailModal').style.display = 'none';
+            }
+
+            // 点击模态框背景关闭
+            document.addEventListener('click', function(event) {
+                const modal = document.getElementById('detailModal');
+                if (event.target === modal) {
+                    closeDetailModal();
+                }
+            });
+        </script>
+</x-admin::layouts>
+

+ 109 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/settings.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($groupData as $group => $data)
+                    <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($data['name'])
+                    </a>
+                @endforeach
+            </div>
+        </div>
+--}}
+        {{-- 配置表单 --}}
+        <div class="bg-white dark:bg-gray-900 rounded-lg shadow">
+            <form method="POST" action="{{ route('admin.growth-value.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>

+ 316 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/growth-value/show.blade.php

@@ -0,0 +1,316 @@
+{{-- 使用 Bagisto 正确的布局组件语法 --}}
+<x-admin::layouts>
+
+    <x-slot:title>
+        {{ __('rewardpoints::app.admin.growth-value.customer-detail') }} - {{ $customer->name }}
+    </x-slot:title>
+
+    {{-- 页面头部 --}}
+    <div class="flex items-center justify-between mb-6">
+        <div class="flex items-center gap-4">
+            <a href="{{ route('admin.growth-value.index') }}" class="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white">
+                <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
+                </svg>
+            </a>
+            <div>
+                <h1 class="text-2xl font-bold text-gray-800 dark:text-white">
+                    {{ __('rewardpoints::app.admin.growth-value.customer-detail') }} - {{ $customer->name }}
+                </h1>
+                <p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
+                    查看和管理会员的成长值详情及历史记录
+                </p>
+            </div>
+        </div>
+
+        <div class="flex items-center gap-3">
+            <button onclick="quickAdjust({{ $customer->id }}, '{{ addslashes($customer->name) }}')"
+                    class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-all duration-200 flex items-center gap-2">
+                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
+                </svg>
+                调整成长值
+            </button>
+        </div>
+    </div>
+
+    {{-- 客户基本信息卡片 --}}
+    <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden mb-6">
+        <div class="border-b border-gray-200 dark:border-gray-800 px-6 py-4">
+            <h3 class="text-lg font-semibold text-gray-800 dark:text-white flex items-center gap-2">
+                <svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
+                </svg>
+                客户基本信息
+            </h3>
+        </div>
+        <div class="p-6">
+            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">姓名</label>
+                    <div class="text-gray-900 dark:text-white">{{ $customer->name }}</div>
+                </div>
+
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">邮箱</label>
+                    <div class="text-gray-900 dark:text-white">{{ $customer->email }}</div>
+                </div>
+
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">当前成长值</label>
+                    <div class="text-2xl font-bold text-green-600 dark:text-green-400">
+                        {{ number_format($growthValueInfo['growth_value']) }}
+                        <span class="text-sm font-normal text-gray-500">分</span>
+                    </div>
+                </div>
+
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">当前等级</label>
+                    <div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300">
+                        {{ $growthValueInfo['growth_level_name'] }}
+                    </div>
+                </div>
+
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">折扣率</label>
+                    <div class="text-gray-900 dark:text-white">
+                        @if($growthValueInfo['growth_discount_rate'] > 0)
+                            <span class="text-orange-600 font-semibold">{{ $growthValueInfo['growth_discount_rate'] }}%</span>
+                        @else
+                            <span class="text-gray-400">-</span>
+                        @endif
+                    </div>
+                </div>
+
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">首单状态</label>
+                    <div>
+                        @if($growthValueInfo['first_order_completed'])
+                            <span class="inline-flex items-center gap-1 text-green-600">
+                                <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
+                                </svg>
+                                已完成
+                            </span>
+                        @else
+                            <span class="text-gray-400">未完成</span>
+                        @endif
+                    </div>
+                </div>
+
+                <div>
+                    <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">成长值过期时间</label>
+                    <div class="text-gray-900 dark:text-white">
+                        @if($growthValueInfo['expires_at'])
+                            <span class="text-yellow-600">{{ \Carbon\Carbon::parse($growthValueInfo['expires_at'])->format('Y-m-d H:i:s') }}</span>
+                        @else
+                            <span class="text-gray-400">永不过期</span>
+                        @endif
+                    </div>
+                </div>
+
+                @if($growthValueInfo['growth_benefits'])
+                    <div>
+                        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">等级权益</label>
+                        <div class="text-gray-900 dark:text-white">
+                            @if(is_array($growthValueInfo['growth_benefits']))
+                                <ul class="list-disc list-inside">
+                                    @foreach($growthValueInfo['growth_benefits'] as $benefit)
+                                        <li class="text-sm">{{ $benefit }}</li>
+                                    @endforeach
+                                </ul>
+                            @else
+                                {{ $growthValueInfo['growth_benefits'] }}
+                            @endif
+                        </div>
+                    </div>
+                @endif
+            </div>
+        </div>
+    </div>
+
+    {{-- 下一等级信息卡片 --}}
+    @if($growthValueInfo['next_level'])
+        <div class="bg-gradient-to-r from-yellow-50 to-orange-50 dark:from-yellow-900/20 dark:to-orange-900/20 rounded-xl border border-yellow-200 dark:border-yellow-800 overflow-hidden mb-6">
+            <div class="px-6 py-4">
+                <div class="flex items-center justify-between flex-wrap gap-4">
+                    <div>
+                        <p class="text-sm text-gray-600 dark:text-gray-400 mb-1">距离下一等级</p>
+                        <p class="text-2xl font-bold text-orange-600 dark:text-orange-400">
+                            {{ number_format($growthValueInfo['points_to_next_level']) }}
+                            <span class="text-sm font-normal">分</span>
+                        </p>
+                        <p class="text-sm text-gray-500 mt-1">
+                            目标等级:<span class="font-semibold text-purple-600">{{ $growthValueInfo['next_level']['growth_level_name'] }}</span>
+                        </p>
+                    </div>
+                    <div class="w-64">
+                        <div class="bg-gray-200 dark:bg-gray-700 rounded-full h-2 overflow-hidden">
+                            @php
+                                $current = $growthValueInfo['growth_value'];
+                                $next = $growthValueInfo['next_level']['min_growth_value'];
+                                $prev = $growthValueInfo['next_level']['prev_min_growth_value'] ?? 0;
+                                $percentage = $next > $prev ? min(100, ($current - $prev) / ($next - $prev) * 100) : 0;
+                            @endphp
+                            <div class="bg-gradient-to-r from-purple-500 to-orange-500 h-2 rounded-full transition-all duration-300" style="width: {{ $percentage }}%"></div>
+                        </div>
+                        <p class="text-xs text-gray-500 text-right mt-1">{{ round($percentage) }}%</p>
+                    </div>
+                </div>
+            </div>
+        </div>
+    @endif
+
+    {{-- 成长值历史记录 --}}
+    <div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
+        <div class="border-b border-gray-200 dark:border-gray-800 px-6 py-4">
+            <h3 class="text-lg font-semibold text-gray-800 dark:text-white flex items-center gap-2">
+                <svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
+                </svg>
+                成长值变动历史
+            </h3>
+        </div>
+
+        <div class="overflow-x-auto">
+            @if($history->isEmpty())
+                <div class="text-center py-12">
+                    <svg class="w-16 h-16 mx-auto text-gray-300 dark:text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
+                    </svg>
+                    <p class="text-gray-500 dark:text-gray-400">{{ __('rewardpoints::app.admin.growth-value.no-history') }}</p>
+                </div>
+            @else
+                <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
+                    <thead class="bg-gray-50 dark:bg-gray-800/50">
+                    <tr>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">事件类型</th>
+                        <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">变动值</th>
+                        <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">累计成长值</th>
+                        <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">等级变化</th>
+                        <th class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">关联订单</th>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">描述</th>
+                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">时间</th>
+                    </tr>
+                    </thead>
+                    <tbody class="divide-y divide-gray-200 dark:divide-gray-800">
+                    @foreach($history as $record)
+                        <tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors duration-150">
+                            <td class="px-6 py-4 whitespace-nowrap">
+                                @if($record->event_type === 'order')
+                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300">
+                                        订单购买
+                                    </span>
+                                @elseif($record->event_type === 'admin_adjust')
+                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300">
+                                        管理员调整
+                                    </span>
+                                @elseif($record->event_type === 'expiration')
+                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">
+                                        过期清零
+                                    </span>
+                                @else
+                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300">
+                                        {{ $record->event_type }}
+                                    </span>
+                                @endif
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-right">
+                                <span class="font-semibold {{ $record->growth_value_earned > 0 ? 'text-green-600' : 'text-red-600' }}">
+                                    {{ $record->growth_value_earned > 0 ? '+' : '' }}{{ number_format($record->growth_value_earned) }}
+                                </span>
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-right font-semibold text-gray-900 dark:text-white">
+                                {{ number_format($record->total_growth_value) }}
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-center">
+                                @if($record->level_before && $record->level_after)
+                                    <div class="flex items-center justify-center gap-2">
+                                        <span class="text-sm text-gray-500">{{ $record->level_before }}</span>
+                                        <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/>
+                                        </svg>
+                                        <span class="text-sm font-semibold text-purple-600">{{ $record->level_after }}</span>
+                                    </div>
+                                @elseif($record->level_after)
+                                    <span class="text-sm font-semibold text-purple-600">{{ $record->level_after }}</span>
+                                @elseif($record->level_before)
+                                    <span class="text-sm text-gray-500 line-through">{{ $record->level_before }}</span>
+                                @else
+                                    <span class="text-gray-400">-</span>
+                                @endif
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-center">
+                                @if($record->order_id)
+                                    <a href="{{ route('admin.sales.orders.view', $record->order_id) }}" target="_blank" class="text-purple-600 hover:text-purple-800">
+                                        #{{ $record->order_id }}
+                                    </a>
+                                @else
+                                    <span class="text-gray-400">-</span>
+                                @endif
+                            </td>
+                            <td class="px-6 py-4 max-w-xs">
+                                <p class="text-sm text-gray-600 dark:text-gray-400 truncate" title="{{ $record->description }}">
+                                    {{ $record->description }}
+                                </p>
+                            </td>
+                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
+                                {{ \Carbon\Carbon::parse($record->created_at)->format('Y-m-d H:i:s') }}
+                            </td>
+                        </tr>
+                    @endforeach
+                    </tbody>
+                </table>
+            @endif
+        </div>
+
+        @if($history->hasPages())
+            <div class="border-t border-gray-200 dark:border-gray-800 px-6 py-4">
+                {{ $history->links() }}
+            </div>
+        @endif
+    </div>
+
+    {{-- JavaScript 脚本 --}}
+    @push('scripts')
+        <script>
+            function quickAdjust(customerId, customerName) {
+                const amount = prompt(`为「${customerName}」调整成长值\n\n输入正数表示增加,负数表示减少`, "100");
+                if (amount === null) return;
+
+                const finalAmount = parseInt(amount);
+                if (isNaN(finalAmount) || finalAmount === 0) {
+                    alert('请输入有效的数字');
+                    return;
+                }
+
+                const formData = new FormData();
+                formData.append('_token', '{{ csrf_token() }}');
+                formData.append('customer_identifier', customerId);
+                formData.append('action', finalAmount > 0 ? 'add' : 'deduct');
+                formData.append('amount', Math.abs(finalAmount));
+                formData.append('description', '管理员快速调整');
+
+                fetch('{{ route("admin.growth-value.adjust-from-list") }}', {
+                    method: 'POST',
+                    body: formData,
+                    headers: { 'X-Requested-With': 'XMLHttpRequest' }
+                })
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.success) {
+                            alert(data.message || '调整成功!');
+                            location.reload();
+                        } else {
+                            alert(data.message || '调整失败');
+                        }
+                    })
+                    .catch(error => {
+                        console.error('Error:', error);
+                        alert('网络错误,请重试');
+                    });
+            }
+        </script>
+    @endpush
+
+</x-admin::layouts>

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

@@ -23,7 +23,7 @@
         @endif
 
         {{-- 分组标签页 --}}
-        <div class="bg-white dark:bg-gray-900 rounded-lg shadow mb-6">
+       <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($groupData as $group => $data)
                     <a href="{{ route('admin.reward-points.settings.index', ['group' => $group]) }}"
@@ -55,7 +55,7 @@
 
                                 @switch($setting->type)
                                     @case('boolean')
-                                        <select name="settings[{{ $setting->code }}]" 
+                                        <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>
@@ -63,9 +63,9 @@
                                         @break
 
                                     @case('number')
-                                        <input type="number" 
+                                        <input type="number"
                                                step="any"
-                                               name="settings[{{ $setting->code }}]" 
+                                               name="settings[{{ $setting->code }}]"
                                                value="{{ old('settings.' . $setting->code, $setting->value) }}"
                                                class="control w-full"
                                                @if($setting->code === 'point_value') step="0.01" @endif
@@ -73,7 +73,7 @@
                                         @break
 
                                     @case('select')
-                                        <select name="settings[{{ $setting->code }}]" 
+                                        <select name="settings[{{ $setting->code }}]"
                                                 class="control w-full">
                                             @foreach(($setting->options ?? []) as $key => $label)
                                                 <option value="{{ $key }}" {{ $setting->value == $key ? 'selected' : '' }}>
@@ -84,8 +84,8 @@
                                         @break
 
                                     @default
-                                        <input type="text" 
-                                               name="settings[{{ $setting->code }}]" 
+                                        <input type="text"
+                                               name="settings[{{ $setting->code }}]"
                                                value="{{ old('settings.' . $setting->code, $setting->value) }}"
                                                class="control w-full">
                                 @endswitch
@@ -106,4 +106,4 @@
             </form>
         </div>
     </div>
-</x-admin::layouts>
+</x-admin::layouts>

+ 16 - 15
packages/Longyi/RewardPoints/src/Resources/views/admin/transactions/index.blade.php

@@ -103,14 +103,15 @@
                     class="flex w-full min-h-[39px] py-2 px-3 border border-gray-300 dark:border-gray-800 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-all hover:border-gray-400 dark:hover:border-gray-400 focus:border-gray-400 dark:focus:border-gray-400"
                 >
                     <option value="">@lang('All Types')</option>
-                    <option value="1" {{ $transactionType == '1' ? 'selected' : '' }}>@lang('Order')</option>
+                    <option value="3" {{ $transactionType == '3' ? 'selected' : '' }}>@lang('Order')</option>
                     <option value="2" {{ $transactionType == '2' ? 'selected' : '' }}>@lang('Registration')</option>
-                    <option value="3" {{ $transactionType == '3' ? 'selected' : '' }}>@lang('Product Review')</option>
-                    <option value="4" {{ $transactionType == '4' ? 'selected' : '' }}>@lang('Daily Sign In')</option>
+                    <option value="4" {{ $transactionType == '4' ? 'selected' : '' }}>@lang('Product Review')</option>
+                    <option value="1" {{ $transactionType == '1' ? 'selected' : '' }}>@lang('Daily Sign In')</option>
                     <option value="5" {{ $transactionType == '5' ? 'selected' : '' }}>@lang('Referral')</option>
                     <option value="6" {{ $transactionType == '6' ? 'selected' : '' }}>@lang('Birthday')</option>
+                    <option value="7" {{ $transactionType == '7' ? 'selected' : '' }}>@lang('Share')</option>
                     <option value="8" {{ $transactionType == '8' ? 'selected' : '' }}>@lang('Subscribe')</option>
-                    <option value="0" {{ $transactionType == '0' ? 'selected' : '' }}>@lang('Admin Adjustment')</option>
+                    <option value="9" {{ $transactionType == '9' ? 'selected' : '' }}>@lang('Login')</option>
                     <option value="99" {{ $transactionType == '99' ? 'selected' : '' }}>@lang('Admin Action')</option>
                 </select>
             </div>
@@ -259,17 +260,17 @@
                         <td class="px-6 py-4 whitespace-nowrap">
                             @php
                                 $typeNames = [
-                                    1 => 'rewardpoints::rewardpoints.admin.transaction-types.order',
-                                    2 => 'rewardpoints::rewardpoints.admin.transaction-types.registration',
-                                    3 => 'rewardpoints::rewardpoints.admin.transaction-types.review',
-                                    4 => 'rewardpoints::rewardpoints.admin.transaction-types.sign-in',
-                                    5 => 'rewardpoints::rewardpoints.admin.transaction-types.referral',
-                                    6 => 'rewardpoints::rewardpoints.admin.transaction-types.birthday',
-                                    7 => 'rewardpoints::rewardpoints.admin.transaction-types.share',
-                                    8 => 'rewardpoints::rewardpoints.admin.transaction-types.subscribe',
-                                    0 => 'rewardpoints::rewardpoints.admin.admin-adjustment',
-                                    99 => 'rewardpoints::rewardpoints.admin.admin-action',
-                                ];
+                                        3 => 'Order',
+                                        2 => 'Registration',
+                                        4 => 'Product Review',
+                                        1 => 'Daily Sign In',
+                                        5 => 'Referral',
+                                        6 => 'Birthday',
+                                        7 => 'Share',
+                                        8 => 'Subscribe',
+                                        9 => 'login',
+                                        99 => 'Admin Action'
+                                    ];
                             @endphp
                             <span class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full
             @if($transaction->amount > 0)

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

@@ -1,6 +1,8 @@
 <?php
 
 use Illuminate\Support\Facades\Route;
+use Longyi\RewardPoints\Http\Controllers\Admin\GrowthValueController;
+use Longyi\RewardPoints\Http\Middleware\ApiCustomerAuthenticate;
 
 // 前台路由 - 客户中心
 Route::group(['middleware' => ['web', 'customer'], 'prefix' => 'customer'], function () {
@@ -20,6 +22,16 @@ Route::group(['middleware' => ['web', 'customer'], 'prefix' => 'customer'], func
         'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getSignStatus'
     ]);
 
+    Route::get('reward-points/history', [
+        'as' => 'customer.reward-points.history',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsHistory'
+    ]);
+
+    Route::get('reward-points/summary', [
+        'as' => 'customer.reward-points.summary',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsSummary'
+    ]);
+
     Route::post('apply-reward-points', [
         'as' => 'checkout.apply-reward-points',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@applyPoints'
@@ -34,6 +46,59 @@ Route::group(['middleware' => ['web', 'customer'], 'prefix' => 'customer'], func
         'as' => 'checkout.reward-points-info',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsInfo'
     ]);
+
+});
+
+// API 路由 - 用于前后端分离场景(排除 CSRF 验证,返回 JSON 错误)
+Route::group(['middleware' => ['api', ApiCustomerAuthenticate::class], 'prefix' => 'api/customer'], function () {
+    Route::post('reward-points/sign-in', [
+        'as' => 'api.customer.reward-points.sign-in',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@signIn'
+    ]);
+
+    Route::get('reward-points/history', [
+        'as' => 'api.customer.reward-points.history',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsHistory'
+    ]);
+
+    Route::get('reward-points/summary', [
+        'as' => 'api.customer.reward-points.summary',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsSummary'
+    ]);
+
+    Route::get('reward-points/sign-status', [
+        'as' => 'api.customer.reward-points.sign-status',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getSignStatus'
+    ]);
+    Route::get('growth-value', [
+        'as' => 'api.customer.growth-value.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\GrowthValueController@index'
+    ]);
+
+    Route::get('growth-value/history', [
+        'as' => 'api.customer.growth-value.history',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\GrowthValueController@history'
+    ]);
+
+    Route::get('growth-value/levels', [
+        'as' => 'api.customer.growth-value.levels',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\GrowthValueController@levels'
+    ]);
+
+    Route::post('apply-reward-points', [
+        'as' => 'api.checkout.apply-reward-points',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@applyPoints'
+    ]);
+
+    Route::post('remove-reward-points', [
+        'as' => 'api.checkout.remove-reward-points',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@removePoints'
+    ]);
+
+    Route::get('reward-points-info', [
+        'as' => 'api.checkout.reward-points-info',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsInfo'
+    ]);
 });
 
 // 后台路由
@@ -131,39 +196,69 @@ Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/reward-points
     ]);
 
     // ========== 客户管理 - 动态路由 ==========
-    // 客户列表(放在动态路由前面也可以,但为了清晰,放在这里)
     Route::get('customers', [
         'as' => 'admin.reward-points.customers.index',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@index'
     ]);
 
-    // 客户详情(动态参数路由,必须放在所有静态路由之后)
     Route::get('customers/{customerId}', [
         'as' => 'admin.reward-points.customers.show',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@show'
     ]);
 
-    // ========== 报表管理 - 静态路由 ==========
-    Route::get('reports/export', [
-        'as' => 'admin.reward-points.reports.export',
-        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@export'
-    ]);
-// ========== 报表管理 - 动态路由 ==========
+    // ========== 报表管理 ==========
     Route::get('reports', [
         'as' => 'admin.reward-points.reports.index',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@index'
     ]);
-// 积分获取记录专用路由
+
+    Route::get('reports/export', [
+        'as' => 'admin.reward-points.reports.export',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@export'
+    ]);
+
+    // 积分获取记录专用路由
     Route::get('transactions/earned', [
         'as' => 'admin.reward-points.transactions.earned',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\TransactionController@earnedIndex'
     ]);
+
     Route::get('transactions', [
         'as' => 'admin.reward-points.transactions.index',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\TransactionController@index'
     ]);
+
     Route::get('transactions/export', [
         'as' => 'admin.reward-points.transactions.export',
         'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\TransactionController@export'
     ]);
+
+    // ========== 成长值管理路由 ==========
+    Route::get('growth-value', [
+        'as' => 'admin.growth-value.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\GrowthValueController@index'
+    ]);
+    Route::get('growth-value-history', [
+        'as' => 'admin.growth-value.history',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\GrowthValueController@history'
+    ]);
+    Route::get('growth-value/{customerId}', [
+        'as' => 'admin.growth-value.show',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\GrowthValueController@show'
+    ]);
+
+    Route::post('growth-value/adjust-from-list', [
+        'as' => 'admin.growth-value.adjust-from-list',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\GrowthValueController@adjustFromList'
+    ]);
+    Route::get('growth-value-settings', [
+        'as' => 'admin.growth-value.settings',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\GrowthValueController@settings'
+    ]);
+
+    Route::post('growth-value-settings/save', [
+        'as' => 'admin.growth-value.settings.save',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\GrowthValueController@saveSettings'
+    ]);
 });
+

+ 442 - 0
packages/Longyi/RewardPoints/src/Services/GrowthValueService.php

@@ -0,0 +1,442 @@
+<?php
+
+namespace Longyi\RewardPoints\Services;
+
+use Longyi\RewardPoints\Models\GrowthValueCustomer;
+use Longyi\RewardPoints\Models\GrowthValueHistory;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Webkul\Customer\Models\Customer;
+use Webkul\Customer\Models\CustomerGroup;
+
+class GrowthValueService
+{
+    use LevelCalculationTrait;
+
+    // 缓存时间(秒)
+    const CACHE_TTL = 3600;
+
+    // 批量处理大小
+    const BATCH_SIZE = 100;
+
+    /**
+     * 处理订单完成事件
+     */
+    public function handleOrderCompleted($order): void
+    {
+        if (!$order->customer_id) {
+            Log::info('Order has no customer, skipping growth value calculation', ['order_id' => $order->id]);
+            return;
+        }
+
+        try {
+            DB::transaction(function () use ($order) {
+                $customerId = $order->customer_id;
+                $orderAmount = (float) $order->grand_total;
+
+                // 获取或创建成长值记录
+                $growthValueCustomer = $this->getOrCreateGrowthValueRecord($customerId);
+
+                // 标记首单完成
+                $isFirstOrder = $this->markFirstOrderIfNeeded($growthValueCustomer);
+
+                // 计算成长值
+                $earnedGrowthValue = $this->calculateGrowthValue($orderAmount);
+                $oldValue = $growthValueCustomer->growth_value;
+                $newValue = $oldValue + $earnedGrowthValue;
+                $oldLevel = $growthValueCustomer->growth_level;
+
+                // 更新成长值
+                $this->updateGrowthValue($growthValueCustomer, $newValue);
+
+                // 检查并更新等级
+                $levelUpdateResult = $this->checkAndUpdateLevel($customerId, $newValue, $oldLevel);
+
+                // 记录历史
+                $this->recordHistory([
+                    'customer_id' => $customerId,
+                    'order_id' => $order->id,
+                    'order_amount' => $orderAmount,
+                    'growth_value_earned' => $earnedGrowthValue,
+                    'total_growth_value' => $newValue,
+                    'level_before' => $levelUpdateResult['level_changed'] ? $oldLevel : null,
+                    'level_after' => $levelUpdateResult['new_level'] ?? null,
+                    'event_type' => 'order',
+                    'description' => "订单 #{$order->increment_id} 完成,获得 {$earnedGrowthValue} 成长值",
+                    'expires_at' => $growthValueCustomer->growth_value_expires_at,
+                ]);
+
+                $this->logGrowthValueChange('Growth value added from order', [
+                    'customer_id' => $customerId,
+                    'order_id' => $order->id,
+                    'earned' => $earnedGrowthValue,
+                    'old_value' => $oldValue,
+                    'new_value' => $newValue,
+                    'old_level' => $oldLevel,
+                    'new_level' => $levelUpdateResult['new_level'] ?? $oldLevel,
+                ]);
+            });
+        } catch (\Exception $e) {
+            Log::error('Error adding growth value from order', [
+                'customer_id' => $order->customer_id ?? null,
+                'order_id' => $order->id ?? null,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+        }
+    }
+
+    /**
+     * 获取或创建成长值记录
+     */
+    private function getOrCreateGrowthValueRecord($customerId): GrowthValueCustomer
+    {
+        $record = GrowthValueCustomer::where('customer_id', $customerId)
+            ->lockForUpdate()
+            ->first();
+
+        if (!$record) {
+            $record = GrowthValueCustomer::create([
+                'customer_id' => $customerId,
+                'growth_value' => 0,
+                'growth_level' => 'V0',
+            ]);
+        }
+
+        return $record;
+    }
+
+    /**
+     * 标记首单完成
+     */
+    private function markFirstOrderIfNeeded(GrowthValueCustomer $record): bool
+    {
+        if (is_null($record->first_order_completed_at)) {
+            $record->first_order_completed_at = now();
+            $record->save();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 更新成长值
+     */
+    private function updateGrowthValue(GrowthValueCustomer $record, int $newValue): void
+    {
+        $record->growth_value = $newValue;
+
+        // 设置过期时间
+        $validityDays = $this->getValidityDays();
+        if ($validityDays > 0) {
+            $record->growth_value_expires_at = Carbon::now()->addDays($validityDays);
+        }
+
+        $record->updated_at = now();
+        $record->save();
+    }
+
+    /**
+     * 计算订单应获得的成长值
+     */
+    protected function calculateGrowthValue(float $orderAmount): int
+    {
+        $ratio = $this->getGrowthValueRatio();
+        $value = floor($orderAmount * $ratio);
+        return max(1, $value);
+    }
+
+    /**
+     * 获取成长值计算比例
+     */
+    protected function getGrowthValueRatio(): float
+    {
+        return (float) $this->getSetting('growth_value_ratio', 1.0);
+    }
+
+    /**
+     * 获取成长值有效期(天)
+     */
+    protected function getValidityDays(): int
+    {
+        return (int) $this->getSetting('growth_value_validity_days', 365);
+    }
+
+    /**
+     * 检查并更新客户等级
+     */
+    public function checkAndUpdateLevel($customerId, $currentGrowthValue, $oldLevel = null): array
+    {
+        $newLevel = $this->calculateLevel($currentGrowthValue);
+
+        if ($oldLevel === $newLevel) {
+            return [
+                'level_changed' => false,
+                'group_changed' => false,
+                'old_level' => $oldLevel,
+                'new_level' => $newLevel,
+            ];
+        }
+
+        $newGroupId = $this->getCustomerGroupIdByLevel($newLevel);
+
+        if (!$newGroupId) {
+            Log::warning('Cannot find customer group for level', [
+                'level' => $newLevel,
+                'customer_id' => $customerId,
+            ]);
+            return ['level_changed' => false];
+        }
+
+        DB::transaction(function () use ($customerId, $newLevel, $newGroupId, $oldLevel, &$result) {
+            // 更新成长值记录中的等级
+            GrowthValueCustomer::where('customer_id', $customerId)->update([
+                'growth_level' => $newLevel,
+                'level_updated_at' => now(),
+                'updated_at' => now(),
+            ]);
+
+            // 更新客户组
+            $customer = Customer::find($customerId);
+            $oldGroupId = $customer->customer_group_id ?? null;
+
+            if ($oldGroupId !== $newGroupId) {
+                Customer::where('id', $customerId)->update([
+                    'customer_group_id' => $newGroupId,
+                    'updated_at' => now(),
+                ]);
+            }
+
+            $result = [
+                'level_changed' => true,
+                'group_changed' => $oldGroupId !== $newGroupId,
+                'old_level' => $oldLevel,
+                'new_level' => $newLevel,
+                'old_group_id' => $oldGroupId,
+                'new_group_id' => $newGroupId,
+            ];
+        });
+
+        $this->logGrowthValueChange('Customer level updated', [
+            'customer_id' => $customerId,
+            'old_level' => $oldLevel,
+            'new_level' => $newLevel,
+        ]);
+
+        return $result;
+    }
+
+    /**
+     * 批量处理过期的成长值
+     */
+    public function checkExpiredGrowthValues(): array
+    {
+        $processed = 0;
+        $affected = 0;
+
+        GrowthValueCustomer::whereNotNull('growth_value_expires_at')
+            ->where('growth_value_expires_at', '<', now())
+            ->where('growth_value', '>', 0)
+            ->chunkById(self::BATCH_SIZE, function ($records) use (&$processed, &$affected) {
+                foreach ($records as $record) {
+                    try {
+                        $result = $this->expireSingleGrowthValue($record);
+                        $processed++;
+                        if ($result) {
+                            $affected++;
+                        }
+                    } catch (\Exception $e) {
+                        Log::error('Error processing expired growth value', [
+                            'customer_id' => $record->customer_id,
+                            'error' => $e->getMessage(),
+                        ]);
+                    }
+                }
+            });
+
+        Log::info('Growth value expiration check completed', [
+            'processed' => $processed,
+            'affected' => $affected,
+        ]);
+
+        return ['processed' => $processed, 'affected' => $affected];
+    }
+
+    /**
+     * 使单个成长值过期
+     */
+    private function expireSingleGrowthValue(GrowthValueCustomer $record): bool
+    {
+        return DB::transaction(function () use ($record) {
+            $oldValue = $record->growth_value;
+            $oldLevel = $record->growth_level;
+
+            // 清零成长值
+            $record->growth_value = 0;
+            $record->growth_value_expires_at = null;
+
+            // 重新计算等级
+            $levelUpdateResult = $this->checkAndUpdateLevel($record->customer_id, 0, $oldLevel);
+
+            $record->updated_at = now();
+            $record->save();
+
+            $this->recordHistory([
+                'customer_id' => $record->customer_id,
+                'growth_value_earned' => -$oldValue,
+                'total_growth_value' => 0,
+                'level_before' => $levelUpdateResult['level_changed'] ? $oldLevel : null,
+                'level_after' => $levelUpdateResult['new_level'] ?? null,
+                'event_type' => 'expiration',
+                'description' => '成长值过期清零',
+            ]);
+
+            return true;
+        });
+    }
+
+    /**
+     * 获取客户成长值信息
+     */
+    public function getCustomerGrowthValue($customerId): array
+    {
+        $growthValueCustomer = GrowthValueCustomer::where('customer_id', $customerId)->first();
+        $customer = Customer::with('group')->find($customerId);
+
+        if (!$growthValueCustomer) {
+            return $this->getDefaultGrowthValueInfo($customer);
+        }
+
+        $currentGroup = $customer->group;
+
+        // 获取下一个等级
+        $nextGroup = CustomerGroup::where('min_growth_value', '>', $growthValueCustomer->growth_value)
+            ->orderBy('min_growth_value', 'asc')
+            ->first();
+
+        return [
+            'growth_value' => $growthValueCustomer->growth_value,
+            'current_group' => $currentGroup,
+            'growth_level_name' => $currentGroup->growth_level_name ?? 'V0',
+            'growth_discount_rate' => $currentGroup->growth_discount_rate ?? 0,
+            'growth_benefits' => $currentGroup->growth_benefits ?? [],
+            'growth_level_icon' => $currentGroup->growth_level_icon ?? null,
+            'next_level' => $nextGroup ? [
+                'group_id' => $nextGroup->id,
+                'group_name' => $nextGroup->name,
+                'growth_level_name' => $nextGroup->growth_level_name,
+                'min_growth_value' => $nextGroup->min_growth_value,
+                'icon' => $nextGroup->growth_level_icon,
+            ] : null,
+            'points_to_next_level' => $nextGroup
+                ? max(0, $nextGroup->min_growth_value - $growthValueCustomer->growth_value)
+                : null,
+            'expires_at' => $growthValueCustomer->growth_value_expires_at,
+            'first_order_completed' => !is_null($growthValueCustomer->first_order_completed_at),
+        ];
+    }
+
+    /**
+     * 获取默认成长值信息
+     */
+    private function getDefaultGrowthValueInfo($customer): array
+    {
+        $defaultGroup = CustomerGroup::where('code', 'general')->first();
+
+        return [
+            'growth_value' => 0,
+            'current_group' => $defaultGroup,
+            'growth_level_name' => $defaultGroup->growth_level_name ?? 'V0',
+            'growth_discount_rate' => $defaultGroup->growth_discount_rate ?? 0,
+            'growth_benefits' => $defaultGroup->growth_benefits ?? [],
+            'growth_level_icon' => $defaultGroup->growth_level_icon ?? null,
+            'next_level' => null,
+            'points_to_next_level' => null,
+            'expires_at' => null,
+            'first_order_completed' => false,
+        ];
+    }
+
+    /**
+     * 手动调整成长值
+     */
+    public function adjustGrowthValue($customerId, $amount, $description = '管理员调整'): array
+    {
+        if ($amount === 0) {
+            throw new \InvalidArgumentException('调整金额不能为0');
+        }
+
+        return DB::transaction(function () use ($customerId, $amount, $description) {
+            $growthValueCustomer = GrowthValueCustomer::where('customer_id', $customerId)
+                ->lockForUpdate()
+                ->first();
+
+            $oldValue = $growthValueCustomer ? $growthValueCustomer->growth_value : 0;
+            $newValue = max(0, $oldValue + $amount);
+            $actualChange = $newValue - $oldValue;
+            $oldLevel = $growthValueCustomer ? $growthValueCustomer->growth_level : null;
+
+            // 更新或创建记录
+            if ($growthValueCustomer) {
+                $growthValueCustomer->growth_value = $newValue;
+                $growthValueCustomer->updated_at = now();
+                $growthValueCustomer->save();
+            } else {
+                $growthValueCustomer = GrowthValueCustomer::create([
+                    'customer_id' => $customerId,
+                    'growth_value' => $newValue,
+                ]);
+            }
+
+            // 检查并更新等级
+            $levelUpdateResult = $this->checkAndUpdateLevel($customerId, $newValue, $oldLevel);
+
+            // 记录历史
+            $history = $this->recordHistory([
+                'customer_id' => $customerId,
+                'growth_value_earned' => $actualChange,
+                'total_growth_value' => $newValue,
+                'level_before' => $levelUpdateResult['level_changed'] ? $oldLevel : null,
+                'level_after' => $levelUpdateResult['new_level'] ?? null,
+                'event_type' => 'admin_adjust',
+                'description' => $description,
+            ]);
+
+            $this->logGrowthValueChange('Growth value adjusted by admin', [
+                'customer_id' => $customerId,
+                'old_value' => $oldValue,
+                'new_value' => $newValue,
+                'change' => $actualChange,
+            ]);
+
+            return [
+                'history' => $history,
+                'level_changed' => $levelUpdateResult['level_changed'],
+                'old_level' => $oldLevel,
+                'new_level' => $levelUpdateResult['new_level'] ?? $oldLevel,
+            ];
+        });
+    }
+
+    /**
+     * 记录成长值历史
+     */
+    private function recordHistory(array $data): GrowthValueHistory
+    {
+        return GrowthValueHistory::create(array_merge([
+            'order_id' => null,
+            'order_amount' => 0,
+            'expires_at' => null,
+            'created_at' => now(),
+        ], $data));
+    }
+
+    /**
+     * 记录日志
+     */
+    private function logGrowthValueChange(string $message, array $context): void
+    {
+        Log::info($message, $context);
+    }
+}

+ 57 - 0
packages/Longyi/RewardPoints/src/Services/LevelCalculationTrait.php

@@ -0,0 +1,57 @@
+<?php
+namespace Longyi\RewardPoints\Services;
+
+use Webkul\Customer\Models\CustomerGroup;
+
+trait LevelCalculationTrait
+{
+    /**
+     * 根据成长值计算等级
+     */
+    protected function calculateLevel($growthValue)
+    {
+        $level = CustomerGroup::where('min_growth_value', '<=', $growthValue)
+            ->orderBy('min_growth_value', 'desc')
+            ->first();
+
+        return $level && $level->growth_level_name
+            ? $level->growth_level_name
+            : 'V1';
+    }
+
+    /**
+     * 根据等级名称获取对应的客户组ID
+     */
+    protected function getCustomerGroupIdByLevel($levelName)
+    {
+        // 根据等级名称查找
+        $customerGroup = CustomerGroup::where('growth_level_name', $levelName)->first();
+
+        if ($customerGroup) {
+            return $customerGroup->id;
+        }
+
+        // 根据 code 查找
+        $customerGroup = CustomerGroup::where('code', strtolower($levelName))->first();
+
+        if ($customerGroup) {
+            return $customerGroup->id;
+        }
+
+        // 返回默认组
+        return optional(CustomerGroup::where('code', 'general')->first())->id;
+    }
+
+    /**
+     * 获取配置值(带缓存)
+     */
+    protected function getSetting($code, $default = null)
+    {
+        $cacheKey = "growth_value_setting_{$code}";
+
+        return cache()->remember($cacheKey, 3600, function () use ($code, $default) {
+            $setting = \Longyi\RewardPoints\Models\RewardPointSetting::where('code', $code)->first();
+            return $setting ? $setting->value : $default;
+        });
+    }
+}

+ 6 - 1
packages/Webkul/Admin/src/DataGrids/Customers/GroupDataGrid.php

@@ -18,7 +18,12 @@ class GroupDataGrid extends DataGrid
             ->select(
                 'id',
                 'code',
-                'name'
+                'name',
+                'growth_level_name',
+                'min_growth_value',
+                'require_first_order',
+                'growth_discount_rate',
+                'growth_benefits'
             );
     }
 

+ 32 - 3
packages/Webkul/Admin/src/Http/Controllers/Customers/CustomerGroupController.php

@@ -50,7 +50,18 @@ class CustomerGroupController extends Controller
         ]), [
             'is_user_defined' => 1,
         ]);
+        // 添加成长值相关字段
+        $growthFields = request()->only([
+            'growth_level_name',
+            'min_growth_value',
+            'require_first_order',
+            'growth_discount_rate',
+            'growth_benefits',
+        ]);
 
+        if (!empty($growthFields)) {
+            $data = array_merge($data, $growthFields);
+        }
         $customerGroup = $this->customerGroupRepository->create($data);
 
         Event::dispatch('customer.customer_group.create.after', $customerGroup);
@@ -70,14 +81,32 @@ class CustomerGroupController extends Controller
         $this->validate(request(), [
             'code' => ['required', 'unique:customer_groups,code,'.$id, new Code],
             'name' => 'required',
+            'min_growth_value' => 'nullable|integer|min:0',
+            'require_first_order' => 'nullable|boolean',
+            'growth_discount_rate' => 'nullable|numeric|min:0|max:100',
+            'growth_benefits' => 'nullable|json',
         ]);
 
-        Event::dispatch('customer.customer_group.update.before', $id);
 
-        $customerGroup = $this->customerGroupRepository->update(request()->only([
+        Event::dispatch('customer.customer_group.update.before', $id);
+        $data = request()->only([
             'code',
             'name',
-        ]), $id);
+        ]);
+
+        // 添加成长值相关字段
+        $growthFields = request()->only([
+            'growth_level_name',
+            'min_growth_value',
+            'require_first_order',
+            'growth_discount_rate',
+            'growth_benefits',
+        ]);
+
+        if (!empty($growthFields)) {
+            $data = array_merge($data, $growthFields);
+        }
+        $customerGroup = $this->customerGroupRepository->update($data, $id);
 
         Event::dispatch('customer.customer_group.update.after', $customerGroup);
 

+ 204 - 45
packages/Webkul/Admin/src/Resources/views/customers/groups/index.blade.php

@@ -122,51 +122,152 @@
                                 </p>
                             </x-slot>
 
-                            <!-- Modal Content -->
-                            <x-slot:content>
-                                <!-- Code -->
-                                <x-admin::form.control-group>
-                                    <x-admin::form.control-group.label class="required">
-                                        @lang('admin::app.customers.groups.index.create.code')
-                                    </x-admin::form.control-group.label>
-
-                                    <x-admin::form.control-group.control
-                                        type="hidden"
-                                        name="id"
-                                    />
-
-                                    <x-admin::form.control-group.control
-                                        type="text"
-                                        id="code"
-                                        name="code"
-                                        rules="required"
-                                        :label="trans('admin::app.customers.groups.index.create.code')"
-                                        :placeholder="trans('admin::app.customers.groups.index.create.code')"
-                                    />
-
-                                    <x-admin::form.control-group.error control-name="code" />
-                                </x-admin::form.control-group>
-
-                                <!-- Last Name -->
-                                <x-admin::form.control-group>
-                                    <x-admin::form.control-group.label class="required">
-                                        @lang('admin::app.customers.groups.index.create.name')
-                                    </x-admin::form.control-group.label>
-
-                                    <x-admin::form.control-group.control
-                                        type="text"
-                                        id="last_name"
-                                        name="name"
-                                        rules="required"
-                                        :label="trans('admin::app.customers.groups.index.create.name')"
-                                        :placeholder="trans('admin::app.customers.groups.index.create.name')"
-                                    />
-
-                                    <x-admin::form.control-group.error control-name="name" />
-                                </x-admin::form.control-group>
-                            </x-slot>
-
-                            <!-- Modal Footer -->
+                                <!-- Modal Content -->
+                                <x-slot:content>
+                                    <!-- Code -->
+                                    <x-admin::form.control-group>
+                                        <x-admin::form.control-group.label class="required">
+                                            @lang('admin::app.customers.groups.index.create.code')
+                                        </x-admin::form.control-group.label>
+
+                                        <x-admin::form.control-group.control
+                                            type="hidden"
+                                            name="id"
+                                        />
+
+                                        <x-admin::form.control-group.control
+                                            type="text"
+                                            id="code"
+                                            name="code"
+                                            rules="required"
+                                            :label="trans('admin::app.customers.groups.index.create.code')"
+                                            :placeholder="trans('admin::app.customers.groups.index.create.code')"
+                                        />
+
+                                        <x-admin::form.control-group.error control-name="code" />
+                                    </x-admin::form.control-group>
+
+                                    <!-- Name -->
+                                    <x-admin::form.control-group>
+                                        <x-admin::form.control-group.label class="required">
+                                            @lang('admin::app.customers.groups.index.create.name')
+                                        </x-admin::form.control-group.label>
+
+                                        <x-admin::form.control-group.control
+                                            type="text"
+                                            id="name"
+                                            name="name"
+                                            rules="required"
+                                            :label="trans('admin::app.customers.groups.index.create.name')"
+                                            :placeholder="trans('admin::app.customers.groups.index.create.name')"
+                                        />
+
+                                        <x-admin::form.control-group.error control-name="name" />
+                                    </x-admin::form.control-group>
+
+                                    {{-- 成长值设置区域 --}}
+                                    <div class="border-t dark:border-gray-800 pt-4 mt-4">
+                                        <p class="text-sm font-semibold text-gray-800 dark:text-white mb-3 flex items-center gap-2">
+                                            <svg class="w-4 h-4 text-purple-500" fill="currentColor" viewBox="0 0 20 20">
+                                                <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
+                                            </svg>
+                                            成长值等级设置
+                                        </p>
+
+                                        <!-- 成长值等级名称 -->
+                                        <x-admin::form.control-group>
+                                            <x-admin::form.control-group.label>
+                                                成长值等级名称
+                                            </x-admin::form.control-group.label>
+
+                                            <x-admin::form.control-group.control
+                                                type="text"
+                                                id="growth_level_name"
+                                                name="growth_level_name"
+                                                :label="'成长值等级名称'"
+                                                :placeholder="'例如:V1、V2'"
+                                            />
+
+                                            <x-admin::form.control-group.error control-name="growth_level_name" />
+                                        </x-admin::form.control-group>
+
+                                        <!-- 最低成长值要求 -->
+                                        <x-admin::form.control-group>
+                                            <x-admin::form.control-group.label>
+                                                最低成长值要求
+                                            </x-admin::form.control-group.label>
+
+                                            <x-admin::form.control-group.control
+                                                type="number"
+                                                id="min_growth_value"
+                                                name="min_growth_value"
+                                                :label="'最低成长值要求'"
+                                                :placeholder="'0'"
+                                                min="0"
+                                            />
+
+                                            <x-admin::form.control-group.error control-name="min_growth_value" />
+                                        </x-admin::form.control-group>
+
+                                        <!-- 需要完成首单 -->
+                                        <x-admin::form.control-group>
+                                            <x-admin::form.control-group.label>
+                                                需要完成首单
+                                            </x-admin::form.control-group.label>
+
+                                            <x-admin::form.control-group.control
+                                                type="select"
+                                                id="require_first_order"
+                                                name="require_first_order"
+                                                :label="'需要完成首单'"
+                                            >
+                                                <option value="0">否</option>
+                                                <option value="1">是</option>
+                                            </x-admin::form.control-group.control>
+
+                                            <x-admin::form.control-group.error control-name="require_first_order" />
+                                        </x-admin::form.control-group>
+
+                                        <!-- 额外折扣率 -->
+                                        <x-admin::form.control-group>
+                                            <x-admin::form.control-group.label>
+                                                额外折扣率(%)
+                                            </x-admin::form.control-group.label>
+
+                                            <x-admin::form.control-group.control
+                                                type="number"
+                                                id="growth_discount_rate"
+                                                name="growth_discount_rate"
+                                                :label="'额外折扣率(%)'"
+                                                :placeholder="'0'"
+                                                min="0"
+                                                max="100"
+                                            />
+
+                                            <x-admin::form.control-group.error control-name="growth_discount_rate" />
+                                        </x-admin::form.control-group>
+
+                                        <!-- 等级权益说明 -->
+                                        <x-admin::form.control-group>
+                                            <x-admin::form.control-group.label>
+                                                等级权益说明
+                                            </x-admin::form.control-group.label>
+
+                                            <x-admin::form.control-group.control
+                                                type="textarea"
+                                                id="growth_benefits_text"
+                                                name="growth_benefits_text"
+                                                :label="'等级权益说明'"
+                                                :placeholder="'每行输入一个权益,例如:&#10;享受2%额外折扣&#10;优先客服支持'"
+                                                rows="3"
+                                            />
+
+                                            <x-admin::form.control-group.error control-name="growth_benefits_text" />
+                                        </x-admin::form.control-group>
+                                    </div>
+                                    </x-slot>
+
+                                    <!-- Modal Footer -->
                             <x-slot:footer>
                                 <!-- Save Button -->
                                 <x-admin::button
@@ -220,6 +321,15 @@
                         if (params.id) {
                             formData.append('_method', 'put');
                         }
+                        // 处理成长值权益文本,转换为 JSON 数组
+                        const benefitsText = formData.get('growth_benefits_text');
+                        if (benefitsText) {
+                            const benefits = benefitsText.split('\n')
+                                .map(line => line.trim())
+                                .filter(line => line.length > 0);
+                            formData.set('growth_benefits', JSON.stringify(benefits));
+                            formData.delete('growth_benefits_text');
+                        }
 
                         this.$axios.post(params.id ? "{{ route('admin.customers.groups.update') }}" : "{{ route('admin.customers.groups.store') }}", formData)
                             .then((response) => {
@@ -246,6 +356,55 @@
                         this.$refs.groupUpdateOrCreateModal.toggle();
 
                         this.$refs.modalForm.setValues(value);
+                        // 如果是编辑模式,加载成长值权益数据和其他字段
+                        setTimeout(() => {
+                            // 设置成长值等级名称
+                            if (value.growth_level_name) {
+                                const levelNameInput = document.getElementById('growth_level_name');
+                                if (levelNameInput) {
+                                    levelNameInput.value = value.growth_level_name;
+                                }
+                            }
+
+                            // 设置最低成长值
+                            if (value.min_growth_value !== undefined) {
+                                const minValueInput = document.getElementById('min_growth_value');
+                                if (minValueInput) {
+                                    minValueInput.value = value.min_growth_value;
+                                }
+                            }
+
+                            // 设置需要完成首单
+                            if (value.require_first_order !== undefined) {
+                                const firstOrderSelect = document.getElementById('require_first_order');
+                                if (firstOrderSelect) {
+                                    firstOrderSelect.value = value.require_first_order ? '1' : '0';
+                                }
+                            }
+
+                            // 设置额外折扣率
+                            if (value.growth_discount_rate !== undefined) {
+                                const discountRateInput = document.getElementById('growth_discount_rate');
+                                if (discountRateInput) {
+                                    discountRateInput.value = value.growth_discount_rate;
+                                }
+                            }
+
+                            // 设置成长值权益说明
+                            if (value.growth_benefits && typeof value.growth_benefits === 'string') {
+                                try {
+                                    const benefits = JSON.parse(value.growth_benefits);
+                                    if (Array.isArray(benefits)) {
+                                        const textarea = document.getElementById('growth_benefits_text');
+                                        if (textarea) {
+                                            textarea.value = benefits.join('\n');
+                                        }
+                                    }
+                                } catch (e) {
+                                    console.error('解析成长值权益失败:', e);
+                                }
+                            }
+                        }, 100);
                     },
                 }
             })

+ 4 - 2
packages/Webkul/BagistoApi/src/State/LoginProcessor.php

@@ -20,7 +20,7 @@ class LoginProcessor implements ProcessorInterface
     public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
     {
         if ($data instanceof LoginInput) {
-            if ($operation->getName() === 'create') {
+            //if ($operation->getName() === 'create') {
                 $this->validator->validateLoginInput($data);
 
                 $customer = Customer::where('email', $data->email)->first();
@@ -60,6 +60,8 @@ class LoginProcessor implements ProcessorInterface
                         'deviceToken' => $deviceToken,
                     ]);
                 }
+                // Dispatch customer login event for reward points and other listeners
+                Event::dispatch('customer.after.login', $customer);
 
                 $token = $customer->createToken('customer-login')->plainTextToken;
 
@@ -71,7 +73,7 @@ class LoginProcessor implements ProcessorInterface
                     'success'  => true,
                     'message'  => __('bagistoapi::app.graphql.login.successful'),
                 ];
-            }
+            //}
         }
 
         return (object) [

+ 5 - 0
packages/Webkul/Customer/src/Models/CustomerGroup.php

@@ -29,6 +29,11 @@ class CustomerGroup extends Model implements CustomerGroupContract
         'name',
         'code',
         'is_user_defined',
+        'growth_level_name',
+        'min_growth_value',
+        'require_first_order',
+        'growth_discount_rate',
+        'growth_benefits',
     ];
 
     /**