From f9ba5173893a67c0710b3b8b53dc49c2e5544525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=98=8A=E5=A4=A9?= <442384644@qq.com> Date: Fri, 23 Oct 2026 14:09:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E9=80=9A=E7=9F=A5=E6=97=A5=E5=BF=97):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BB=9F=E8=AE=A1=E6=81=92=E4=B8=BA=200?= =?UTF-8?q?=E3=80=81=E7=8A=B6=E6=80=81=E7=AD=9B=E9=80=89=E4=B8=A2=E5=A4=B1?= =?UTF-8?q?=E5=B9=B6=E8=A1=A5=E5=85=A8=E5=88=97=E8=A1=A8=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端 - NoticeLogDao::getStatistics() 原实现复用同一个查询构造器,where 条件累积, 导致 failed 恒为 0、success 含全部状态;改为每次基于独立条件 count。 - 抽出 buildWhere() 统一组装条件,列表与统计共用;统计不叠加状态条件。 - status=0(待发送)是合法筛选值,原 empty() 判断会丢弃,改为 isset + 非空串判断。 - page/limit 做整数与上限(100)校验,避免非法分页参数。 - 列表接口返回 { list, count },供前端分页使用。 前端 - 补充分页组件,搜索/重置/清理后回到第一页并同时刷新列表与统计。 - 兼容列表接口旧格式(数组)与新格式({ list, count })。 --- .../notification/NoticeLogController.php | 10 ++-- backend/app/dao/message/NoticeLogDao.php | 24 ++++++--- .../message/admin/NoticeLogServices.php | 50 +++++++++++++------ .../pages/setting/notification/log/index.vue | 49 +++++++++++++++--- 4 files changed, 100 insertions(+), 33 deletions(-) diff --git a/backend/app/adminapi/controller/setting/notification/NoticeLogController.php b/backend/app/adminapi/controller/setting/notification/NoticeLogController.php index 9fe5b4a0d..3c24d3852 100644 --- a/backend/app/adminapi/controller/setting/notification/NoticeLogController.php +++ b/backend/app/adminapi/controller/setting/notification/NoticeLogController.php @@ -35,10 +35,11 @@ class NoticeLogController extends AuthController ['start_time', ''], ['end_time', ''], ]); - $page = $this->request->get('page', 1); - $limit = $this->request->get('limit', 20); + $page = max(1, (int)$this->request->get('page', 1)); + $limit = (int)$this->request->get('limit', 20); + $limit = $limit > 0 && $limit <= 100 ? $limit : 20; - return app('json')->success($this->services->getList($filter, (int)$page, (int)$limit)); + return app('json')->success($this->services->getList($filter, $page, $limit)); } public function detail(int $id) @@ -55,7 +56,10 @@ class NoticeLogController extends AuthController public function statistics() { + // 与列表保持同一套筛选条件(状态筛选不参与统计) $filter = $this->request->getMore([ + ['mark', ''], + ['uid', ''], ['channel_key', ''], ['start_time', ''], ['end_time', ''], diff --git a/backend/app/dao/message/NoticeLogDao.php b/backend/app/dao/message/NoticeLogDao.php index 5cd31ef69..3553eedce 100644 --- a/backend/app/dao/message/NoticeLogDao.php +++ b/backend/app/dao/message/NoticeLogDao.php @@ -32,16 +32,26 @@ class NoticeLogDao extends BaseDao ->toArray(); } + /** + * 统计符合条件的记录数 + * @param array $where + * @return int + */ + public function getCount(array $where): int + { + return (int)$this->getModel()->where($where)->count(); + } + public function getStatistics(array $where): array { - $query = $this->getModel()->where($where); - $total = $query->count(); - $success = $query->where('status', NoticeLog::STATUS_SUCCESS)->count(); - $failed = $query->where('status', NoticeLog::STATUS_FAILED)->count(); + // 每次 count 都基于独立的查询构造器,避免 where 条件累积(原实现复用 $query,导致 failed 恒为 0) + $total = $this->getCount($where); + $success = $this->getCount(array_merge($where, ['status' => NoticeLog::STATUS_SUCCESS])); + $failed = $this->getCount(array_merge($where, ['status' => NoticeLog::STATUS_FAILED])); return [ - 'total' => (int) $total, - 'success' => (int) $success, - 'failed' => (int) $failed, + 'total' => $total, + 'success' => $success, + 'failed' => $failed, 'success_rate' => $total > 0 ? round($success / $total * 100, 2) : 0, ]; } diff --git a/backend/app/services/message/admin/NoticeLogServices.php b/backend/app/services/message/admin/NoticeLogServices.php index d458f2a4c..090e688b9 100644 --- a/backend/app/services/message/admin/NoticeLogServices.php +++ b/backend/app/services/message/admin/NoticeLogServices.php @@ -24,26 +24,48 @@ class NoticeLogServices extends BaseService $this->dao = $dao; } - public function getList(array $filter = [], int $page = 1, int $limit = 20): array + /** + * 组装查询条件 + * @param array $filter 筛选条件 + * @param bool $withStatus 是否附加状态条件(统计口径不按状态过滤,否则成功/失败恒为 0) + * @return array + */ + protected function buildWhere(array $filter, bool $withStatus = true): array { $where = []; if (!empty($filter['mark'])) { - $where['mark'] = $filter['mark']; + $where['mark'] = trim((string)$filter['mark']); } if (!empty($filter['uid'])) { - $where['uid'] = $filter['uid']; + $where['uid'] = (int)$filter['uid']; } if (!empty($filter['channel_key'])) { $where['channel_key'] = $filter['channel_key']; } - if (!empty($filter['status'])) { - $where['status'] = $filter['status']; + // 状态 0(待发送)是合法筛选值,不能用 empty 判断,否则会被丢弃 + if ($withStatus && isset($filter['status']) && $filter['status'] !== '' && $filter['status'] !== null) { + $where['status'] = (int)$filter['status']; } if (!empty($filter['start_time']) && !empty($filter['end_time'])) { - $where['send_time'] = [$filter['start_time'], $filter['end_time']]; + $where['send_time'] = [(int)$filter['start_time'], (int)$filter['end_time']]; } + return $where; + } - return $this->dao->getList($where, '*', $page, $limit); + /** + * 发送记录列表 + * @param array $filter + * @param int $page + * @param int $limit + * @return array{list: array, count: int} + */ + public function getList(array $filter = [], int $page = 1, int $limit = 20): array + { + $where = $this->buildWhere($filter); + return [ + 'list' => $this->dao->getList($where, '*', $page, $limit), + 'count' => $this->dao->getCount($where), + ]; } public function getDetail(int $id): array @@ -52,16 +74,14 @@ class NoticeLogServices extends BaseService return $info ? $info->toArray() : []; } + /** + * 发送统计(与列表使用同一套筛选条件,状态筛选除外) + * @param array $filter + * @return array + */ public function getStatistics(array $filter = []): array { - $where = []; - if (!empty($filter['channel_key'])) { - $where['channel_key'] = $filter['channel_key']; - } - if (!empty($filter['start_time']) && !empty($filter['end_time'])) { - $where['send_time'] = [$filter['start_time'], $filter['end_time']]; - } - return $this->dao->getStatistics($where); + return $this->dao->getStatistics($this->buildWhere($filter, false)); } public function createLog(string $mark, int $uid, string $channelKey, string $channelName, string $title = '', string $content = ''): int diff --git a/frontend/admin/src/pages/setting/notification/log/index.vue b/frontend/admin/src/pages/setting/notification/log/index.vue index 9d7d87f95..63edc4b04 100644 --- a/frontend/admin/src/pages/setting/notification/log/index.vue +++ b/frontend/admin/src/pages/setting/notification/log/index.vue @@ -33,7 +33,7 @@ - 搜索 + 搜索 重置 @@ -137,6 +137,16 @@ +
+ +
+ 清理历史日志 @@ -231,6 +241,12 @@ const loading = ref(false); const mediaStore = useMediaStore(); const labelWidth = computed(() => (mediaStore.isMobile ? undefined : '80px')); const labelPosition = computed(() => (mediaStore.isMobile ? 'top' : 'right')); +// 分页状态:后端列表接口返回 { list, count } +const list = reactive({ + page: 1, + limit: 15, + total: 0, +}); const filter = ref({ channel_key: '', status: '', @@ -251,9 +267,17 @@ const cleanForm = reactive({ function getList() { loading.value = true; - noticeLogList(filter.value) + noticeLogList({ ...filter.value, page: list.page, limit: list.limit }) .then((res) => { - logList.value = res.data; + const data = res.data; + // 兼容旧格式(直接返回数组)与新格式({ list, count }) + if (Array.isArray(data)) { + logList.value = data; + list.total = data.length; + } else { + logList.value = data?.list || []; + list.total = Number(data?.count || 0); + } loading.value = false; }) .catch(() => { @@ -261,9 +285,17 @@ function getList() { }); } function getStatistics() { - noticeLogStatistics(filter.value).then((res) => { - statistics.value = res.data; - }); + noticeLogStatistics(filter.value) + .then((res) => { + statistics.value = res.data; + }) + .catch(() => {}); +} +// 搜索:回到第一页并同时刷新列表与统计,避免筛选后仍停留在旧页码 +function searchList() { + list.page = 1; + getList(); + getStatistics(); } function resetFilter() { filter.value = { @@ -271,6 +303,7 @@ function resetFilter() { status: '', mark: '', }; + list.page = 1; getList(); getStatistics(); } @@ -305,8 +338,8 @@ function doClean() { .then((res) => { ElMessage.success(res.msg); cleanVisible.value = false; - getList(); - getStatistics(); + // 清理后数据量变化,回到第一页重新加载 + searchList(); }) .catch((err) => { ElMessage.error(err.msg);