优化: 所有剩余UI/UX待办一批完成(持久化+AuditLog+解耦+total+原12大改+P2)
持久化(P1-c):新建 usePersistedRef composable,Tasks/AuditLog/ProjectDetail 等接入 localStorage
AuditLog(P1-d):后端 list_tool_executions 加 WHERE 筛选+返 {items,total,has_more},前端对接+长列折叠+筛选持久化
数据源解耦(P1-g):ProjectDetail projectTasks 按 project_id 独立加载 + ChatInput @项目联想独立加载(不读 store.tasks 当前页)
GitChanges(12a):后端 get_module_commits 加 git rev-list --count 返 total,前端显真实总数
原12大改:Dashboard 统计卡压底行(1)/Projects 列表卡片视图(2)/project_event 埋点排序(3)/TaskDetail 重设计(4)/IdeaDetail 重设计(5)/KnowledgeDetail 重设计(6)/界面持久化+侧栏Ctrl+B+审批数字键(7)/ProjectDetail 三栏改两栏(10)
P2打磨:文件浏览器(FileTree去重/FilePreview行号.md Diff/selectedFilePath归位)/settings反馈(假保存/端口校验)/AI会话(try-catch/scrollIntoView)/后端计数(move_queue事件/timeline total/workflow分页/import_batch分块)/杂项(TopBar/ConfirmDialog键盘/CIStatus i18n/ToolResultBody/ModuleNode/ApprovalDialog全选)
This commit is contained in:
+247
-138
@@ -1,15 +1,42 @@
|
||||
<template>
|
||||
<div class="task-detail">
|
||||
<!-- 页面头部 -->
|
||||
<header class="page-header">
|
||||
<!-- 页面头部(吸顶):标题 + 状态 + 优先级 在左,推进按钮在右(滚动始终可见)。
|
||||
Problem4 ① 推进按钮从面板内移到 header 右侧,与 refresh 同区。-->
|
||||
<header class="page-header task-detail-header">
|
||||
<div class="header-left">
|
||||
<router-link to="/tasks" class="back-link">{{ $t('taskDetail.backToList') }}</router-link>
|
||||
<h1>{{ task?.title ?? '...' }}</h1>
|
||||
<span v-if="task" class="status-tag" :class="taskStatusClass(task.status)">{{ $t(taskStatusLabel(task.status)) }}</span>
|
||||
<!-- F-04 review 轮次显示(>0 才显示,in_review→in_progress / testing→in_review 退回时后端 +1) -->
|
||||
<span v-if="task && task.review_rounds > 0" class="review-rounds-badge">
|
||||
{{ $t('taskDetail.reviewRounds', { n: task.review_rounds }) }}
|
||||
</span>
|
||||
<span v-if="task" class="priority-badge" :class="priorityClass(task.priority)">{{ priorityLabel(task.priority) }}</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-ghost" type="button" @click="refresh">{{ $t('taskDetail.refresh') }}</button>
|
||||
<button class="btn btn-ghost btn-sm" type="button" @click="refresh">{{ $t('taskDetail.refresh') }}</button>
|
||||
<!-- F-260616-06 ①-1 / B-41 工作流推进按钮(联动任务,按 target_status 后端自动选推进链模板)
|
||||
与手动 advance 并存:仅当当前态的 primary 前向推进目标 ∈ {in_progress, testing, done}
|
||||
(即 template_for 有对应模板)时显示。手动 advance 仍走 advance_task 直 IPC;工作流推进
|
||||
走 run_workflow,经 AiNode/HumanNode 执行后再由后端回调推进任务。
|
||||
独立 loading 标志 wfAdvancing 与 advancing 互不干扰。 -->
|
||||
<button
|
||||
v-if="wfAdvanceAction"
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
:disabled="wfAdvancing || advancing"
|
||||
@click="handleWorkflowAdvance(wfAdvanceAction.target)"
|
||||
>{{ wfAdvancing ? $t('taskDetail.workflowAdvancing') : $t('taskDetail.workflowAdvance') }}</button>
|
||||
<!-- F-05 推进按钮:按当前 status 显示状态机合法下一态(todo/done/cancelled 终态无按钮) -->
|
||||
<button
|
||||
v-for="act in advanceActions"
|
||||
:key="act.target"
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="act.variant"
|
||||
:disabled="advancing"
|
||||
@click="handleAdvance(act.target)"
|
||||
>{{ advancing ? $t('taskDetail.advancing') : $t(act.label) }}</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -17,144 +44,111 @@
|
||||
<div v-if="loading" class="empty-hint">{{ $t('taskDetail.loading') }}</div>
|
||||
<div v-else-if="errorMsg" class="empty-hint error-hint">⚠ {{ errorMsg }}</div>
|
||||
|
||||
<!-- 主体 -->
|
||||
<!-- 主体:两栏布局(Problem4 ⑤)。左栏=描述+关联信息+时间戳,右栏=任务产出+工作流。
|
||||
窄屏自动单列。-->
|
||||
<div v-else-if="task" class="detail-grid">
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<h2>{{ $t('taskDetail.infoTitle') }}</h2>
|
||||
</div>
|
||||
<div class="task-info">
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.title') }}</span>
|
||||
<span class="value">{{ task.title }}</span>
|
||||
</div>
|
||||
<div class="info-item info-block">
|
||||
<span class="label">{{ $t('taskDetail.description') }}</span>
|
||||
<!-- ============ 左栏 ============ -->
|
||||
<div class="left-column">
|
||||
<!-- 描述(可折叠:Problem4 ② 超 400px 显展开按钮) -->
|
||||
<section v-if="task.description" class="panel">
|
||||
<div class="panel-header"><h2>{{ $t('taskDetail.description') }}</h2></div>
|
||||
<div class="description-wrap" :style="descWrapStyle">
|
||||
<!-- B-24:任务描述 Markdown 渲染(## 标题 / - 列表 等),v-html 经 useMarkdown DOMPurify sanitize -->
|
||||
<span
|
||||
class="value description ai-md"
|
||||
v-if="task.description"
|
||||
v-html="renderedDesc"
|
||||
></span>
|
||||
<span v-else class="value description">—</span>
|
||||
<span ref="descEl" class="value description ai-md" v-html="renderedDesc"></span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.status') }}</span>
|
||||
<span class="value">
|
||||
<span class="status-tag" :class="taskStatusClass(task.status)">{{ $t(taskStatusLabel(task.status)) }}</span>
|
||||
<!-- F-04 review 轮次显示(>0 才显示,in_review→in_progress / testing→in_review 退回时后端 +1) -->
|
||||
<span v-if="task.review_rounds > 0" class="review-rounds-badge">
|
||||
{{ $t('taskDetail.reviewRounds', { n: task.review_rounds }) }}
|
||||
<button
|
||||
v-if="descCollapsible"
|
||||
class="btn btn-ghost btn-sm desc-toggle"
|
||||
type="button"
|
||||
@click="descExpanded = !descExpanded"
|
||||
>{{ descExpanded ? $t('taskDetail.collapse') : $t('taskDetail.expand') }}</button>
|
||||
<!-- 折叠态淡出渐变蒙层(展开态不显) -->
|
||||
<div v-if="descCollapsible && !descExpanded" class="desc-fade"></div>
|
||||
</section>
|
||||
|
||||
<!-- 关联信息(Problem4 ③④ 去重 + 空值不显行)。
|
||||
标题/状态/优先级已在 header 显示,这里不重复。-->
|
||||
<section class="panel">
|
||||
<div class="panel-header"><h2>{{ $t('taskDetail.relatedTitle') }}</h2></div>
|
||||
<div class="task-info">
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.project') }}</span>
|
||||
<span class="value">
|
||||
<router-link v-if="task.project_id" :to="`/projects/${task.project_id}`" class="project-link">
|
||||
{{ projectName }}
|
||||
</router-link>
|
||||
<span v-else>—</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- F-260619-01:关联灵感(1对1 单向,idea_id 解析为友好 title,非裸 id) -->
|
||||
<div v-if="task.idea_id" class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.relatedIdea') }}</span>
|
||||
<span class="value">
|
||||
<router-link :to="`/ideas/${task.idea_id}`" class="project-link">{{ ideaTitle }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 分支(有值才显) -->
|
||||
<div v-if="task.branch_name" class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.branch') }}</span>
|
||||
<span class="value">
|
||||
<span class="branch-tag"><span class="branch-icon">⑂</span>{{ task.branch_name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 负责人(空值不显行,Problem4 ③) -->
|
||||
<div v-if="task.assignee" class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.assignee') }}</span>
|
||||
<span class="value">{{ task.assignee }}</span>
|
||||
</div>
|
||||
<!-- 基础分支(空值不显行) -->
|
||||
<div v-if="task.base_branch" class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.baseBranch') }}</span>
|
||||
<span class="value mono">{{ task.base_branch }}</span>
|
||||
</div>
|
||||
<!-- 工作流定义(空值不显行) -->
|
||||
<div v-if="task.workflow_def_id" class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.workflowDef') }}</span>
|
||||
<span class="value mono">{{ task.workflow_def_id }}</span>
|
||||
</div>
|
||||
<!-- 创建/更新时间:最底小字(Problem4 ⑥) -->
|
||||
<div class="info-item timestamps">
|
||||
<span class="timestamp">{{ $t('taskDetail.createdAt') }}: {{ formatDate(task.created_at) }}</span>
|
||||
<span class="timestamp">{{ $t('taskDetail.updatedAt') }}: {{ formatDate(task.updated_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ============ 右栏:任务产出 + 工作流进度 ============ -->
|
||||
<div class="right-column">
|
||||
<!-- F-AiNodeSelfReview: 任务产出 + AI 自审结果展示(抽至 TaskOutputCard 子组件,零行为变更) -->
|
||||
<section class="panel">
|
||||
<div class="panel-header"><h2>{{ $t('taskDetail.output') }}</h2></div>
|
||||
<!-- 无产出时占位(任务未跑过 AiNode 产出),右栏保留产出栏框架便于后续产出出现即填入 -->
|
||||
<div v-if="!task.output_json" class="empty-hint output-empty">—</div>
|
||||
<TaskOutputCard v-else :output-json="task.output_json" />
|
||||
</section>
|
||||
|
||||
<!-- B-41 工作流推进轻量进度(从面板内 advance-row 提取,推进按钮已移 header) -->
|
||||
<section v-if="wfAdvancing || wfProgressHint" class="panel">
|
||||
<div class="panel-header"><h2>{{ $t('taskDetail.workflowAdvanceTitle') }}</h2></div>
|
||||
<div class="wf-progress">
|
||||
<span v-if="wfRunningNode">{{ $t('taskDetail.workflowStepRunning', { node: wfRunningNode }) }}</span>
|
||||
<span v-else-if="wfDoneTotal > 0" class="wf-progress-count">
|
||||
{{ $t('taskDetail.workflowStepsProgress', { done: wfDoneCount, total: wfDoneTotal }) }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- F-05 推进按钮:按当前 status 显示状态机合法下一态(todo/done/cancelled 终态无按钮) -->
|
||||
<div v-if="advanceActions.length" class="info-item info-block advance-row">
|
||||
<span class="label">{{ $t('taskDetail.advanceTitle') }}</span>
|
||||
<div class="advance-actions">
|
||||
<button
|
||||
v-for="act in advanceActions"
|
||||
:key="act.target"
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="act.variant"
|
||||
:disabled="advancing"
|
||||
@click="handleAdvance(act.target)"
|
||||
>{{ advancing ? $t('taskDetail.advancing') : $t(act.label) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- F-260616-06 ①-1 / B-41 工作流推进按钮(联动任务,按 target_status 后端自动选推进链模板)
|
||||
与手动 advance 并存:仅当当前态的 primary 前向推进目标 ∈ {in_progress, testing, done}
|
||||
(即 template_for 有对应模板)时显示。手动 advance 仍走 advance_task 直 IPC;工作流推进
|
||||
走 run_workflow,经 AiNode/HumanNode 执行后再由后端回调推进任务。
|
||||
独立 loading 标志 wfAdvancing 与 advancing 互不干扰。 -->
|
||||
<div v-if="wfAdvanceAction" class="info-item info-block advance-row">
|
||||
<span class="label">{{ $t('taskDetail.workflowAdvanceTitle') }}</span>
|
||||
<div class="advance-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
:disabled="wfAdvancing || advancing"
|
||||
@click="handleWorkflowAdvance(wfAdvanceAction.target)"
|
||||
>{{ wfAdvancing ? $t('taskDetail.workflowAdvancing') : $t('taskDetail.workflowAdvance') }}</button>
|
||||
</div>
|
||||
<!-- B-41 轻量进度(监听 workflow-event: NodeStarted/NodeCompleted/WorkflowFailed)
|
||||
完整进度条/预估留后续批;完成/失败后任务态由后端回调推进,df-data-changed 自动刷新 task -->
|
||||
<div v-if="wfAdvancing || wfProgressHint" class="wf-progress">
|
||||
<span v-if="wfRunningNode">{{ $t('taskDetail.workflowStepRunning', { node: wfRunningNode }) }}</span>
|
||||
<span v-else-if="wfDoneTotal > 0" class="wf-progress-count">
|
||||
{{ $t('taskDetail.workflowStepsProgress', { done: wfDoneCount, total: wfDoneTotal }) }}
|
||||
</span>
|
||||
<span v-if="wfCompletedHint" class="wf-progress-hint">{{ $t('taskDetail.workflowCompletedHint') }}</span>
|
||||
<span v-if="wfFailedHint" class="wf-progress-hint wf-progress-hint-fail">{{ $t('taskDetail.workflowFailedHint') }}</span>
|
||||
</div>
|
||||
<span v-if="wfCompletedHint" class="wf-progress-hint">{{ $t('taskDetail.workflowCompletedHint') }}</span>
|
||||
<span v-if="wfFailedHint" class="wf-progress-hint wf-progress-hint-fail">{{ $t('taskDetail.workflowFailedHint') }}</span>
|
||||
</div>
|
||||
<!-- 工作流 DAG 结构 -->
|
||||
<WorkflowDagDisplay v-if="wfDagJson" :dag-json="wfDagJson" :node-statuses="wfNodeStatuses" />
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.priority') }}</span>
|
||||
<span class="value">
|
||||
<span class="priority-badge" :class="priorityClass(task.priority)">{{ priorityLabel(task.priority) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.project') }}</span>
|
||||
<span class="value">
|
||||
<router-link v-if="task.project_id" :to="`/projects/${task.project_id}`" class="project-link">
|
||||
{{ projectName }}
|
||||
</router-link>
|
||||
<span v-else>—</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- F-260619-01:关联灵感(1对1 单向,idea_id 解析为友好 title,非裸 id) -->
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.relatedIdea') }}</span>
|
||||
<span class="value">
|
||||
<router-link v-if="task.idea_id" :to="`/ideas/${task.idea_id}`" class="project-link">
|
||||
{{ ideaTitle }}
|
||||
</router-link>
|
||||
<span v-else>—</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.branch') }}</span>
|
||||
<span class="value">
|
||||
<span v-if="task.branch_name" class="branch-tag">
|
||||
<span class="branch-icon">⑂</span>{{ task.branch_name }}
|
||||
</span>
|
||||
<span v-else>—</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.assignee') }}</span>
|
||||
<span class="value">{{ task.assignee ?? '—' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.baseBranch') }}</span>
|
||||
<span class="value">{{ task.base_branch ?? '—' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.workflowDef') }}</span>
|
||||
<span class="value mono">{{ task.workflow_def_id ?? '—' }}</span>
|
||||
</div>
|
||||
<!-- F-AiNodeSelfReview: 任务产出 + AI 自审结果展示(抽至 TaskOutputCard 子组件,零行为变更) -->
|
||||
<TaskOutputCard :output-json="task.output_json" />
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.createdAt') }}</span>
|
||||
<span class="value">{{ formatDate(task.created_at) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.updatedAt') }}</span>
|
||||
<span class="value">{{ formatDate(task.updated_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
@@ -223,6 +217,37 @@ const { rendered: renderedDesc, ensureLoaded } = useRendered(
|
||||
() => task.value?.description ?? '',
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// Problem4 ②:描述可折叠 — 超 400px 显展开按钮(测真实 DOM 高度)
|
||||
// ------------------------------------------------------------
|
||||
// 不用字符数近似(渲染后 Markdown 列表/标题行高不一,字符数与 px 无线性关系),
|
||||
// 改用 descEl 实测 scrollHeight:首次渲染 + task 切换后 nextTick 测量。
|
||||
// descCollapsible=true 时才显按钮;默认折叠(!descExpanded)即首次进入截 400px。
|
||||
// 折叠态 max-height 由 descWrapStyle 内联注入(展开态不设上限,自适应)。
|
||||
// .description-wrap 的 overflow:hidden + transition 让折叠/展开有过渡。
|
||||
// 蒙层 .desc-fade 暗示折叠态下方还有内容。
|
||||
const DESC_COLLAPSE_PX = 400
|
||||
const descEl = ref<HTMLElement | null>(null)
|
||||
const descExpanded = ref(false)
|
||||
const descOverflow = ref(false) // 真实高度是否超阈值(控制按钮显隐)
|
||||
|
||||
const descCollapsible = computed(() => descOverflow.value)
|
||||
|
||||
// 折叠态 max-height 内联(过渡友好):展开态不设上限(自适应)
|
||||
const descWrapStyle = computed(() =>
|
||||
descExpanded.value ? {} : { maxHeight: DESC_COLLAPSE_PX + 'px' },
|
||||
)
|
||||
|
||||
async function measureDescHeight() {
|
||||
// descEl 是 <span>(描述内容),自身无 max-height 限制 → scrollHeight 即其真实渲染高度,
|
||||
// 不受父 .description-wrap 的 max-height 裁剪影响(span 在父内若被裁,其 scrollHeight
|
||||
// 仍反映完整内容高)。直接读无需临时改父样式。
|
||||
await nextTick()
|
||||
const el = descEl.value
|
||||
if (!el) { descOverflow.value = false; return }
|
||||
descOverflow.value = el.scrollHeight > DESC_COLLAPSE_PX
|
||||
}
|
||||
|
||||
// F-AiNodeSelfReview: output_json 解析 + AI 产出/自审渲染已抽至 TaskOutputCard 子组件
|
||||
// (components/task/TaskOutputCard.vue),父级只传 output-json prop,零行为变更。
|
||||
|
||||
@@ -448,6 +473,11 @@ function refresh() {
|
||||
// 路由参数变化(id 变化)时重新加载
|
||||
watch(taskId, () => { load() })
|
||||
|
||||
// Problem4 ②:切换 task / 描述渲染完成 → 重置折叠态 + 重测高度。
|
||||
// renderedDesc 异步(Markdown 经 marked 渲染),内容变化后 nextTick 测真实 px。
|
||||
watch(() => task.value?.id, () => { descExpanded.value = false })
|
||||
watch(renderedDesc, () => { measureDescHeight() })
|
||||
|
||||
// B-260616-18: 数据变更联动刷新 unlistener(onMounted 注册,onBeforeUnmount 释放)
|
||||
// 对齐 AiChat _unlistenToolSlow 生命周期模式。本视图绕 store 直调 taskApi.get/projectApi.list,
|
||||
// 不享受 store 全局 df-data-changed 监听(该监听只刷 store.tasks 列表,不含本视图的当前 task 单体),
|
||||
@@ -492,15 +522,33 @@ onBeforeUnmount(() => {
|
||||
.task-detail { padding: 16px 20px 20px; }
|
||||
|
||||
/* page-header / btn / back-link 等已提取到 global.css 全局 */
|
||||
.header-left { display: flex; align-items: center; gap: 12px; }
|
||||
|
||||
/* F-05 推进操作区 */
|
||||
.advance-row .advance-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
.header-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
||||
.header-left h1 {
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Problem4 ① 吸顶 header(推进按钮始终可见) */
|
||||
.task-detail-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--df-bg);
|
||||
/* 留底部细分割线,与全局 page-header margin-bottom 协同(0.5px 边框规范) */
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 0.5px solid var(--df-border);
|
||||
/* 顶负 padding 抵消 .task-detail 的 16px 顶 padding,吸顶贴顶 */
|
||||
margin: -16px -20px 0;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
/* header-actions 内多个推进按钮 + refresh 排列(对齐全局 .header-actions gap:10px) */
|
||||
.task-detail-header .header-actions { flex-wrap: wrap; justify-content: flex-end; }
|
||||
|
||||
/* advance 操作区已移入 header-actions,旧 .advance-row 样式删除 */
|
||||
|
||||
/* B-41 工作流推进轻量进度提示 */
|
||||
.wf-progress {
|
||||
display: flex;
|
||||
@@ -552,7 +600,21 @@ onBeforeUnmount(() => {
|
||||
.priority-low { background: rgba(90,99,128,0.2); color: var(--df-text-dim); }
|
||||
|
||||
/* ===== 面板 ===== */
|
||||
.detail-grid { display: grid; grid-template-columns: 1fr; gap: var(--df-gap-grid); }
|
||||
/* Problem4 ⑤ 宽屏两栏:左栏(描述+关联信息+时间戳) / 右栏(任务产出+工作流进度)。
|
||||
窄屏(<960px)单列。对齐 ProjectDetail 两栏模式但阈值更宽(产出栏更窄)。
|
||||
不设 margin-top —— 全局 .page-header 已有 margin-bottom: var(--df-gap-page) 提供分隔。*/
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 380px;
|
||||
gap: var(--df-gap-page);
|
||||
align-items: start; /* 两栏顶对齐,长栏不撑高短栏 */
|
||||
}
|
||||
.left-column, .right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--df-gap-grid);
|
||||
min-width: 0; /* 网格项防溢出(长 url/代码块) */
|
||||
}
|
||||
|
||||
/* .panel / .panel-header 基础样式已收敛至全局 components.css(DRY 收口 B-260619)。 */
|
||||
|
||||
@@ -565,8 +627,44 @@ onBeforeUnmount(() => {
|
||||
/* B-260615-31:字段同行布局(label 固定宽 + value 占余),描述字段 info-block 保持块状)
|
||||
基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
|
||||
此处仅保留本组件特有覆盖。 */
|
||||
/* 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; }
|
||||
/* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li)。
|
||||
Problem4 重设计:描述区从 .info-item 内移到独立 .description-wrap(可折叠容器),
|
||||
故选择器改 .description-wrap。display:block 让 <span> 有块盒模型,scrollHeight 可读。*/
|
||||
.description-wrap .value.description { line-height: 1.5; display: block; }
|
||||
|
||||
/* Problem4 ② 描述可折叠:折叠态 max-height(内联注入 400px) + overflow hidden + 过渡。
|
||||
展开态 max-height none(内联不设),自然撑开。蒙层 .desc-fade 暗示下方有内容。*/
|
||||
.description-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.25s var(--df-ease);
|
||||
}
|
||||
.desc-toggle {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.desc-fade {
|
||||
/* 折叠态底部渐变蒙层,提示"下方还有内容"(展开态不渲染此元素) */
|
||||
position: absolute;
|
||||
left: 0; right: 0; bottom: 0;
|
||||
height: 32px;
|
||||
background: linear-gradient(to bottom, transparent, var(--df-bg-card));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Problem4 ⑥ 创建/更新时间最底小字(独立行,非 label/value 对) */
|
||||
.info-item.timestamps {
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
padding-top: var(--df-gap-grid);
|
||||
border-top: 0.5px solid var(--df-border);
|
||||
}
|
||||
.timestamp {
|
||||
font-size: 11px;
|
||||
color: var(--df-text-dim);
|
||||
}
|
||||
|
||||
/* 右栏空产出占位提示 */
|
||||
.output-empty { padding: 24px 12px; }
|
||||
|
||||
/* F-AiNodeSelfReview output_json 区块样式已随子组件移至 TaskOutputCard.vue */
|
||||
|
||||
@@ -599,4 +697,15 @@ onBeforeUnmount(() => {
|
||||
font-size: 13px; color: var(--df-text-dim);
|
||||
}
|
||||
.error-hint { color: var(--df-danger); }
|
||||
|
||||
/* Problem4 ⑤ 响应式:窄屏(<960px)两栏退单列,产出栏移到下方 */
|
||||
@media (max-width: 960px) {
|
||||
.detail-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* 窄屏 header-actions 内推进按钮可能换行,允许并压缩间距 */
|
||||
@media (max-width: 720px) {
|
||||
.task-detail-header { flex-wrap: wrap; }
|
||||
.task-detail-header .header-left { flex: 1 1 100%; margin-bottom: 8px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user