优化: 所有剩余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:
@@ -2,58 +2,62 @@
|
||||
<section class="idea-detail-panel">
|
||||
<div class="detail-header">
|
||||
<h2 class="detail-title">{{ idea.title }}</h2>
|
||||
<span class="status-tag" :class="'status-' + idea.status">{{ $t(statusLabelKey(idea.status)) }}</span>
|
||||
<div class="detail-header-actions">
|
||||
<!-- P0-② 转化为项目按钮前置到标题行右侧(原底部操作区按钮移入),保持 promoting 态 -->
|
||||
<button
|
||||
v-if="idea.status === 'approved' && !idea.promoted_to"
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="promoting"
|
||||
@click="$emit('promote')"
|
||||
>
|
||||
{{ promoting ? $t('ideas.promoting') : $t('ideas.promoteToProject') }}
|
||||
</button>
|
||||
<router-link
|
||||
v-else-if="idea.promoted_to"
|
||||
class="btn btn-primary btn-sm"
|
||||
:to="`/projects/${idea.promoted_to}`"
|
||||
>
|
||||
🚀 {{ $t('ideas.promotedProject') }} →
|
||||
</router-link>
|
||||
<span class="status-tag" :class="'status-' + idea.status">{{ $t(statusLabelKey(idea.status)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- B-260615-25:灵感描述 Markdown 渲染,复用 useMarkdown composable(同 B-24 TaskDetail),空值回退 — -->
|
||||
<template v-if="editing">
|
||||
<textarea v-model="editDesc" class="detail-desc-edit" rows="4"></textarea>
|
||||
<div class="desc-edit-actions">
|
||||
<button class="btn btn-primary btn-sm" @click="saveEdit">{{ $t('ideas.saveDesc') }}</button>
|
||||
<button class="btn btn-ghost btn-sm" @click="cancelEdit">{{ $t('ideas.cancelEdit') }}</button>
|
||||
<button class="btn btn-primary btn-sm" :disabled="savingDesc" @click="saveEdit">
|
||||
{{ savingDesc ? $t('common.loading') : $t('ideas.saveDesc') }}
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" :disabled="savingDesc" @click="cancelEdit">{{ $t('ideas.cancelEdit') }}</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p
|
||||
<!-- P1-③ 描述超阈值折叠:max-height:300px 截断,溢出显「展开/收起」(沿用 ProjectCard 测量模式) -->
|
||||
<div
|
||||
v-if="idea.description"
|
||||
class="detail-desc ai-md"
|
||||
v-html="renderedDesc"
|
||||
></p>
|
||||
class="detail-desc-wrap"
|
||||
:class="{ 'is-expanded': descExpanded }"
|
||||
>
|
||||
<p
|
||||
ref="descRef"
|
||||
class="detail-desc ai-md"
|
||||
v-html="renderedDesc"
|
||||
></p>
|
||||
<button v-if="descOverflow" class="desc-toggle" @click="descExpanded = !descExpanded">
|
||||
{{ descExpanded ? $t('common.collapse') : $t('common.expand') }}
|
||||
</button>
|
||||
</div>
|
||||
<p v-else class="detail-desc">—</p>
|
||||
<button class="btn btn-ghost btn-sm desc-edit-btn" @click="startEdit">{{ $t('ideas.editDesc') }}</button>
|
||||
<button class="btn btn-ghost btn-sm desc-edit-btn" :disabled="savingDesc" @click="startEdit">{{ $t('ideas.editDesc') }}</button>
|
||||
</template>
|
||||
|
||||
<!-- 对抗式评估 -->
|
||||
<div class="detail-section">
|
||||
<h3>{{ $t('ideas.adversarialTitle') }} <span class="eval-mode-tag">{{ $t(evalModeLabelKey()) }}</span></h3>
|
||||
<div v-if="adversarialEval" class="adversarial-eval">
|
||||
<!-- 正反方观点 -->
|
||||
<div class="debate-container">
|
||||
<div class="debate-column positive">
|
||||
<h4>{{ $t('ideas.positive') }}</h4>
|
||||
<div class="confidence-bar">
|
||||
<div class="confidence-fill" :style="{ width: (adversarialEval.positive_strength * 100) + '%' }"></div>
|
||||
</div>
|
||||
<div class="confidence-text">{{ $t('ideas.confidence', { n: (adversarialEval.positive_strength * 100).toFixed(0) }) }}</div>
|
||||
<p class="thesis">{{ adversarialEval.positive.thesis }}</p>
|
||||
<ul>
|
||||
<li v-for="evidence in adversarialEval.positive.evidence" :key="evidence">• {{ evidence }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="debate-column negative">
|
||||
<h4>{{ $t('ideas.negative') }}</h4>
|
||||
<div class="confidence-bar">
|
||||
<div class="confidence-fill" :style="{ width: (adversarialEval.negative_strength * 100) + '%' }"></div>
|
||||
</div>
|
||||
<div class="confidence-text">{{ $t('ideas.confidence', { n: (adversarialEval.negative_strength * 100).toFixed(0) }) }}</div>
|
||||
<p class="thesis">{{ adversarialEval.negative.thesis }}</p>
|
||||
<ul>
|
||||
<li v-for="evidence in adversarialEval.negative.evidence" :key="evidence">• {{ evidence }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 分析师结论 -->
|
||||
<!-- P0-① 评估结论前置:分析师结论(徽章/评分/倾向/摘要)提到正反方论据之前,
|
||||
一眼看到评估结果;详细论据(正反方/行动建议)折叠,点击展开 -->
|
||||
<div class="analyst-conclusion">
|
||||
<h4>{{ $t('ideas.analystTitle') }}</h4>
|
||||
<div class="assessment-badge" :class="assessmentClass(adversarialEval.recommendation)">
|
||||
@@ -66,21 +70,57 @@
|
||||
<p class="summary">{{ adversarialEval.analyst.summary }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 行动建议 -->
|
||||
<div class="action-recommendations">
|
||||
<h4>{{ $t('ideas.actionTitle') }}</h4>
|
||||
<ul>
|
||||
<li v-for="action in adversarialEval.action_items" :key="action">• {{ action }}</li>
|
||||
</ul>
|
||||
<!-- 详细论据(正反方观点 + 行动建议):折叠态默认收起,降低首屏信息密度 -->
|
||||
<div class="debate-toggle-row">
|
||||
<button class="debate-toggle-btn" @click="debateExpanded = !debateExpanded">
|
||||
{{ debateExpanded ? $t('common.collapse') : $t('common.expand') }} · {{ $t('ideas.positive') }} / {{ $t('ideas.negative') }}
|
||||
<span class="debate-toggle-arrow" :class="{ 'is-open': debateExpanded }">▾</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-show="debateExpanded" class="debate-detail">
|
||||
<div class="debate-container">
|
||||
<div class="debate-column positive">
|
||||
<h4>{{ $t('ideas.positive') }}</h4>
|
||||
<div class="confidence-bar">
|
||||
<div class="confidence-fill" :style="{ width: (adversarialEval.positive_strength * 100) + '%' }"></div>
|
||||
</div>
|
||||
<div class="confidence-text">{{ $t('ideas.confidence', { n: (adversarialEval.positive_strength * 100).toFixed(0) }) }}</div>
|
||||
<p class="thesis">{{ adversarialEval.positive.thesis }}</p>
|
||||
<ul>
|
||||
<li v-for="evidence in adversarialEval.positive.evidence" :key="evidence">• {{ evidence }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="debate-column negative">
|
||||
<h4>{{ $t('ideas.negative') }}</h4>
|
||||
<div class="confidence-bar">
|
||||
<div class="confidence-fill" :style="{ width: (adversarialEval.negative_strength * 100) + '%' }"></div>
|
||||
</div>
|
||||
<div class="confidence-text">{{ $t('ideas.confidence', { n: (adversarialEval.negative_strength * 100).toFixed(0) }) }}</div>
|
||||
<p class="thesis">{{ adversarialEval.negative.thesis }}</p>
|
||||
<ul>
|
||||
<li v-for="evidence in adversarialEval.negative.evidence" :key="evidence">• {{ evidence }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 行动建议 -->
|
||||
<div class="action-recommendations">
|
||||
<h4>{{ $t('ideas.actionTitle') }}</h4>
|
||||
<ul>
|
||||
<li v-for="action in adversarialEval.action_items" :key="action">• {{ action }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="eval-report eval-report--muted">
|
||||
<button class="btn-evaluate" :disabled="evaluating" @click="$emit('evaluate')">
|
||||
{{ evaluating ? $t('ideas.evaluating') : $t('ideas.startEval') }}
|
||||
</button>
|
||||
<!-- P1-⑥ 重试样式:错误提示分两行,重试用 ghost btn-sm(替代突兀的内联 accent 按钮) -->
|
||||
<div v-if="evalError" class="eval-error">
|
||||
⚠️ {{ evalError }}
|
||||
<button class="btn-evaluate btn-retry" :disabled="evaluating" @click="$emit('evaluate')">
|
||||
<p class="eval-error-msg">⚠️ {{ evalError }}</p>
|
||||
<button class="btn btn-ghost btn-sm btn-retry" :disabled="evaluating" @click="$emit('evaluate')">
|
||||
{{ $t('ideas.retryEval') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -186,34 +226,37 @@
|
||||
<div v-else class="eval-report eval-report--muted">{{ $t('ideas.noTags') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态管理 -->
|
||||
<!-- 状态管理:P1-④ select → badge 点击(当前态显色 badge,点击展开下拉选项) -->
|
||||
<div class="detail-section">
|
||||
<h3>{{ $t('ideas.statusTitle') }}</h3>
|
||||
<div class="status-controls">
|
||||
<select :value="idea.status" @change="onStatusChange" class="status-select">
|
||||
<option v-for="s in statusOptions" :key="s.value" :value="s.value">{{ $t(s.labelKey) }}</option>
|
||||
</select>
|
||||
<div class="status-badge-select" v-click-outside="closeStatusMenu">
|
||||
<button
|
||||
class="status-tag status-current"
|
||||
:class="'status-' + idea.status"
|
||||
@click="statusMenuOpen = !statusMenuOpen"
|
||||
>
|
||||
{{ $t(statusLabelKey(idea.status)) }}
|
||||
<span class="status-caret" :class="{ 'is-open': statusMenuOpen }">▾</span>
|
||||
</button>
|
||||
<ul v-show="statusMenuOpen" class="status-menu">
|
||||
<li
|
||||
v-for="s in statusOptions"
|
||||
:key="s.value"
|
||||
class="status-menu-item"
|
||||
:class="{ active: s.value === idea.status }"
|
||||
@click="pickStatus(s.value)"
|
||||
>
|
||||
<span class="status-tag" :class="'status-' + s.value">{{ $t(s.labelKey) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作 -->
|
||||
<!-- 操作(P0-② 立项按钮已上移至标题行,此处仅留删除) -->
|
||||
<div class="detail-section" style="margin-top:8px">
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
v-if="idea.status === 'approved' && !idea.promoted_to"
|
||||
class="btn btn-primary"
|
||||
:disabled="promoting"
|
||||
@click="$emit('promote')"
|
||||
>
|
||||
{{ promoting ? $t('ideas.promoting') : $t('ideas.promoteToProject') }}
|
||||
</button>
|
||||
<router-link
|
||||
v-if="idea.promoted_to"
|
||||
class="btn btn-primary"
|
||||
:to="`/projects/${idea.promoted_to}`"
|
||||
>
|
||||
🚀 {{ $t('ideas.promotedProject') }} →
|
||||
</router-link>
|
||||
<button class="btn btn-ghost" :disabled="deleting" @click="$emit('delete')">
|
||||
{{ deleting ? $t('ideas.deleting') : $t('ideas.deleteIdea') }}
|
||||
</button>
|
||||
@@ -223,7 +266,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { computed, ref, watch, onMounted, nextTick, type Directive } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { parseTags } from '../../stores/knowledge'
|
||||
import { useProjectStore } from '../../stores/project'
|
||||
@@ -257,21 +300,91 @@ const store = useProjectStore()
|
||||
// 描述可编辑模式
|
||||
const editing = ref(false)
|
||||
const editDesc = ref('')
|
||||
// P1-⑥ 描述编辑 loading:saveEdit 为异步(emit → 父 store.updateIdea → IPC),期间禁用按钮防双击
|
||||
const savingDesc = ref(false)
|
||||
|
||||
function startEdit() {
|
||||
editDesc.value = props.idea.description ?? ''
|
||||
editing.value = true
|
||||
}
|
||||
|
||||
function saveEdit() {
|
||||
emit('update-desc', editDesc.value)
|
||||
editing.value = false
|
||||
// P1-⑥ saveEdit 改异步:emit 后父组件 store.updateIdea 是 Promise,但 emit 不回传结果;
|
||||
// 这里用本地 savingDesc 包裹一个最小延迟窗口(等待父组件 patch 同步 props.idea.description
|
||||
// 完成),保证 UI 反馈一致。父组件 updateIdea 失败会 throw 但不冒泡回子组件(沿用既有契约)。
|
||||
async function saveEdit() {
|
||||
if (savingDesc.value) return
|
||||
savingDesc.value = true
|
||||
try {
|
||||
emit('update-desc', editDesc.value)
|
||||
editing.value = false
|
||||
// 等下一帧让父组件 patch 生效再释放(微观反馈,失败也释放不卡死)
|
||||
await nextTick()
|
||||
} finally {
|
||||
savingDesc.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
editing.value = false
|
||||
}
|
||||
|
||||
// P1-③ 描述溢出测量(沿用 ProjectCard 测量模式):scrollHeight > clientHeight 说明被
|
||||
// CSS max-height 截断。Markdown 异步渲染 + 切 idea 都需重测。
|
||||
const descRef = ref<HTMLParagraphElement | null>(null)
|
||||
const descOverflow = ref(false)
|
||||
const descExpanded = ref(false)
|
||||
|
||||
// P0-① 详细论据(正反方/行动建议)折叠:默认收起,降低首屏信息密度,结论前置后用户按需展开
|
||||
const debateExpanded = ref(false)
|
||||
|
||||
function measureDesc() {
|
||||
const el = descRef.value
|
||||
if (!el) return
|
||||
descOverflow.value = el.scrollHeight - el.clientHeight > 1
|
||||
}
|
||||
|
||||
// 切换 idea 重置展开态 + 重测;描述 Markdown 异步渲染完成会触发 props.idea.description
|
||||
// 不变但 DOM 文本变,因此额外 watch renderedDesc(渲染输出)重测 —— 见 useRendered 之后
|
||||
watch(() => props.idea.id, () => {
|
||||
descExpanded.value = false
|
||||
debateExpanded.value = false
|
||||
void nextTick(measureDesc)
|
||||
})
|
||||
watch(() => props.idea.description, () => void nextTick(measureDesc))
|
||||
|
||||
// P1-④ 状态切换 select → badge 点击:点击当前 badge 展开/收起下拉菜单,
|
||||
// 选中项后 emit('status-change') 交父组件确认+落库(沿用既有契约,不破坏 statusOptions 来源)。
|
||||
const statusMenuOpen = ref(false)
|
||||
function closeStatusMenu() {
|
||||
statusMenuOpen.value = false
|
||||
}
|
||||
function pickStatus(s: IdeaStatus) {
|
||||
statusMenuOpen.value = false
|
||||
if (s === props.idea.status) return
|
||||
emit('status-change', s)
|
||||
}
|
||||
|
||||
// v-click-outside:轻量内联指令(状态菜单点击外部关闭),不引入新依赖。
|
||||
// mounted 时绑定 mousedown 监听,点击落点不在 el 内 → 调 binding.value()。
|
||||
interface ClickOutsideEl extends HTMLElement { _dfClickOutside?: ((e: MouseEvent) => void) | null }
|
||||
const vClickOutside: Directive<HTMLElement, () => void> = {
|
||||
mounted(el, binding) {
|
||||
const host = el as ClickOutsideEl
|
||||
host._dfClickOutside = (e: MouseEvent) => {
|
||||
if (el.contains(e.target as Node)) return
|
||||
binding.value?.()
|
||||
}
|
||||
document.addEventListener('mousedown', host._dfClickOutside)
|
||||
},
|
||||
unmounted(el) {
|
||||
const host = el as ClickOutsideEl
|
||||
if (host._dfClickOutside) {
|
||||
document.removeEventListener('mousedown', host._dfClickOutside)
|
||||
host._dfClickOutside = null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// ===== 关联灵感(related_ids JSON 数组字符串)=====
|
||||
// 解析 props.idea.related_ids(JSON 字符串数组,null/空/非法 → [])
|
||||
const relatedIds = computed<string[]>(() => {
|
||||
@@ -336,6 +449,8 @@ function statusLabelKey(status: IdeaStatus): string {
|
||||
const { rendered: renderedDesc, ensureLoaded: ensureMdLoaded } = useRendered(
|
||||
() => props.idea.description ?? '',
|
||||
)
|
||||
// Markdown 异步渲染完成(renderedDesc 变化)后重测描述溢出(max-height 截断阈值依赖渲染后的高度)
|
||||
watch(renderedDesc, () => void nextTick(measureDesc))
|
||||
|
||||
// parseScores / assessmentClass / assessmentLabel 抽到 ../../utils/ideaEval 复用(消除与 ProjectDetail 的 DRY 重复)。
|
||||
|
||||
@@ -383,9 +498,7 @@ function sentimentClass(sentiment: number) {
|
||||
return 'neutral'
|
||||
}
|
||||
|
||||
function onStatusChange(e: Event) {
|
||||
emit('status-change', (e.target as HTMLSelectElement).value as IdeaStatus)
|
||||
}
|
||||
// (原 onStatusChange select 处理器已移除:状态切换改 badge 点击 pickStatus 触发 emit)
|
||||
|
||||
// ===== 评估历史(版本时间线)=====
|
||||
const evalHistory = ref<IdeaEvaluationRecord[]>([])
|
||||
@@ -463,9 +576,43 @@ watch(() => props.idea.id, loadHistory)
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: var(--df-gap-head);
|
||||
}
|
||||
.detail-title { font-size: 20px; font-weight: 500; color: var(--df-text); }
|
||||
.detail-title { font-size: 20px; font-weight: 500; color: var(--df-text); flex: 1; min-width: 0; }
|
||||
/* P0-② 标题行右侧操作组:立项按钮 + 状态 badge 一行排布 */
|
||||
.detail-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* P1-③ 描述折叠容器:默认 max-height:300px 截断,展开态取消 */
|
||||
.detail-desc-wrap {
|
||||
position: relative;
|
||||
margin-bottom: var(--df-gap-page);
|
||||
}
|
||||
.detail-desc-wrap .detail-desc { margin-bottom: 0; }
|
||||
.detail-desc-wrap:not(.is-expanded) .detail-desc {
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
/* 底部渐变提示「下方还有」,与 max-height 截断视觉呼应 */
|
||||
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
|
||||
mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
|
||||
}
|
||||
.desc-toggle {
|
||||
display: inline-block;
|
||||
margin-top: 6px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--df-accent);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.desc-toggle:hover { text-decoration: underline; }
|
||||
|
||||
.detail-desc {
|
||||
font-size: 14px;
|
||||
@@ -647,6 +794,28 @@ watch(() => props.idea.id, loadHistory)
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
/* P0-① 详细论据折叠切换条 */
|
||||
.debate-toggle-row { margin-bottom: 0.75rem; }
|
||||
.debate-toggle-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--df-accent);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.debate-toggle-btn:hover { text-decoration: underline; }
|
||||
.debate-toggle-arrow {
|
||||
font-size: 10px;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.debate-toggle-arrow.is-open { transform: rotate(180deg); }
|
||||
.debate-detail { margin-top: 0.5rem; }
|
||||
.debate-detail .debate-container { margin-bottom: 0.75rem; }
|
||||
|
||||
.btn-evaluate {
|
||||
background: var(--df-accent);
|
||||
color: #fff;
|
||||
@@ -662,11 +831,14 @@ watch(() => props.idea.id, loadHistory)
|
||||
background: var(--df-accent-hover);
|
||||
}
|
||||
|
||||
/* P1-⑥ 重试样式:错误提示与重试按钮分行,重试用 ghost btn-sm(替代突兀的内联 accent 按钮) */
|
||||
.eval-error {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--df-danger);
|
||||
}
|
||||
.eval-error-msg { margin: 0 0 8px; }
|
||||
.btn-retry { margin-top: 4px; }
|
||||
|
||||
.eval-mode-tag {
|
||||
font-size: 11px;
|
||||
@@ -826,35 +998,69 @@ watch(() => props.idea.id, loadHistory)
|
||||
background: var(--df-bg-raised);
|
||||
}
|
||||
|
||||
/* 评估失败重试按钮(内联于错误提示) */
|
||||
.btn-retry {
|
||||
margin-left: 8px;
|
||||
padding: 2px 10px;
|
||||
font-size: 11px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* (旧 .btn-retry 内联 accent 按钮样式已废弃:重试改用 ghost btn-sm,统一样式收敛) */
|
||||
|
||||
/* ===== 状态管理 ===== */
|
||||
/* ===== 状态管理:P1-④ select → badge 点击下拉 ===== */
|
||||
.status-controls {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.status-select {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-sm);
|
||||
background: var(--df-bg);
|
||||
color: var(--df-text);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
.status-badge-select {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--df-accent);
|
||||
background: var(--df-bg-raised);
|
||||
/* 当前态 badge:点击展开下拉(复用全局 .status-tag 显色,加 cursor + caret) */
|
||||
.status-current {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
user-select: none;
|
||||
border: 0.5px solid transparent;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.status-current:hover { border-color: var(--df-accent); }
|
||||
|
||||
.status-caret {
|
||||
font-size: 10px;
|
||||
opacity: 0.7;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.status-caret.is-open { transform: rotate(180deg); }
|
||||
|
||||
/* 下拉菜单(绝对定位浮层) */
|
||||
.status-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 6px;
|
||||
min-width: 160px;
|
||||
background: var(--df-bg-card);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-sm);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.status-menu-item {
|
||||
cursor: pointer;
|
||||
padding: 4px 6px;
|
||||
border-radius: var(--df-radius-xs);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.status-menu-item:hover { background: rgba(108, 99, 255, 0.08); }
|
||||
.status-menu-item.active { background: rgba(108, 99, 255, 0.12); font-weight: 500; }
|
||||
/* menu 内 badge 取消 emoji 之外的全局 hover 等副作用,仅作色标 */
|
||||
.status-menu-item .status-tag { cursor: pointer; }
|
||||
|
||||
/* ═══ 响应式:窄屏对抗式评估双栏堆叠 ═══ */
|
||||
@media (max-width: 760px) {
|
||||
|
||||
Reference in New Issue
Block a user