fix(通知日志): 修复统计恒为 0、状态筛选丢失并补全列表分页

后端
- NoticeLogDao::getStatistics() 原实现复用同一个查询构造器,where 条件累积,
  导致 failed 恒为 0、success 含全部状态;改为每次基于独立条件 count。
- 抽出 buildWhere() 统一组装条件,列表与统计共用;统计不叠加状态条件。
- status=0(待发送)是合法筛选值,原 empty() 判断会丢弃,改为 isset + 非空串判断。
- page/limit 做整数与上限(100)校验,避免非法分页参数。
- 列表接口返回 { list, count },供前端分页使用。

前端
- 补充分页组件,搜索/重置/清理后回到第一页并同时刷新列表与统计。
- 兼容列表接口旧格式(数组)与新格式({ list, count })。
This commit is contained in:
吴昊天 2026-10-23 14:09:35 +08:00
parent 05d46ed2f1
commit f9ba517389
4 changed files with 100 additions and 33 deletions

View File

@ -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', ''],

View File

@ -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,
];
}

View File

@ -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

View File

@ -33,7 +33,7 @@
<el-input v-model="filter.mark" placeholder="请输入消息标识" clearable class="form_content_width" />
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="getList">搜索</el-button>
<el-button type="primary" v-db-click @click="searchList">搜索</el-button>
<el-button v-db-click @click="resetFilter">重置</el-button>
</el-form-item>
</el-form>
@ -137,6 +137,16 @@
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="list.total"
:total="list.total"
v-model:page="list.page"
v-model:limit="list.limit"
@pagination="getList"
/>
</div>
<el-row class="mt14" style="text-align: right">
<el-button type="danger" v-db-click @click="clean">清理历史日志</el-button>
</el-row>
@ -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);