build / build (push) Successful in 1m58s
- 操作列: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 迁移
213 lines
7.2 KiB
Vue
213 lines
7.2 KiB
Vue
<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>
|