<script lang="ts" setup>
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { MesTraceIntegrityApi } from '#/api/mes/trace/integrity';
|
|
import { gsap } from 'gsap';
|
|
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
import { Page } from '@vben/common-ui';
|
import { IconifyIcon } from '@vben/icons';
|
|
import {
|
Button,
|
Card,
|
InputNumber,
|
message,
|
Select,
|
Tag,
|
} from 'ant-design-vue';
|
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import {
|
getTraceIntegrityPage,
|
verifyTraceIntegrity,
|
} from '#/api/mes/trace/integrity';
|
|
import { BIZ_TYPE_OPTIONS, getBizTypeLabel, useGridColumns, useGridFormSchema } from './data';
|
|
defineOptions({ name: 'MesTraceIntegrity' });
|
|
/** 校验输入 */
|
const verifyBizType = ref('BATCH');
|
const verifyBizId = ref<number>();
|
const verifyLoading = ref(false);
|
|
/** 校验结果 */
|
const verifyResult = ref<MesTraceIntegrityApi.VerifyResult>();
|
|
/** GSAP 入场动画上下文(卸载时自动还原) */
|
let animationContext: gsap.Context | undefined;
|
let resultTween: gsap.core.Tween | undefined;
|
|
/** 执行校验 */
|
async function handleVerify() {
|
if (!verifyBizId.value) {
|
message.warning('请输入业务记录编号');
|
return;
|
}
|
if (verifyLoading.value) return;
|
verifyLoading.value = true;
|
try {
|
verifyResult.value = await verifyTraceIntegrity(
|
verifyBizType.value,
|
verifyBizId.value,
|
);
|
} catch {
|
message.error('校验失败,请稍后重试');
|
} finally {
|
verifyLoading.value = false;
|
}
|
}
|
|
onMounted(async () => {
|
await nextTick();
|
animationContext = gsap.context(() => {
|
const reduceMotion = window.matchMedia(
|
'(prefers-reduced-motion: reduce)',
|
).matches;
|
gsap.from('.mes-it-verify, .mes-it-grid', {
|
autoAlpha: reduceMotion ? 1 : 0,
|
y: reduceMotion ? 0 : 14,
|
duration: reduceMotion ? 0 : 0.45,
|
stagger: reduceMotion ? 0 : 0.08,
|
ease: 'power2.out',
|
});
|
});
|
});
|
|
onUnmounted(() => {
|
resultTween?.kill();
|
animationContext?.revert();
|
});
|
|
/** 校验结果卡片出现时的轻量入场 */
|
watch(verifyResult, async (result) => {
|
if (!result) return;
|
await nextTick();
|
resultTween?.kill();
|
resultTween = gsap.from('.mes-it-result', {
|
autoAlpha: 0,
|
y: 8,
|
duration: 0.35,
|
ease: 'power2.out',
|
});
|
});
|
|
const [Grid, gridApi] = useVbenVxeGrid({
|
formOptions: {
|
schema: useGridFormSchema(),
|
},
|
gridOptions: {
|
columns: useGridColumns(),
|
height: 'auto',
|
keepSource: true,
|
proxyConfig: {
|
ajax: {
|
query: async ({ page }, formValues) => {
|
return await getTraceIntegrityPage({
|
pageNo: page.currentPage,
|
pageSize: page.pageSize,
|
...formValues,
|
});
|
},
|
},
|
},
|
rowConfig: {
|
keyField: 'id',
|
isHover: true,
|
},
|
toolbarConfig: {
|
refresh: true,
|
search: true,
|
},
|
} as VxeTableGridOptions<MesTraceIntegrityApi.Record>,
|
});
|
</script>
|
|
<template>
|
<Page auto-content-height>
|
<Card title="数据完整性校验" class="mes-it-verify mb-4">
|
<div class="flex flex-wrap items-center gap-3">
|
<span class="text-muted-foreground text-sm">业务类型</span>
|
<Select
|
v-model:value="verifyBizType"
|
:options="BIZ_TYPE_OPTIONS"
|
class="w-40"
|
placeholder="请选择业务类型"
|
/>
|
<span class="text-muted-foreground text-sm">业务编号</span>
|
<InputNumber
|
v-model:value="verifyBizId"
|
class="w-48"
|
:min="1"
|
:precision="0"
|
placeholder="请输入业务记录编号"
|
@press-enter="handleVerify"
|
/>
|
<Button
|
v-access:code="['mes:trace-integrity:verify']"
|
type="primary"
|
:loading="verifyLoading"
|
@click="handleVerify"
|
>
|
发起校验
|
</Button>
|
</div>
|
|
<div
|
v-if="verifyResult"
|
class="mes-it-result mt-4 rounded-lg border border-dashed p-4"
|
:class="
|
verifyResult.matched
|
? 'border-[#67c23a] bg-[#67c23a]/5'
|
: 'border-[#f56c6c] bg-[#f56c6c]/5'
|
"
|
>
|
<div class="flex items-center gap-2">
|
<IconifyIcon
|
class="size-6"
|
:class="
|
verifyResult.matched ? 'text-[#67c23a]' : 'text-[#f56c6c]'
|
"
|
:icon="
|
verifyResult.matched ? 'lucide:circle-check' : 'lucide:circle-x'
|
"
|
/>
|
<span class="text-base font-bold">
|
{{ verifyResult.matched ? '校验通过' : '校验不通过' }}
|
</span>
|
<span class="text-muted-foreground ml-2 text-sm">
|
{{ verifyResult.message }}
|
</span>
|
</div>
|
<div class="mt-3 grid gap-3 text-sm md:grid-cols-2">
|
<div class="min-w-0">
|
<div class="text-muted-foreground mb-1">业务信息</div>
|
<div class="break-all">
|
{{ getBizTypeLabel(verifyResult.bizType) }}
|
<span class="text-muted-foreground">#{{ verifyResult.bizId }}</span>
|
<Tag v-if="verifyResult.bizCode" class="ml-1">
|
{{ verifyResult.bizCode }}
|
</Tag>
|
</div>
|
</div>
|
<div class="min-w-0">
|
<div class="text-muted-foreground mb-1">存储摘要(SHA-256)</div>
|
<div class="break-all font-mono text-xs">
|
{{ verifyResult.storedHash }}
|
</div>
|
</div>
|
<div class="min-w-0">
|
<div class="text-muted-foreground mb-1">当前摘要(SHA-256)</div>
|
<div
|
class="break-all font-mono text-xs"
|
:class="verifyResult.matched ? '' : 'text-[#f56c6c]'"
|
>
|
{{ verifyResult.currentHash }}
|
</div>
|
</div>
|
<div v-if="verifyResult.bagSetHash" class="min-w-0">
|
<div class="text-muted-foreground mb-1">袋码集合摘要</div>
|
<div class="break-all font-mono text-xs">
|
{{ verifyResult.bagSetHash }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</Card>
|
|
<Grid class="mes-it-grid" table-title="完整性记录列表">
|
<template #bizType="{ row }">
|
<Tag color="blue">{{ getBizTypeLabel(row.bizType) }}</Tag>
|
</template>
|
<template #contentHash="{ row }">
|
<span class="font-mono text-xs">{{ row.contentHash }}</span>
|
</template>
|
</Grid>
|
</Page>
|
</template>
|