头像批量上传

master
lion 1 day ago
parent d2010c251c
commit 550d75025c

@ -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();
}
}

@ -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"]);

Loading…
Cancel
Save