1. 优化一些问题
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled

This commit is contained in:
李琦
2026-05-20 10:12:09 +08:00
parent 90a9bde586
commit 68fd7b0045
14 changed files with 873 additions and 134 deletions

View File

@@ -0,0 +1,15 @@
import { requestClient } from '#/api/request';
const prefix = 'log/';
export async function getHyTransitBatchList(params: Record<string, any>) {
return requestClient.get<any>(`${prefix}hy-transit-batch-list`, { params });
}
export async function getHyTransitRecordList(params: Record<string, any>) {
return requestClient.get<any>(`${prefix}hy-transit-record-list`, { params });
}
export async function getHyTransitRecordDetail(params: { id: number }) {
return requestClient.get<any>(`${prefix}hy-transit-record-detail`, { params });
}

View File

@@ -0,0 +1,61 @@
import type { VbenFormProps } from '#/adapter/form';
export const formOptions: VbenFormProps = {
collapsed: false,
schema: [
{
component: 'VbenSelect',
fieldName: 'callback_status',
label: '回调状态',
componentProps: {
allowClear: true,
placeholder: '全部',
options: [
{ label: '等待回调', value: 'waiting' },
{ label: '成功', value: 'success' },
{ label: '失败', value: 'failed' },
],
},
},
{
component: 'VbenInput',
fieldName: 'anchor_date',
label: '锚定日',
componentProps: { placeholder: 'YYYY-MM-DD' },
},
{
component: 'VbenInput',
fieldName: 'buss_id',
label: '业务ID',
},
{
component: 'VbenInput',
fieldName: 'batch_id',
label: '批次ID',
},
{
component: 'VbenInput',
fieldName: 'register_id',
label: '挂号ID',
},
{
component: 'VbenInput',
fieldName: 'prescription_id',
label: '处方ID',
},
{
component: 'VbenInput',
fieldName: 'order_id',
label: '订单ID',
},
{
component: 'VbenInput',
fieldName: 'prescription_no',
label: '处方编号',
},
],
showCollapseButton: true,
submitButtonOptions: { content: '查询' },
submitOnChange: false,
submitOnEnter: true,
};

View File

@@ -0,0 +1,47 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getHyTransitRecordList } from '../../api';
export const gridOptions: VxeGridProps = {
columns: [
{ field: 'id', title: 'ID', width: 80 },
{ field: 'batch_id', title: '批次', width: 80 },
{ field: 'step_txt', title: '类型', width: 100 },
{ field: 'register_id', title: '挂号ID', width: 90 },
{ field: 'prescription_id', title: '处方ID', width: 90 },
{ field: 'order_id', title: '订单ID', width: 90 },
{ field: 'prescription_no', title: '处方编号', width: 140 },
{ field: 'patient_name', title: '患者', width: 90 },
{ field: 'push_status_txt', title: '上报', width: 90 },
{ field: 'callback_status_txt', title: '回调', width: 90, slots: { default: 'callback_status' } },
{
field: 'error_summary',
title: '校验/报错',
minWidth: 200,
showOverflow: true,
slots: { default: 'error_summary' },
},
{ field: 'callback_at', title: '回调时间', width: 160, slots: { default: 'callback_at' } },
{ type: 'html', title: '操作', width: 100, slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getHyTransitRecordList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
search: true,
refresh: true,
zoom: true,
},
};

View File

@@ -0,0 +1,197 @@
<script lang="ts" setup>
import { Page, useVbenModal } from '@vben/common-ui';
import { Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { getHyTransitRecordList } from '../api';
import FailDetailModal from '../components/fail-detail-modal.vue';
import { formOptions } from './config/search';
import { hasErrorTextClass } from '../utils';
import { gridOptions as baseGridOptions } from './config/table';
function formatTs(ts?: number) {
if (!ts) return '-';
return new Date(ts * 1000).toLocaleString();
}
const statusColor: Record<string, string> = {
waiting: 'orange',
success: 'green',
failed: 'error',
};
function callbackTagColor(status?: string) {
return statusColor[status ?? ''] || 'default';
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions: {
...baseGridOptions,
proxyConfig: {
...baseGridOptions.proxyConfig,
ajax: {
query: async ({ page }, formValues) => {
const res = await getHyTransitRecordList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
const callbackOpts = res?.form_options?.callback_status;
if (callbackOpts?.length) {
gridApi.formApi?.updateSchema([
{
fieldName: 'callback_status',
componentProps: {
allowClear: true,
placeholder: '全部',
options: callbackOpts,
},
},
]);
}
return res;
},
},
},
},
});
const [DetailModal, detailModalApi] = useVbenModal({
connectedComponent: FailDetailModal,
});
const showDetail = (row: { id: number }) => {
detailModalApi.setData({ id: row.id });
detailModalApi.open();
};
</script>
<template>
<Page auto-content-height title="监管回调结果">
<DetailModal />
<Grid>
<template #callback_status="{ row }">
<Tag :color="callbackTagColor(row.callback_status)">
{{ row.callback_status_txt || row.callback_status }}
</Tag>
</template>
<template #error_summary="{ row }">
<span :class="hasErrorTextClass(row.error_summary)">
{{ row.error_summary || '-' }}
</span>
</template>
<template #callback_at="{ row }">
{{ formatTs(row.callback_at) }}
</template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看回调',
type: 'link',
onClick: () => showDetail(row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,104 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions, Spin } from 'ant-design-vue';
import { getHyTransitRecordDetail } from '../api';
import { hasErrorTextClass } from '../utils';
const loading = ref(false);
const data = ref<Record<string, any>>({});
const payloadText = computed(() => {
const raw = data.value.payload_json;
if (!raw) return '';
try {
return JSON.stringify(JSON.parse(raw), null, 2);
} catch {
return String(raw);
}
});
const responseText = computed(() => {
const raw = data.value.response_body;
if (!raw) return '';
try {
return JSON.stringify(JSON.parse(raw), null, 2);
} catch {
return String(raw);
}
});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: true,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm() {
modalApi.close();
},
onOpenChange: async (isOpen: boolean) => {
if (!isOpen) return;
const id = modalApi.getData()?.id;
if (!id) return;
loading.value = true;
try {
data.value = (await getHyTransitRecordDetail({ id })) ?? {};
} finally {
loading.value = false;
}
},
});
</script>
<template>
<Modal class="w-[80%]" title="监管回调详情">
<Spin :spinning="loading">
<Descriptions bordered :column="2" class="mb-4">
<Descriptions.Item label="记录ID">{{ data.id }}</Descriptions.Item>
<Descriptions.Item label="批次ID">{{ data.batch_id }}</Descriptions.Item>
<Descriptions.Item label="监管类型">{{ data.step_txt || data.step_label || data.step }}</Descriptions.Item>
<Descriptions.Item label="业务ID">{{ data.buss_id }}</Descriptions.Item>
<Descriptions.Item label="挂号ID">{{ data.register_id || '-' }}</Descriptions.Item>
<Descriptions.Item label="处方ID">{{ data.prescription_id || '-' }}</Descriptions.Item>
<Descriptions.Item label="订单ID">{{ data.order_id || '-' }}</Descriptions.Item>
<Descriptions.Item label="处方编号">{{ data.prescription_no || '-' }}</Descriptions.Item>
<Descriptions.Item label="患者">{{ data.patient_name || '-' }}</Descriptions.Item>
<Descriptions.Item label="文件ID">{{ data.recipe_file_id || '-' }}</Descriptions.Item>
<Descriptions.Item label="接口">{{ data.service_method }}</Descriptions.Item>
<Descriptions.Item label="上报状态">{{ data.push_status_txt || data.push_status_label || data.push_status }}</Descriptions.Item>
<Descriptions.Item label="回调状态">{{ data.callback_status_txt || data.callback_status_label || data.callback_status }}</Descriptions.Item>
<Descriptions.Item label="HTTP">{{ data.http_code }}</Descriptions.Item>
<Descriptions.Item label="msgCode">{{ data.msg_code }}</Descriptions.Item>
<Descriptions.Item label="trace_id" :span="2">{{ data.trace_id }}</Descriptions.Item>
<Descriptions.Item label="校验错误" :span="2">
<span
:class="
hasErrorTextClass(
(data.validation_errors_label || data.validation_errors || []).join('、'),
)
"
>
{{ (data.validation_errors_label || data.validation_errors || []).join('、') || '-' }}
</span>
</Descriptions.Item>
<Descriptions.Item label="错误摘要" :span="2">
<span :class="hasErrorTextClass(data.error_summary || data.error_message)">
{{ data.error_summary || data.error_message || '-' }}
</span>
</Descriptions.Item>
</Descriptions>
<div class="mb-2 font-medium">组包数据 payload_json</div>
<pre
class=" rounded border bg-muted p-3 text-xs dark:bg-[#020817] dark:text-gray-200"
>{{ payloadText }}</pre>
<div class="mb-2 mt-4 font-medium">监管响应 response_body</div>
<pre
class=" rounded border bg-muted p-3 text-xs dark:bg-[#020817] dark:text-gray-200"
>{{ responseText }}</pre>
</Spin>
</Modal>
</template>

View File

@@ -0,0 +1,180 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { getHyTransitRecordList } from '../api';
import { hasErrorTextClass } from '../utils';
import FailDetailModal from './fail-detail-modal.vue';
const batchId = ref(0);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: true,
draggable: true,
onOpenChange(isOpen: boolean) {
if (isOpen) {
batchId.value = modalApi.getData()?.batchId ?? 0;
gridApi.reload();
}
},
});
const [DetailModal, detailModalApi] = useVbenModal({
connectedComponent: FailDetailModal,
});
const showDetail = (row: { id: number }) => {
detailModalApi.setData({ id: row.id });
detailModalApi.open();
};
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
height: 480,
columns: [
{ field: 'id', title: 'ID', width: 70 },
{ field: 'step_txt', title: '类型', width: 100 },
{ field: 'register_id', title: '挂号ID', width: 85 },
{ field: 'prescription_id', title: '处方ID', width: 85 },
{ field: 'order_id', title: '订单ID', width: 85 },
{ field: 'prescription_no', title: '处方编号', width: 130 },
{ field: 'patient_name', title: '患者', width: 80 },
{ field: 'pull_status_txt', title: '拉取', width: 80 },
{ field: 'push_status_txt', title: '上报', width: 80 },
{ field: 'callback_status_txt', title: '回调', width: 80 },
{
field: 'error_summary',
title: '校验/报错',
minWidth: 180,
showOverflow: true,
slots: { default: 'error_summary' },
},
{ type: 'html', title: '操作', width: 100, slots: { default: 'action' } },
],
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getHyTransitRecordList({
page: page.currentPage,
pageSize: page.pageSize,
batch_id: batchId.value,
});
},
},
},
},
});
</script>
<template>
<Modal class="w-[90%]" title="批次明细">
<DetailModal />
<Grid>
<template #error_summary="{ row }">
<span :class="hasErrorTextClass(row.error_summary)">
{{ row.error_summary || '-' }}
</span>
</template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看回调',
type: 'link',
onClick: () => showDetail(row),
},
]"
/>
</template>
</Grid>
</Modal>
</template>

View File

@@ -0,0 +1,37 @@
import type { VbenFormProps } from '#/adapter/form';
export const formOptions: VbenFormProps = {
collapsed: false,
schema: [
{
component: 'VbenInput',
fieldName: 'anchor_date',
label: '锚定日',
componentProps: { placeholder: 'YYYY-MM-DD' },
},
{
component: 'VbenSelect',
fieldName: 'step',
label: '接口类型',
componentProps: {
allowClear: true,
options: [
{ label: '咨询', value: 'consult' },
{ label: '复诊', value: 'referral' },
{ label: '处方', value: 'recipe' },
{ label: '核销', value: 'verification' },
{ label: '全部', value: 'all' },
],
},
},
{
component: 'VbenInput',
fieldName: 'batch_id',
label: '批次ID',
},
],
showCollapseButton: true,
submitButtonOptions: { content: '查询' },
submitOnChange: false,
submitOnEnter: true,
};

View File

@@ -0,0 +1,44 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getHyTransitBatchList } from '../../api';
export const gridOptions: VxeGridProps = {
columns: [
{ field: 'id', title: '批次ID', width: 90 },
{ field: 'anchor_date', title: '锚定日', width: 120 },
{ field: 'step_txt', title: '类型', width: 110 },
{ field: 'pull_status_txt', title: '拉取状态', width: 100 },
{ field: 'total_count', title: '总数', width: 80 },
{ field: 'success_count', title: '成功', width: 80 },
{
field: 'failed_count',
title: '失败',
width: 80,
slots: { default: 'failed_count' },
},
{ field: 'skipped_count', title: '跳过', width: 80 },
{ field: 'callback_status_txt', title: '回调状态', width: 100 },
{ field: 'started_at', title: '开始时间', width: 160, slots: { default: 'started_at' } },
{ type: 'html', title: '操作', width: 120, slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getHyTransitBatchList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
search: true,
refresh: true,
zoom: true,
},
};

View File

@@ -0,0 +1,165 @@
<script lang="ts" setup>
import { Page, useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { getHyTransitBatchList } from '../api';
import RecordModal from '../components/record-list-modal.vue';
import { formOptions } from './config/search';
import { countErrorClass } from '../utils';
import { gridOptions as baseGridOptions } from './config/table';
function formatTs(ts?: number) {
if (!ts) return '-';
return new Date(ts * 1000).toLocaleString();
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions: {
...baseGridOptions,
proxyConfig: {
...baseGridOptions.proxyConfig,
ajax: {
query: async ({ page }, formValues) => {
const res = await getHyTransitBatchList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
const stepOpts = res?.form_options?.step;
if (stepOpts?.length) {
gridApi.formApi?.updateSchema([
{
fieldName: 'step',
componentProps: {
allowClear: true,
options: stepOpts,
},
},
]);
}
return res;
},
},
},
},
});
const [RecordListModal, recordModalApi] = useVbenModal({
connectedComponent: RecordModal,
});
const showRecords = (row: { id: number }) => {
recordModalApi.setData({ batchId: row.id });
recordModalApi.open();
};
</script>
<template>
<Page auto-content-height title="监管拉取记录">
<RecordListModal />
<Grid>
<template #failed_count="{ row }">
<span :class="countErrorClass(row.failed_count)">
{{ row.failed_count }}
</span>
</template>
<template #started_at="{ row }">
{{ formatTs(row.started_at) }}
</template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看明细',
type: 'link',
onClick: () => showRecords(row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,9 @@
/** 计数类字段:大于 0 时标红(适配亮/暗主题) */
export function countErrorClass(count?: number) {
return Number(count) > 0 ? 'text-destructive font-semibold' : '';
}
/** 有错误文案时标红 */
export function hasErrorTextClass(text?: string) {
return text?.trim() ? 'text-destructive' : '';
}