From 4a8a5f68c2f898d0b79bf961f6a5b2b1f5d531f3 Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Mon, 12 Jan 2026 17:28:03 +0800 Subject: [PATCH] update --- app/Console/Commands/SendEmail.php | 15 +- .../Admin/EmailRecordController.php | 120 +++++++- .../Admin/StockCompanyController.php | 279 ++++++++++++++++++ app/Models/EmailRecord.php | 20 ++ app/Models/EmailRecordUser.php | 28 +- app/Models/StockCompany.php | 30 ++ ..._12_171043_create_stock_companys_table.php | 42 +++ ...add_attachments_to_email_records_table.php | 33 +++ routes/api.php | 8 + 9 files changed, 569 insertions(+), 6 deletions(-) create mode 100644 app/Http/Controllers/Admin/StockCompanyController.php create mode 100644 app/Models/StockCompany.php create mode 100644 database/migrations/2026_01_12_171043_create_stock_companys_table.php create mode 100644 database/migrations/2026_01_12_172322_add_attachments_to_email_records_table.php diff --git a/app/Console/Commands/SendEmail.php b/app/Console/Commands/SendEmail.php index 02e5cb1..814832c 100755 --- a/app/Console/Commands/SendEmail.php +++ b/app/Console/Commands/SendEmail.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use App\Models\EmailRecord; use App\Models\EmailRecordUser; +use App\Models\Upload; use App\Models\User; use App\Repositories\MeetRepository; use Illuminate\Console\Command; @@ -51,6 +52,16 @@ class SendEmail extends Command foreach ($emailRecords as $records) { // 获取模版配置 $emailTemplate = $records->emailTemplate; + // 获取附件 + $attachments = []; + if (!empty($records->attachments)) { + $attachmentIds = explode(',', $records->attachments); + $attachmentIds = array_filter(array_map('trim', $attachmentIds)); + if (!empty($attachmentIds)) { + $attachments = Upload::whereIn('id', $attachmentIds)->get(); + } + } + // 获取未发送人员 $emailRecordUsers = $records->emailRecordUsers->where('status', 0); foreach ($emailRecordUsers as $recordUser) { @@ -59,8 +70,8 @@ class SendEmail extends Command // 替换后的内容 $template = EmailRecordUser::template($emailTemplate->content, $recordUser->var_data); try { - // 发送邮件 - EmailRecordUser::email($title, $template, $recordUser->email); + // 发送邮件(带附件) + EmailRecordUser::email($title, $template, $recordUser->email, $attachments); $recordUser->status = 1; } catch (\Exception $e) { $recordUser->status = 2; diff --git a/app/Http/Controllers/Admin/EmailRecordController.php b/app/Http/Controllers/Admin/EmailRecordController.php index 9d79a3c..f56bba1 100644 --- a/app/Http/Controllers/Admin/EmailRecordController.php +++ b/app/Http/Controllers/Admin/EmailRecordController.php @@ -15,6 +15,7 @@ use App\Models\EmailRecord; use App\Models\EmailRecordUser; use App\Models\EmailTemplate; use App\Models\SupplyDemand; +use App\Models\Upload; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; @@ -177,6 +178,8 @@ class EmailRecordController extends BaseController * @OA\Parameter(name="subject", in="query", @OA\Schema(type="string", maxLength=255), required=false, description="主题"), * @OA\Parameter(name="email_template_id", in="query", @OA\Schema(type="string", format="mediumtext"), required=false, description="邮件模版id"), * @OA\Parameter(name="email_record_users", in="query", @OA\Schema(type="string", format="mediumtext"), required=false, description="二维数组,包括建明:email,var_data自定义数据"), + * @OA\Parameter(name="attachments", in="query", @OA\Schema(type="array", @OA\Items(type="file")), required=false, description="附件文件数组,支持多个附件"), + * @OA\Parameter(name="attachment_ids", in="query", @OA\Schema(type="string"), required=false, description="附件ID,多个用英文逗号分隔(如果已上传附件)"), * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"), * @OA\Response( * response="200", @@ -199,6 +202,63 @@ class EmailRecordController extends BaseController $all['admin_id'] = $this->getUserId(); $all['department_id'] = $this->getUser()->department_id; } + + // 处理附件上传 + $attachmentIds = []; + + // 如果提供了已上传的附件ID + if (isset($all['attachment_ids']) && !empty($all['attachment_ids'])) { + $attachmentIds = array_merge($attachmentIds, explode(',', $all['attachment_ids'])); + $attachmentIds = array_filter(array_map('trim', $attachmentIds)); + } + + // 如果有新上传的附件文件 + if (\request()->hasFile('attachments')) { + $files = \request()->file('attachments'); + // 如果是单个文件,转换为数组 + if (!is_array($files)) { + $files = [$files]; + } + + foreach ($files as $file) { + if ($file && $file->isValid()) { + // 获取文件大小,单位B + $fileSize = floor($file->getSize()); + if ($fileSize >= 50 * 1024 * 1024) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '文件必须小于50M']); + } + // 过滤文件后缀 + $ext = $file->getClientOriginalExtension(); + if (in_array($ext, ['zip', 'rar', 'ppt', 'pptx', 'xls', 'xlsx', 'doc', 'docx', 'png', 'gif', 'jpg', 'jpeg', 'pdf', 'mp4'])) { + // 保存目录 + $dir = 'files'; + // 文件名 + $fileName = time() . uniqid() . '.' . $ext; + $file->storeAs($dir, $fileName, ['disk' => 'public']); + // 写入上传文件记录表 + $uploadData = [ + 'original_name' => $file->getClientOriginalName(), + 'folder' => 'storage/' . $dir, + 'name' => $fileName, + 'extension' => $ext, + 'size' => $fileSize, + 'creator_id' => $this->getUserId(), + 'created_at' => date('Y-m-d H:i:s') + ]; + $uploadId = Upload::insertGetId($uploadData); + $attachmentIds[] = $uploadId; + } else { + return $this->fail([ResponseCode::ERROR_BUSINESS, '文件格式错误,仅支持:zip、rar、ppt、pptx、xls、xlsx、doc、docx、png、gif、jpg、jpeg、pdf、mp4']); + } + } + } + } + + // 保存附件ID(用逗号分隔) + if (!empty($attachmentIds)) { + $all['attachments'] = implode(',', $attachmentIds); + } + $original = $model->getOriginal(); $model->fill($all); $model->save(); @@ -226,6 +286,8 @@ class EmailRecordController extends BaseController * @OA\Parameter(name="subject", in="query", @OA\Schema(type="string", format="date"), required=false, description="标题"), * @OA\Parameter(name="email", in="query", @OA\Schema(type="string", format="date"), required=false, description="邮箱地址"), * @OA\Parameter(name="var_data", in="query", @OA\Schema(type="string", format="mediumtext"), required=false, description="数组,var_data自定义数据"), + * @OA\Parameter(name="attachments", in="query", @OA\Schema(type="array", @OA\Items(type="file")), required=false, description="附件文件数组,支持多个附件"), + * @OA\Parameter(name="attachment_ids", in="query", @OA\Schema(type="string"), required=false, description="附件ID,多个用英文逗号分隔(如果已上传附件)"), * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"), * @OA\Response( * response="200", @@ -251,9 +313,63 @@ class EmailRecordController extends BaseController $title = EmailRecordUser::template($all['subject'], $all['var_data']); // 替换后的内容 $template = EmailRecordUser::template($emailTemplate->content, $all['var_data']); + + // 处理附件 + $attachments = []; + + // 如果提供了已上传的附件ID + if (isset($all['attachment_ids']) && !empty($all['attachment_ids'])) { + $attachmentIds = explode(',', $all['attachment_ids']); + $attachmentIds = array_filter(array_map('trim', $attachmentIds)); + $attachments = Upload::whereIn('id', $attachmentIds)->get(); + } + + // 如果有新上传的附件文件 + if (\request()->hasFile('attachments')) { + $files = \request()->file('attachments'); + // 如果是单个文件,转换为数组 + if (!is_array($files)) { + $files = [$files]; + } + + foreach ($files as $file) { + if ($file && $file->isValid()) { + // 获取文件大小,单位B + $fileSize = floor($file->getSize()); + if ($fileSize >= 50 * 1024 * 1024) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '文件必须小于50M']); + } + // 过滤文件后缀 + $ext = $file->getClientOriginalExtension(); + if (in_array($ext, ['zip', 'rar', 'ppt', 'pptx', 'xls', 'xlsx', 'doc', 'docx', 'png', 'gif', 'jpg', 'jpeg', 'pdf', 'mp4'])) { + // 保存目录 + $dir = 'files'; + // 文件名 + $fileName = time() . uniqid() . '.' . $ext; + $file->storeAs($dir, $fileName, ['disk' => 'public']); + // 写入上传文件记录表 + $uploadData = [ + 'original_name' => $file->getClientOriginalName(), + 'folder' => 'storage/' . $dir, + 'name' => $fileName, + 'extension' => $ext, + 'size' => $fileSize, + 'creator_id' => $this->getUserId(), + 'created_at' => date('Y-m-d H:i:s') + ]; + $uploadId = Upload::insertGetId($uploadData); + $upload = Upload::find($uploadId); + $attachments[] = $upload; + } else { + return $this->fail([ResponseCode::ERROR_BUSINESS, '文件格式错误,仅支持:zip、rar、ppt、pptx、xls、xlsx、doc、docx、png、gif、jpg、jpeg、pdf、mp4']); + } + } + } + } + try { - // 发送邮件 - EmailRecordUser::email($title, $template, $all['email']); + // 发送邮件(带附件) + EmailRecordUser::email($title, $template, $all['email'], $attachments); return $this->success("发送成功"); } catch (\Exception $e) { return $this->fail([ResponseCode::ERROR_BUSINESS, $e->getMessage()]); diff --git a/app/Http/Controllers/Admin/StockCompanyController.php b/app/Http/Controllers/Admin/StockCompanyController.php new file mode 100644 index 0000000..b73d651 --- /dev/null +++ b/app/Http/Controllers/Admin/StockCompanyController.php @@ -0,0 +1,279 @@ +all(); + + $list = $this->model->with(underlineToHump($all['show_relation'] ?? [])) + ->where(function ($query) use ($all) { + if (isset($all['filter']) && !empty($all['filter'])) { + foreach ($all['filter'] as $condition) { + $key = $condition['key'] ?? null; + $op = $condition['op'] ?? null; + $value = $condition['value'] ?? null; + if (!isset($key) || !isset($op) || !isset($value)) { + continue; + } + + // 等于 + if ($op == 'eq') { + $query->where($key, $value); + } + // 不等于 + if ($op == 'neq') { + $query->where($key, '!=', $value); + } + // 大于 + if ($op == 'gt') { + $query->where($key, '>', $value); + } + // 大于等于 + if ($op == 'egt') { + $query->where($key, '>=', $value); + } + // 小于 + if ($op == 'lt') { + $query->where($key, '<', $value); + } + // 小于等于 + if ($op == 'elt') { + $query->where($key, '<=', $value); + } + // 模糊搜索 + if ($op == 'like') { + $query->where($key, 'like', '%' . $value . '%'); + } + // 否定模糊搜索 + if ($op == 'notlike') { + $query->where($key, 'not like', '%' . $value . '%'); + } + // null搜索 + if ($op == 'null') { + $query->whereNull($key); + } + // notnull搜索 + if ($op == 'notnull') { + $query->whereNotNull($key); + } + // 范围搜索 + if ($op == 'range') { + list($from, $to) = explode(',', $value); + if (empty($from) || empty($to)) { + continue; + } + $query->whereBetween($key, [$from, $to]); + } + } + } + })->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc'); + + if (isset($all['is_export']) && !empty($all['is_export'])) { + $list = $list->get()->toArray(); + $export_fields = $all['export_fields'] ?? []; + // 导出文件名字 + $tableName = $this->model->getTable(); + $filename = (new CustomForm())->getTableComment($tableName); + return Excel::download(new BaseExport($export_fields, $list, $tableName), $filename . date('YmdHis') . '.xlsx'); + } else { + // 输出 + $list = $list->paginate($all['page_size'] ?? 20); + } + return $this->success($list); + } + + /** + * @OA\Get( + * path="/api/admin/stock-company/show", + * tags={"上市公司管理"}, + * summary="详情", + * description="", + * @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"), + * @OA\Parameter(name="show_relation", in="query", @OA\Schema(type="string"), required=false, description="需要输出的关联关系数组,填写输出指定数据"), + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"), + * @OA\Response( + * response="200", + * description="暂无" + * ) + * ) + */ + public function show() + { + return parent::show(); + } + + /** + * @OA\Post( + * path="/api/admin/stock-company/save", + * tags={"上市公司管理"}, + * summary="保存", + * description="", + * @OA\Parameter(name="id", in="query", @OA\Schema(type="int"), required=false, description="Id(存在更新,不存在新增)"), + * @OA\Parameter(name="company_name", in="query", @OA\Schema(type="string", nullable=true), description="公司名称"), + * @OA\Parameter(name="stock_date", in="query", @OA\Schema(type="string", format="date", nullable=true), description="上市时间"), + * @OA\Parameter(name="enrollment_date", in="query", @OA\Schema(type="string", format="date", nullable=true), description="入学时间"), + * @OA\Parameter(name="is_after_enrollment", in="query", @OA\Schema(type="string", nullable=true), description="是否入学后上市-0否1是"), + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"), + * @OA\Response( + * response="200", + * description="操作成功" + * ) + * ) + */ + public function save() + { + $all = \request()->all(); + DB::beginTransaction(); + try { + if (isset($all['id'])) { + $model = $this->model->find($all['id']); + if (empty($model)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']); + } + } else { + $model = $this->model; + $all['admin_id'] = $this->getUserId(); + $all['department_id'] = $this->getUser()->department_id; + } + + // 如果提供了上市时间和入学时间,自动计算是否入学后上市 + if (isset($all['stock_date']) && isset($all['enrollment_date'])) { + $stockDate = strtotime($all['stock_date']); + $enrollmentDate = strtotime($all['enrollment_date']); + $all['is_after_enrollment'] = ($stockDate > $enrollmentDate) ? 1 : 0; + } + + $original = $model->getOriginal(); + $model->fill($all); + $model->save(); + DB::commit(); + // 记录日志 + $this->saveLogs($original, $model); + return $this->success($model); + } catch (\Exception $exception) { + DB::rollBack(); + return $this->fail([$exception->getCode(), $exception->getMessage()]); + } + } + + /** + * @OA\Get( + * path="/api/admin/stock-company/destroy", + * tags={"上市公司管理"}, + * summary="删除", + * description="", + * @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"), + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"), + * @OA\Response( + * response="200", + * description="暂无" + * ) + * ) + */ + public function destroy() + { + return parent::destroy(); + } + + /** + * @OA\Get( + * path="/api/admin/stock-company/this-year-new", + * tags={"上市公司管理"}, + * summary="今年新增上市公司", + * description="", + * @OA\Parameter(name="year", in="query", @OA\Schema(type="string"), required=false, description="年份,默认为当前年份"), + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"), + * @OA\Response( + * response="200", + * description="暂无" + * ) + * ) + */ + public function thisYearNew() + { + $all = request()->all(); + $year = $all['year'] ?? date('Y'); + $startDate = $year . '-01-01'; + $endDate = $year . '-12-31'; + + $list = $this->model->whereBetween('stock_date', [$startDate, $endDate]) + ->orderBy('stock_date', 'desc') + ->get(); + + return $this->success([ + 'year' => $year, + 'total' => $list->count(), + 'list' => $list + ]); + } + + /** + * @OA\Get( + * path="/api/admin/stock-company/after-enrollment", + * tags={"上市公司管理"}, + * summary="入学后新上市公司数据显示", + * description="", + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"), + * @OA\Response( + * response="200", + * description="暂无" + * ) + * ) + */ + public function afterEnrollment() + { + $list = $this->model->where('is_after_enrollment', 1) + ->orderBy('stock_date', 'desc') + ->get(); + + return $this->success([ + 'total' => $list->count(), + 'list' => $list + ]); + } + +} + diff --git a/app/Models/EmailRecord.php b/app/Models/EmailRecord.php index be0f8dd..ce5788b 100755 --- a/app/Models/EmailRecord.php +++ b/app/Models/EmailRecord.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Cache; class EmailRecord extends SoftDeletesModel { + protected $appends = ['attachment_files']; public function emailTemplate() { @@ -19,5 +20,24 @@ class EmailRecord extends SoftDeletesModel return $this->hasMany(EmailRecordUser::class, 'email_record_id', 'id'); } + /** + * 获取附件文件对象数组 + */ + public function getAttachmentFilesAttribute() + { + if (empty($this->attachments)) { + return []; + } + + $attachmentIds = explode(',', $this->attachments); + $attachmentIds = array_filter(array_map('trim', $attachmentIds)); + + if (empty($attachmentIds)) { + return []; + } + + return Upload::whereIn('id', $attachmentIds)->get(); + } + } diff --git a/app/Models/EmailRecordUser.php b/app/Models/EmailRecordUser.php index b67a904..9ba858d 100755 --- a/app/Models/EmailRecordUser.php +++ b/app/Models/EmailRecordUser.php @@ -34,12 +34,36 @@ class EmailRecordUser extends SoftDeletesModel /** * 发送邮件 + * @param string $title 邮件标题 + * @param string $template 邮件内容 + * @param string $email 收件人邮箱 + * @param array $attachments 附件数组,可以是 Upload 对象数组或文件路径数组 */ - public static function email($title, $template, $email) + public static function email($title, $template, $email, $attachments = []) { - Mail::send('email', compact('template'), function ($message) use ($email, $title) { + Mail::send('email', compact('template'), function ($message) use ($email, $title, $attachments) { $message->from(env('MAIL_USERNAME'), '苏州科技商学院'); $message->to($email)->subject($title); + + // 添加附件 + if (!empty($attachments)) { + foreach ($attachments as $attachment) { + if ($attachment instanceof Upload) { + // 如果是 Upload 对象,获取文件路径 + // folder 格式为 'storage/files',实际文件存储在 storage/app/public/files + $folderPath = str_replace('storage/', '', $attachment->folder); + $filePath = storage_path('app/public/' . $folderPath . '/' . $attachment->name); + if (file_exists($filePath)) { + $message->attach($filePath, [ + 'as' => $attachment->original_name, + ]); + } + } elseif (is_string($attachment) && file_exists($attachment)) { + // 如果是文件路径字符串 + $message->attach($attachment); + } + } + } }); return true; } diff --git a/app/Models/StockCompany.php b/app/Models/StockCompany.php new file mode 100644 index 0000000..813aa88 --- /dev/null +++ b/app/Models/StockCompany.php @@ -0,0 +1,30 @@ + '否', + 1 => '是', + ]; + return $array[$this->is_after_enrollment] ?? '否'; + } +} + diff --git a/database/migrations/2026_01_12_171043_create_stock_companys_table.php b/database/migrations/2026_01_12_171043_create_stock_companys_table.php new file mode 100644 index 0000000..2906f5e --- /dev/null +++ b/database/migrations/2026_01_12_171043_create_stock_companys_table.php @@ -0,0 +1,42 @@ +id(); + $table->integer('admin_id')->nullable()->comment('管理员ID'); + $table->integer('department_id')->nullable()->comment('部门ID'); + // 公司名称 + $table->string('company_name')->nullable()->comment('公司名称'); + // 上市时间 + $table->date('stock_date')->nullable()->comment('上市时间'); + // 入学时间 + $table->date('enrollment_date')->nullable()->comment('入学时间'); + // 是否入学后上市 + $table->tinyInteger('is_after_enrollment')->default(0)->comment('是否入学后上市:0否1是'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('stock_companys'); + } +}; + diff --git a/database/migrations/2026_01_12_172322_add_attachments_to_email_records_table.php b/database/migrations/2026_01_12_172322_add_attachments_to_email_records_table.php new file mode 100644 index 0000000..ec68ae2 --- /dev/null +++ b/database/migrations/2026_01_12_172322_add_attachments_to_email_records_table.php @@ -0,0 +1,33 @@ +text('attachments')->nullable()->comment('附件ID,多个附件用英文逗号分隔'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('email_records', function (Blueprint $table) { + $table->dropColumn('attachments'); + }); + } +}; + diff --git a/routes/api.php b/routes/api.php index b989792..7abb577 100755 --- a/routes/api.php +++ b/routes/api.php @@ -242,6 +242,14 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () { Route::post('company/save', [\App\Http\Controllers\Admin\CompanyController::class, "save"]); Route::get('company/destroy', [\App\Http\Controllers\Admin\CompanyController::class, "destroy"]); + // 上市公司管理 + Route::get('stock-company/index', [\App\Http\Controllers\Admin\StockCompanyController::class, "index"]); + Route::get('stock-company/show', [\App\Http\Controllers\Admin\StockCompanyController::class, "show"]); + Route::post('stock-company/save', [\App\Http\Controllers\Admin\StockCompanyController::class, "save"]); + Route::get('stock-company/destroy', [\App\Http\Controllers\Admin\StockCompanyController::class, "destroy"]); + Route::get('stock-company/this-year-new', [\App\Http\Controllers\Admin\StockCompanyController::class, "thisYearNew"]); + Route::get('stock-company/after-enrollment', [\App\Http\Controllers\Admin\StockCompanyController::class, "afterEnrollment"]); + // 签到管理 Route::get('course-content-check/index', [\App\Http\Controllers\Admin\CourseContentCheckController::class, "index"]); Route::get('course-content-check/show', [\App\Http\Controllers\Admin\CourseContentCheckController::class, "show"]);