From 550d75025c1cd0f49f6c31e2a6d5b54d54ed7090 Mon Sep 17 00:00:00 2001 From: lion <120344285@qq.com> Date: Thu, 21 May 2026 14:40:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=B4=E5=83=8F=E6=89=B9=E9=87=8F=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Admin/UserController.php | 221 ++++++++++++++++++ routes/api.php | 2 + 2 files changed, 223 insertions(+) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 01eb8af..8489473 100755 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -1233,4 +1233,225 @@ class UserController extends BaseController } } + /** + * 批量头像预览:按课程 + 文件名(姓名)匹配学员,不上传、不更新 + */ + public function batchHeadimgPreview() + { + $all = \request()->all(); + $validator = Validator::make($all, [ + 'course_id' => 'required|integer', + ], [ + 'course_id.required' => '请选择课程', + ]); + if ($validator->fails()) { + return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); + } + + $courseId = (int) $all['course_id']; + $course = Course::find($courseId); + if (!$course) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']); + } + + $files = \request()->file('files'); + if (empty($files)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '请上传头像图片']); + } + if (!is_array($files)) { + $files = [$files]; + } + + $students = $this->getCourseStudents((int) $courseId); + $list = []; + $stats = ['ready' => 0, 'unmatched' => 0, 'duplicate' => 0, 'invalid' => 0]; + + foreach ($files as $index => $file) { + $filename = $file->getClientOriginalName(); + $name = trim(pathinfo($filename, PATHINFO_FILENAME)); + $item = [ + 'index' => $index, + 'filename' => $filename, + 'name' => $name, + 'status' => 'unmatched', + 'status_text' => '未匹配', + 'user_id' => null, + 'username' => '', + 'mobile' => '', + 'no' => '', + 'current_headimgurl' => '', + 'candidates' => [], + ]; + + if (!$file->isValid()) { + $item['status'] = 'invalid'; + $item['status_text'] = '文件无效'; + $stats['invalid']++; + $list[] = $item; + continue; + } + + $ext = strtolower($file->getClientOriginalExtension()); + if (!in_array($ext, ['png', 'gif', 'jpg', 'jpeg', 'bmp', 'svg', 'webp'])) { + $item['status'] = 'invalid'; + $item['status_text'] = '文件格式不支持'; + $stats['invalid']++; + $list[] = $item; + continue; + } + + if ($file->getSize() > 500 * 1024) { + $item['status'] = 'invalid'; + $item['status_text'] = '文件超过500KB'; + $stats['invalid']++; + $list[] = $item; + continue; + } + + if ($name === '') { + $item['status'] = 'invalid'; + $item['status_text'] = '文件名无效'; + $stats['invalid']++; + $list[] = $item; + continue; + } + + $matched = $this->matchStudentsByName($students, $name); + if ($matched->isEmpty()) { + $stats['unmatched']++; + } elseif ($matched->count() > 1) { + $item['status'] = 'duplicate'; + $item['status_text'] = '重名(' . $matched->count() . '人)'; + $item['candidates'] = $matched->map(function ($user) { + return [ + 'user_id' => $user->id, + 'username' => $user->username, + 'mobile' => $user->mobile, + 'no' => $user->no, + ]; + })->values()->all(); + $stats['duplicate']++; + } else { + $user = $matched->first(); + $item['status'] = 'ready'; + $item['status_text'] = '可更新'; + $item['user_id'] = $user->id; + $item['username'] = $user->username; + $item['mobile'] = $user->mobile; + $item['no'] = $user->no; + $item['current_headimgurl'] = $user->headimgurl; + $stats['ready']++; + } + + $list[] = $item; + } + + return $this->success([ + 'course_id' => $courseId, + 'course_name' => $course->name, + 'list' => $list, + 'stats' => $stats, + 'student_total' => $students->count(), + ]); + } + + /** + * 批量头像导入:仅更新预览中匹配成功且属于该课程的学员 + */ + public function batchHeadimgImport() + { + $all = \request()->all(); + $validator = Validator::make($all, [ + 'course_id' => 'required|integer', + 'items' => 'required|array|min:1', + 'items.*.user_id' => 'required|integer', + 'items.*.headimgurl' => 'required|string', + ], [ + 'course_id.required' => '请选择课程', + 'items.required' => '导入数据不能为空', + ]); + if ($validator->fails()) { + return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); + } + + $courseId = (int) $all['course_id']; + $course = Course::find($courseId); + if (!$course) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']); + } + + $courseUserIds = CourseSign::where('course_id', $courseId)->pluck('user_id')->unique()->filter()->values()->all(); + $items = $all['items']; + $updatedCount = 0; + $failedRecords = []; + + DB::beginTransaction(); + try { + foreach ($items as $index => $item) { + $userId = (int) ($item['user_id'] ?? 0); + $headimgurl = trim($item['headimgurl'] ?? ''); + + if (!in_array($userId, $courseUserIds, true)) { + $failedRecords[] = [ + 'index' => $index, + 'user_id' => $userId, + 'reason' => '该学员不属于所选课程', + ]; + continue; + } + + $user = $this->model->find($userId); + if (!$user) { + $failedRecords[] = [ + 'index' => $index, + 'user_id' => $userId, + 'reason' => '学员不存在', + ]; + continue; + } + + $user->headimgurl = $headimgurl; + $user->save(); + $updatedCount++; + } + + DB::commit(); + + return $this->success([ + 'total' => count($items), + 'updated_count' => $updatedCount, + 'failed_count' => count($failedRecords), + 'failed_records' => $failedRecords, + ]); + } catch (\Exception $exception) { + DB::rollBack(); + return $this->fail([$exception->getCode(), $exception->getMessage()]); + } + } + + /** + * 获取课程下的学员(去重) + */ + protected function getCourseStudents(int $courseId) + { + $userIds = CourseSign::where('course_id', $courseId)->pluck('user_id')->unique()->filter(); + if ($userIds->isEmpty()) { + return collect(); + } + + return User::whereIn('id', $userIds)->get(); + } + + /** + * 按姓名精确匹配学员 + */ + protected function matchStudentsByName($students, string $name) + { + return $students->filter(function ($user) use ($name) { + $username = trim((string) ($user->username ?? '')); + $realName = trim((string) ($user->name ?? '')); + return $username === $name || $realName === $name; + })->values(); + } + } diff --git a/routes/api.php b/routes/api.php index 950b083..3de505f 100755 --- a/routes/api.php +++ b/routes/api.php @@ -116,6 +116,8 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () { Route::post('users/import-special', [\App\Http\Controllers\Admin\UserController::class, "importSpecial"]); Route::post('users/batch-update-schoolmate', [\App\Http\Controllers\Admin\UserController::class, "batchUpdateSchoolmate"]); Route::post('users/batch-update', [\App\Http\Controllers\Admin\UserController::class, "batchUpdate"]); + Route::post('users/batch-headimg-preview', [\App\Http\Controllers\Admin\UserController::class, "batchHeadimgPreview"]); + Route::post('users/batch-headimg-import', [\App\Http\Controllers\Admin\UserController::class, "batchHeadimgImport"]); // 老师管理 Route::get('teachers/index', [\App\Http\Controllers\Admin\TeacherController::class, "index"]);