'站内信', NoticeService::CHANNEL_EMAIL => '邮箱', ]; /** * @param App $app */ public function __construct(App $app) { parent::__construct($app); $this->model = new SystemNoticeTarget(); // 场景中文映射(mine 列表场景下拉/展示用) $sceneOptions = []; foreach (NoticeService::scenes() as $scene => $info) { $sceneOptions[$scene] = $info['title']; } $this->assign('select_list_notice_scene', $sceneOptions, true); } /** * 我的站内信列表. * @NodeAnotation(title="我的消息", auth=false) */ public function mine() { if ($this->request->isAjax()) { if (input('selectFields')) { return $this->selectList(); } list($page, $limit, $where) = $this->buildTableParames(); $adminId = (int) $this->getAdminId(); $count = $this->model ->withJoin('notice', 'LEFT') ->where('system_notice_target.admin_id', $adminId) ->where('system_notice_target.channel', NoticeService::CHANNEL_INMAIL) // 软删通知不进个人域(管理页删除后此处消失) ->where('system_notice.delete_time', 0) ->where($where) ->count(); $list = $this->model ->withJoin('notice', 'LEFT') ->where('system_notice_target.admin_id', $adminId) ->where('system_notice_target.channel', NoticeService::CHANNEL_INMAIL) ->where('system_notice.delete_time', 0) ->where($where) ->page($page, $limit) ->order($this->sort) ->select(); $data = [ 'code' => 0, 'msg' => '', 'count' => $count, 'data' => $list, ]; return json($data); } return $this->fetch(); } /** * 消息详情(进入即自动标记已读;先校验归属再标记,防越权). * @NodeAnotation(title="消息详情", auth=false) */ public function read($id) { $adminId = (int) $this->getAdminId(); $row = $this->model ->withJoin('notice', 'LEFT') ->where('system_notice_target.id', (int) $id) ->where('system_notice_target.admin_id', $adminId) // 软删通知不进个人域(管理页删除后详情不可打开) ->where('system_notice.delete_time', 0) ->find(); if (empty($row)) { $this->error('消息不存在或无权查看'); } if (empty($row->read_time)) { NoticeService::markRead([(int) $id], $adminId); $row->read_time = time(); } // 提取平铺变量供模板渲染(notice 关联模型直接取值,规避模板链式访问歧义) $notice = $row->notice; $sceneTitleMap = []; foreach (NoticeService::scenes() as $scene => $info) { $sceneTitleMap[$scene] = $info['title']; } $msg = [ 'id' => (int) $row->id, 'title' => (string) ($notice->title ?? '(通知已删除)'), 'scene_title' => $sceneTitleMap[(string) ($notice->scene ?? '')] ?? (string) ($notice->scene ?? '-'), 'content' => (string) ($notice->content ?? ''), 'read_time' => (int) $row->read_time, // create_time 是自动时间戳字段,模型属性读取会格式化为日期字符串, // 再 (int) 强转只会取到前导年份(详情页显示 1970),必须取原始数据 'create_time' => (int) $row->getData('create_time'), ]; $this->assign('msg', $msg); return $this->fetch(); } /** * 标记已读(批量). * @NodeAnotation(title="标记已读", auth=false) */ public function markRead() { $this->checkPostRequest(); $targetIds = $this->request->post('target_ids'); if (empty($targetIds)) { // 表格勾选批量提交(data-request+checkbox)的默认字段名是 id $targetIds = $this->request->post('id'); } $targetIds = is_array($targetIds) ? $targetIds : explode(',', (string) $targetIds); $targetIds = array_values(array_filter(array_map('intval', $targetIds))); $count = NoticeService::markRead($targetIds, (int) $this->getAdminId()); $this->success('已标记 ' . $count . ' 条消息为已读'); } /** * 全部站内信标记已读. * @NodeAnotation(title="全部标记已读", auth=false) */ public function markAllRead() { $this->checkPostRequest(); NoticeService::markAllRead((int) $this->getAdminId()); $this->success('已全部标记为已读'); } /** * 未读站内信数(顶栏铃铛轮询接口). * @NodeAnotation(title="未读消息数", auth=false) */ public function unreadCount() { $count = NoticeService::unreadCount((int) $this->getAdminId()); $this->success(null, ['count' => $count]); } /** * 订阅设置页(场景×渠道矩阵,纯渲染不写库). * * 无记录=默认订阅;locked 场景恒选中且置灰(必选)。 * @NodeAnotation(title="订阅设置", auth=false) */ public function subscribe() { $adminId = (int) $this->getAdminId(); $rows = Db::name('system_notice_subscribe') ->where('admin_id', $adminId) ->select(); $subscribeMap = []; foreach ($rows as $row) { $subscribeMap[$row['scene'] . '|' . $row['channel']] = (int) $row['status']; } $matrix = []; foreach (NoticeService::scenes() as $scene => $info) { $locked = !empty($info['locked']); $cells = []; foreach (array_keys(self::SUBSCRIBE_CHANNELS) as $channel) { $checked = $locked || ($subscribeMap[$scene . '|' . $channel] ?? 1) === 1; $cells[] = [ 'name' => 'matrix[' . $scene . '][' . $channel . ']', 'checked' => $checked ? 'checked' : '', 'disabled' => $locked ? 'disabled' : '', ]; } $matrix[] = [ 'scene' => $scene, 'title' => $info['title'], 'locked' => $locked, 'cells' => $cells, ]; } $this->assign('matrix', $matrix); $this->assign('channels', self::SUBSCRIBE_CHANNELS); return $this->fetch(); } /** * 保存订阅设置(POST matrix:逐场景×渠道 upsert). * * locked 场景直接忽略不入库(不可关闭语义,不报错); * 仅写当前会话管理员的订阅记录。 * @NodeAnotation(title="保存订阅设置", auth=false) */ public function saveSubscribe() { $this->checkPostRequest(); $adminId = (int) $this->getAdminId(); $matrix = (array) $this->request->post('matrix'); if ($matrix === []) { $this->error('没有需要保存的订阅设置'); } $scenes = NoticeService::scenes(); $now = time(); foreach ($matrix as $scene => $channels) { // 仅注册场景可保存;locked 场景忽略(不写任何记录) if (!isset($scenes[$scene]) || !empty($scenes[$scene]['locked'])) { continue; } foreach ((array) $channels as $channel => $value) { if (!isset(self::SUBSCRIBE_CHANNELS[$channel])) { continue; } $status = ((int) $value === 1) ? 1 : 0; $row = Db::name('system_notice_subscribe') ->where('admin_id', $adminId) ->where('scene', (string) $scene) ->where('channel', (string) $channel) ->find(); if ($row) { Db::name('system_notice_subscribe') ->where('id', $row['id']) ->update(['status' => $status, 'update_time' => $now]); } else { Db::name('system_notice_subscribe')->insert([ 'admin_id' => $adminId, 'scene' => (string) $scene, 'channel' => (string) $channel, 'status' => $status, 'create_time' => $now, 'update_time' => $now, ]); } } } $this->success('订阅设置已保存'); } }