feat(1.4.0-beta1): 1. 调整重试任务和重试管理包路径

This commit is contained in:
opensnail 2025-02-18 23:19:24 +08:00
parent 27546ed6bb
commit 820501785e
12 changed files with 490 additions and 668 deletions

View File

@ -344,8 +344,8 @@ const local: App.I18n.Schema = {
notify_config: 'Notify Config',
retry: 'Retry Task',
retry_task: 'Retry Task',
retry_info: "'Retry Task Management",
retry_scene: 'Retry Scene',
retry_log: 'Retry Log',
'retry_dead-letter': 'Retry Dead Letter',
user: 'User',
user_manager: 'User Info',

View File

@ -344,12 +344,12 @@ const local: App.I18n.Schema = {
notify_recipient: '通知人',
notify_config: '通知配置',
retry: '重试任务',
retry_task: '任务管理',
retry_task: '重试任务',
retry_info: '任务管理',
'retry_dead-letter': '死信任务',
user: '用户管理',
user_manager: '用户信息',
retry_scene: '重试场景',
retry_log: '重试日志',
workflow: '工作流',
workflow_task: '任务管理',
workflow_batch: '执行批次',

View File

@ -31,7 +31,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
notify_recipient: () => import("@/views/notify/recipient/index.vue"),
pods: () => import("@/views/pods/index.vue"),
"retry_dead-letter": () => import("@/views/retry/dead-letter/index.vue"),
retry_log: () => import("@/views/retry/log/index.vue"),
retry_info: () => import("@/views/retry/info/index.vue"),
retry_scene: () => import("@/views/retry/scene/index.vue"),
retry_task: () => import("@/views/retry/task/index.vue"),
user_manager: () => import("@/views/user/manager/index.vue"),

View File

@ -234,15 +234,13 @@ export const generatedRoutes: GeneratedRoute[] = [
}
},
{
name: 'retry_log',
path: '/retry/log',
component: 'view.retry_log',
name: 'retry_info',
path: '/retry/info',
component: 'view.retry_info',
meta: {
title: 'retry_log',
i18nKey: 'route.retry_log',
icon: 'tabler:logs',
order: 20,
keepAlive: false
title: 'retry_info',
i18nKey: 'route.retry_info',
icon: 'octicon:tasklist'
}
},
{
@ -264,8 +262,7 @@ export const generatedRoutes: GeneratedRoute[] = [
meta: {
title: 'retry_task',
i18nKey: 'route.retry_task',
icon: 'octicon:tasklist',
order: 10,
icon: 'carbon:batch-job',
keepAlive: false
}
}

View File

@ -143,22 +143,22 @@ const cardData = computed<CardData[]>(() => [
{
label: $t('common.success'),
value: props.modelValue?.retryTask.finishNum ?? 0,
click: () => routerPushByKey('retry_log', { state: { retryStatus: 1 } })
click: () => routerPushByKey('retry_info', { state: { retryStatus: 1 } })
},
{
label: $t('common.running'),
value: props.modelValue?.retryTask.runningNum ?? 0,
click: () => routerPushByKey('retry_log', { state: { retryStatus: 0 } })
click: () => routerPushByKey('retry_info', { state: { retryStatus: 0 } })
},
{
label: $t('page.home.retryTask.status.maxRetryTimes'),
value: props.modelValue?.retryTask.maxCountNum ?? 0,
click: () => routerPushByKey('retry_log', { state: { retryStatus: 2 } })
click: () => routerPushByKey('retry_info', { state: { retryStatus: 2 } })
},
{
label: $t('page.home.retryTask.status.pauseRetry'),
value: props.modelValue?.retryTask.suspendNum ?? 0,
click: () => routerPushByKey('retry_log', { state: { retryStatus: 3 } })
click: () => routerPushByKey('retry_info', { state: { retryStatus: 3 } })
}
]
},

View File

@ -0,0 +1,364 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { onMounted, ref } from 'vue';
import { useBoolean } from '~/packages/hooks';
import {
fetchBatchDeleteRetryTask,
fetchExecuteCallbackTask,
fetchExecuteRetryTask,
fetchGetAllGroupNameList,
fetchGetRetryTaskById,
fetchGetRetryTaskList,
fetchUpdateRetryTaskStatus
} from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { retryStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
import { tagColor } from '@/utils/common';
import RetryTaskOperateDrawer from './modules/retry-operate-drawer.vue';
import RetryTaskBatchAddDrawer from './modules/retr-batch-add-drawer.vue';
import RetryTaskSearch from './modules/retry-search.vue';
import RetryTaskDetailDrawerVue from './modules/retry-detail-drawer.vue';
/** 详情页属性数据 */
const detailData = ref<Api.Retry.Retry | null>();
/** 详情页可见状态 */
const { bool: detailVisible, setTrue: openDetail } = useBoolean(false);
const appStore = useAppStore();
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetRetryTaskList,
apiParams: {
page: 1,
size: 10,
groupName: null,
sceneName: null,
idempotentId: null,
bizNo: null,
retryStatus: null
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48,
disabled: row => row.retryStatus === 0
},
{
key: 'id',
title: $t('common.index'),
align: 'center',
width: 128,
render: row => {
async function showDetailDrawer() {
await loadRetryInfo(row);
openDetail();
}
return (
<n-button text tag="a" type="primary" onClick={showDetailDrawer} class="ws-normal">
{row.id}
</n-button>
);
}
},
{
key: 'groupName',
title: $t('page.retryTask.groupName'),
align: 'left',
resizable: true,
minWidth: 120,
maxWidth: 250
},
{
key: 'sceneName',
title: $t('page.retryTask.sceneName'),
align: 'left',
minWidth: 120
},
{
key: 'nextTriggerAt',
title: $t('page.retryTask.nextTriggerAt'),
align: 'left',
resizable: true,
minWidth: 120,
maxWidth: 150
},
{
key: 'retryCount',
title: $t('page.retryTask.retryCount'),
align: 'center',
width: 80
},
{
key: 'retryStatus',
title: $t('page.retryTask.retryStatus'),
align: 'left',
width: 120,
render: row => {
if (row.retryStatus === null) {
return null;
}
const label = $t(retryStatusTypeRecord[row.retryStatus!]);
return <NTag type={tagColor(row.retryStatus!)}>{label}</NTag>;
}
},
{
key: 'taskType',
title: $t('page.retryTask.taskType'),
align: 'left',
width: 100,
render: row => {
if (row.taskType === null) {
return null;
}
const tagMap: Record<Api.Retry.TaskType, NaiveUI.ThemeColor> = {
1: 'warning',
2: 'error'
};
const label = $t(retryTaskTypeRecord[row.taskType!]);
return <NTag type={tagMap[row.taskType!]}>{label}</NTag>;
}
},
{
key: 'idempotentId',
title: $t('page.retryTask.idempotentId'),
align: 'left',
resizable: true,
minWidth: 150,
maxWidth: 300
},
{
key: 'bizNo',
title: $t('page.retryTask.bizNo'),
align: 'left',
resizable: true,
minWidth: 150,
maxWidth: 300
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 260,
fixed: 'right',
render: row => (
<div class="flex-center gap-8px">
{/* 非[完成,最大次数], 显示[执行]按钮 */}
{row.retryStatus !== 1 && row.retryStatus !== 2 ? (
<>
<NPopconfirm onPositiveClick={() => handleExecute(row.groupName!, row.id! as any, row.taskType!)}>
{{
default: () => $t('common.confirmExecute'),
trigger: () => (
<NButton type="info" text ghost size="small">
{$t('common.execute')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{/* 非[完成,最大次数], 显示[完成]按钮 */}
{row.retryStatus !== 1 && row.retryStatus !== 2 ? (
<>
<NPopconfirm onPositiveClick={() => handleFinish(Number(row.id!), row.groupName!)}>
{{
default: () => $t('common.confirmFinish'),
trigger: () => (
<NButton type="warning" text ghost size="small">
{$t('common.finish')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{/* 重试中, 显示[停止]按钮 */}
{row.retryStatus === 0 ? (
<>
<NPopconfirm onPositiveClick={() => handlePause(Number(row.id!), row.groupName!)}>
{{
default: () => $t('common.confirmPause'),
trigger: () => (
<NButton type="success" text ghost size="small">
{$t('common.pause')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{/* 暂停, 显示[开始]按钮 */}
{row.retryStatus === 3 ? (
<>
<NPopconfirm onPositiveClick={() => handleResume(Number(row.id!), row.groupName!)}>
{{
default: () => $t('common.confirmResume'),
trigger: () => (
<NButton type="info" text ghost size="small">
{$t('common.resume')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{
<NPopconfirm onPositiveClick={() => handleDelete(row.groupName!, row.id!)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" text ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
}
</div>
)
}
]
});
const {
drawerVisible,
operateType,
handleAdd,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
const { bool: batchAddDrawerVisible, setTrue: openBatchAddDrawer } = useBoolean();
async function handleDelete(groupName: string, id: string) {
const { error } = await fetchBatchDeleteRetryTask({ groupName, ids: [id] });
if (error) return;
onDeleted();
}
async function loadRetryInfo(row: Api.Retry.Retry) {
const res = await fetchGetRetryTaskById(row.id!, row.groupName!);
detailData.value = (res.data as Api.Retry.Retry) || null;
}
async function handleBatchDelete() {
const ids: string[] = checkedRowKeys.value as string[];
if (ids.length === 0) return;
const groupName = data.value[0].groupName;
const { error } = await fetchBatchDeleteRetryTask({ groupName, ids });
if (error) return;
onBatchDeleted();
}
function handleBatchAdd() {
openBatchAddDrawer();
}
function handleExecute(groupName: string, retryId: number, type: Api.Retry.TaskType) {
if (type === 1) {
fetchExecuteRetryTask({ groupName, retryIds: [retryId] });
return;
}
if (type === 2) {
fetchExecuteCallbackTask({ groupName, retryIds: [retryId] });
}
}
function handleResume(id: number, groupName: string) {
updateRetryTaskStatus(id, groupName, 0);
}
function handlePause(id: number, groupName: string) {
updateRetryTaskStatus(id, groupName, 3);
}
function handleFinish(id: number, groupName: string) {
updateRetryTaskStatus(id, groupName, 1);
}
async function updateRetryTaskStatus(id: number, groupName: string, retryStatus: Api.Retry.RetryStatusType) {
const { error } = await fetchUpdateRetryTaskStatus({ id, groupName, retryStatus });
if (error) return;
window.$message?.success($t('common.updateSuccess'));
getData();
}
onMounted(async () => {
const { error, data: groupList } = await fetchGetAllGroupNameList();
if (!error && groupList.length > 0) {
searchParams.groupName = groupList[0];
getData();
}
});
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<RetryTaskSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
<NCard
:title="$t('page.retryTask.title')"
:bordered="false"
size="small"
class="sm:flex-1-hidden card-wrapper"
header-class="view-card-header"
>
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
>
<template #addAfter>
<NButton size="small" ghost type="primary" @click="handleBatchAdd">
<template #icon>
<icon-ic-round-plus class="text-icon" />
</template>
{{ $t('common.batchAdd') }}
</NButton>
</template>
</TableHeaderOperation>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
:flex-height="!appStore.isMobile"
:scroll-x="2000"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
<RetryTaskOperateDrawer v-model:visible="drawerVisible" :operate-type="operateType" @submitted="getData" />
<RetryTaskBatchAddDrawer v-model:visible="batchAddDrawerVisible" @submitted="getData" />
<RetryTaskDetailDrawerVue v-model:visible="detailVisible" :row-data="detailData" />
</NCard>
</div>
</template>
<style scoped></style>

View File

@ -1,201 +0,0 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { ref } from 'vue';
import { useBoolean } from '@sa/hooks';
import { fetchBatchDeleteRetryLog, fetchDeleteRetryLog, fetchRetryLogById, fetchRetryLogPageList } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
import { monthRangeISO8601, tagColor } from '@/utils/common';
import RetryLogSearch from './modules/retry-log-search.vue';
import RetryLogDetailDrawer from './modules/retry-log-detail-drawer.vue';
const appStore = useAppStore();
/** 详情页属性数据 */
const detailData = ref<Api.RetryTask.RetryTask | null>();
/** 详情页可见状态 */
const { bool: detailVisible, setTrue: openDetail } = useBoolean(false);
const taskStatus = history.state.taskStatus;
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchRetryLogPageList,
apiParams: {
page: 1,
size: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
groupName: null,
sceneName: null,
taskStatus: null,
datetimeRange: monthRangeISO8601()
},
searchParams: {
taskStatus
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48,
disabled: row => row.taskStatus === 1
},
{
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64,
render: row => {
async function showDetailDrawer() {
await loadRetryInfo(row);
openDetail();
}
return (
<n-button text tag="a" type="primary" onClick={showDetailDrawer} class="ws-normal">
{row.id}
</n-button>
);
}
},
{
key: 'groupName',
title: $t('page.retryLog.groupName'),
align: 'left',
minWidth: 120
},
{
key: 'sceneName',
title: $t('page.retryLog.sceneName'),
align: 'left',
minWidth: 120
},
{
key: 'taskStatus',
title: $t('page.retryLog.retryStatus'),
align: 'left',
minWidth: 120,
render: row => {
if (row.taskStatus === null) {
return null;
}
const tagMap: Record<number, NaiveUI.ThemeColor> = {
1: 'info',
2: 'info',
3: 'info',
4: 'error',
5: 'error',
6: 'error'
};
const label = $t(retryTaskStatusTypeRecord[row.taskStatus!]);
return <NTag type={tagMap[row.taskStatus!]}>{label}</NTag>;
}
},
{
key: 'taskType',
title: $t('page.retryLog.taskType'),
align: 'left',
minWidth: 120,
render: row => {
if (row.taskType === null) {
return null;
}
const label = $t(retryTaskTypeRecord[row.taskType!]);
return <NTag type={tagColor(row.taskType!)}>{label}</NTag>;
}
},
{
key: 'createDt',
title: $t('page.retryLog.createDt'),
align: 'left',
minWidth: 120
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 80,
render: row => (
<div class="flex-center gap-8px">
{row.taskStatus === 1 || row.taskStatus === 2 ? (
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" text ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
) : (
''
)}
</div>
)
}
]
});
const { checkedRowKeys, onDeleted, onBatchDeleted } = useTableOperate(data, getData);
async function handleBatchDelete() {
const { error } = await fetchBatchDeleteRetryLog(checkedRowKeys.value as any[]);
if (error) return;
onBatchDeleted();
}
async function handleDelete(id: any) {
const { error } = await fetchDeleteRetryLog(id);
if (error) return;
onDeleted();
}
async function loadRetryInfo(row: Api.RetryTask.RetryTask) {
const res = await fetchRetryLogById(row.id!);
detailData.value = (res.data as Api.RetryTask.RetryTask) || null;
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<RetryLogSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
<NCard
:title="$t('page.retryLog.title')"
:bordered="false"
size="small"
class="sm:flex-1-hidden card-wrapper"
header-class="view-card-header"
>
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
:show-add="false"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
</NCard>
<RetryLogDetailDrawer v-model:visible="detailVisible" :row-data="detailData" />
</div>
</template>
<style scoped></style>

View File

@ -1,56 +0,0 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { tagColor } from '@/utils/common';
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
defineOptions({
name: 'SceneDetailDrawer'
});
interface Props {
/** row data */
rowData?: Api.RetryTask.RetryTask | null;
}
defineProps<Props>();
const visible = defineModel<boolean>('visible', {
default: false
});
</script>
<template>
<DetailDrawer v-model="visible" :title="$t('page.retryLog.detail')" :width="['50%', '90%']">
<NTabs type="segment" animated>
<NTabPane :name="0" :tab="$t('page.log.info')">
<NDescriptions label-placement="top" bordered :column="2">
<NDescriptionsItem :label="$t('page.retryLog.groupName')" :span="2">
{{ rowData?.groupName }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryLog.sceneName')" :span="2">
{{ rowData?.sceneName }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryLog.retryStatus')" :span="1">
<NTag :type="tagColor(rowData?.taskStatus!)">
{{ $t(retryTaskStatusTypeRecord[rowData?.taskStatus!]) }}
</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryLog.taskType')" :span="1">
<NTag :type="tagColor(rowData?.taskType!)">{{ $t(retryTaskTypeRecord[rowData?.taskType!]) }}</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('common.createDt')">{{ rowData?.createDt }}</NDescriptionsItem>
</NDescriptions>
</NTabPane>
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
<LogDrawer :drawer="false" type="retry" :task-data="rowData!" />
</NTabPane>
</NTabs>
</DetailDrawer>
</template>
<style scoped>
:deep(.virtual-list) {
height: calc(100vh - 166px) !important;
max-height: calc(100vh - 166px) !important;
}
</style>

View File

@ -1,67 +0,0 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { translateOptions } from '@/utils/common';
import { retryStatusTypeOptions } from '@/constants/business';
import SelectGroup from '@/components/common/select-group.vue';
import SelectScene from '@/components/common/select-scene.vue';
import DatetimeRange from '@/components/common/datetime-range.vue';
defineOptions({
name: 'RetryLogSearch'
});
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
const emit = defineEmits<Emits>();
const model = defineModel<Api.RetryTask.RetryTaskSearchParams>('model', { required: true });
function reset() {
emit('reset');
}
function search() {
emit('search');
}
</script>
<template>
<SearchForm btn-span="24 xl:3" :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.sceneName')" path="sceneName" class="pr-24px">
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" clearable />
</NFormItemGi>
<!-- <NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.UniqueId')" path="UniqueId" class="pr-24px">-->
<!-- <NInput v-model:value="model.uniqueId" :placeholder="$t('page.retryLog.form.UniqueId')" clearable />-->
<!-- </NFormItemGi>-->
<!-- <NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.idempotentId')" path="idempotentId" class="pr-24px">-->
<!-- <NInput v-model:value="model.idempotentId" :placeholder="$t('page.retryLog.form.idempotentId')" clearable />-->
<!-- </NFormItemGi>-->
<!-- <NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.bizNo')" path="bizNo" class="pr-24px">-->
<!-- <NInput v-model:value="model.bizNo" :placeholder="$t('page.retryLog.form.bizNo')" clearable />-->
<!-- </NFormItemGi>-->
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.retryStatus')" path="taskBatchStatus" class="pr-24px">
<NSelect
v-model:value="model.taskStatus"
:placeholder="$t('page.retryTask.form.retryStatus')"
:options="translateOptions(retryStatusTypeOptions)"
clearable
/>
</NFormItemGi>
<NFormItemGi
span="24 s:24 m:15 l:12 xl:9"
:label="$t('page.common.createTime')"
path="datetimeRange"
class="pr-24px"
>
<DatetimeRange v-model:value="model.datetimeRange!" />
</NFormItemGi>
</SearchForm>
</template>
<style scoped></style>

View File

@ -1,56 +1,51 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { useBoolean } from '@sa/hooks';
import { onMounted, ref } from 'vue';
import {
fetchBatchDeleteRetryTask,
fetchExecuteCallbackTask,
fetchExecuteRetryTask,
fetchGetAllGroupNameList,
fetchGetRetryTaskById,
fetchGetRetryTaskList,
fetchUpdateRetryTaskStatus
} from '@/service/api';
import { ref } from 'vue';
import { useBoolean } from '~/packages/hooks';
import { fetchBatchDeleteRetryLog, fetchDeleteRetryLog, fetchRetryLogById, fetchRetryLogPageList } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { retryStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
import { tagColor } from '@/utils/common';
import RetryTaskOperateDrawer from './modules/retry-task-operate-drawer.vue';
import RetryTaskBatchAddDrawer from './modules/retry-task-batch-add-drawer.vue';
import RetryTaskSearch from './modules/retry-task-search.vue';
import RetryTaskDetailDrawerVue from './modules/retry-task-detail-drawer.vue';
/** 详情页属性数据 */
const detailData = ref<Api.Retry.Retry | null>();
/** 详情页可见状态 */
const { bool: detailVisible, setTrue: openDetail } = useBoolean(false);
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
import { monthRangeISO8601, tagColor } from '@/utils/common';
import RetryLogSearch from './modules/retry-task-search.vue';
import RetryLogDetailDrawer from './modules/retry-task-detail-drawer.vue';
const appStore = useAppStore();
/** 详情页属性数据 */
const detailData = ref<Api.RetryTask.RetryTask | null>();
/** 详情页可见状态 */
const { bool: detailVisible, setTrue: openDetail } = useBoolean(false);
const taskStatus = history.state.taskStatus;
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetRetryTaskList,
apiFn: fetchRetryLogPageList,
apiParams: {
page: 1,
size: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
groupName: null,
sceneName: null,
idempotentId: null,
bizNo: null,
retryStatus: null
taskStatus: null,
datetimeRange: monthRangeISO8601()
},
searchParams: {
taskStatus
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48,
disabled: row => row.retryStatus === 0
disabled: row => row.taskStatus === 1
},
{
key: 'id',
title: $t('common.index'),
align: 'center',
width: 128,
width: 64,
render: row => {
async function showDetailDrawer() {
await loadRetryInfo(row);
@ -66,162 +61,68 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
},
{
key: 'groupName',
title: $t('page.retryTask.groupName'),
align: 'left',
resizable: true,
minWidth: 120,
maxWidth: 250
},
{
key: 'sceneName',
title: $t('page.retryTask.sceneName'),
title: $t('page.retryLog.groupName'),
align: 'left',
minWidth: 120
},
{
key: 'nextTriggerAt',
title: $t('page.retryTask.nextTriggerAt'),
key: 'sceneName',
title: $t('page.retryLog.sceneName'),
align: 'left',
minWidth: 120
},
{
key: 'taskStatus',
title: $t('page.retryLog.retryStatus'),
align: 'left',
resizable: true,
minWidth: 120,
maxWidth: 150
},
{
key: 'retryCount',
title: $t('page.retryTask.retryCount'),
align: 'center',
width: 80
},
{
key: 'retryStatus',
title: $t('page.retryTask.retryStatus'),
align: 'left',
width: 120,
render: row => {
if (row.retryStatus === null) {
if (row.taskStatus === null) {
return null;
}
const label = $t(retryStatusTypeRecord[row.retryStatus!]);
const tagMap: Record<number, NaiveUI.ThemeColor> = {
1: 'info',
2: 'info',
3: 'info',
4: 'error',
5: 'error',
6: 'error'
};
return <NTag type={tagColor(row.retryStatus!)}>{label}</NTag>;
const label = $t(retryTaskStatusTypeRecord[row.taskStatus!]);
return <NTag type={tagMap[row.taskStatus!]}>{label}</NTag>;
}
},
{
key: 'taskType',
title: $t('page.retryTask.taskType'),
title: $t('page.retryLog.taskType'),
align: 'left',
width: 100,
minWidth: 120,
render: row => {
if (row.taskType === null) {
return null;
}
const tagMap: Record<Api.Retry.TaskType, NaiveUI.ThemeColor> = {
1: 'warning',
2: 'error'
};
const label = $t(retryTaskTypeRecord[row.taskType!]);
return <NTag type={tagMap[row.taskType!]}>{label}</NTag>;
return <NTag type={tagColor(row.taskType!)}>{label}</NTag>;
}
},
{
key: 'idempotentId',
title: $t('page.retryTask.idempotentId'),
key: 'createDt',
title: $t('page.retryLog.createDt'),
align: 'left',
resizable: true,
minWidth: 150,
maxWidth: 300
},
{
key: 'bizNo',
title: $t('page.retryTask.bizNo'),
align: 'left',
resizable: true,
minWidth: 150,
maxWidth: 300
minWidth: 120
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 260,
fixed: 'right',
width: 80,
render: row => (
<div class="flex-center gap-8px">
{/* 非[完成,最大次数], 显示[执行]按钮 */}
{row.retryStatus !== 1 && row.retryStatus !== 2 ? (
<>
<NPopconfirm onPositiveClick={() => handleExecute(row.groupName!, row.id! as any, row.taskType!)}>
{{
default: () => $t('common.confirmExecute'),
trigger: () => (
<NButton type="info" text ghost size="small">
{$t('common.execute')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{/* 非[完成,最大次数], 显示[完成]按钮 */}
{row.retryStatus !== 1 && row.retryStatus !== 2 ? (
<>
<NPopconfirm onPositiveClick={() => handleFinish(Number(row.id!), row.groupName!)}>
{{
default: () => $t('common.confirmFinish'),
trigger: () => (
<NButton type="warning" text ghost size="small">
{$t('common.finish')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{/* 重试中, 显示[停止]按钮 */}
{row.retryStatus === 0 ? (
<>
<NPopconfirm onPositiveClick={() => handlePause(Number(row.id!), row.groupName!)}>
{{
default: () => $t('common.confirmPause'),
trigger: () => (
<NButton type="success" text ghost size="small">
{$t('common.pause')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{/* 暂停, 显示[开始]按钮 */}
{row.retryStatus === 3 ? (
<>
<NPopconfirm onPositiveClick={() => handleResume(Number(row.id!), row.groupName!)}>
{{
default: () => $t('common.confirmResume'),
trigger: () => (
<NButton type="info" text ghost size="small">
{$t('common.resume')}
</NButton>
)
}}
</NPopconfirm>
<n-divider vertical />
</>
) : (
''
)}
{
<NPopconfirm onPositiveClick={() => handleDelete(row.groupName!, row.id!)}>
{row.taskStatus === 1 || row.taskStatus === 2 ? (
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
@ -231,93 +132,40 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
)
}}
</NPopconfirm>
}
) : (
''
)}
</div>
)
}
]
});
const {
drawerVisible,
operateType,
handleAdd,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
const { bool: batchAddDrawerVisible, setTrue: openBatchAddDrawer } = useBoolean();
async function handleDelete(groupName: string, id: string) {
const { error } = await fetchBatchDeleteRetryTask({ groupName, ids: [id] });
if (error) return;
onDeleted();
}
async function loadRetryInfo(row: Api.Retry.Retry) {
const res = await fetchGetRetryTaskById(row.id!, row.groupName!);
detailData.value = (res.data as Api.Retry.Retry) || null;
}
const { checkedRowKeys, onDeleted, onBatchDeleted } = useTableOperate(data, getData);
async function handleBatchDelete() {
const ids: string[] = checkedRowKeys.value as string[];
if (ids.length === 0) return;
const groupName = data.value[0].groupName;
const { error } = await fetchBatchDeleteRetryTask({ groupName, ids });
const { error } = await fetchBatchDeleteRetryLog(checkedRowKeys.value as any[]);
if (error) return;
onBatchDeleted();
}
function handleBatchAdd() {
openBatchAddDrawer();
}
function handleExecute(groupName: string, retryId: number, type: Api.Retry.TaskType) {
if (type === 1) {
fetchExecuteRetryTask({ groupName, retryIds: [retryId] });
return;
}
if (type === 2) {
fetchExecuteCallbackTask({ groupName, retryIds: [retryId] });
}
}
function handleResume(id: number, groupName: string) {
updateRetryTaskStatus(id, groupName, 0);
}
function handlePause(id: number, groupName: string) {
updateRetryTaskStatus(id, groupName, 3);
}
function handleFinish(id: number, groupName: string) {
updateRetryTaskStatus(id, groupName, 1);
}
async function updateRetryTaskStatus(id: number, groupName: string, retryStatus: Api.Retry.RetryStatusType) {
const { error } = await fetchUpdateRetryTaskStatus({ id, groupName, retryStatus });
async function handleDelete(id: any) {
const { error } = await fetchDeleteRetryLog(id);
if (error) return;
window.$message?.success($t('common.updateSuccess'));
getData();
onDeleted();
}
onMounted(async () => {
const { error, data: groupList } = await fetchGetAllGroupNameList();
if (!error && groupList.length > 0) {
searchParams.groupName = groupList[0];
getData();
}
});
async function loadRetryInfo(row: Api.RetryTask.RetryTask) {
const res = await fetchRetryLogById(row.id!);
detailData.value = (res.data as Api.RetryTask.RetryTask) || null;
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<RetryTaskSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
<RetryLogSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
<NCard
:title="$t('page.retryTask.title')"
:title="$t('page.retryLog.title')"
:bordered="false"
size="small"
class="sm:flex-1-hidden card-wrapper"
@ -328,36 +176,25 @@ onMounted(async () => {
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
:show-add="false"
@delete="handleBatchDelete"
@refresh="getData"
>
<template #addAfter>
<NButton size="small" ghost type="primary" @click="handleBatchAdd">
<template #icon>
<icon-ic-round-plus class="text-icon" />
</template>
{{ $t('common.batchAdd') }}
</NButton>
</template>
</TableHeaderOperation>
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
:flex-height="!appStore.isMobile"
:scroll-x="2000"
:scroll-x="962"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
<RetryTaskOperateDrawer v-model:visible="drawerVisible" :operate-type="operateType" @submitted="getData" />
<RetryTaskBatchAddDrawer v-model:visible="batchAddDrawerVisible" @submitted="getData" />
<RetryTaskDetailDrawerVue v-model:visible="detailVisible" :row-data="detailData" />
</NCard>
<RetryLogDetailDrawer v-model:visible="detailVisible" :row-data="detailData" />
</div>
</template>

View File

@ -1,9 +1,7 @@
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { $t } from '@/locales';
import { tagColor } from '@/utils/common';
import { retryStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
import { fetchRetryLogList } from '@/service/api/log';
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
defineOptions({
name: 'SceneDetailDrawer'
@ -11,102 +9,43 @@ defineOptions({
interface Props {
/** row data */
rowData?: Api.Retry.Retry | null;
rowData?: Api.RetryTask.RetryTask | null;
}
const props = defineProps<Props>();
defineProps<Props>();
const visible = defineModel<boolean>('visible', {
default: false
});
const logList = ref<Api.JobLog.JobMessage[]>([]);
const interval = ref<NodeJS.Timeout>();
const controller = new AbortController();
const finished = ref<boolean>(false);
let startId = '0';
let fromIndex: number = 0;
async function getLogList() {
const { data: logData, error } = await fetchRetryLogList({
groupName: props.rowData!.groupName,
retryTaskId: props.rowData!.id! as any,
startId,
fromIndex,
size: 50
});
if (!error) {
finished.value = logData.finished;
startId = logData.nextStartId;
fromIndex = logData.fromIndex;
if (logData.message) {
logList.value.push(...logData.message);
logList.value.sort((a, b) => Number.parseInt(a.time_stamp, 10) - Number.parseInt(b.time_stamp, 10));
}
if (!finished.value) {
clearTimeout(interval.value);
interval.value = setTimeout(getLogList, 1000);
}
}
}
const handleUpdateTab = async (value: number) => {
if (value === 1 && logList.value.length === 0) {
await getLogList();
}
};
const stopLog = () => {
finished.value = true;
controller.abort();
clearTimeout(interval.value);
interval.value = undefined;
};
onBeforeUnmount(() => {
stopLog();
});
</script>
<template>
<OperateDrawer v-model="visible" :title="$t('page.retryTask.detail')">
<NTabs type="segment" animated @update:value="handleUpdateTab">
<DetailDrawer v-model="visible" :title="$t('page.retryLog.detail')" :width="['50%', '90%']">
<NTabs type="segment" animated>
<NTabPane :name="0" :tab="$t('page.log.info')">
<NDescriptions label-placement="top" bordered :column="2">
<NDescriptionsItem :label="$t('page.retryTask.groupName')" :span="2">
<NDescriptionsItem :label="$t('page.retryLog.groupName')" :span="2">
{{ rowData?.groupName }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.sceneName')" :span="2">
<NDescriptionsItem :label="$t('page.retryLog.sceneName')" :span="2">
{{ rowData?.sceneName }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.nextTriggerAt')" :span="1">
{{ rowData?.nextTriggerAt }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.retryCount')" :span="1">
{{ rowData?.retryCount }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.retryStatus')" :span="1">
<NTag :type="tagColor(rowData?.retryStatus!)">
{{ $t(retryStatusTypeRecord[rowData?.retryStatus!]) }}
<NDescriptionsItem :label="$t('page.retryLog.retryStatus')" :span="1">
<NTag :type="tagColor(rowData?.taskStatus!)">
{{ $t(retryTaskStatusTypeRecord[rowData?.taskStatus!]) }}
</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.taskType')" :span="1">
<NDescriptionsItem :label="$t('page.retryLog.taskType')" :span="1">
<NTag :type="tagColor(rowData?.taskType!)">{{ $t(retryTaskTypeRecord[rowData?.taskType!]) }}</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.bizNo')" :span="2">{{ rowData?.bizNo }}</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.idempotentId')" :span="2">
{{ rowData?.idempotentId }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.executorName')" :span="2">
{{ rowData?.executorName }}
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.retryTask.argsStr')" :span="2">{{ rowData?.argsStr }}</NDescriptionsItem>
<NDescriptionsItem :label="$t('common.createDt')" :span="1">{{ rowData?.createDt }}</NDescriptionsItem>
<NDescriptionsItem :label="$t('common.updateDt')" :span="1">{{ rowData?.updateDt }}</NDescriptionsItem>
<NDescriptionsItem :label="$t('common.createDt')">{{ rowData?.createDt }}</NDescriptionsItem>
</NDescriptions>
</NTabPane>
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
<LogDrawer :drawer="false" type="retry" :task-data="rowData!" />
</NTabPane>
</NTabs>
</OperateDrawer>
</DetailDrawer>
</template>
<style scoped>

View File

@ -4,9 +4,10 @@ import { translateOptions } from '@/utils/common';
import { retryStatusTypeOptions } from '@/constants/business';
import SelectGroup from '@/components/common/select-group.vue';
import SelectScene from '@/components/common/select-scene.vue';
import DatetimeRange from '@/components/common/datetime-range.vue';
defineOptions({
name: 'RetryTaskSearch'
name: 'RetryLogSearch'
});
interface Emits {
@ -16,7 +17,7 @@ interface Emits {
const emit = defineEmits<Emits>();
const model = defineModel<Api.Retry.RetrySearchParams>('model', { required: true });
const model = defineModel<Api.RetryTask.RetryTaskSearchParams>('model', { required: true });
function reset() {
emit('reset');
@ -28,30 +29,38 @@ function search() {
</script>
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.groupName')" path="groupName" class="pr-24px">
<SearchForm btn-span="24 xl:3" :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.sceneName')" path="sceneName" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.sceneName')" path="sceneName" class="pr-24px">
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" clearable />
</NFormItemGi>
<!-- <NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.uniqueId')" path="uniqueId" class="pr-24px">-->
<!-- <NInput v-model:value="model.uniqueId" :placeholder="$t('page.retryTask.form.uniqueId')" clearable />-->
<!-- <NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.UniqueId')" path="UniqueId" class="pr-24px">-->
<!-- <NInput v-model:value="model.uniqueId" :placeholder="$t('page.retryLog.form.UniqueId')" clearable />-->
<!-- </NFormItemGi>-->
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.idempotentId')" path="idempotentId" class="pr-24px">
<NInput v-model:value="model.idempotentId" :placeholder="$t('page.retryTask.form.idempotentId')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.bizNo')" path="bizNo" class="pr-24px">
<NInput v-model:value="model.bizNo" :placeholder="$t('page.retryTask.form.bizNo')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.retryStatus')" path="retryStatus" class="pr-24px">
<!-- <NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.idempotentId')" path="idempotentId" class="pr-24px">-->
<!-- <NInput v-model:value="model.idempotentId" :placeholder="$t('page.retryLog.form.idempotentId')" clearable />-->
<!-- </NFormItemGi>-->
<!-- <NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.bizNo')" path="bizNo" class="pr-24px">-->
<!-- <NInput v-model:value="model.bizNo" :placeholder="$t('page.retryLog.form.bizNo')" clearable />-->
<!-- </NFormItemGi>-->
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.retryStatus')" path="taskBatchStatus" class="pr-24px">
<NSelect
v-model:value="model.retryStatus"
v-model:value="model.taskStatus"
:placeholder="$t('page.retryTask.form.retryStatus')"
:options="translateOptions(retryStatusTypeOptions)"
clearable
/>
</NFormItemGi>
<NFormItemGi
span="24 s:24 m:15 l:12 xl:9"
:label="$t('page.common.createTime')"
path="datetimeRange"
class="pr-24px"
>
<DatetimeRange v-model:value="model.datetimeRange!" />
</NFormItemGi>
</SearchForm>
</template>