diff --git a/src/components/task/TaskOutputCard.vue b/src/components/task/TaskOutputCard.vue
new file mode 100644
index 0000000..66d8c62
--- /dev/null
+++ b/src/components/task/TaskOutputCard.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
{{ $t('taskDetail.output') }}
+
+
+
+
{{ reviewVerdictLabel }}
+
{{ parsedOutput.review.summary }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/TaskDetail.vue b/src/views/TaskDetail.vue
index 939198c..0e0c201 100644
--- a/src/views/TaskDetail.vue
+++ b/src/views/TaskDetail.vue
@@ -125,23 +125,8 @@
{{ $t('taskDetail.workflowDef') }}
{{ task.workflow_def_id ?? '—' }}
-
-
-
-
{{ $t('taskDetail.output') }}
-
-
-
-
{{ reviewVerdictLabel }}
-
{{ parsedOutput.review.summary }}
-
-
-
-
-
-
+
+
{{ $t('taskDetail.createdAt') }}
{{ formatDate(task.created_at) }}
@@ -172,6 +157,7 @@ import {
priorityClass,
} from '../constants/project'
import type { TaskRecord, ProjectRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types'
+import TaskOutputCard from '@/components/task/TaskOutputCard.vue'
const { t } = useI18n()
const route = useRoute()
@@ -210,56 +196,8 @@ const { rendered: renderedDesc, ensureLoaded } = useRendered(
() => task.value?.description ?? '',
)
-// ============================================================
-// F-AiNodeSelfReview: output_json 解析 + AI 产出 Markdown 渲染
-// ------------------------------------------------------------
-// output_json schema(与 df-nodes ai_node.rs 写产出现对齐):
-// { text, model?, usage?, review?: { verdict: 'pass'|'fail'|'unknown', summary?, suggestions? } }
-// parsedOutput try/catch: JSON.parse 边界必要兜底(LLM 自审输出可能不合规/老数据格式漂移),
-// 解析失败返回 null → v-if 守卫整块不渲染(不裸显报错)。
-interface TaskOutputReview {
- verdict?: string
- summary?: string
- suggestions?: unknown
-}
-interface TaskOutput {
- text?: string
- model?: string
- usage?: unknown
- review?: TaskOutputReview
-}
-const parsedOutput = computed(() => {
- const raw = task.value?.output_json
- if (!raw) return null
- try {
- return JSON.parse(raw) as TaskOutput
- } catch {
- return null
- }
-})
-// AI 产出正文渲染(复用模块单例渲染器,与 renderedDesc 共享 marked/DOMPurify/缓存)
-const { rendered: renderedOutput } = useRendered(
- () => parsedOutput.value?.text ?? '',
-)
-// review.verdict 红绿标:pass→绿(success),fail→红(danger),未知/缺失→灰(dim)
-const reviewVerdictClass = computed(() => {
- const v = parsedOutput.value?.review?.verdict
- if (v === 'pass') return 'review-pass'
- if (v === 'fail') return 'review-fail'
- return 'review-unknown'
-})
-// review.verdict 标签(pass/fail/unknown 走 i18n,异常值兜底 unknown)
-const reviewVerdictLabel = computed(() => {
- const v = parsedOutput.value?.review?.verdict
- if (v === 'pass') return t('taskDetail.review.pass')
- if (v === 'fail') return t('taskDetail.review.fail')
- return t('taskDetail.review.unknown')
-})
-// review.suggestions 列表(数组守卫,非数组时返回空避免 v-for 报错)
-const reviewSuggestions = computed(() => {
- const s = parsedOutput.value?.review?.suggestions
- return Array.isArray(s) ? s.map(String) : []
-})
+// F-AiNodeSelfReview: output_json 解析 + AI 产出/自审渲染已抽至 TaskOutputCard 子组件
+// (components/task/TaskOutputCard.vue),父级只传 output-json prop,零行为变更。
const projectName = computed(() => {
const p = projects.value.find(p => p.id === task.value?.project_id)
@@ -640,34 +578,7 @@ onBeforeUnmount(() => {
/* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li) */
.info-item .value.description { line-height: 1.5; }
-/* ===== F-AiNodeSelfReview: output_json 区块(产出 + 自审结论卡) ===== */
-.output-block .output-body {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-/* 自审结论卡:verdict 红绿标(pass 绿 / fail 红 / unknown 灰),复用全局 --df-success/danger token */
-.review-card {
- display: flex;
- flex-direction: column;
- gap: 4px;
- padding: 8px 12px;
- border-radius: var(--df-radius-sm);
- border: 0.5px solid var(--df-border);
- font-size: 13px;
-}
-.review-pass { background: var(--df-success-bg); border-color: rgba(61,219,160,0.3); }
-.review-fail { background: var(--df-danger-bg); border-color: rgba(240,101,101,0.3); }
-.review-unknown { background: var(--df-bg-card); }
-.review-verdict { font-weight: 600; }
-.review-pass .review-verdict { color: var(--df-success); }
-.review-fail .review-verdict { color: var(--df-danger); }
-.review-unknown .review-verdict { color: var(--df-text-dim); }
-.review-summary { color: var(--df-text-secondary); line-height: 1.5; }
-.review-suggestions { margin: 4px 0 0; padding-left: 20px; color: var(--df-text-secondary); }
-.review-suggestions li { line-height: 1.6; }
-/* AI 产出正文(继承全局 .ai-md 块级样式,line-height 对齐描述) */
-.output-text { line-height: 1.5; }
+/* F-AiNodeSelfReview output_json 区块样式已随子组件移至 TaskOutputCard.vue */
/* ===== 任务描述 Markdown 渲染(B-24,基础样式收敛至全局 ai-md.css) ===== */