diff --git a/app/admin/controller/Notice.php b/app/admin/controller/Notice.php new file mode 100644 index 0000000..f76186f --- /dev/null +++ b/app/admin/controller/Notice.php @@ -0,0 +1,14 @@ + '站内信', + 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($where) + ->count(); + $list = $this->model + ->withJoin('notice', 'LEFT') + ->where('system_notice_target.admin_id', $adminId) + ->where('system_notice_target.channel', NoticeService::CHANNEL_INMAIL) + ->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) + ->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) $row->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('订阅设置已保存'); + } +} diff --git a/extend/base/admin/view/notice/_common.js b/extend/base/admin/view/notice/_common.js new file mode 100644 index 0000000..c0e66ef --- /dev/null +++ b/extend/base/admin/view/notice/_common.js @@ -0,0 +1,11 @@ +var init = { + tableElem: '#currentTable', + tableRenderId: 'currentTableRenderId', + indexUrl: 'notice/mine', + addUrl: '', + editUrl: '', + readUrl: 'notice/read', + deleteUrl: '', + exportUrl: '', + modifyUrl: '', +}; diff --git a/extend/base/admin/view/notice/mine.html b/extend/base/admin/view/notice/mine.html new file mode 100644 index 0000000..9475165 --- /dev/null +++ b/extend/base/admin/view/notice/mine.html @@ -0,0 +1,7 @@ +
| 场景 | +{$msg.scene_title} | +
| 接收时间 | +{$msg.create_time|date="Y-m-d H:i:s"} | +
| 内容 | +{$msg.content} | +