<script lang="ts" setup>
|
import type { AuditEventDetail, AuditOperation } from './types';
|
|
import { computed, reactive, toRefs } from 'vue';
|
|
import { useMessage, usePermission } from '@jnpf/hooks';
|
import { BasicDrawer, useDrawerInner } from '@jnpf/ui/drawer';
|
import { formatToDateTime } from '@jnpf/utils';
|
|
import { Descriptions, DescriptionsItem, Empty, Result } from 'ant-design-vue';
|
|
import AuditDiffList from '#/components/FormExtraPanel/AuditDiffList.vue';
|
|
import { getAuditActionLabel } from './auditOperation';
|
import { auditOperationQuery } from './auditOperationQuery';
|
import { AUDIT_DETAIL_PERMISSION } from './types';
|
|
interface DiffLoadResult {
|
detail: AuditEventDetail | null;
|
error: unknown;
|
}
|
|
interface DrawerState {
|
details: AuditEventDetail[];
|
error: boolean;
|
operation: AuditOperation | null;
|
partialError: boolean;
|
}
|
|
const state = reactive<DrawerState>({
|
details: [],
|
error: false,
|
operation: null,
|
partialError: false,
|
});
|
const { error, operation, partialError } = toRefs(state);
|
const { createMessage } = useMessage();
|
const { hasBtnP } = usePermission();
|
const [registerDrawer, { changeLoading, closeDrawer }] = useDrawerInner(init);
|
let loadSequence = 0;
|
|
const drawerTitle = computed(() => {
|
if (!state.operation) return '字段变更详情';
|
return `字段变更 · ${state.operation.bizCode || getAuditActionLabel(state.operation)}`;
|
});
|
const displayedDiffs = computed(() => state.details[0]?.fieldDiffList ?? []);
|
const displayedParseError = computed(() => state.details[0]?.fieldDiffParseError ?? false);
|
|
async function init(data: { operation: AuditOperation }) {
|
state.operation = data.operation;
|
state.details = [];
|
state.error = false;
|
state.partialError = false;
|
if (!hasBtnP(AUDIT_DETAIL_PERMISSION, false)) {
|
closeDrawer();
|
createMessage.warning('无权查看字段变更详情');
|
return;
|
}
|
await loadDiffs();
|
}
|
|
async function loadDiffs() {
|
if (!state.operation) return;
|
const currentSequence = ++loadSequence;
|
const eventIds = [state.operation.id];
|
state.details = [];
|
state.error = false;
|
state.partialError = false;
|
changeLoading(true);
|
try {
|
const results = await Promise.all(
|
eventIds.map(async (eventId): Promise<DiffLoadResult> => {
|
try {
|
return { detail: await auditOperationQuery.detail(eventId), error: null };
|
} catch (requestError) {
|
return { detail: null, error: requestError };
|
}
|
}),
|
);
|
if (currentSequence !== loadSequence) return;
|
|
if (results.some((result) => result.error && isForbiddenError(result.error))) {
|
closeDrawer();
|
createMessage.warning('审计详情权限已失效');
|
return;
|
}
|
|
const failedCount = results.filter((result) => result.error).length;
|
state.details = results
|
.flatMap((result) => (result.detail ? [result.detail] : []))
|
.filter((detail) => detail.fieldDiffList.length > 0 || detail.fieldDiffParseError);
|
state.partialError = failedCount > 0 && state.details.length > 0;
|
state.error = failedCount > 0 && state.details.length === 0;
|
} finally {
|
if (currentSequence === loadSequence) changeLoading(false);
|
}
|
}
|
|
function displayTime(value?: string) {
|
return value ? formatToDateTime(value, 'YYYY-MM-DD HH:mm:ss') : '—';
|
}
|
|
function isForbiddenError(errorValue: unknown) {
|
const message = errorValue instanceof Error ? errorValue.message : '';
|
return /403|forbidden|无权|权限|拒绝/i.test(message);
|
}
|
</script>
|
|
<template>
|
<BasicDrawer
|
v-bind="$attrs"
|
@register="registerDrawer"
|
:title="drawerTitle"
|
width="min(820px, calc(100vw - 16px))"
|
class="audit-diff-drawer"
|
:keyboard="true"
|
destroy-on-close>
|
<div class="audit-diff-drawer-body">
|
<Result v-if="error" status="error" title="字段变更加载失败" sub-title="请稍后重试。">
|
<template #extra>
|
<a-button type="primary" @click="loadDiffs">重试</a-button>
|
</template>
|
</Result>
|
|
<template v-else-if="operation">
|
<a-alert
|
v-if="partialError"
|
class="audit-diff-warning"
|
type="warning"
|
show-icon
|
message="部分关联事件加载失败"
|
description="已展示成功加载的字段变更,可稍后重试查看完整内容。">
|
<template #action>
|
<a-button size="small" @click="loadDiffs">重试</a-button>
|
</template>
|
</a-alert>
|
|
<section class="audit-diff-overview">
|
<Descriptions bordered size="small" :column="{ xs: 1, sm: 1, md: 2 }">
|
<DescriptionsItem label="操作">{{ getAuditActionLabel(operation) }}</DescriptionsItem>
|
<DescriptionsItem label="操作时间">{{ displayTime(operation.eventTime) }}</DescriptionsItem>
|
<DescriptionsItem label="操作人">{{ operation.operatorName || '未知用户' }}</DescriptionsItem>
|
<DescriptionsItem label="业务单号">{{ operation.bizCode || '—' }}</DescriptionsItem>
|
</Descriptions>
|
</section>
|
|
<section class="audit-diff-content">
|
<h3>字段变更</h3>
|
<div class="audit-diff-summary">
|
本次操作共 <strong>{{ displayedDiffs.length }}</strong> 处字段变更
|
</div>
|
|
<Empty v-if="!displayedDiffs.length && !displayedParseError" description="字段明细为空或暂不可访问" :image="undefined" />
|
<AuditDiffList
|
v-else
|
:diffs="displayedDiffs"
|
hide-technical-field-names
|
layout="table"
|
mask-sensitive
|
:parse-error="displayedParseError"
|
:show-total="false" />
|
</section>
|
</template>
|
</div>
|
</BasicDrawer>
|
</template>
|
|
<style lang="scss" scoped>
|
.audit-diff-drawer-body {
|
min-width: 0;
|
padding: 0 20px 24px;
|
}
|
|
.audit-diff-warning {
|
margin-top: 16px;
|
}
|
|
.audit-diff-overview,
|
.audit-diff-content {
|
padding: 20px 0;
|
|
:deep(.ant-descriptions-item-content) {
|
min-width: 0;
|
overflow-wrap: anywhere;
|
}
|
}
|
|
.audit-diff-content {
|
border-top: 1px solid var(--border-color-base1);
|
|
h3 {
|
margin: 0 0 8px;
|
font-size: 15px;
|
font-weight: 600;
|
}
|
}
|
|
.audit-diff-summary {
|
margin-bottom: 12px;
|
color: var(--text-color-secondary);
|
|
strong {
|
color: var(--primary-color);
|
}
|
}
|
|
@media (max-width: 640px) {
|
.audit-diff-drawer-body {
|
padding: 0 12px 16px;
|
}
|
|
.audit-diff-overview,
|
.audit-diff-content {
|
padding: 16px 0;
|
}
|
}
|
</style>
|