diff --git a/app/admin/controller/operation/UserNoticeRead.php b/app/admin/controller/operation/UserNoticeRead.php index 593e57e..1d788c4 100644 --- a/app/admin/controller/operation/UserNoticeRead.php +++ b/app/admin/controller/operation/UserNoticeRead.php @@ -7,7 +7,7 @@ use support\Response; use Webman\Http\Request as WebmanRequest; /** - * 用户公告阅读记录 + * 用户公告阅读记录(只读,由客户端确认已读写入) */ class UserNoticeRead extends Backend { @@ -33,6 +33,42 @@ class UserNoticeRead extends Backend return null; } + public function add(WebmanRequest $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + return $this->error(__('User notice read records are client-generated; manual creation is not allowed')); + } + + public function edit(WebmanRequest $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + return $this->error(__('User notice read record cannot be edited')); + } + + public function del(WebmanRequest $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + return $this->error(__('User notice read record cannot be deleted')); + } + + public function sortable(WebmanRequest $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + return $this->error(__('Sorting is not supported')); + } + protected function _index(): Response { if ($this->request && $this->request->get('select')) { diff --git a/app/api/controller/Notice.php b/app/api/controller/Notice.php index 3729696..bb31b29 100644 --- a/app/api/controller/Notice.php +++ b/app/api/controller/Notice.php @@ -25,23 +25,28 @@ class Notice extends MobileBase 'list_rows' => $pageSize, ]); - $noticeIds = []; + $popoutNoticeIds = []; foreach ($paginate->items() as $row) { - $noticeIds[] = $row->id; + if ($this->intValue($row->notice_type, 0) === 1) { + $popoutNoticeIds[] = $row->id; + } } - $readRows = []; - if ($noticeIds !== []) { - $readRows = UserNoticeRead::where('user_id', $this->auth->id)->whereIn('notice_id', $noticeIds)->column('notice_id'); + $readMap = []; + if ($popoutNoticeIds !== []) { + $readRows = UserNoticeRead::where('user_id', $this->auth->id)->whereIn('notice_id', $popoutNoticeIds)->column('notice_id'); + $readMap = array_flip($readRows); } - $readMap = array_flip($readRows); $list = []; foreach ($paginate->items() as $row) { + $isPopout = $this->intValue($row->notice_type, 0) === 1; $list[] = [ 'notice_id' => $row->id, 'title' => $row->title, - 'notice_type' => $this->intValue($row->notice_type, 0) === 1 ? 'popout' : 'silent', - 'is_read' => isset($readMap[$row->id]), + 'content' => $row->content, + 'notice_type' => $isPopout ? 'popout' : 'silent', + 'must_confirm' => $isPopout, + 'is_read' => $isPopout && isset($readMap[$row->id]), 'publish_time' => $row->publish_at, ]; } @@ -49,30 +54,6 @@ class Notice extends MobileBase return $this->mobileSuccess(['list' => $list]); } - public function noticeDetail(Request $request): Response - { - $response = $this->initializeMobile($request); - if ($response !== null) { - return $response; - } - $id = $this->intValue($request->input('notice_id', 0), 0); - if ($id < 1) { - return $this->mobileError(1001, 'Missing parameters'); - } - $notice = OperationNotice::where('id', $id)->where('status', 1)->find(); - if (!$notice) { - return $this->mobileError(2004, 'Notice does not exist'); - } - return $this->mobileSuccess([ - 'notice_id' => $notice->id, - 'title' => $notice->title, - 'content' => $notice->content, - 'notice_type' => $this->intValue($notice->notice_type, 0) === 1 ? 'popout' : 'silent', - 'must_confirm' => $this->intValue($notice->notice_type, 0) === 1, - 'publish_time' => $notice->publish_at, - ]); - } - public function noticeConfirm(Request $request): Response { $response = $this->initializeMobile($request); @@ -87,14 +68,18 @@ class Notice extends MobileBase if (!$notice) { return $this->mobileError(2004, 'Notice does not exist'); } + if ($this->intValue($notice->notice_type, 0) !== 1) { + return $this->mobileError(2004, 'Notice does not require confirmation'); + } - $exists = UserNoticeRead::where('user_id', $this->auth->id)->where('notice_id', $noticeId)->find(); + $readRow = UserNoticeRead::where('user_id', $this->auth->id)->where('notice_id', $noticeId)->find(); $now = time(); - if ($exists) { - $exists->save([ - 'confirmed' => 1, - 'read_at' => $now, - ]); + if ($readRow) { + $readRow->read_at = $now; + if ($this->intValue($readRow->confirmed, 0) !== 1) { + $readRow->confirmed = 1; + } + $readRow->save(); } else { UserNoticeRead::create([ 'user_id' => $this->auth->id, diff --git a/app/api/lang/en.php b/app/api/lang/en.php index df582d0..de02219 100644 --- a/app/api/lang/en.php +++ b/app/api/lang/en.php @@ -43,6 +43,7 @@ return [ 'Current process does not allow this operation' => 'Current process does not allow this operation', 'Order does not exist' => 'Order does not exist', 'Notice does not exist' => 'Notice does not exist', + 'Notice does not require confirmation' => 'This notice does not require confirmation', // Deposit / Withdraw 'Idempotency key is too long' => 'Idempotency key is too long', 'Idempotency key conflict' => 'Idempotency key conflict, please do not submit repeatedly', diff --git a/app/api/lang/zh-cn.php b/app/api/lang/zh-cn.php index 9718829..8ecb8b2 100644 --- a/app/api/lang/zh-cn.php +++ b/app/api/lang/zh-cn.php @@ -75,6 +75,7 @@ return [ 'Current process does not allow this operation' => '当前流程不允许该操作', 'Order does not exist' => '订单不存在', 'Notice does not exist' => '公告不存在', + 'Notice does not require confirmation' => '该公告无需确认已读', // 充值 / 提现 'Idempotency key is too long' => '幂等键过长', 'Idempotency key conflict' => '幂等键冲突(请勿重复提交)', diff --git a/app/common/lang/en/service.php b/app/common/lang/en/service.php index eb1b262..67ba33c 100644 --- a/app/common/lang/en/service.php +++ b/app/common/lang/en/service.php @@ -38,6 +38,10 @@ return [ 'Wallet record cannot be edited' => 'Wallet record cannot be edited', 'Wallet record cannot be deleted' => 'Wallet record cannot be deleted', + 'User notice read records are client-generated; manual creation is not allowed' => 'User notice read records are client-generated; manual creation is not allowed', + 'User notice read record cannot be edited' => 'User notice read record cannot be edited', + 'User notice read record cannot be deleted' => 'User notice read record cannot be deleted', + 'Admin wallet does not allow manual creation' => 'Admin wallet does not allow manual creation', 'Admin wallet does not allow manual editing' => 'Admin wallet does not allow manual editing', 'Admin wallet does not allow deletion' => 'Admin wallet does not allow deletion', diff --git a/app/common/lang/zh-cn/service.php b/app/common/lang/zh-cn/service.php index 393c605..78f1048 100644 --- a/app/common/lang/zh-cn/service.php +++ b/app/common/lang/zh-cn/service.php @@ -38,6 +38,10 @@ return [ 'Wallet record cannot be edited' => '钱包流水不可编辑', 'Wallet record cannot be deleted' => '钱包流水不可删除', + 'User notice read records are client-generated; manual creation is not allowed' => '用户阅读记录由客户端确认已读写入,禁止后台手工新增', + 'User notice read record cannot be edited' => '用户阅读记录不可编辑', + 'User notice read record cannot be deleted' => '用户阅读记录不可删除', + 'Admin wallet does not allow manual creation' => '管理员钱包不允许手动新增', 'Admin wallet does not allow manual editing' => '管理员钱包不允许手动编辑', 'Admin wallet does not allow deletion' => '管理员钱包不允许删除', diff --git a/config/route.php b/config/route.php index 5059713..b880d4a 100644 --- a/config/route.php +++ b/config/route.php @@ -153,7 +153,6 @@ Route::add(['GET', 'POST'], '/api/finance/withdrawDetail', [\app\api\controller\ Route::add(['GET', 'POST'], '/api/finance/withdrawList', [\app\api\controller\Finance::class, 'withdrawList']); Route::get('/api/notice/noticeList', [\app\api\controller\Notice::class, 'noticeList']); -Route::get('/api/notice/noticeDetail', [\app\api\controller\Notice::class, 'noticeDetail']); Route::get('/api/notice/noticeConfirm', [\app\api\controller\Notice::class, 'noticeConfirm']); // ==================== Admin 路由 ==================== diff --git a/docs/36字花-移动端接口设计草案.md b/docs/36字花-移动端接口设计草案.md index b307f84..9267635 100644 --- a/docs/36字花-移动端接口设计草案.md +++ b/docs/36字花-移动端接口设计草案.md @@ -13,7 +13,7 @@ - **请求方法**:所有移动端业务接口(`/api/*`,不含 `/api/v1/authToken`)一律使用 `POST` 调用;查询类接口同时兼容 `GET`(便于浏览器/调试工具直接访问),客户端统一走 `POST` - `POST` 时请求头 `Content-Type: application/json`,参数放在 JSON body - `GET` 兼容模式下,参数走 URL query string - - **例外**:公告模块 `/api/notice/noticeList`、`/api/notice/noticeDetail`、`/api/notice/noticeConfirm` **仅支持 `GET`**,参数一律走 URL query string + - **例外**:公告模块 `/api/notice/noticeList`、`/api/notice/noticeConfirm` **仅支持 `GET`**,参数一律走 URL query string - 鉴权类接口 `/api/v1/authToken` 仍为 `GET` - 时间:UTC 时间戳(秒) + 服务端时区配置 - 金额:数字传输(如 `"100.00"`),客户端展示统一保留两位小数(存储仍为 `decimal(18,2)`) @@ -723,30 +723,25 @@ - `list`:array - `notice_id`:int(含义:公告 ID) - `title`:string(含义:公告标题) + - `content`:string(含义:公告正文,原详情接口字段) - `notice_type`:string(`silent`/`popout`,含义:公告类型) - - `is_read`:bool(含义:当前用户是否已读) + - `must_confirm`:bool(含义:是否必须手动确认;强弹窗为 `true`) + - `is_read`:bool(含义:当前用户是否已确认;**仅 `popout` 强弹窗有效**,`silent` 恒为 `false`,不写入阅读记录) - `publish_time`:int(含义:发布时间) -### 6.2 公告详情 -- **GET** `/api/notice/noticeDetail` +> **阅读记录口径**:`user_notice_read` 仅用于强弹窗(`notice_type=popout`)的确认已读;静默信箱(`silent`)不生成、不查询阅读记录。 -请求参数(query string): -- `id`:int,必填(含义:公告 ID) - -返回参数: -- `notice_id`:int(含义:公告 ID) -- `title`:string(含义:公告标题) -- `content`:string(含义:公告正文) -- `notice_type`:string(含义:公告类型) -- `must_confirm`:bool(含义:是否必须手动确认) -- `publish_time`:int(含义:发布时间) - -### 6.3 强弹窗确认已读 +### 6.2 强弹窗确认已读 - **GET** `/api/notice/noticeConfirm` 请求参数(query string): - `notice_id`:int(含义:待确认公告 ID) +行为说明: +- **仅强弹窗**(`notice_type=popout`)可调用;静默公告调用返回业务错误 +- 若当前用户对该公告已有阅读记录:仅更新 `read_at`;若尚未确认则同时将 `confirmed` 置为 `1` +- 若无阅读记录:创建一条已确认记录 + 返回参数: - `notice_id`:int(含义:已确认公告 ID) - `confirmed`:bool(含义:确认结果) @@ -880,8 +875,8 @@ ## 8.3 公告强触达流程 1. 客户端监听 `notice.popout` -2. 拉取详情 `GET /api/notice/noticeDetail` -3. 用户勾选确认 `GET /api/notice/noticeConfirm?notice_id=...` +2. 拉取列表 `GET /api/notice/noticeList`(列表项已含 `content`、`must_confirm`) +3. 用户勾选确认 `GET /api/notice/noticeConfirm?notice_id=...`(若已有阅读记录则仅更新 `read_at`) 4. 未确认前可由前端阻断下注入口 --- diff --git a/web/src/views/backend/operation/userNoticeRead/index.vue b/web/src/views/backend/operation/userNoticeRead/index.vue index 2cb37be..6a2cb11 100644 --- a/web/src/views/backend/operation/userNoticeRead/index.vue +++ b/web/src/views/backend/operation/userNoticeRead/index.vue @@ -3,21 +3,18 @@
- - -