Files
UserbaseFront/src/views/push/PushLog.vue
T
chenxing 3b0f29b0d1
build / build (push) Successful in 1m58s
feat(接入规范验收修订轮): 操作列收口text形态+通道撑满高度+主题色批量落地
- 操作列:group-user/操作日志/开放接口日志/推送日志四页手写 h('a') 迁 actionColumn({mode:"text"}),颜色归通道 var(--brand);推送日志「重试」改 disabledWhen 置灰保位
- group-user 状态列 h('Tag')/h('Badge') 字符串组件名改 import 真组件(Vue3 坑)
- 高度:日志三页根节点改 page-wrapper yh-fill;TablePageAlovaNew 对齐 flow-center 同构版(.table-region + useElementHeight 实测 + 页长记忆);RoleAuthzList 转 tree-wrapper 骨架(外边界/间隙 16)
- 主题色:当前学校/当前组/当前角色、应用管理左类型面板、角色管理卡片操作位硬编码蓝改 var(--brand) 系令牌
- 顶栏房子图标钮删除(§4:首页入口收敛面包屑「首页」)
- 含 2026-10-10 试点未提交件:tableActions.ts 七函数、main.scss .op-cell、AppList/ModuleManage/RoleUserManage 迁移
2026-10-10 23:39:32 +08:00

213 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-wrapper yh-fill">
<div class="main-container">
<div class="search-wrap fix">
<Select v-model="dataParams.appKey" class="ml-12" clearable filterable placeholder="应用"
style="width: 180px" @on-change="handleSearch">
<Option v-for="opt in appOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
</Select>
<Select v-model="dataParams.interfacePath" class="ml-12" clearable placeholder="接口" style="width: 160px"
@on-change="handleSearch">
<Option v-for="opt in interfaceOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
</Select>
<Select v-model="dataParams.success" class="ml-12" clearable placeholder="调用结果" style="width: 120px"
@on-change="handleSearch">
<Option :value="1">成功</Option>
<Option :value="0">失败</Option>
</Select>
<DatePicker v-model="startDate" type="date" placeholder="开始时间" format="yyyy-MM-dd" :editable="false"
:clearable="true" class="ml-12" style="width: 140px" @on-change="v => onStartChange(v)" />
<DatePicker v-model="endDate" type="date" placeholder="结束时间" format="yyyy-MM-dd" :editable="false"
:clearable="true" class="ml-12" style="width: 140px" @on-change="v => onEndChange(v)" />
<Button type="primary" class="ml-12" @click="handleSearch">查询</Button>
</div>
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" />
</div>
<PushLogDetailModal v-model="detailVisible" :row="currentRow" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Message } from 'view-ui-plus';
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
import Utils from '@/utils';
import PushLogDetailModal from './PushLogDetailModal.vue';
import { actionColumn } from '@/utils/tableActions';
defineOptions({ name: 'PushLog' });
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
const interfaceOptions = [
{ value: 'group/SyncGroup', label: '组全量同步' },
{ value: 'user/SyncUser', label: '用户全量同步' },
{ value: 'group/AddGroup', label: '添加组' },
{ value: 'group/UpdateGroup', label: '更新组' },
{ value: 'group/DeleteGroup', label: '删除组' },
{ value: 'user/AddUser', label: '添加用户' },
{ value: 'user/UpdateUser', label: '更新用户' },
{ value: 'user/DeleteUser', label: '删除用户' },
];
const appOptions = ref<{ value: number; label: string }[]>([]);
interface DataParams {
appKey?: number;
interfacePath?: string;
success?: number;
startTime?: string;
endTime?: string;
}
const dataParams = ref<DataParams>({
appKey: undefined,
interfacePath: undefined,
success: undefined,
startTime: undefined,
endTime: undefined,
});
const startDate = ref<Date | null>(null);
const endDate = ref<Date | null>(null);
function onStartChange(val: string) {
dataParams.value.startTime = val ? `${val} 00:00:00` : undefined;
handleSearch();
}
function onEndChange(val: string) {
dataParams.value.endTime = val ? `${val} 23:59:59` : undefined;
handleSearch();
}
function handleSearch() {
tableRef.value?.refreshData();
}
function clean(obj: Record<string, any>) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null && v !== ''));
}
const tableApi = (params: { pageIndex: number; pageSize: number }) => {
return (Apis as any).PushSubscription.recordList({
params: clean({
appKey: dataParams.value.appKey,
interfacePath: dataParams.value.interfacePath,
success: dataParams.value.success,
startTime: dataParams.value.startTime,
endTime: dataParams.value.endTime,
pageIndex: params.pageIndex,
pageSize: params.pageSize,
}),
cacheFor: null,
});
};
const columns = [
{ type: 'index', title: '序号', width: 70, align: 'center' },
{ title: '应用', key: 'appName', ellipsis: true, tooltip: true },
{
title: '接口',
key: 'interfaceLabel',
width: 140,
ellipsis: true,
tooltip: true,
render: (h: any, { row }: any) =>
h('span', { attrs: { title: row.interfacePath } }, row.interfaceLabel || row.interfacePath || '-'),
},
{
title: '调用结果',
key: 'success',
width: 120,
align: 'center',
render: (h: any, { row }: any) =>
h('span', { class: row.success === 1 ? 'text-success' : 'text-error' }, row.success === 1 ? '成功' : '失败'),
},
{
title: '日志时间',
key: 'createdAt',
width: 200,
render: (h: any, { row }: any) =>
h('span', row.createdAt ? Utils.dateFormat(row.createdAt, 'yyyy-MM-dd HH:mm:ss') : '-'),
},
{
title: '重试',
key: 'attemptNo',
width: 80,
align: 'center',
render: (h: any, { row }: any) => h('span', `第${row.attemptNo || 1}次`),
},
{
title: '耗时(ms)',
key: 'costMs',
width: 100,
align: 'right',
render: (h: any, { row }: any) => h('span', row.costMs != null ? `${row.costMs}` : '-'),
},
{
title: '请求体',
key: 'payloadSize',
width: 100,
align: 'right',
render: (h: any, { row }: any) => h('span', row.payloadSize != null && row.payloadSize > 0 ? Utils.getFileSize(row.payloadSize) : '-'),
},
// 重试按行数据(成功与否)变化 → disabledWhen 置灰保位(§5.2 按钮位置稳定性红线),禁止 when 隐藏
actionColumn([
{ key: 'detail', label: '查看详情', on: (r: any) => openDetail(r) },
{ key: 'retry', label: '重试', on: (r: any) => handleRetry(r), danger: true, disabledWhen: (r: any) => r.success === 1 },
], { mode: 'text', width: 150 }),
];
const detailVisible = ref(false);
const currentRow = ref<Record<string, any> | null>(null);
function openDetail(row: any) {
currentRow.value = row;
detailVisible.value = true;
}
async function handleRetry(row: any) {
try {
await (Apis as any).PushSubscription.retry({ pathParams: { id: row.id }, cacheFor: null });
Message.success('已重新投递,稍后在列表中查看结果');
tableRef.value?.refreshData();
} catch (e: any) {
console.warn(e?.message || '重试失败');
}
}
async function loadApps() {
try {
const res = await (Apis as any).AppSettings.appList({
params: { pageIndex: 1, pageSize: 9999 },
cacheFor: null,
});
const list = (res && res.content) || [];
appOptions.value = list.map((a: any) => ({ value: a.appKey, label: `${a.name}(${a.appKey})` }));
} catch (e: any) {
appOptions.value = [];
}
}
loadApps();
</script>
<style lang="scss" scoped>
.search-wrap {
.ivu-select,
.ivu-input-wrapper,
.ivu-date-picker {
vertical-align: middle;
}
}
</style>