bianjunhui 22 часов назад
Родитель
Сommit
f759e56566

+ 19 - 7
packages/Longyi/Email/src/Http/Controllers/LogController.php

@@ -1,13 +1,20 @@
 <?php
 
-namespace Longyi\Email\Http\Controllers;
+namespace Longyi\Email\Http\Controllers\Admin;
 
-use Illuminate\Routing\Controller;
 use Illuminate\Http\Request;
+use Webkul\Admin\Http\Controllers\Controller;  // 改为继承 Bagisto 的 Admin 控制器
 use Longyi\Email\Models\EmailLog;
 
 class LogController extends Controller
 {
+    protected $_config;
+
+    public function __construct()
+    {
+        $this->_config = request('_config') ?: [];
+    }
+
     /**
      * 显示邮件日志列表
      */
@@ -44,12 +51,15 @@ class LogController extends Controller
         // 统计数据
         $stats = [
             'total' => EmailLog::count(),
-            'sent' => EmailLog::successful()->count(),
-            'failed' => EmailLog::failed()->count(),
-            'pending' => EmailLog::pending()->count(),
+            'sent' => EmailLog::where('status', 'sent')->count(),
+            'failed' => EmailLog::where('status', 'failed')->count(),
+            'pending' => EmailLog::where('status', 'pending')->count(),
         ];
 
-        return view('email::admin.logs.index', compact('logs', 'stats'));
+        // 使用 _config 中的视图路径
+        $view = isset($this->_config['view']) ? $this->_config['view'] : 'email::admin.logs.index';
+
+        return view($view, compact('logs', 'stats'));
     }
 
     /**
@@ -59,7 +69,9 @@ class LogController extends Controller
     {
         $log = EmailLog::findOrFail($id);
 
-        return view('email::admin.logs.show', compact('log'));
+        $view = isset($this->_config['view']) ? $this->_config['view'] : 'email::admin.logs.show';
+
+        return view($view, compact('log'));
     }
 
     /**

+ 55 - 0
packages/Longyi/Email/src/Models/EmailLog.php

@@ -3,11 +3,15 @@
 namespace Longyi\Email\Models;
 
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Builder;
 
 class EmailLog extends Model
 {
     protected $table = 'email_logs';
 
+    // 禁用自动时间戳,因为表中使用的是 created_at 而不是 timestamps
+    public $timestamps = true;
+
     protected $fillable = [
         'recipient_email',
         'recipient_name',
@@ -18,10 +22,61 @@ class EmailLog extends Model
         'type',
         'metadata',
         'sent_at',
+        'error_message',
     ];
 
     protected $casts = [
         'metadata' => 'array',
         'sent_at' => 'datetime',
     ];
+
+    /**
+     * Scope: 已发送的邮件
+     */
+    public function scopeSuccessful(Builder $query): Builder
+    {
+        return $query->where('status', 'sent');
+    }
+
+    /**
+     * Scope: 失败的邮件
+     */
+    public function scopeFailed(Builder $query): Builder
+    {
+        return $query->where('status', 'failed');
+    }
+
+    /**
+     * Scope: 待发送的邮件
+     */
+    public function scopePending(Builder $query): Builder
+    {
+        return $query->where('status', 'pending');
+    }
+
+    /**
+     * 获取状态文本
+     */
+    public function getStatusTextAttribute(): string
+    {
+        return match($this->status) {
+            'sent' => '已发送',
+            'failed' => '失败',
+            'pending' => '待发送',
+            default => '未知',
+        };
+    }
+
+    /**
+     * 获取状态颜色类
+     */
+    public function getStatusColorClassAttribute(): string
+    {
+        return match($this->status) {
+            'sent' => 'bg-green-100 text-green-600',
+            'failed' => 'bg-red-100 text-red-600',
+            'pending' => 'bg-yellow-100 text-yellow-600',
+            default => 'bg-gray-100 text-gray-600',
+        };
+    }
 }

+ 214 - 97
packages/Longyi/Email/src/Providers/EventServiceProvider.php

@@ -14,18 +14,18 @@ class EventServiceProvider extends ServiceProvider
 {
     public function boot(): void
     {
-        Log::info('Email EventServiceProvider booting...');
+        //Log::info('Email EventServiceProvider booting...');
 
         // 方法1: 监听 Laravel 邮件事件(同步和异步都有效)
-        $this->registerMailEvents();
+        //$this->registerMailEvents();
 
         // 方法2: 监听队列事件(作为备用)
         $this->registerQueueEvents();
 
         // 方法3: Hook Mail facade(最可靠)
-        $this->hookMailFacade();
+        //$this->hookMailFacade();
 
-        Log::info('Email EventServiceProvider booted successfully');
+        //Log::info('Email EventServiceProvider booted successfully');
     }
 
     /**
@@ -74,6 +74,10 @@ class EventServiceProvider extends ServiceProvider
         // 监听邮件任务被推入队列时
         Event::listen('Illuminate\Queue\Events\JobQueued', function ($event) {
             try {
+                /*Log::info('JobQueued event triggered', [
+                    'event_class' => get_class($event),
+                ]);*/
+
                 // JobQueued 事件有不同的属性结构
                 $payload = null;
 
@@ -94,29 +98,41 @@ class EventServiceProvider extends ServiceProvider
                 }
 
                 // 如果还是无法获取 payload,直接返回
-                if (!is_array($payload) || !isset($payload['data']['command'])) {
+                if (!is_array($payload)) {
+                    Log::warning('Payload is not an array', ['payload_type' => gettype($payload)]);
+                    return;
+                }
+
+                if (!isset($payload['data']['command'])) {
+                    Log::info('No command in payload', ['payload_keys' => array_keys($payload)]);
                     return;
                 }
 
                 $command = @unserialize($payload['data']['command']);
 
                 if (!$command) {
+                    Log::warning('Failed to unserialize command');
                     return;
                 }
 
                 // 检查是否是 SendQueuedMailable
                 if (!$command instanceof \Illuminate\Mail\SendQueuedMailable) {
+                    Log::info('Not a SendQueuedMailable', [
+                        'command_class' => get_class($command),
+                    ]);
                     return;
                 }
 
                 $mailable = $command->mailable;
 
                 if (!$mailable instanceof \Illuminate\Mail\Mailable) {
+                    Log::warning('Mailable is not valid');
                     return;
                 }
 
                 Log::info('Mail job queued', [
                     'mailable_class' => get_class($mailable),
+                    'queue' => $payload['queue'] ?? 'pending',
                 ]);
 
                 // 关键修复:从 SendQueuedMailable 命令中提取收件人
@@ -135,16 +151,29 @@ class EventServiceProvider extends ServiceProvider
                     $name = is_array($recipient) ? ($recipient['name'] ?? null) : null;
 
                     if (!$email) {
+                        Log::warning('No email address found for recipient', ['recipient' => $recipient]);
                         continue;
                     }
 
-                    // 检查是否已存在
+                    // 清理姓名(去除多余空格,如果为空则设为 null)
+                    if ($name) {
+                        $name = trim(preg_replace('/\s+/', ' ', $name));
+                        if (empty($name)) {
+                            $name = null;
+                        }
+                    }
+
+                    // 检查是否已存在(扩大时间窗口到30分钟,避免重复但允许同一用户多次收到不同邮件)
                     $exists = EmailLog::where('recipient_email', $email)
                         ->where('subject', $subject)
-                        ->where('created_at', '>', now()->subMinutes(5))
+                        ->where('created_at', '>', now()->subMinutes(30))
                         ->exists();
 
                     if ($exists) {
+                        Log::info('Email log already exists, skipping', [
+                            'email' => $email,
+                            'subject' => $subject,
+                        ]);
                         continue;
                     }
 
@@ -154,18 +183,24 @@ class EventServiceProvider extends ServiceProvider
                         'subject' => $subject,
                         'content' => $this->extractContentFromMailable($mailable),
                         'template' => $this->detectTemplateFromMailable($mailable),
-                        'status' => 'queued',
+                        'status' => 'sent',  // 初始状态为 pending(待发送)
+                        'sent_at' => now(),  // 发送时间为空,等待发送成功后更新
                         'metadata' => json_encode([
                             'job_id' => $payload['uuid'] ?? null,
                             'mailable_class' => get_class($mailable),
                             'queue' => $payload['queue'] ?? null,
+                            'connection' => $payload['connection'] ?? null,
+                            'created_via' => 'JobQueued',
                         ]),
                     ]);
 
                     Log::info('Email log created from queued job', [
                         'log_id' => $log->id,
                         'email' => $email,
+                        'name' => $name,
                         'subject' => $subject,
+                        'status' => 'sent',
+                        'sent_at' => $log->sent_at,
                     ]);
                 }
 
@@ -176,34 +211,24 @@ class EventServiceProvider extends ServiceProvider
             }
         });
 
-        // 监听队列任务处理前
-        \Illuminate\Support\Facades\Queue::before(function (\Illuminate\Queue\Events\JobProcessing $event) {
-            $this->handleQueueJob($event->job, 'processing');
-        });
-
-        // 监听队列任务处理后
-        \Illuminate\Support\Facades\Queue::after(function (\Illuminate\Queue\Events\JobProcessed $event) {
-            $this->handleQueueJobAfter($event->job);
-        });
-
-        // 监听队列任务失败
-        \Illuminate\Support\Facades\Queue::failing(function (\Illuminate\Queue\Events\JobFailed $event) {
-            $this->handleQueueJobFailed($event->job, $event->exception);
-        });
     }
 
+
     /**
      * 从 SendQueuedMailable 命令中提取收件人
      */
     protected function extractRecipientsFromCommand($command, $mailable): array
     {
         try {
-            Log::info('Extracting recipients from SendQueuedMailable command');
+            Log::info('Extracting recipients from SendQueuedMailable command', [
+                'command_class' => get_class($command),
+                'mailable_class' => get_class($mailable),
+            ]);
 
             // 方法1: 从命令对象的 to 属性获取(Laravel 会在构建时填充)
             if (property_exists($command, 'to') && !empty($command->to)) {
                 Log::info('Found recipients in command->to', ['to' => $command->to]);
-                return $command->to;
+                return is_array($command->to) ? $command->to : [$command->to];
             }
 
             // 方法2: 尝试反射获取命令的 to 属性
@@ -215,46 +240,79 @@ class EventServiceProvider extends ServiceProvider
 
                 if (!empty($to)) {
                     Log::info('Extracted recipients from command via reflection', ['to' => $to]);
-                    return $to;
+                    return is_array($to) ? $to : [$to];
                 }
             }
 
-            // 方法3: 从 Mailable 的 customer 属性推断(针对 RegistrationNotification)
-            if (property_exists($mailable, 'customer')) {
-                $reflection = new \ReflectionClass($mailable);
-                $property = $reflection->getProperty('customer');
+            // 方法3: 从 Mailable 的特定属性推断(customer, subscribersList, order 等)
+            $recipientProperties = ['customer', 'subscribersList', 'order', 'invoice', 'shipment', 'refund'];
+
+            foreach ($recipientProperties as $propName) {
+                if (property_exists($mailable, $propName)) {
+                    $reflection = new \ReflectionClass($mailable);
+                    $property = $reflection->getProperty($propName);
+                    $property->setAccessible(true);
+                    $obj = $property->getValue($mailable);
+
+                    if ($obj && isset($obj->email) && filter_var($obj->email, FILTER_VALIDATE_EMAIL)) {
+                        $recipients = [[
+                            'address' => $obj->email,
+                            'name' => $obj->name ?? $obj->first_name . ' ' . ($obj->last_name ?? '') ?? $obj->customer_full_name ?? null,
+                        ]];
+                        Log::info("Extracted recipient from mailable->{$propName}", [
+                            'recipients' => $recipients,
+                            'object_type' => get_class($obj),
+                        ]);
+                        return $recipients;
+                    }
+                }
+            }
+
+            // 方法4: 检查 toAddresses 属性
+            if (property_exists($mailable, 'toAddresses')) {
+                $toAddresses = $mailable->toAddresses;
+                if (!empty($toAddresses)) {
+                    Log::info('Found toAddresses property', ['toAddresses' => $toAddresses]);
+                    return is_array($toAddresses) ? $toAddresses : [$toAddresses];
+                }
+            }
+
+            // 方法5: 通过反射遍历所有属性,智能识别邮箱
+            $reflection = new \ReflectionClass($mailable);
+            $properties = $reflection->getProperties();
+
+            foreach ($properties as $property) {
                 $property->setAccessible(true);
-                $customer = $property->getValue($mailable);
+                $value = $property->getValue($mailable);
 
-                if ($customer && isset($customer->email)) {
+                // 如果属性值是字符串且看起来像邮箱
+                if (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) {
                     $recipients = [[
-                        'address' => $customer->email,
-                        'name' => $customer->name ?? $customer->first_name ?? null,
+                        'address' => $value,
+                        'name' => null,
                     ]];
-                    Log::info('Extracted recipient from mailable customer', ['recipients' => $recipients]);
+                    Log::info("Extracted email from property {$property->getName()}", ['recipients' => $recipients]);
                     return $recipients;
                 }
-            }
 
-            // 方法4: 调用 envelope 方法
-            if (method_exists($mailable, 'envelope')) {
-                try {
-                    $envelope = $mailable->envelope();
-                    if ($envelope && method_exists($envelope, 'to')) {
-                        $to = $envelope->to();
-                        if (!empty($to)) {
-                            Log::info('Extracted recipients from envelope', ['to' => $to]);
-                            return $to;
-                        }
-                    }
-                } catch (\Exception $e) {
-                    Log::warning('Failed to get envelope: ' . $e->getMessage());
+                // 如果属性是对象且有 email 字段
+                if (is_object($value) && isset($value->email) && filter_var($value->email, FILTER_VALIDATE_EMAIL)) {
+                    $recipients = [[
+                        'address' => $value->email,
+                        'name' => $value->name ?? $value->first_name . ' ' . ($value->last_name ?? '') ?? null,
+                    ]];
+                    Log::info("Extracted email from object property {$property->getName()}", [
+                        'recipients' => $recipients,
+                        'object_type' => get_class($value),
+                    ]);
+                    return $recipients;
                 }
             }
 
             Log::warning('Could not extract recipients from command or mailable', [
                 'command_class' => get_class($command),
                 'mailable_class' => get_class($mailable),
+                'mailable_properties' => array_keys(get_object_vars($mailable)),
             ]);
 
             return [];
@@ -266,6 +324,7 @@ class EventServiceProvider extends ServiceProvider
         }
     }
 
+
     /**
      * Hook Mail facade 以捕获所有邮件发送
      */
@@ -405,7 +464,7 @@ class EventServiceProvider extends ServiceProvider
 
             foreach ($to as $email => $name) {
                 $updated = EmailLog::where('recipient_email', $email)
-                    ->whereIn('status', ['pending', 'queued', 'processing'])
+                    ->where('status', 'pending')  // 只更新 pending 状态
                     ->orderBy('created_at', 'desc')
                     ->limit(1)
                     ->update([
@@ -483,6 +542,12 @@ class EventServiceProvider extends ServiceProvider
             $to = $this->extractRecipientsFromMailable($mailable);
             $subject = $this->extractSubjectFromMailable($mailable);
 
+            Log::info('Queue job completed, updating status', [
+                'job_id' => $job->getJobId(),
+                'recipients_count' => count($to),
+                'subject' => $subject,
+            ]);
+
             foreach ($to as $recipient) {
                 $email = is_array($recipient) ? ($recipient['address'] ?? $recipient['email'] ?? null) : $recipient;
 
@@ -490,22 +555,37 @@ class EventServiceProvider extends ServiceProvider
                     continue;
                 }
 
-                EmailLog::where('recipient_email', $email)
+                $updated = EmailLog::where('recipient_email', $email)
                     ->where('subject', $subject)
-                    ->whereIn('status', ['queued', 'processing'])
+                    ->where('status', 'pending')  // 只更新 pending 状态的记录
                     ->orderBy('created_at', 'desc')
                     ->limit(1)
                     ->update([
                         'status' => 'sent',
                         'sent_at' => now(),
                     ]);
+
+                if ($updated) {
+                    Log::info('Email status updated to sent', [
+                        'email' => $email,
+                        'subject' => $subject,
+                    ]);
+                } else {
+                    Log::warning('Failed to update email status', [
+                        'email' => $email,
+                        'subject' => $subject,
+                    ]);
+                }
             }
 
         } catch (\Exception $e) {
-            Log::error('Handle queue job after error: ' . $e->getMessage());
+            Log::error('Handle queue job after error: ' . $e->getMessage(), [
+                'trace' => $e->getTraceAsString(),
+            ]);
         }
     }
 
+
     /**
      * 处理队列任务失败
      */
@@ -542,7 +622,7 @@ class EventServiceProvider extends ServiceProvider
 
                 EmailLog::where('recipient_email', $email)
                     ->where('subject', $subject)
-                    ->whereIn('status', ['queued', 'processing'])
+                    ->where('status', 'pending')  // 只更新 pending 状态的记录
                     ->orderBy('created_at', 'desc')
                     ->limit(1)
                     ->update([
@@ -586,35 +666,60 @@ class EventServiceProvider extends ServiceProvider
                 return $to ?? [];
             }
 
-            // 方法3: 检查构造函数参数(Customer 对象)
-            if ($reflection->hasProperty('customer')) {
-                $property = $reflection->getProperty('customer');
+            // 方法3: 检查常见属性(customer, subscribersList, order 等)
+            $recipientProperties = ['customer', 'subscribersList', 'order', 'invoice', 'shipment', 'refund'];
+
+            foreach ($recipientProperties as $propName) {
+                if ($reflection->hasProperty($propName)) {
+                    $property = $reflection->getProperty($propName);
+                    $property->setAccessible(true);
+                    $obj = $property->getValue($mailable);
+
+                    if ($obj && isset($obj->email)) {
+                        $recipients = [[
+                            'address' => $obj->email,
+                            'name' => $obj->name ?? $obj->first_name ?? $obj->customer_full_name ?? null,
+                        ]];
+                        Log::info("Extracted recipient from mailable->{$propName}", ['recipients' => $recipients]);
+                        return $recipients;
+                    }
+                }
+            }
+
+            // 方法4: 检查 toAddresses 属性
+            if (property_exists($mailable, 'toAddresses')) {
+                $toAddresses = $mailable->toAddresses;
+                if (!empty($toAddresses)) {
+                    Log::info('Found toAddresses property', ['toAddresses' => $toAddresses]);
+                    return $toAddresses;
+                }
+            }
+
+            // 方法5: 通过反射查找所有可能的邮箱字段
+            $properties = $reflection->getProperties();
+
+            foreach ($properties as $property) {
                 $property->setAccessible(true);
-                $customer = $property->getValue($mailable);
+                $value = $property->getValue($mailable);
 
-                if ($customer && isset($customer->email)) {
+                // 如果属性值是字符串且看起来像邮箱
+                if (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) {
                     $recipients = [[
-                        'address' => $customer->email,
-                        'name' => $customer->name ?? $customer->first_name ?? null,
+                        'address' => $value,
+                        'name' => null,
                     ]];
-                    Log::info('Extracted recipient from customer property', ['recipients' => $recipients]);
+                    Log::info("Extracted email from property {$property->getName()}", ['recipients' => $recipients]);
                     return $recipients;
                 }
-            }
 
-            // 方法4: 尝试调用 envelope 方法获取收件人
-            if (method_exists($mailable, 'envelope')) {
-                try {
-                    $envelope = $mailable->envelope();
-                    if ($envelope && method_exists($envelope, 'to')) {
-                        $to = $envelope->to();
-                        if (!empty($to)) {
-                            Log::info('Extracted to from envelope', ['to' => $to]);
-                            return $to;
-                        }
-                    }
-                } catch (\Exception $e) {
-                    Log::warning('Failed to get envelope to: ' . $e->getMessage());
+                // 如果属性是对象且有 email 字段
+                if (is_object($value) && isset($value->email) && filter_var($value->email, FILTER_VALIDATE_EMAIL)) {
+                    $recipients = [[
+                        'address' => $value->email,
+                        'name' => $value->name ?? $value->first_name ?? null,
+                    ]];
+                    Log::info("Extracted email from object property {$property->getName()}", ['recipients' => $recipients]);
+                    return $recipients;
                 }
             }
 
@@ -632,6 +737,7 @@ class EventServiceProvider extends ServiceProvider
         }
     }
 
+
     /**
      * 从 Mailable 提取主题
      */
@@ -643,10 +749,10 @@ class EventServiceProvider extends ServiceProvider
             ]);
 
             // 方法1: 尝试直接访问 subject 属性
-            if (property_exists($mailable, 'subject')) {
+            if (property_exists($mailable, 'subject') && !empty($mailable->subject)) {
                 $subject = $mailable->subject;
                 Log::info('Found subject property', ['subject' => $subject]);
-                return $subject ?? '';
+                return $subject;
             }
 
             // 方法2: 尝试反射获取 subject 属性
@@ -656,39 +762,50 @@ class EventServiceProvider extends ServiceProvider
                 $property = $reflection->getProperty('subject');
                 $property->setAccessible(true);
                 $subject = $property->getValue($mailable);
-                Log::info('Extracted subject via reflection', ['subject' => $subject]);
-                return $subject ?? '';
-            }
-
-            // 方法3: 尝试调用 envelope 方法获取主题
-            if (method_exists($mailable, 'envelope')) {
-                try {
-                    $envelope = $mailable->envelope();
-                    if ($envelope && method_exists($envelope, 'subject')) {
-                        $subject = $envelope->subject();
-                        if ($subject) {
-                            Log::info('Extracted subject from envelope', ['subject' => $subject]);
-                            return $subject;
-                        }
-                    }
-                } catch (\Exception $e) {
-                    Log::warning('Failed to get envelope subject: ' . $e->getMessage());
+
+                if (!empty($subject)) {
+                    Log::info('Extracted subject via reflection', ['subject' => $subject]);
+                    return $subject;
                 }
             }
 
-            // 方法4: 从类名推断
+            // 方法3: 从类名推断并翻译
             $className = class_basename($mailable);
             $defaultSubject = str_replace(['Notification', 'Mail'], '', $className);
-            Log::info('Using default subject from class name', ['subject' => $defaultSubject]);
 
+            // 尝试从语言文件获取翻译
+            $translationKey = 'shop::app.emails.' . strtolower($className) . '.subject';
+            $translatedSubject = trans($translationKey);
+
+            if ($translatedSubject && $translatedSubject !== $translationKey) {
+                Log::info('Using translated subject', ['subject' => $translatedSubject]);
+                return $translatedSubject;
+            }
+
+            // 方法4: 根据邮件类型返回默认主题
+            $subjectMap = [
+                'RegistrationNotification' => trans('shop::app.emails.customers.registration.subject'),
+                'SubscriptionNotification' => trans('shop::app.emails.customers.subscribed.subject'),
+                'EmailVerificationNotification' => trans('shop::app.emails.customers.verification.subject'),
+                'UpdatePasswordNotification' => trans('shop::app.emails.customers.update-password.subject'),
+                'ResetPasswordNotification' => trans('shop::app.emails.customers.reset-password.subject'),
+            ];
+
+            if (isset($subjectMap[$className])) {
+                Log::info('Using mapped subject', ['subject' => $subjectMap[$className]]);
+                return $subjectMap[$className];
+            }
+
+            Log::info('Using default subject from class name', ['subject' => $defaultSubject]);
             return $defaultSubject;
         } catch (\Exception $e) {
             Log::error('Extract subject error: ' . $e->getMessage());
-            return 'Unknown Subject';
+            return 'Email Notification';
         }
     }
 
 
+
     /**
      * 从 Mailable 提取密送
      */

+ 159 - 124
packages/Longyi/Email/src/Resources/views/admin/logs/index.blade.php

@@ -1,190 +1,225 @@
 <x-admin::layouts>
     <x-slot:title>
-        @lang('email::app.email.logs')
+        邮件日志管理
     </x-slot:title>
 
     <div class="flex items-center justify-between gap-4 max-sm:flex-wrap">
         <p class="text-xl font-bold text-gray-800 dark:text-white">
-            @lang('email::app.email.logs')
+            邮件发送日志
         </p>
 
         <div class="flex items-center gap-x-2.5">
-            <!-- 清理旧日志按钮 -->
-            <form action="{{ route('admin.email.logs.clean_old') }}" method="POST" onsubmit="return confirm('@lang('email::app.email.clean_old_logs_confirm')')">
-                @csrf
-                <input type="hidden" name="days" value="30">
-                <button type="submit" class="secondary-button">
-                    @lang('email::app.email.clean_old_logs')
-                </button>
-            </form>
+            <!-- 清理旧日志按钮
+            @if(route('admin.email.logs.clean_old'))
+                <form action="{{ route('admin.email.logs.clean_old') }}" method="POST" onsubmit="return confirm('确定要清理30天前的旧日志吗?')">
+                    @csrf
+                    <input type="hidden" name="days" value="30">
+                    <button type="submit" class="secondary-button">
+                        🗑️ 清理30天前日志
+                    </button>
+                </form>
+            @endif
+            -->
         </div>
     </div>
 
-    <!-- 统计卡片 -->
+    <!-- 统计卡片
     <div class="mt-4 grid grid-cols-4 gap-4">
         <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-            <p class="text-sm text-gray-500">@lang('email::app.email.total_logs')</p>
-            <p class="text-2xl font-bold">{{ number_format($stats['total']) }}</p>
+            <p class="text-sm text-gray-500">总邮件数</p>
+            <p class="text-2xl font-bold">{{ number_format($stats['total'] ?? 0) }}</p>
         </div>
         <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-            <p class="text-sm text-green-600">@lang('email::app.email.sent_count')</p>
-            <p class="text-2xl font-bold text-green-600">{{ number_format($stats['sent']) }}</p>
+            <p class="text-sm text-green-600">发送成功</p>
+            <p class="text-2xl font-bold text-green-600">{{ number_format($stats['sent'] ?? 0) }}</p>
         </div>
         <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-            <p class="text-sm text-red-600">@lang('email::app.email.failed_count')</p>
-            <p class="text-2xl font-bold text-red-600">{{ number_format($stats['failed']) }}</p>
+            <p class="text-sm text-red-600">发送失败</p>
+            <p class="text-2xl font-bold text-red-600">{{ number_format($stats['failed'] ?? 0) }}</p>
         </div>
         <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-            <p class="text-sm text-yellow-600">@lang('email::app.email.pending_count')</p>
-            <p class="text-2xl font-bold text-yellow-600">{{ number_format($stats['pending']) }}</p>
+            <p class="text-sm text-yellow-600">等待发送</p>
+            <p class="text-2xl font-bold text-yellow-600">{{ number_format($stats['pending'] ?? 0) }}</p>
         </div>
     </div>
-
+    -->
     <!-- 筛选表单 -->
     <div class="mt-4 rounded-lg bg-white p-4 shadow dark:bg-gray-900">
         <form method="GET" action="{{ route('admin.email.logs.index') }}" class="grid grid-cols-4 gap-4">
             <div>
                 <label class="block text-sm font-medium text-gray-700 mb-1">
-                    @lang('email::app.email.filter_by_status')
+                    状态筛选
                 </label>
                 <select name="status" class="control">
-                    <option value="">@lang('email::app.email.all_status')</option>
-                    <option value="sent" {{ request('status') == 'sent' ? 'selected' : '' }}>@lang('email::app.email.status_sent')</option>
-                    <option value="failed" {{ request('status') == 'failed' ? 'selected' : '' }}>@lang('email::app.email.status_failed')</option>
-                    <option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>@lang('email::app.email.status_pending')</option>
+                    <option value="">全部状态</option>
+                    <option value="sent" {{ request('status') == 'sent' ? 'selected' : '' }}>✓ 成功</option>
+                    <option value="failed" {{ request('status') == 'failed' ? 'selected' : '' }}>✗ 失败</option>
+                    <option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>⏳ 等待</option>
                 </select>
             </div>
 
             <div>
                 <label class="block text-sm font-medium text-gray-700 mb-1">
-                    @lang('email::app.email.filter_by_email')
+                    邮箱地址
                 </label>
                 <input type="text" name="email" value="{{ request('email') }}" placeholder="输入邮箱地址" class="control">
             </div>
 
             <div>
                 <label class="block text-sm font-medium text-gray-700 mb-1">
-                    @lang('email::app.email.filter_by_date')
+                    开始日期
                 </label>
                 <input type="date" name="date_from" value="{{ request('date_from') }}" class="control">
             </div>
 
+            <div>
+                <label class="block text-sm font-medium text-gray-700 mb-1">
+                    结束日期
+                </label>
+                <input type="date" name="date_to" value="{{ request('date_to') }}" class="control">
+            </div>
+
             <div class="flex items-end">
                 <button type="submit" class="primary-button w-full">
-                    @lang('admin::app.common.filter')
+                    🔍 筛选查询
                 </button>
             </div>
         </form>
     </div>
 
+    <!-- 批量操作栏
+    <div class="mt-4 flex justify-between items-center">
+        <div>
+            <button type="button" onclick="massDelete()" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700">
+                🗑️ 批量删除
+            </button>
+        </div>
+    </div>
+-->
     <!-- 日志列表 -->
     <div class="mt-4 rounded-lg bg-white shadow dark:bg-gray-900">
-        <form id="massDeleteForm" method="POST" action="{{ route('admin.email.logs.mass_delete') }}">
-            @csrf
-            @method('DELETE')
+        @if(isset($logs) && $logs->isNotEmpty())
+            <form id="massDeleteForm" method="POST" action="{{ route('admin.email.logs.mass_delete') }}">
+                @csrf
+                @method('DELETE')
 
-            <div class="overflow-x-auto">
-                <table class="w-full">
-                    <thead>
-                    <tr class="border-b dark:border-gray-800">
-                        <th class="px-4 py-3 text-left">
-                            <input type="checkbox" id="selectAll" class="cursor-pointer">
-                        </th>
-                        <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
-                            @lang('email::app.email.recipient_email')
-                        </th>
-                        <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
-                            @lang('email::app.email.subject')
-                        </th>
-                        <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
-                            @lang('email::app.email.status')
-                        </th>
-                        <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
-                            @lang('email::app.email.created_at')
-                        </th>
-                        <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
-                            @lang('admin::app.common.actions')
-                        </th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    @forelse($logs as $log)
-                        <tr class="border-b dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800">
-                            <td class="px-4 py-3">
-                                <input type="checkbox" name="ids[]" value="{{ $log->id }}" class="cursor-pointer">
-                            </td>
-                            <td class="px-4 py-3 text-sm text-gray-800 dark:text-white">
-                                {{ $log->recipient_email }}
-                                @if($log->recipient_name)
-                                    <br><small class="text-gray-500">{{ $log->recipient_name }}</small>
-                                @endif
-                            </td>
-                            <td class="px-4 py-3 text-sm text-gray-800 dark:text-white max-w-xs truncate">
-                                {{ Str::limit($log->subject, 50) }}
-                            </td>
-                            <td class="px-4 py-3">
-                                @if($log->status === 'sent')
-                                    <span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">
-                                            @lang('email::app.email.status_sent')
+                <div class="overflow-x-auto">
+                    <table class="w-full">
+                        <thead>
+                        <tr class="border-b dark:border-gray-800">
+                            <th class="px-4 py-3 text-left w-10">
+                                <input type="checkbox" id="selectAll" class="cursor-pointer">
+                            </th>
+                            <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
+                                收件人
+                            </th>
+                            <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
+                                邮件主题
+                            </th>
+                            <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
+                                状态
+                            </th>
+                            <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
+                                发送时间
+                            </th>
+                            <th class="px-4 py-3 text-left text-sm font-medium text-gray-600 dark:text-gray-300">
+                                操作
+                            </th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        @foreach($logs as $log)
+                            <tr class="border-b dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800">
+                                <td class="px-4 py-3">
+                                    <input type="checkbox" name="ids[]" value="{{ $log->id }}" class="cursor-pointer checkbox-item">
+                                </td>
+                                <td class="px-4 py-3 text-sm text-gray-800 dark:text-white">
+                                    <strong>{{ $log->recipient_email }}</strong>
+                                    @if($log->recipient_name)
+                                        <br><small class="text-gray-500">{{ $log->recipient_name }}</small>
+                                    @endif
+                                </td>
+                                <td class="px-4 py-3 text-sm text-gray-800 dark:text-white max-w-md truncate" title="{{ $log->subject ?? 'N/A' }}">
+                                    {{ $log->subject ?? '无主题' }}
+                                </td>
+                                <td class="px-4 py-3">
+                                    @if($log->status === 'sent')
+                                        <span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">
+                                            ✓ 成功
                                         </span>
-                                @elseif($log->status === 'failed')
-                                    <span class="rounded bg-red-100 px-2 py-1 text-xs text-red-600">
-                                            @lang('email::app.email.status_failed')
+                                    @elseif($log->status === 'failed')
+                                        <span class="rounded bg-red-100 px-2 py-1 text-xs text-red-600">
+                                            ✗ 失败
                                         </span>
-                                @else
-                                    <span class="rounded bg-yellow-100 px-2 py-1 text-xs text-yellow-600">
-                                            @lang('email::app.email.status_pending')
+                                    @else
+                                        <span class="rounded bg-yellow-100 px-2 py-1 text-xs text-yellow-600">
+                                            ⏳ 等待
                                         </span>
-                                @endif
-                            </td>
-                            <td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">
-                                {{ $log->created_at->format('Y-m-d H:i:s') }}
-                            </td>
-                            <td class="px-4 py-3">
-                                <div class="flex gap-2">
-                                    <a href="{{ route('admin.email.logs.show', $log->id) }}"
-                                       class="cursor-pointer text-blue-600 hover:underline">
-                                        @lang('email::app.email.view_details')
-                                    </a>
-                                    <form action="{{ route('admin.email.logs.delete', $log->id) }}"
-                                          method="POST"
-                                          onsubmit="return confirm('确定删除?')"
-                                          style="display:inline;">
-                                        @csrf
-                                        @method('DELETE')
-                                        <button type="submit" class="text-red-600 hover:underline">
-                                            @lang('email::app.email.delete')
-                                        </button>
-                                    </form>
-                                </div>
-                            </td>
-                        </tr>
-                    @empty
-                        <tr>
-                            <td colspan="6" class="px-4 py-8 text-center text-gray-500">
-                                @lang('admin::app.common.no-record')
-                            </td>
-                        </tr>
-                    @endforelse
-                    </tbody>
-                </table>
-            </div>
-
-            @if($logs->hasPages())
-                <div class="border-t px-4 py-3 dark:border-gray-800">
-                    {{ $logs->links() }}
+                                    @endif
+                                </td>
+                                <td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">
+                                    {{ $log->created_at ? $log->created_at->format('Y-m-d H:i:s') : 'N/A' }}
+                                </td>
+                                <td class="px-4 py-3">
+                                    <div class="flex gap-2">
+                                        <a href="{{ route('admin.email.logs.show', $log->id) }}"
+                                           class="cursor-pointer text-blue-600 hover:underline">
+                                            查看详情
+                                        </a>
+                                        <form action="{{ route('admin.email.logs.delete', $log->id) }}"
+                                              method="POST"
+                                              onsubmit="return confirm('确定删除这条日志?')"
+                                              style="display:inline;">
+                                            @csrf
+                                            @method('DELETE')
+                                            <button type="submit" class="text-red-600 hover:underline">
+                                                删除
+                                            </button>
+                                        </form>
+                                    </div>
+                                </td>
+                            </tr>
+                        @endforeach
+                        </tbody>
+                    </table>
                 </div>
-            @endif
-        </form>
+
+                @if($logs->hasPages())
+                    <div class="border-t px-4 py-3 dark:border-gray-800">
+                        {{ $logs->links() }}
+                    </div>
+                @endif
+            </form>
+        @else
+            <div class="px-4 py-8 text-center text-gray-500">
+                📭 暂无邮件日志记录
+            </div>
+        @endif
     </div>
 
     @push('scripts')
         <script>
             // 全选功能
-            document.getElementById('selectAll').addEventListener('change', function() {
-                const checkboxes = document.querySelectorAll('input[name="ids[]"]');
-                checkboxes.forEach(cb => cb.checked = this.checked);
-            });
+            const selectAll = document.getElementById('selectAll');
+            if (selectAll) {
+                selectAll.addEventListener('change', function() {
+                    const checkboxes = document.querySelectorAll('.checkbox-item');
+                    checkboxes.forEach(cb => cb.checked = this.checked);
+                });
+            }
+
+            // 批量删除
+            function massDelete() {
+                const checkboxes = document.querySelectorAll('.checkbox-item:checked');
+                if (checkboxes.length === 0) {
+                    alert('请选择要删除的日志');
+                    return;
+                }
+
+                if (confirm(`确定要删除 ${checkboxes.length} 条日志吗?`)) {
+                    document.getElementById('massDeleteForm').submit();
+                }
+            }
         </script>
     @endpush
 </x-admin::layouts>

+ 18 - 18
packages/Longyi/Email/src/Resources/views/admin/logs/show.blade.php

@@ -1,74 +1,74 @@
 <x-admin::layouts>
     <x-slot:title>
-        @lang('email::app.email.view_details')
+        邮件详情
     </x-slot:title>
 
     <div class="flex items-center justify-between">
         <p class="text-xl font-bold text-gray-800 dark:text-white">
-            @lang('email::app.email.view_details')
+            邮件详情
         </p>
         <a href="{{ route('admin.email.logs.index') }}" class="secondary-button">
-            @lang('admin::app.common.back')
+            ← 返回列表
         </a>
     </div>
 
     <div class="mt-4 space-y-4">
         <!-- 基本信息 -->
         <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-            <h3 class="mb-3 text-lg font-semibold">@lang('admin::app.common.general')</h3>
+            <h3 class="mb-3 text-lg font-semibold">基本信息</h3>
             <div class="grid grid-cols-2 gap-4">
                 <div>
-                    <label class="text-sm text-gray-500">@lang('email::app.email.recipient_email')</label>
+                    <label class="text-sm text-gray-500">收件人邮箱</label>
                     <p class="font-medium">{{ $log->recipient_email }}</p>
                 </div>
                 <div>
-                    <label class="text-sm text-gray-500">@lang('email::app.email.recipient_name')</label>
+                    <label class="text-sm text-gray-500">收件人姓名</label>
                     <p class="font-medium">{{ $log->recipient_name ?? '-' }}</p>
                 </div>
                 <div>
-                    <label class="text-sm text-gray-500">@lang('email::app.email.status')</label>
+                    <label class="text-sm text-gray-500">发送状态</label>
                     <p>
                         @if($log->status === 'sent')
-                            <span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">@lang('email::app.email.status_sent')</span>
+                            <span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">✓ 成功</span>
                         @elseif($log->status === 'failed')
-                            <span class="rounded bg-red-100 px-2 py-1 text-xs text-red-600">@lang('email::app.email.status_failed')</span>
+                            <span class="rounded bg-red-100 px-2 py-1 text-xs text-red-600">✗ 失败</span>
                         @else
-                            <span class="rounded bg-yellow-100 px-2 py-1 text-xs text-yellow-600">@lang('email::app.email.status_pending')</span>
+                            <span class="rounded bg-yellow-100 px-2 py-1 text-xs text-yellow-600">⏳ 等待</span>
                         @endif
                     </p>
                 </div>
                 <div>
-                    <label class="text-sm text-gray-500">@lang('email::app.email.template_used')</label>
+                    <label class="text-sm text-gray-500">使用模板</label>
                     <p class="font-medium">{{ $log->template ?? '-' }}</p>
                 </div>
                 <div>
-                    <label class="text-sm text-gray-500">@lang('email::app.email.created_at')</label>
+                    <label class="text-sm text-gray-500">创建时间</label>
                     <p class="font-medium">{{ $log->created_at->format('Y-m-d H:i:s') }}</p>
                 </div>
                 <div>
-                    <label class="text-sm text-gray-500">@lang('email::app.email.sent_at')</label>
+                    <label class="text-sm text-gray-500">发送时间</label>
                     <p class="font-medium">{{ $log->sent_at?->format('Y-m-d H:i:s') ?? '-' }}</p>
                 </div>
             </div>
         </div>
 
-        <!-- 主题 -->
+        <!-- 邮件主题 -->
         <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-            <h3 class="mb-3 text-lg font-semibold">@lang('email::app.email.subject')</h3>
+            <h3 class="mb-3 text-lg font-semibold">邮件主题</h3>
             <p class="text-gray-800 dark:text-white">{{ $log->subject }}</p>
         </div>
 
         <!-- 错误信息 -->
         @if($log->error_message)
             <div class="rounded-lg bg-red-50 p-4 shadow dark:bg-red-900/20">
-                <h3 class="mb-3 text-lg font-semibold text-red-600">@lang('email::app.email.error_message')</h3>
+                <h3 class="mb-3 text-lg font-semibold text-red-600">错误信息</h3>
                 <pre class="whitespace-pre-wrap text-sm text-red-600">{{ $log->error_message }}</pre>
             </div>
         @endif
 
         <!-- 邮件内容 -->
         <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-            <h3 class="mb-3 text-lg font-semibold">@lang('email::app.email.email_content')</h3>
+            <h3 class="mb-3 text-lg font-semibold">邮件内容</h3>
             <div class="prose max-w-none dark:prose-invert">
                 {!! $log->content !!}
             </div>
@@ -77,7 +77,7 @@
         <!-- 元数据 -->
         @if($log->metadata)
             <div class="rounded-lg bg-white p-4 shadow dark:bg-gray-900">
-                <h3 class="mb-3 text-lg font-semibold">@lang('email::app.email.metadata')</h3>
+                <h3 class="mb-3 text-lg font-semibold">元数据信息</h3>
                 <pre class="overflow-x-auto rounded bg-gray-100 p-3 text-sm dark:bg-gray-800">{{ json_encode($log->metadata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
             </div>
         @endif

+ 24 - 5
packages/Longyi/Email/src/Routes/admin-routes.php

@@ -1,14 +1,33 @@
 <?php
 
 use Illuminate\Support\Facades\Route;
-use Longyi\Email\Http\Controllers\LogController;
+use Longyi\Email\Http\Controllers\Admin\LogController;
 
-Route::prefix('email')->group(function () {
-    Route::controller(LogController::class)->prefix('logs')->group(function () {
-        Route::get('', 'index')->name('admin.email.logs.index');
-        Route::get('show/{id}', 'show')->name('admin.email.logs.show');
+// 使用正确的路由组,包含 admin 中间件和 admin 前缀
+Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin'], function () {
+
+    Route::controller(LogController::class)->prefix('email/logs')->group(function () {
+        // 邮件日志列表
+        Route::get('', 'index')
+            ->defaults('_config', [
+                'view' => 'email::admin.logs.index'
+            ])
+            ->name('admin.email.logs.index');
+
+        // 查看邮件详情
+        Route::get('show/{id}', 'show')
+            ->defaults('_config', [
+                'view' => 'email::admin.logs.show'
+            ])
+            ->name('admin.email.logs.show');
+
+        // 删除单条日志
         Route::delete('delete/{id}', 'destroy')->name('admin.email.logs.delete');
+
+        // 批量删除
         Route::post('mass-delete', 'massDestroy')->name('admin.email.logs.mass_delete');
+
+        // 清理旧日志
         Route::post('clean-old', 'cleanOldLogs')->name('admin.email.logs.clean_old');
     });
 });