modelClass = new AppointmentRecordModel(); $this->assign('statusList', $this->modelClass->getStatusList()); $this->assign('submitSmsStatusList', $this->modelClass->getSubmitSmsStatusList()); $this->assign('batchSmsStatusList', $this->modelClass->getBatchSmsStatusList()); $this->assign('notifyChannelList', $this->modelClass->getNotifyChannelList()); $activityList = Db::name('cms_yuyue')->field('id,title')->order('id desc')->select()->toArray(); $this->assign('activityList', $activityList); } protected function getTranslatedActivity(int $activityId, string $lang): array { $lang = strtolower(trim($lang)); if ($lang === '') { $lang = 'cn'; } $query = Db::name('cms_yuyue_data')->where('did', $activityId); $row = (clone $query)->where('lang', $lang)->find(); if (!$row && $lang !== 'cn') { $row = (clone $query)->where('lang', 'cn')->find(); } return is_array($row) ? $row : []; } protected function loadActivityConfig(int $activityId, string $lang): array { $activity = Db::name('cms_yuyue')->where('id', $activityId)->find(); if (!is_array($activity) || empty($activity)) { return []; } $translatedActivity = $this->getTranslatedActivity($activityId, $lang); if (is_array($translatedActivity) && !empty($translatedActivity)) { $activity = array_merge($activity, $translatedActivity); } return $activity; } protected function sendAppointmentEmail(string $email, string $subject, string $content): array { try { $config = get_addon_config('phpmailer'); if (!$config) { return [false, 'NO_CONFIG', '未找到 phpmailer 插件配置']; } $mailer = new \addons\phpmailer\library\Mailer($config); $result = $mailer->to($email)->subject($subject)->message($content, true)->send(); if ($result) { return [true, 'SUCCESS', '邮件发送成功']; } return [false, 'FAIL', $mailer->getError() ?: '邮件发送失败']; } catch (\Throwable $e) { return [false, 'EXCEPTION', $e->getMessage()]; } } public function index() { if ($this->request->isAjax()) { $page = (int)$this->request->param('page/d', 1); $limit = (int)$this->request->param('limit/d', 20); $sort = (string)$this->request->param('sort/s', 'id'); $order = (string)$this->request->param('order/s', 'desc'); $activityId = (int)$this->request->param('activity_id/d', 0); $mobile = trim((string)$this->request->param('mobile/s', '')); $email = trim((string)$this->request->param('email/s', '')); $lang = trim((string)$this->request->param('lang/s', '')); $notifyChannel = trim((string)$this->request->param('notify_channel/s', '')); $status = $this->request->param('status/s', ''); $appointmentAutoSms = $this->request->param('appointment_auto_sms/s', ''); $submitSmsStatus = $this->request->param('submit_sms_status/s', ''); $batchSmsStatus = $this->request->param('batch_sms_status/s', ''); $query = $this->modelClass; if ($activityId > 0) { $query = $query->where('activity_id', $activityId); } if ($appointmentAutoSms !== '') { $activityIds = Db::name('cms_yuyue')->where('appointment_auto_sms', (int)$appointmentAutoSms)->column('id'); $activityIds = $activityIds ?: [0]; $query = $query->where('activity_id', 'in', $activityIds); } if ($mobile !== '') { $query = $query->where('mobile', 'like', '%' . $mobile . '%'); } if ($email !== '') { $query = $query->where('email', 'like', '%' . $email . '%'); } if ($lang !== '') { $query = $query->where('lang', $lang); } if ($notifyChannel !== '') { $query = $query->where('notify_channel', $notifyChannel); } if ($status !== '') { $query = $query->where('status', (int)$status); } if ($submitSmsStatus !== '') { $query = $query->where('submit_sms_status', (int)$submitSmsStatus); } if ($batchSmsStatus !== '') { $query = $query->where('batch_sms_status', (int)$batchSmsStatus); } $count = $query->count(); $data = $query->order($sort, $order)->page($page, $limit)->select(); $activityMap = Db::name('cms_yuyue')->column('appointment_auto_sms', 'id'); foreach ($data as $item) { $item->appointment_auto_sms = isset($activityMap[$item->activity_id]) ? (int)$activityMap[$item->activity_id] : 0; } return json(['code' => 0, 'count' => $count, 'data' => $data]); } return $this->fetch(); } public function sendSms() { if (!$this->request->isPost()) { $this->error('请求方式错误'); } $ids = $this->request->post('ids/a', []); if (!$ids) { $this->error('请先选择预约记录'); } if (!Event::hasListener('sms_notice')) { $this->error('未检测到短信通知插件监听 sms_notice'); } $rows = $this->modelClass->where('id', 'in', $ids)->where('notify_channel', 'sms')->select(); if ($rows->isEmpty()) { $this->error('未找到可发送的短信预约记录'); } $success = 0; $failed = 0; $skipped = 0; foreach ($rows as $row) { if ((int)$row->batch_sms_status === 1) { $skipped++; continue; } $now = time(); $activityConfig = $this->loadActivityConfig((int)$row->activity_id, (string)$row->lang); if (empty($activityConfig)) { AppointmentSmsLogModel::create([ 'record_id' => $row->id, 'activity_id' => $row->activity_id, 'mobile' => $row->mobile, 'sms_type' => 'appointment_batch', 'template_code' => '', 'content' => '', 'status' => 2, 'response_code' => 'NO_ACTIVITY', 'response_msg' => '活动配置不存在,无法发送统一短信', 'retry_count' => 0, 'send_time' => 0, 'createtime' => $now, 'updatetime' => $now, ]); $row->batch_sms_status = 2; $row->updatetime = $now; $row->save(); $failed++; continue; } $activityTitle = trim((string)($activityConfig['title'] ?? $row->activity_title ?? '')); $batchTpl = trim((string)($activityConfig['batch_sms_content'] ?? '')); $content = $batchTpl !== '' ? str_replace('{title}', $activityTitle, $batchTpl) : '您预约的【' . $activityTitle . '】已有最新动态,欢迎及时关注官方发布信息。'; $sendResult = SmsLib::notice($row->mobile, $content, null); AppointmentSmsLogModel::create([ 'record_id' => $row->id, 'activity_id' => $row->activity_id, 'mobile' => $row->mobile, 'sms_type' => 'appointment_batch', 'template_code' => '', 'content' => $content, 'status' => $sendResult ? 1 : 2, 'response_code' => $sendResult ? 'SUCCESS' : 'FAIL', 'response_msg' => $sendResult ? '后台批量短信发送成功' : '后台批量短信发送失败', 'retry_count' => 0, 'send_time' => $sendResult ? $now : 0, 'createtime' => $now, 'updatetime' => $now, ]); $row->activity_title = $activityTitle; $row->batch_sms_status = $sendResult ? 1 : 2; $row->batch_sms_time = $sendResult ? $now : $row->batch_sms_time; $row->updatetime = $now; $row->save(); $sendResult ? $success++ : $failed++; } $this->success('统一发送短信完成:成功 ' . $success . ' 条,失败 ' . $failed . ' 条,跳过 ' . $skipped . ' 条'); } public function sendEmail() { if (!$this->request->isPost()) { $this->error('请求方式错误'); } $ids = $this->request->post('ids/a', []); if (!$ids) { $this->error('请先选择预约记录'); } $rows = $this->modelClass->where('id', 'in', $ids)->where('notify_channel', 'email')->select(); if ($rows->isEmpty()) { $this->error('未找到可发送的邮件预约记录'); } $success = 0; $failed = 0; $skipped = 0; foreach ($rows as $row) { if ((int)$row->batch_sms_status === 1) { $skipped++; continue; } $now = time(); $sendResult = false; $responseCode = 'FAIL'; $responseMsg = ''; $subject = ''; $content = ''; try { if (!$row->email || !Validate::is($row->email, 'email')) { throw new \RuntimeException('邮箱为空或格式不正确'); } $activityConfig = $this->loadActivityConfig((int)$row->activity_id, (string)$row->lang); if (empty($activityConfig)) { throw new \RuntimeException('活动配置不存在,无法发送统一邮件'); } $activityTitle = trim((string)($activityConfig['title'] ?? $row->activity_title ?? '')); $batchTpl = trim((string)($activityConfig['batch_sms_content'] ?? '')); $content = $batchTpl !== '' ? str_replace('{title}', $activityTitle, $batchTpl) : 'You reserved [' . $activityTitle . ']. We have updates for you. Please stay tuned.'; $subject = $activityTitle . ' Update Notification'; [$sendResult, $responseCode, $responseMsg] = $this->sendAppointmentEmail($row->email, $subject, $content); $row->activity_title = $activityTitle; } catch (\Throwable $e) { $sendResult = false; $responseCode = 'EXCEPTION'; $responseMsg = $e->getMessage(); } AppointmentEmailLogModel::create([ 'record_id' => $row->id, 'activity_id' => $row->activity_id, 'email' => $row->email, 'mail_type' => 'appointment_batch', 'subject' => $subject, 'content' => $content, 'status' => $sendResult ? 1 : 2, 'response_code' => $responseCode, 'response_msg' => $responseMsg ?: ($sendResult ? '后台批量邮件发送成功' : '后台批量邮件发送失败'), 'retry_count' => 0, 'send_time' => $sendResult ? $now : 0, 'createtime' => $now, 'updatetime' => $now, ]); $row->batch_sms_status = $sendResult ? 1 : 2; $row->batch_sms_time = $sendResult ? $now : $row->batch_sms_time; $row->updatetime = $now; $row->save(); $sendResult ? $success++ : $failed++; } $this->success('统一发送邮件完成:成功 ' . $success . ' 条,失败 ' . $failed . ' 条,跳过 ' . $skipped . ' 条'); } public function resendSms() { if (!$this->request->isPost()) { $this->error('请求方式错误'); } $id = (int)$this->request->post('id/d', 0); if ($id <= 0) { $this->error('参数错误'); } $row = $this->modelClass->where('id', $id)->find(); if (!$row) { $this->error('预约记录不存在'); } $activity = $this->loadActivityConfig((int)$row->activity_id, (string)$row->lang); if (empty($activity)) { $this->error('活动配置不存在,无法补发通知'); } $activityTitle = trim((string)($activity['title'] ?? $row->activity_title ?? '')); $submitTpl = trim((string)($activity['submit_sms_content'] ?? '')); $now = time(); if ($row->notify_channel === 'email') { $content = $submitTpl !== '' ? str_replace('{title}', $activityTitle, $submitTpl) : 'You have successfully reserved [' . $activityTitle . ']. We will notify you by email when updates are available.'; $subject = $activityTitle . ' Reservation Confirmation'; [$sendResult, $responseCode, $responseMsg] = $this->sendAppointmentEmail($row->email, $subject, $content); AppointmentEmailLogModel::create([ 'record_id' => $row->id, 'activity_id' => $row->activity_id, 'email' => $row->email, 'mail_type' => 'appointment_resend', 'subject' => $subject, 'content' => $content, 'status' => $sendResult ? 1 : 2, 'response_code' => $responseCode, 'response_msg' => $responseMsg ?: ($sendResult ? '后台单条补发邮件成功' : '后台单条补发邮件失败'), 'retry_count' => 0, 'send_time' => $sendResult ? $now : 0, 'createtime' => $now, 'updatetime' => $now, ]); } else { if (!Event::hasListener('sms_notice')) { $this->error('未检测到短信通知插件监听 sms_notice'); } $content = $submitTpl !== '' ? str_replace('{title}', $activityTitle, $submitTpl) : '您已成功预约【' . $activityTitle . '】,新品动态发布后我们将第一时间短信通知您。'; $sendResult = SmsLib::notice($row->mobile, $content, null); AppointmentSmsLogModel::create([ 'record_id' => $row->id, 'activity_id' => $row->activity_id, 'mobile' => $row->mobile, 'sms_type' => 'appointment_resend', 'template_code' => '', 'content' => $content, 'status' => $sendResult ? 1 : 2, 'response_code' => $sendResult ? 'SUCCESS' : 'FAIL', 'response_msg' => $sendResult ? '后台单条补发短信成功' : '后台单条补发短信失败', 'retry_count' => 0, 'send_time' => $sendResult ? $now : 0, 'createtime' => $now, 'updatetime' => $now, ]); } $row->activity_title = $activityTitle; $row->submit_sms_status = $sendResult ? 1 : 2; $row->submit_sms_time = $sendResult ? $now : $row->submit_sms_time; $row->updatetime = $now; $row->save(); if ($sendResult) { $this->success($row->notify_channel === 'email' ? '补发邮件成功' : '补发短信成功'); } $this->error($row->notify_channel === 'email' ? '补发邮件失败' : '补发短信失败'); } public function del() { if (!$this->request->isPost()) { $this->error('请求方式错误'); } $ids = $this->request->post('ids/a', []); if (!$ids) { $this->error('请先选择要删除的预约记录'); } $rows = $this->modelClass->where('id', 'in', $ids)->select(); if ($rows->isEmpty()) { $this->error('未找到可删除的预约记录'); } Db::startTrans(); try { AppointmentSmsLogModel::where('record_id', 'in', $ids)->delete(); AppointmentEmailLogModel::where('record_id', 'in', $ids)->delete(); $count = $this->modelClass->where('id', 'in', $ids)->delete(); Db::commit(); } catch (\Throwable $e) { Db::rollback(); $this->error('删除失败:' . $e->getMessage()); } $this->success('已删除 ' . $count . ' 条预约记录'); } public function smslog() { $smsModel = new AppointmentSmsLogModel(); if ($this->request->isAjax()) { [$page, $limit, $where, $sort, $order] = $this->buildTableParames(); $mobile = trim((string)$this->request->param('mobile/s', '')); $smsType = trim((string)$this->request->param('sms_type/s', '')); $status = $this->request->param('status/s', ''); $query = $smsModel->where($where); if ($mobile !== '') { $query->where('mobile', 'like', '%' . $mobile . '%'); } if ($smsType !== '') { $query->where('sms_type', 'like', '%' . $smsType . '%'); } if ($status !== '') { $query->where('status', (int)$status); } $count = $query->order($sort, $order)->count(); $data = $query->order($sort, $order)->page($page, $limit)->select(); return json(['code' => 0, 'count' => $count, 'data' => $data]); } $this->assign('smsLogStatusList', $smsModel->getStatusList()); return $this->fetch('appointment/smslog'); } public function emaillog() { $emailModel = new AppointmentEmailLogModel(); if ($this->request->isAjax()) { [$page, $limit, $where, $sort, $order] = $this->buildTableParames(); $email = trim((string)$this->request->param('email/s', '')); $recordId = (int)$this->request->param('record_id/d', 0); $activityId = (int)$this->request->param('activity_id/d', 0); $mailType = trim((string)$this->request->param('mail_type/s', '')); $status = $this->request->param('status/s', ''); $sendTimeStart = trim((string)$this->request->param('send_time_start/s', '')); $sendTimeEnd = trim((string)$this->request->param('send_time_end/s', '')); $query = $emailModel->where($where); if ($email !== '') { $query->where('email', 'like', '%' . $email . '%'); } if ($recordId > 0) { $query->where('record_id', $recordId); } if ($activityId > 0) { $query->where('activity_id', $activityId); } if ($mailType !== '') { $query->where('mail_type', 'like', '%' . $mailType . '%'); } if ($status !== '') { $query->where('status', (int)$status); } if ($sendTimeStart !== '') { $query->where('send_time', '>=', strtotime($sendTimeStart)); } if ($sendTimeEnd !== '') { $query->where('send_time', '<=', strtotime($sendTimeEnd)); } $count = $query->order($sort, $order)->count(); $data = $query->order($sort, $order)->page($page, $limit)->select(); return json(['code' => 0, 'count' => $count, 'data' => $data]); } $this->assign('emailLogStatusList', $emailModel->getStatusList()); return $this->fetch('appointment/emaillog'); } public function delSmslog() { if (!$this->request->isPost()) { $this->error('请求方式错误'); } $ids = $this->request->post('ids/a', []); if (!$ids) { $this->error('请先选择要删除的短信日志'); } $rows = AppointmentSmsLogModel::where('id', 'in', $ids)->select(); if ($rows->isEmpty()) { $this->error('未找到可删除的短信日志'); } $count = AppointmentSmsLogModel::where('id', 'in', $ids)->delete(); $this->success('已删除 ' . $count . ' 条短信日志'); } public function delEmaillog() { if (!$this->request->isPost()) { $this->error('请求方式错误'); } $ids = $this->request->post('ids/a', []); if (!$ids) { $this->error('请先选择要删除的邮件日志'); } $rows = AppointmentEmailLogModel::where('id', 'in', $ids)->select(); if ($rows->isEmpty()) { $this->error('未找到可删除的邮件日志'); } $count = AppointmentEmailLogModel::where('id', 'in', $ids)->delete(); $this->success('已删除 ' . $count . ' 条邮件日志'); } }