diff --git a/apps/admin-web/src/components/GeneratedArticleLinksDrawer.vue b/apps/admin-web/src/components/GeneratedArticleLinksDrawer.vue new file mode 100644 index 0000000..46d92cc --- /dev/null +++ b/apps/admin-web/src/components/GeneratedArticleLinksDrawer.vue @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + {{ articleTitle(item, index) }} + + + + {{ getGenerateStatusMeta(item.generate_status).label }} + + + + {{ formatDateTime(item.created_at) }} + + + + + {{ t('custom.instant.openArticle') }} + + + + + + + + + + + diff --git a/apps/admin-web/src/components/InstantTaskTab.vue b/apps/admin-web/src/components/InstantTaskTab.vue index d2bd67c..0013911 100644 --- a/apps/admin-web/src/components/InstantTaskTab.vue +++ b/apps/admin-web/src/components/InstantTaskTab.vue @@ -7,7 +7,7 @@ import type { Dayjs } from 'dayjs' import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' -import ArticleDetailDrawer from '@/components/ArticleDetailDrawer.vue' +import GeneratedArticleLinksDrawer from '@/components/GeneratedArticleLinksDrawer.vue' import { instantTasksApi, promptRulesApi } from '@/lib/api' import { formatDateTime, getGenerateStatusMeta } from '@/lib/display' import { formatPublishPlatformList } from '@/lib/publish-platforms' @@ -17,8 +17,8 @@ const { t } = useI18n() const page = ref(1) const pageSize = ref(20) const createdRange = ref<[Dayjs, Dayjs] | null>(null) -const detailOpen = ref(false) -const selectedArticleId = ref(null) +const articleLinksOpen = ref(false) +const selectedTask = ref(null) const draftFilters = reactive<{ prompt_rule_id?: number @@ -54,6 +54,7 @@ const statusOptions = computed(() => [ { label: getGenerateStatusMeta('queued').label, value: 'queued' }, { label: getGenerateStatusMeta('running').label, value: 'running' }, { label: getGenerateStatusMeta('completed').label, value: 'completed' }, + { label: getGenerateStatusMeta('partial_success').label, value: 'partial_success' }, { label: getGenerateStatusMeta('failed').label, value: 'failed' }, ]) @@ -135,12 +136,14 @@ function handleTableChange(nextPage: number, nextPageSize: number): void { pageSize.value = nextPageSize } -function openArticle(task: InstantTaskItem): void { - if (!task.article_id) { +const selectedTaskArticles = computed(() => selectedTask.value?.articles ?? []) + +function openTaskArticles(task: InstantTaskItem): void { + if (!task.articles?.length) { return } - selectedArticleId.value = task.article_id - detailOpen.value = true + selectedTask.value = task + articleLinksOpen.value = true } function startPolling(): void { @@ -281,8 +284,8 @@ onBeforeUnmount(() => { shape="circle" size="small" class="action-btn action-eye" - :disabled="!record.article_id" - @click="openArticle(record)" + :disabled="!record.articles?.length" + @click="openTaskArticles(record)" > @@ -293,10 +296,9 @@ onBeforeUnmount(() => { - diff --git a/apps/admin-web/src/components/ScheduleTaskTab.vue b/apps/admin-web/src/components/ScheduleTaskTab.vue index edff780..2b92dc8 100644 --- a/apps/admin-web/src/components/ScheduleTaskTab.vue +++ b/apps/admin-web/src/components/ScheduleTaskTab.vue @@ -2,6 +2,8 @@ import { DeleteOutlined, EditOutlined, + EyeOutlined, + LoadingOutlined, PauseCircleOutlined, PlayCircleOutlined, } from '@ant-design/icons-vue' @@ -9,12 +11,13 @@ import type { ScheduleTask, ScheduleTaskListParams } from '@geo/shared-types' import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query' import { message, type TableColumnsType } from 'ant-design-vue' import type { Dayjs } from 'dayjs' -import { computed, reactive, ref } from 'vue' +import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' +import GeneratedArticleLinksDrawer from '@/components/GeneratedArticleLinksDrawer.vue' import ScheduleTaskModal from '@/components/ScheduleTaskModal.vue' import { promptRulesApi, schedulesApi } from '@/lib/api' -import { formatCronExecutionTime, formatDateTime } from '@/lib/display' +import { formatDateTime, getGenerateStatusMeta, hasActiveGenerationStatus } from '@/lib/display' import { formatError } from '@/lib/errors' import { formatPublishPlatformList } from '@/lib/publish-platforms' @@ -26,6 +29,8 @@ const editingTask = ref(null) const page = ref(1) const pageSize = ref(20) const createdRange = ref<[Dayjs, Dayjs] | null>(null) +const articleLinksOpen = ref(false) +const selectedTask = ref(null) const draftFilters = reactive<{ prompt_rule_id?: number @@ -88,11 +93,16 @@ const columns = computed>(() => [ key: 'prompt_rule_name', width: 180, }, - { title: t('custom.instant.executionStatus'), dataIndex: 'status', key: 'status', width: 140 }, + { + title: t('custom.instant.executionStatus'), + dataIndex: 'generation_status', + key: 'generation_status', + width: 140, + }, { title: t('custom.instant.executionTime'), - dataIndex: 'cron_expr', - key: 'cron_expr', + dataIndex: 'execution_time', + key: 'execution_time', width: 170, }, { @@ -113,7 +123,7 @@ const columns = computed>(() => [ key: 'created_at', width: 170, }, - { title: t('common.actions'), key: 'actions', width: 140, fixed: 'right', align: 'right' }, + { title: t('common.actions'), key: 'actions', width: 176, fixed: 'right', align: 'right' }, ]) const removeMutation = useMutation({ @@ -165,6 +175,45 @@ function handleTableChange(nextPage: number, nextPageSize: number): void { page.value = nextPage pageSize.value = nextPageSize } + +const selectedTaskArticles = computed(() => selectedTask.value?.generated_articles ?? []) + +function openTaskArticles(task: ScheduleTask): void { + selectedTask.value = task + articleLinksOpen.value = true +} + +let pollingTimer: number | null = null + +function startPolling(): void { + if (pollingTimer !== null) return + pollingTimer = window.setInterval(() => { + void listQuery.refetch() + }, 3000) +} + +function stopPolling(): void { + if (pollingTimer === null) return + window.clearInterval(pollingTimer) + pollingTimer = null +} + +watch( + () => listQuery.data.value?.items ?? [], + (items) => { + const hasActive = items.some((item) => hasActiveGenerationStatus(item.generation_status)) + if (hasActive) { + startPolling() + } else { + stopPolling() + } + }, + { immediate: true }, +) + +onBeforeUnmount(() => { + stopPolling() +}) @@ -240,17 +289,16 @@ function handleTableChange(nextPage: number, nextPageSize: number): void { {{ record.prompt_rule_name || '--' }} - - - {{ - record.status === 'enabled' - ? t('custom.schedule.enabled') - : t('custom.schedule.disabled') - }} + + + + + {{ getGenerateStatusMeta(record.generation_status).label }} + - - {{ formatCronExecutionTime(record.cron_expr) }} + + {{ formatDateTime(record.execution_time) }} @@ -269,6 +317,19 @@ function handleTableChange(nextPage: number, nextPageSize: number): void { + + + + + + + + @@ -362,6 +427,12 @@ function handleTableChange(nextPage: number, nextPageSize: number): void { justify-content: flex-end; } +.task-tab__status { + display: inline-flex; + align-items: center; + gap: 4px; +} + @media (max-width: 1400px) { .task-tab__filters { grid-template-columns: repeat(2, minmax(260px, 1fr)); diff --git a/apps/admin-web/src/i18n/messages/en-US.ts b/apps/admin-web/src/i18n/messages/en-US.ts index ce56232..09dacee 100644 --- a/apps/admin-web/src/i18n/messages/en-US.ts +++ b/apps/admin-web/src/i18n/messages/en-US.ts @@ -664,6 +664,8 @@ const enUS = { generating: 'Generating', running: 'Running', completed: 'Completed', + partial_success: 'Partially successful', + partial_completed: 'Partially successful', failed: 'Failed', }, publish: { @@ -1589,6 +1591,9 @@ const enUS = { executionStatus: 'Execution status', executionTime: 'Execution time', createdTime: 'Created time', + generatedArticles: 'Generated article links', + articleLinksEmpty: 'No generated articles', + openArticle: 'Open article', empty: 'No instant tasks yet.', }, filters: { diff --git a/apps/admin-web/src/i18n/messages/zh-CN.ts b/apps/admin-web/src/i18n/messages/zh-CN.ts index 5d6481f..b527dc4 100644 --- a/apps/admin-web/src/i18n/messages/zh-CN.ts +++ b/apps/admin-web/src/i18n/messages/zh-CN.ts @@ -634,6 +634,8 @@ const zhCN = { generating: "生成中", running: "运行中", completed: "已完成", + partial_success: "部分成功", + partial_completed: "部分成功", failed: "生成失败", }, publish: { @@ -1508,6 +1510,9 @@ const zhCN = { executionStatus: "执行状态", executionTime: "执行时间", createdTime: "创建时间", + generatedArticles: "生成文章链接", + articleLinksEmpty: "暂无生成文章", + openArticle: "打开文章", empty: "还未创建即时任务,暂无相关数据~", }, filters: { diff --git a/apps/admin-web/src/lib/display.ts b/apps/admin-web/src/lib/display.ts index 8016aa2..13352e2 100644 --- a/apps/admin-web/src/lib/display.ts +++ b/apps/admin-web/src/lib/display.ts @@ -8,6 +8,8 @@ const generateStatusMap: Record = { generating: { label: 'status.generate.generating', color: 'processing' }, running: { label: 'status.generate.running', color: 'processing' }, completed: { label: 'status.generate.completed', color: 'success' }, + partial_success: { label: 'status.generate.partial_success', color: 'warning' }, + partial_completed: { label: 'status.generate.partial_completed', color: 'warning' }, failed: { label: 'status.generate.failed', color: 'error' }, } diff --git a/apps/admin-web/src/views/ArticleEditorView.vue b/apps/admin-web/src/views/ArticleEditorView.vue index fca6fd2..5c5db72 100644 --- a/apps/admin-web/src/views/ArticleEditorView.vue +++ b/apps/admin-web/src/views/ArticleEditorView.vue @@ -344,6 +344,12 @@ async function cleanupEmptyFreeCreate(): Promise { } function navigateBack(): void { + const returnTo = normalizeReturnTo(route.query.return_to) + if (returnTo) { + void router.push(returnTo) + return + } + if (window.history.length > 1) { void router.back() return @@ -352,6 +358,18 @@ function navigateBack(): void { void router.push('/articles/templates') } +function normalizeReturnTo(value: unknown): string | null { + const raw = Array.isArray(value) ? value[0] : value + if (typeof raw !== 'string') { + return null + } + const trimmed = raw.trim() + if (!trimmed || !trimmed.startsWith('/') || trimmed.startsWith('//')) { + return null + } + return trimmed +} + function handleStay(): void { leaveModalOpen.value = false } diff --git a/apps/admin-web/src/views/CustomGenerateView.vue b/apps/admin-web/src/views/CustomGenerateView.vue index 33ce7c7..097b8a8 100644 --- a/apps/admin-web/src/views/CustomGenerateView.vue +++ b/apps/admin-web/src/views/CustomGenerateView.vue @@ -1,7 +1,8 @@