Răsfoiți Sursa

增加老师登录逻辑

yemeishu 3 săptămâni în urmă
părinte
comite
80eb130867

+ 29 - 8
app/Filament/Pages/IntelligentExamGeneration.php

@@ -2,6 +2,7 @@
 
 namespace App\Filament\Pages;
 
+use App\Filament\Traits\HasUserRole;
 use App\Services\KnowledgeGraphService;
 use App\Services\LearningAnalyticsService;
 use App\Services\QuestionBankService;
@@ -18,6 +19,7 @@ use Illuminate\Support\Facades\Cache; // Add Cache import
 
 class IntelligentExamGeneration extends Page
 {
+    use HasUserRole;
     protected static ?string $title = '智能出卷';
     protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-duplicate';
     protected static ?string $navigationLabel = '智能出卷';
@@ -73,7 +75,20 @@ class IntelligentExamGeneration extends Page
 
     public function mount(): void
     {
-        $this->selectedTeacherId = request()->query('teacher_id', $this->selectedTeacherId);
+        // 初始化用户角色检查
+        $this->initializeUserRole();
+
+        // 如果是老师,自动选择当前老师
+        if ($this->isTeacher) {
+            $teacherId = $this->getCurrentTeacherId();
+            if ($teacherId) {
+                $this->selectedTeacherId = $teacherId;
+            }
+        } else {
+            // 管理员模式:从 URL 参数获取
+            $this->selectedTeacherId = request()->query('teacher_id', $this->selectedTeacherId);
+        }
+
         $this->selectedStudentId = request()->query('student_id', $this->selectedStudentId);
 
         // 如果只传了 student_id,补全 teacher_id 以便学生下拉加载
@@ -194,8 +209,7 @@ class IntelligentExamGeneration extends Page
     public function teachers(): array
     {
         try {
-            // 首先获取teachers表中的老师
-            $teachers = \App\Models\Teacher::query()->from('teachers as t')
+            $query = \App\Models\Teacher::query()->from('teachers as t')
                 ->leftJoin('users as u', 't.teacher_id', '=', 'u.user_id')
                 ->select(
                     't.teacher_id',
@@ -203,10 +217,17 @@ class IntelligentExamGeneration extends Page
                     't.subject',
                     'u.username',
                     'u.email'
-                )
-                ->orderBy('t.name')
-                ->orderBy('t.name')
-                ->get();
+                );
+
+            // 如果是老师,只返回自己
+            if ($this->isTeacher) {
+                $teacherId = $this->getCurrentTeacherId();
+                if ($teacherId) {
+                    $query->where('t.teacher_id', $teacherId);
+                }
+            }
+
+            $teachers = $query->orderBy('t.name')->get();
 
             // 如果有学生但没有对应的老师记录,添加一个"未知老师"条目
             $teacherIds = $teachers->pluck('teacher_id')->toArray();
@@ -234,7 +255,7 @@ class IntelligentExamGeneration extends Page
                 usort($teachersArray, function($a, $b) {
                     return strcmp($a->name, $b->name);
                 });
-                
+
                 return $teachersArray;
             }
 

+ 27 - 5
app/Filament/Pages/MistakeBook.php

@@ -2,6 +2,7 @@
 
 namespace App\Filament\Pages;
 
+use App\Filament\Traits\HasUserRole;
 use App\Models\Student;
 use App\Services\KnowledgeServiceApi;
 use App\Services\MistakeBookService;
@@ -18,6 +19,7 @@ use App\Models\Teacher;
 
 class MistakeBook extends Page
 {
+    use HasUserRole;
     protected static ?string $title = '错题本';
 
     protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-bookmark';
@@ -71,7 +73,19 @@ class MistakeBook extends Page
 
     public function mount(Request $request): void
     {
-        $this->teacherId = (string) ($request->input('teacher_id') ?? '');
+        // 初始化用户角色检查
+        $this->initializeUserRole();
+
+        // 如果是老师,自动选择当前老师
+        if ($this->isTeacher) {
+            $teacherId = $this->getCurrentTeacherId();
+            if ($teacherId) {
+                $this->teacherId = $teacherId;
+            }
+        } else {
+            $this->teacherId = (string) ($request->input('teacher_id') ?? '');
+        }
+
         $this->studentId = (string) ($request->input('student_id') ?? '');
         $this->filters['time_range'] = (string) ($request->input('range') ?? 'last_30');
 
@@ -273,7 +287,7 @@ class MistakeBook extends Page
     public function teachers(): array
     {
         try {
-            $teachers = Teacher::query()
+            $query = Teacher::query()
                 ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id')
                 ->select(
                     'teachers.teacher_id',
@@ -281,9 +295,17 @@ class MistakeBook extends Page
                     'teachers.subject',
                     'u.username',
                     'u.email'
-                )
-                ->orderBy('teachers.name')
-                ->get();
+                );
+
+            // 如果是老师,只返回自己
+            if ($this->isTeacher) {
+                $teacherId = $this->getCurrentTeacherId();
+                if ($teacherId) {
+                    $query->where('teachers.teacher_id', $teacherId);
+                }
+            }
+
+            $teachers = $query->orderBy('teachers.name')->get();
 
             $teacherIds = $teachers->pluck('teacher_id')->toArray();
             $missingTeacherIds = Student::query()

+ 28 - 7
app/Filament/Pages/StudentDashboard.php

@@ -3,6 +3,7 @@
 namespace App\Filament\Pages;
 
 use App\Filament\Traits\HandlesMindmapDetails;
+use App\Filament\Traits\HasUserRole;
 use App\Models\Student;
 use App\Models\Teacher;
 use App\Services\LearningAnalyticsService;
@@ -22,7 +23,7 @@ use App\Services\MistakeBookService;
 
 class StudentDashboard extends Page
 {
-    use HandlesMindmapDetails;
+    use HasUserRole, HandlesMindmapDetails;
 
     use \Filament\Pages\Concerns\InteractsWithFormActions;
 
@@ -52,8 +53,20 @@ class StudentDashboard extends Page
 
     public function mount(Request $request): void
     {
-        // 从请求中获取老师ID
-        $this->teacherId = (string) ($request->input('teacher_id') ?? '');
+        // 初始化用户角色检查
+        $this->initializeUserRole();
+
+        // 如果是老师,自动选择当前老师
+        if ($this->isTeacher) {
+            $teacherId = $this->getCurrentTeacherId();
+            if ($teacherId) {
+                $this->teacherId = $teacherId;
+            }
+        } else {
+            // 从请求中获取老师ID
+            $this->teacherId = (string) ($request->input('teacher_id') ?? '');
+        }
+
         // 从请求中获取学生ID
         $this->studentId = (string) ($request->input('student_id') ?? '');
 
@@ -74,7 +87,7 @@ class StudentDashboard extends Page
     public function teachers(): array
     {
         try {
-            $teachers = Teacher::query()
+            $query = Teacher::query()
                 ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id')
                 ->select(
                     'teachers.teacher_id',
@@ -82,9 +95,17 @@ class StudentDashboard extends Page
                     'teachers.subject',
                     'u.username',
                     'u.email'
-                )
-                ->orderBy('teachers.name')
-                ->get();
+                );
+
+            // 如果是老师,只返回自己
+            if ($this->isTeacher) {
+                $teacherId = $this->getCurrentTeacherId();
+                if ($teacherId) {
+                    $query->where('teachers.teacher_id', $teacherId);
+                }
+            }
+
+            $teachers = $query->orderBy('teachers.name')->get();
 
             // 检查是否有学生没有对应的老师记录
             $teacherIds = $teachers->pluck('teacher_id')->toArray();

+ 7 - 1
app/Filament/Pages/StudentKnowledgeGraphPage.php

@@ -2,14 +2,17 @@
 
 namespace App\Filament\Pages;
 
+use App\Filament\Traits\HasUserRole;
+use App\Livewire\StudentKnowledgeGraph;
 use BackedEnum;
 use UnitEnum;
 use Filament\Pages\Page;
-use App\Livewire\StudentKnowledgeGraph;
 use Illuminate\Support\Arr;
 
 class StudentKnowledgeGraphPage extends Page
 {
+    use HasUserRole;
+
     protected static BackedEnum | string | null $navigationIcon = 'heroicon-o-chart-bar';
 
     protected static ?string $navigationLabel = '学生知识图谱';
@@ -25,6 +28,9 @@ class StudentKnowledgeGraphPage extends Page
 
     public function mount(): void
     {
+        // 初始化用户角色检查
+        $this->initializeUserRole();
+
         // Page 类不需要 authorizeResourceAccess
         $this->showNodeDetails = filter_var(
             request()->query('show_details', true),

+ 5 - 6
app/Filament/Pages/StudentManagement.php

@@ -2,6 +2,7 @@
 
 namespace App\Filament\Pages;
 
+use App\Filament\Traits\HasUserRole;
 use App\Filament\Widgets\StudentStatsWidget;
 use App\Models\Student;
 use App\Models\Teacher;
@@ -20,7 +21,7 @@ use UnitEnum;
 
 class StudentManagement extends Page implements HasTable
 {
-    use InteractsWithTable;
+    use HasUserRole, InteractsWithTable;
 
     protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-academic-cap';
 
@@ -36,11 +37,9 @@ class StudentManagement extends Page implements HasTable
 
     public ?string $selectedTeacherName = null;
 
-    public bool $isTeacher = false;
-
     public function mount(): void
     {
-        $this->isTeacher = auth()->user()?->isTeacher() ?? false;
+        $this->initializeUserRole();
     }
 
     public function getTitle(): string
@@ -86,7 +85,7 @@ class StudentManagement extends Page implements HasTable
                     ->when($this->selectedTeacherId, fn ($query) => $query->where('teacher_id', $this->selectedTeacherId))
                     ->when($currentUser?->isTeacher() ?? false, function ($query) use ($currentUser) {
                         // 如果是老师登录,只显示该老师的学生
-                        $teacherId = $currentUser->teacher?->teacher_id;
+                        $teacherId = $this->getCurrentTeacherId();
                         if ($teacherId) {
                             $query->where('teacher_id', $teacherId);
                         }
@@ -295,7 +294,7 @@ class StudentManagement extends Page implements HasTable
                         ->select('student_id', 'name', 'grade', 'class_name', 'teacher_id')
                         ->orderBy('name'),
                 ])
-                ->where('teacher_id', $currentUser->teacher?->teacher_id)
+                ->where('teacher_id', $this->getCurrentTeacherId())
                 ->withCount('students')
                 ->withMax('students', 'updated_at')
                 ->first();

+ 27 - 6
app/Filament/Pages/UploadExamPaper.php

@@ -2,6 +2,7 @@
 
 namespace App\Filament\Pages;
 
+use App\Filament\Traits\HasUserRole;
 use App\Jobs\ProcessOCRRecord;
 use App\Models\OCRRecord;
 use App\Models\Student;
@@ -17,7 +18,7 @@ use UnitEnum;
 
 class UploadExamPaper extends Page
 {
-    use WithFileUploads;
+    use HasUserRole, WithFileUploads;
 
     protected static ?string $title = '上传考试卷子';
     protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-arrow-up-tray';
@@ -40,7 +41,19 @@ class UploadExamPaper extends Page
 
     public function mount()
     {
-        $this->teacherId = null;
+        // 初始化用户角色检查
+        $this->initializeUserRole();
+
+        // 如果是老师,自动选择当前老师
+        if ($this->isTeacher) {
+            $teacherId = $this->getCurrentTeacherId();
+            if ($teacherId) {
+                $this->teacherId = $teacherId;
+            }
+        } else {
+            $this->teacherId = null;
+        }
+
         $this->studentId = null;
         $this->uploadedImage = null;
         $this->paperType = null;
@@ -53,7 +66,7 @@ class UploadExamPaper extends Page
     public function teachers(): array
     {
         try {
-            $teachers = Teacher::query()
+            $query = Teacher::query()
                 ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id')
                 ->select(
                     'teachers.teacher_id',
@@ -61,9 +74,17 @@ class UploadExamPaper extends Page
                     'teachers.subject',
                     'u.username',
                     'u.email'
-                )
-                ->orderBy('teachers.name')
-                ->get();
+                );
+
+            // 如果是老师,只返回自己
+            if ($this->isTeacher) {
+                $teacherId = $this->getCurrentTeacherId();
+                if ($teacherId) {
+                    $query->where('teachers.teacher_id', $teacherId);
+                }
+            }
+
+            $teachers = $query->orderBy('teachers.name')->get();
 
             // 检查是否有学生没有对应的老师记录
             $teacherIds = $teachers->pluck('teacher_id')->toArray();

+ 4 - 6
app/Filament/Resources/StudentResource.php

@@ -177,13 +177,11 @@ class StudentResource extends Resource
 
     protected static function teacherOptionsForCurrentUser(): array
     {
-        $currentUser = auth()->user();
-
         // 如果是老师登录,只返回自己的选项
-        if ($currentUser?->isTeacher() ?? false) {
-            $teacherId = $currentUser->teacher?->teacher_id;
-            $teacherName = $currentUser->teacher?->user?->full_name
-                ?? $currentUser->teacher?->name
+        if (auth()->user()?->isTeacher() ?? false) {
+            $teacherId = auth()->user()->teacher?->teacher_id;
+            $teacherName = auth()->user()->teacher?->user?->full_name
+                ?? auth()->user()->teacher?->name
                 ?? '当前老师';
 
             if ($teacherId) {

+ 58 - 0
app/Filament/Traits/HasUserRole.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Filament\Traits;
+
+use Illuminate\Support\Facades\Auth;
+
+trait HasUserRole
+{
+    public bool $isTeacher = false;
+
+    /**
+     * 初始化用户角色检查
+     */
+    public function initializeUserRole(): void
+    {
+        $this->isTeacher = Auth::user()?->isTeacher() ?? false;
+    }
+
+    /**
+     * 获取当前用户ID
+     */
+    public function getCurrentUserId(): ?int
+    {
+        return Auth::id();
+    }
+
+    /**
+     * 获取当前老师ID
+     */
+    public function getCurrentTeacherId(): ?string
+    {
+        return Auth::user()?->teacher?->teacher_id;
+    }
+
+    /**
+     * 判断当前用户是否是管理员
+     */
+    public function isAdmin(): bool
+    {
+        return !$this->isTeacher;
+    }
+
+    /**
+     * 判断当前用户是否是老师
+     */
+    public function isCurrentUserTeacher(): bool
+    {
+        return $this->isTeacher;
+    }
+
+    /**
+     * 获取当前用户类型描述
+     */
+    public function getCurrentUserType(): string
+    {
+        return $this->isTeacher ? 'teacher' : 'admin';
+    }
+}

+ 1 - 1
app/Filament/Widgets/StudentStatsWidget.php

@@ -19,7 +19,7 @@ class StudentStatsWidget extends BaseWidget
         // 构建基础查询(带老师过滤)
         $baseQuery = DB::table('students');
         if ($isTeacher) {
-            $teacherId = $currentUser->teacher?->teacher_id;
+            $teacherId = $currentUser?->teacher?->teacher_id;
             if ($teacherId) {
                 $baseQuery->where('teacher_id', $teacherId);
             }

+ 259 - 0
app/Livewire/StudentKnowledgeGraph.php

@@ -0,0 +1,259 @@
+<?php
+
+namespace App\Livewire;
+
+use Livewire\Component;
+use App\Models\Student;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\DB;
+
+class StudentKnowledgeGraph extends Component
+{
+    public $selectedStudentId = null;
+    public $selectedStudent = null;
+    public $knowledgePoints = [];
+    public $dependencies = [];
+    public $masteryData = [];
+    public $statistics = [];
+    public $learningPath = [];
+    public $isLoading = false;
+
+    public $students = [];
+
+    protected $rules = [
+        'selectedStudentId' => 'required|exists:students,student_id',
+    ];
+
+    public function mount()
+    {
+        $this->loadStudents();
+    }
+
+    public function updatedSelectedStudentId($value)
+    {
+        if ($value) {
+            $this->loadStudentData($value);
+        } else {
+            $this->resetData();
+        }
+    }
+
+    public function loadStudents()
+    {
+        $query = DB::table('students')
+            ->select('student_id', 'name', 'grade', 'class_name')
+            ->orderBy('grade')
+            ->orderBy('class_name')
+            ->orderBy('name');
+
+        // 如果是老师,只加载自己的学生
+        $currentUser = auth()->user();
+        if ($currentUser?->isTeacher() ?? false) {
+            $teacherId = $currentUser->teacher?->teacher_id;
+            if ($teacherId) {
+                $query->where('teacher_id', $teacherId);
+            }
+        }
+
+        $this->students = $query
+            ->get()
+            ->map(function ($student) {
+                return [
+                    'id' => $student->student_id,
+                    'label' => "{$student->name} ({$student->grade}-{$student->class_name})",
+                ];
+            })
+            ->toArray();
+    }
+
+    public function loadStudentData($studentId)
+    {
+        $this->isLoading = true;
+
+        try {
+            // 获取学生信息
+            $this->selectedStudent = DB::table('students')
+                ->where('student_id', $studentId)
+                ->first();
+
+            // 调用LearningAnalytics API获取知识图谱数据
+            $this->fetchKnowledgeGraphData($studentId);
+
+        } catch (\Exception $e) {
+            session()->flash('error', '加载数据失败:' . $e->getMessage());
+            \Log::error('加载学生知识图谱失败', [
+                'student_id' => $studentId,
+                'error' => $e->getMessage(),
+            ]);
+        }
+
+        $this->isLoading = false;
+    }
+
+    private function fetchKnowledgeGraphData($studentId)
+    {
+        $baseUrl = config('services.learning_analytics.url', 'http://localhost:5010');
+
+        try {
+            // 获取掌握度数据
+            $masteryResponse = Http::timeout(10)->get($baseUrl . '/api/mastery/' . $studentId);
+            if ($masteryResponse->successful()) {
+                $this->masteryData = $masteryResponse->json();
+            }
+
+            // 获取依赖关系
+            $dependencyResponse = Http::timeout(10)->get($baseUrl . '/api/knowledge/dependencies');
+            if ($dependencyResponse->successful()) {
+                $this->dependencies = $dependencyResponse->json();
+            }
+
+            // 获取统计信息
+            $statsResponse = Http::timeout(10)->get($baseUrl . '/api/mastery/' . $studentId . '/statistics');
+            if ($statsResponse->successful()) {
+                $this->statistics = $statsResponse->json();
+            }
+
+            // 获取学习路径
+            $pathResponse = Http::timeout(10)->get($baseUrl . '/api/learning-path/' . $studentId);
+            if ($pathResponse->successful()) {
+                $this->learningPath = $pathResponse->json();
+            }
+
+            // 构建知识点图谱数据
+            $this->buildKnowledgeGraphData();
+
+        } catch (\Exception $e) {
+            \Log::warning('LearningAnalytics API调用失败,使用本地数据', [
+                'error' => $e->getMessage(),
+            ]);
+
+            // 如果API调用失败,使用本地模拟数据
+            $this->loadMockData($studentId);
+        }
+    }
+
+    private function buildKnowledgeGraphData()
+    {
+        $nodes = [];
+        $links = [];
+
+        // 处理掌握度数据,构建节点
+        if (isset($this->masteryData['masteries'])) {
+            foreach ($this->masteryData['masteries'] as $mastery) {
+                $nodes[] = [
+                    'id' => $mastery['kp_code'],
+                    'label' => $mastery['kp_code'],
+                    'mastery' => $mastery['mastery_level'],
+                    'color' => $this->getMasteryColor($mastery['mastery_level']),
+                    'size' => $this->getMasterySize($mastery['mastery_level']),
+                ];
+            }
+        }
+
+        // 处理依赖关系,构建边
+        if (isset($this->dependencies['dependencies'])) {
+            foreach ($this->dependencies['dependencies'] as $dep) {
+                $links[] = [
+                    'source' => $dep['prerequisite_kp'],
+                    'target' => $dep['dependent_kp'],
+                    'strength' => $dep['influence_weight'],
+                    'type' => $dep['dependency_type'],
+                ];
+            }
+        }
+
+        $this->knowledgePoints = [
+            'nodes' => $nodes,
+            'links' => $links,
+        ];
+    }
+
+    private function loadMockData($studentId)
+    {
+        // 模拟数据,用于演示
+        $mockKnowledgePoints = [
+            'R01' => ['name' => '有理数', 'mastery' => 0.85],
+            'R02' => ['name' => '整式运算', 'mastery' => 0.72],
+            'R03' => ['name' => '一元一次方程', 'mastery' => 0.65],
+            'R04' => ['name' => '因式分解', 'mastery' => 0.45],
+            'R05' => ['name' => '二次方程', 'mastery' => 0.30],
+            'R06' => ['name' => '二次函数', 'mastery' => 0.25],
+            'R07' => ['name' => '几何图形', 'mastery' => 0.78],
+            'R08' => ['name' => '三角形', 'mastery' => 0.68],
+        ];
+
+        $nodes = [];
+        foreach ($mockKnowledgePoints as $code => $data) {
+            $nodes[] = [
+                'id' => $code,
+                'label' => $data['name'],
+                'mastery' => $data['mastery'],
+                'color' => $this->getMasteryColor($data['mastery']),
+                'size' => $this->getMasterySize($data['mastery']),
+            ];
+        }
+
+        $links = [
+            ['source' => 'R01', 'target' => 'R02', 'strength' => 0.9, 'type' => 'must'],
+            ['source' => 'R02', 'target' => 'R03', 'strength' => 0.8, 'type' => 'must'],
+            ['source' => 'R02', 'target' => 'R04', 'strength' => 0.7, 'type' => 'should'],
+            ['source' => 'R03', 'target' => 'R05', 'strength' => 0.9, 'type' => 'must'],
+            ['source' => 'R04', 'target' => 'R05', 'strength' => 0.8, 'type' => 'should'],
+            ['source' => 'R05', 'target' => 'R06', 'strength' => 0.9, 'type' => 'must'],
+            ['source' => 'R07', 'target' => 'R08', 'strength' => 0.8, 'type' => 'should'],
+        ];
+
+        $this->knowledgePoints = [
+            'nodes' => $nodes,
+            'links' => $links,
+        ];
+
+        $this->masteryData = [
+            'masteries' => array_map(function ($code, $data) use ($studentId) {
+                return [
+                    'student_id' => $studentId,
+                    'kp_code' => $code,
+                    'mastery_level' => $data['mastery'],
+                    'confidence_level' => 0.8,
+                ];
+            }, array_keys($mockKnowledgePoints), $mockKnowledgePoints),
+        ];
+
+        $this->statistics = [
+            'total_knowledge_points' => count($mockKnowledgePoints),
+            'average_mastery' => array_sum(array_column($mockKnowledgePoints, 'mastery')) / count($mockKnowledgePoints),
+            'high_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] >= 0.7)),
+            'medium_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] >= 0.4 && $d['mastery'] < 0.7)),
+            'low_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] < 0.4)),
+        ];
+    }
+
+    private function getMasteryColor($mastery)
+    {
+        if ($mastery >= 0.8) return '#10b981'; // 绿色 - 优秀
+        if ($mastery >= 0.6) return '#3b82f6'; // 蓝色 - 良好
+        if ($mastery >= 0.4) return '#f59e0b'; // 黄色 - 中等
+        if ($mastery >= 0.2) return '#f97316'; // 橙色 - 待提高
+        return '#ef4444'; // 红色 - 薄弱
+    }
+
+    private function getMasterySize($mastery)
+    {
+        return max(10, $mastery * 40); // 最小10px,最大40px
+    }
+
+    private function resetData()
+    {
+        $this->selectedStudent = null;
+        $this->knowledgePoints = [];
+        $this->dependencies = [];
+        $this->masteryData = [];
+        $this->statistics = [];
+        $this->learningPath = [];
+    }
+
+    public function render()
+    {
+        return view('livewire.student-knowledge-graph');
+    }
+}

+ 132 - 126
app/Services/MathFormulaProcessor.php

@@ -6,12 +6,11 @@ class MathFormulaProcessor
 {
     /**
      * 处理数学公式,确保有正确的 LaTeX 标记
-     * 
-     * 策略:
-     * 1. 清理 HTML 标签和实体
-     * 2. 规范化反斜杠(处理多重转义)
-     * 3. 修复丢失反斜杠的常见 LaTeX 命令
-     * 4. 确保公式被正确的定界符包裹
+     *
+     * 优化策略:最小化干预,只修复真正需要修复的问题
+     * 1. 检查是否已有正确的 LaTeX 标记,如有则直接返回
+     * 2. 只在检测到明显错误时才进行修复
+     * 3. 优先保护正确的数学表达式不被破坏
      */
     public static function processFormulas(string $content): string
     {
@@ -19,84 +18,92 @@ class MathFormulaProcessor
             return $content;
         }
 
-        // 1. 基础清理
-        // 递归解码 HTML 实体
+        // 0. 基础清理:解码 HTML 实体
         $decoded = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
         while ($decoded !== $content) {
             $content = $decoded;
             $decoded = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
         }
-        
         $content = trim($content);
 
-        // 2. 规范化反斜杠 - 修复重复转义问题
-        // 暂时禁用修复功能,避免进一步污染
-        // $content = self::fixCorruptedFormulas($content);
-
-        // 3. 修复常见 LaTeX 命令
-        $commands = [
-            'sqrt', 'frac', 'times', 'div', 'pm', 'cdot', 
-            'sin', 'cos', 'tan', 'log', 'ln', 'lim',
-            'alpha', 'beta', 'gamma', 'theta', 'pi', 'sigma', 'omega', 'Delta',
-            'leq', 'geq', 'neq', 'approx', 'infty',
-            'sum', 'prod', 'int', 'partial', 'nabla'
-        ];
-        
-        $pattern = '/(?<!\\\\)\b(' . implode('|', $commands) . ')\b/';
-        $content = preg_replace($pattern, '\\\\$1', $content);
-
-        // 4. 规范化 LaTeX 命令中的空格 (OCR 常见问题)
-        // 4.1 移除 LaTeX 命令后的空格: \frac { -> \frac{
-        $content = preg_replace('/\\\\([a-zA-Z]+)\s+\{/', '\\\\$1{', $content);
-        
-        // 4.2 移除花括号内的前导和尾随空格: { 1 } -> {1}
-        $content = preg_replace('/\{\s+/', '{', $content);
-        $content = preg_replace('/\s+\}/', '}', $content);
-        
-        // 4.3 移除上标/下标符号周围的空格: x ^ { a } -> x^{a}
-        $content = preg_replace('/\s*\^\s*\{\s*/', '^{', $content);
-        $content = preg_replace('/\s*_\s*\{\s*/', '_{', $content);
-        
-        // 4.4 移除 \left 和 \right 后的空格: \left ( -> \left(
-        $content = preg_replace('/\\\\(left|right)\s+/', '\\\\$1', $content);
-        
-        // 4.5 移除括号内侧的空格: ( x ) -> (x)
-        $content = preg_replace('/\(\s+/', '(', $content);
-        $content = preg_replace('/\s+\)/', ')', $content);
-        
-        // 4.6 规范化多个连续空格为单个空格
-        $content = preg_replace('/\s+/', ' ', $content);
-        
-        // 4.7 清理 OCR 错误产生的多余 $ 符号
-        // 移除花括号内的 $: {a$} -> {a}
-        $content = preg_replace('/\{([^}]*)\$+([^}]*)\}/', '{$1$2}', $content);
-        // 移除末尾的多余 $$$
-        $content = preg_replace('/\$+\s*$/', '', $content);
-        // 移除开头的多余 $$$
-        $content = preg_replace('/^\s*\$+/', '', $content);
-        // 移除连续的 $$$ (3个或更多)
-        $content = preg_replace('/\$\$\$+/', '$$', $content);
-
-        // 5. 处理定界符
-        // 如果内容已经是完整的公式(被 $ 或 $$ 包裹),则保持原样
+        // 1. 如果已有正确的定界符,只清理内部 HTML,不做其他修改
         if (self::hasDelimiters($content)) {
-            $content = self::cleanInsideDelimiters($content);
-            return $content;
+            return self::cleanInsideDelimiters($content);
         }
 
-        // 6. 智能包装 (统一处理混合内容)
-        // 无论是纯文本还是富文本,都使用智能识别来包裹公式
-        // 这能同时处理:
-        // - "已知函数 f(x) = ..." (未包裹的混合内容)
-        // - "验证:$2x...$" (部分包裹的混合内容)
-        // - "4x^2 - 25y^2" (未包裹的纯公式)
-        
-        // 先清理已有的定界符内部
-        $content = self::cleanInsideDelimiters($content);
-        // 然后智能包裹剩余的数学部分
-        $content = self::smartWrapMixedContent($content);
+        // 2. 检测内容类型:纯数学、混合内容还是纯文本
+        $contentType = self::detectContentType($content);
 
-        return $content;
+        // 3. 根据内容类型采取不同的处理策略
+        switch ($contentType) {
+            case 'pure_math':
+                // 纯数学表达式,如 "4x^2 - 25y^2" 或 "f(x) = x^2 - 4x + 5"
+                return self::wrapPureMath($content);
+
+            case 'mixed_content':
+                // 混合内容,如 "已知函数 f(x) = x^2 - 4x + 5,求最小值"
+                return self::smartWrapMixedContent($content);
+
+            case 'plain_text':
+            default:
+                // 纯文本,不需要处理
+                return $content;
+        }
+    }
+
+    /**
+     * 检测内容类型
+     * 优化:加入中文检测,避免包裹包含中文的混合内容
+     */
+    private static function detectContentType(string $content): string
+    {
+        // 检查是否有定界符
+        if (self::hasDelimiters($content)) {
+            return 'delimited';
+        }
+
+        // 检查是否包含数学特征
+        $hasMathFeatures = self::containsMathFeatures($content);
+
+        // 如果不包含数学特征,返回纯文本
+        if (!$hasMathFeatures) {
+            return 'plain_text';
+        }
+
+        // 检查是否包含中文字符
+        if (preg_match('/[\x{4e00}-\x{9fa5}]/u', $content)) {
+            // 包含中文 + 数学特征 = 混合内容,需要智能提取数学部分
+            return 'mixed_content';
+        }
+
+        // 检查是否包含长文本(超过一定长度的字母组合)
+        $hasLongText = preg_match('/[a-zA-Z]{8,}/', $content);
+
+        if ($hasLongText) {
+            // 包含长文本,可能是混合内容,但不包裹(保守策略)
+            return 'plain_text';
+        }
+
+        // 检查是纯数学还是混合内容
+        // 混合内容:同时包含数学特征和普通英文单词
+        $hasPlainText = preg_match('/\b[a-zA-Z]{3,7}\b/', $content) &&
+                       !preg_match('/^[a-zA-Z0-9\+\-\*\/\=\s\.\^\(\)\_\{\}]+$/', $content);
+
+        if ($hasPlainText) {
+            return 'mixed_content';
+        }
+
+        return 'pure_math';
+    }
+
+    /**
+     * 包裹纯数学表达式
+     * 优化:只添加定界符,不修改内容本身
+     */
+    private static function wrapPureMath(string $content): string
+    {
+        // 已经是纯数学格式,直接用 $ 包裹
+        return '$' . $content . '$';
     }
 
     /**
@@ -144,67 +151,50 @@ class MathFormulaProcessor
 
     /**
      * 智能识别并包裹富文本中的数学公式
+     * 支持:函数定义、导数表达式、LaTeX命令、数学运算
      */
     private static function smartWrapMixedContent(string $content): string
     {
-        // 正则策略:匹配 HTML 标签 OR 数学公式候选
-        // 捕获组 1: HTML 标签 (忽略)
-        // 捕获组 2: 已有的定界符 (忽略)
-        // 捕获组 3: 数学公式 (处理)
-        
+        // 匹配策略:只匹配明确的数学表达式,避免误判
         $tagPattern = '<[^>]+>';
-        
-        // 匹配已有的定界符
         $existingDelimiterPattern = '(?:\$\$[\s\S]*?\$\$|\$[\s\S]*?\$|\\\\\([\s\S]*?\\\\\)|\\\\\[[\s\S]*?\\\\\])';
-        
-        // 数学公式特征:
-        // 1. 函数定义: f(x) = ...
-        // 2. 等式/不等式: ... = ..., ... > ..., ... < ...
-        // 3. 包含 LaTeX 命令: \sqrt, \frac 等
-        // 4. 包含上标/下标: x^2, a_n
-        
-        // 匹配函数定义或等式 (例如 f(x) = 2x^2 + 1)
-        // 必须包含 = 或 > 或 <,且周围有类数学字符
-        $equationPattern = '(?<![\w\\\\])(?:[a-zA-Z]\([a-zA-Z0-9,]+\)|[a-zA-Z0-9\^_\{\}]+)\s*[=<>]\s*[\w\s\+\-\*\/\^\.\(\)\{\}\\\\]+(?=\s|$|<|[.,;])';
-        
-        // 匹配显式 LaTeX 命令 (例如 \sqrt{...})
-        $latexPattern = '\\\\[a-zA-Z]+(?:\{[^\}]*\})?';
-        
-        // 匹配简单的代数项 (例如 x^2, a_n) - 需谨慎,避免匹配普通单词
-        $algebraPattern = '(?<![\w\\\\])[a-zA-Z0-9]+\^[\w\{]+';
-
-        // 匹配多项式/复杂表达式 (例如 4x^2 - 25y^2, 2x \times 2 + ...)
-        // 特征:包含变量、数字、运算符 (+, -, *, /)、LaTeX命令、上标/下标
-        // 必须包含至少一个运算符,且长度适中
-        $polynomialPattern = '(?<![\w\\\\])(?:[a-zA-Z0-9\.]+(?:[\^_\{\}][a-zA-Z0-9\.\{\}]+)?|\\\\[a-zA-Z]+(?:\{[^\}]*\})?)(?:\s*[\+\-\*\/]\s*(?:[a-zA-Z0-9\.]+(?:[\^_\{\}][a-zA-Z0-9\.\{\}]+)?|\\\\[a-zA-Z]+(?:\{[^\}]*\})?))+';
-
-        $pattern = "/($tagPattern)|($existingDelimiterPattern)|($equationPattern|$polynomialPattern|$latexPattern|$algebraPattern)/u";
+
+        // 数学公式模式(按优先级排列)
+        $patterns = [
+            // 1. 函数定义: f(x) = 2x^3 - 3x^2 + 4x - 5
+            "[a-zA-Z]'?\\([a-zA-Z0-9,\\s]+\\)\\s*=\\s*[a-zA-Z0-9\\+\\-\\*\\/\\^\\s\\.\\(\\)\\_\\{\\}]+",
+            // 2. 导数/函数调用: f'(1), g(5), sin(x)
+            "[a-zA-Z]+'?\\([a-zA-Z0-9\\+\\-\\*\\/\\^\\s\\.]+\\)",
+            // 3. LaTeX 命令: \frac{1}{2}
+            "\\\\[a-zA-Z]+\\{[^}]*\\}(?:\\{[^}]*\\})?",
+            // 4. 数学表达式: x^2 + y^2, 2x - 3
+            "[a-zA-Z0-9]+[\\^_][a-zA-Z0-9\\{\\}]+(?:\\s*[\\+\\-\\*\\/]\\s*[a-zA-Z0-9\\^_\\{\\}\\.]+)*",
+        ];
+
+        $mathPattern = '(?:' . implode('|', $patterns) . ')';
+        $pattern = "/($tagPattern)|($existingDelimiterPattern)|($mathPattern)/u";
 
         return preg_replace_callback($pattern, function ($matches) {
-            // 如果是 HTML 标签 (组1),原样返回
+            // HTML 标签,原样返回
             if (!empty($matches[1])) {
                 return $matches[1];
             }
-            
-            // 如果是已有的定界符 (组2),原样返回
+
+            // 已有的定界符,原样返回
             if (!empty($matches[2])) {
                 return $matches[2];
             }
-            
-            // 如果是数学公式 (组3)
+
+            // 数学公式,添加 $ 包裹
             if (!empty($matches[3])) {
-                $math = $matches[3];
-                // 再次检查是否已经包裹 (虽然外层逻辑应该处理了,但为了安全)
+                $math = trim($matches[3]);
+                // 再次检查是否已经包裹
                 if (str_contains($math, '$')) {
                     return $math;
                 }
-                // 排除纯数字或普通单词误判
-                if (preg_match('/^[a-zA-Z0-9\s]+$/', $math)) {
-                    return $math;
-                }
                 return '$' . $math . '$';
             }
-            
+
             return $matches[0];
         }, $content);
     }
@@ -237,26 +227,42 @@ class MathFormulaProcessor
 
     /**
      * 检测数学特征
+     * 优化:更精确的检测,减少误判
      */
     private static function containsMathFeatures(string $content): bool
     {
         // 1. 检查是否有 LaTeX 命令
-        if (strpos($content, '\\') !== false) {
+        if (preg_match('/\\\\[a-zA-Z]+\{?/', $content)) {
             return true;
         }
 
-        // 2. 检查数学符号
-        $symbols = ['+', '-', '*', '/', '=', '<', '>', '^', '_', '{', '}'];
-        foreach ($symbols as $symbol) {
-            if (strpos($content, $symbol) !== false) {
-                // 排除普通文本中的符号(如连字符),这里做一个简单的宽容判断
-                // 如果有数字紧随其后,或者是特定组合
-                return true;
-            }
+        // 2. 检查函数定义或等式(如 f(x) =, g(x) =)
+        // 必须是:字母+括号+等号+数学内容
+        if (preg_match('/[a-zA-Z]\([a-zA-Z0-9,\s]+\)\s*=\s*[a-zA-Z0-9\+\-\*\/\^\.\(\)\s\\\\_\{]+/', $content)) {
+            return true;
+        }
+
+        // 3. 检查纯数学表达式(只包含数字、变量、运算符、括号)
+        // 严格的数学表达式:必须包含字母和运算符,且没有中文字符
+        if (preg_match('/^[a-zA-Z0-9\+\-\*\/\=\s\.\^\(\)\_\{\}]+$/', $content) &&
+            preg_match('/[a-zA-Z]/', $content) &&
+            preg_match('/[\+\-\*\/\=\^]/', $content)) {
+            return true;
+        }
+
+        // 4. 检查包含变量的数学表达式(带约束)
+        // 必须有明确的运算符连接,且周围是数学内容
+        if (preg_match('/[a-zA-Z0-9\.\^\_\{\}]\s*[\+\-\*\/]\s*[a-zA-Z0-9\.\^\_\{\}\(\)]/', $content)) {
+            return true;
+        }
+
+        // 5. 检查分数形式(如 \frac{}{})
+        if (preg_match('/\\\\frac\{/', $content)) {
+            return true;
         }
 
-        // 3. 检查数字和字母的组合 (如 2x, x^2)
-        if (preg_match('/[a-zA-Z]\d|\d[a-zA-Z]/', $content)) {
+        // 6. 检查上标或下标(仅当与数字/字母组合时)
+        if (preg_match('/[a-zA-Z0-9]\s*[\^_]\s*[a-zA-Z0-9]/', $content)) {
             return true;
         }
 

+ 19 - 0
resources/views/filament/pages/intelligent-exam-generation-simple.blade.php

@@ -212,6 +212,7 @@
             </div>
             <div class="space-y-6">
                 <!-- 直接在父组件中显示教师和学生选择,避免组件间通信问题 -->
+                @if(!$this->isTeacher)
                 <div class="grid grid-cols-2 gap-4">
                     <div class="selection-card border rounded-lg p-4">
                         <label class="block text-sm font-medium text-gray-700 mb-2">选择教师</label>
@@ -250,6 +251,24 @@
                         </select>
                     </div>
                 </div>
+                @else
+                <div class="grid grid-cols-1 gap-4">
+                    <div class="selection-card border rounded-lg p-4">
+                        <label class="block text-sm font-medium text-gray-700 mb-2">选择学生</label>
+                        <select
+                            wire:model.live="selectedStudentId"
+                            class="form-select w-full px-3 py-2 rounded-lg text-sm"
+                        >
+                            <option value="">-- 请选择学生 --</option>
+                            @foreach($this->students as $student)
+                                <option value="{{ $student->student_id }}" @if(request('student_id') == $student->student_id) selected @endif>
+                                    {{ trim($student->name ?? $student->student_id) . " ({$student->grade} - {$student->class_name})" }}
+                                </option>
+                            @endforeach
+                        </select>
+                    </div>
+                </div>
+                @endif
 
                 <!-- 显示当前选择状态 -->
                 <div class="mt-4 p-4 bg-gray-50 rounded-lg">

+ 3 - 0
resources/views/filament/pages/mistake-book.blade.php

@@ -9,6 +9,8 @@
                 </div>
                 <div class="lg:col-span-2">
                     <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+                        {{-- 选择老师(老师登录时隐藏) --}}
+                        @if(!$this->isTeacher)
                         <div class="form-control w-full">
                             <label class="label">
                                 <span class="label-text font-medium">选择老师 <span class="text-error">*</span></span>
@@ -25,6 +27,7 @@
                                 @endforeach
                             </select>
                         </div>
+                        @endif
                         <div class="form-control w-full">
                             <label class="label">
                                 <span class="label-text font-medium">选择学生 <span class="text-error">*</span></span>

+ 3 - 1
resources/views/filament/pages/student-dashboard.blade.php

@@ -19,7 +19,8 @@
             {{-- 选择器区域 --}}
             <div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
                 <div class="flex flex-col gap-3 lg:flex-row lg:items-center">
-                    {{-- 选择老师 --}}
+                    {{-- 选择老师(老师登录时隐藏) --}}
+                    @if(!$this->isTeacher)
                     <div class="form-control w-full lg:flex-1">
                         <label class="label">
                             <span class="label-text font-medium">选择老师</span>
@@ -39,6 +40,7 @@
                             <span class="label-text text-xs text-gray-500">选择要查看的老师</span>
                         </label>
                     </div>
+                    @endif
 
                     {{-- 选择学生 --}}
                     <div class="form-control w-full lg:flex-1">

+ 3 - 1
resources/views/filament/pages/upload-exam-paper.blade.php

@@ -41,7 +41,8 @@
                 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                     {{-- 左侧:选择老师和学生 --}}
                     <div class="space-y-4">
-                        {{-- 选择老师 --}}
+                        {{-- 选择老师(老师登录时隐藏) --}}
+                        @if(!$this->isTeacher)
                         <div class="form-control w-full">
                             <label class="label">
                                 <span class="label-text font-medium">选择老师 <span class="text-error">*</span></span>
@@ -58,6 +59,7 @@
                                 @endforeach
                             </select>
                         </div>
+                        @endif
 
                         {{-- 选择学生 --}}
                         <div class="form-control w-full">

+ 31 - 0
test_math_processor.php

@@ -0,0 +1,31 @@
+<?php
+
+require __DIR__ . '/vendor/autoload.php';
+
+use App\Services\MathFormulaProcessor;
+
+echo "=== 测试数学公式处理器优化效果 ===\n\n";
+
+// 测试用例
+$testCases = [
+    '已知函数 f(x) = x^2 - 4x + 5,求最小值',
+    'f(x) = x^2 - 4x + 5',
+    '4x^2 - 25y^2',
+    '验证:$2x^2 + 1$',
+    '这是一段普通文本,不包含数学公式',
+    '\frac{1}{2} + \frac{1}{3}',
+    'x^2 + y^2 = 1',
+    '已知函数 g(x) = 2x + 3,求 g(5)',
+];
+
+foreach ($testCases as $index => $testCase) {
+    echo "测试用例 " . ($index + 1) . ":\n";
+    echo "输入: " . $testCase . "\n";
+
+    $result = MathFormulaProcessor::processFormulas($testCase);
+
+    echo "输出: " . $result . "\n";
+    echo "---\n\n";
+}
+
+echo "=== 测试完成 ===\n";